################################################################################
#
# Copyright (c) 2011 The MadGraph5_aMC@NLO Development team and Contributors
#
# This file is a part of the MadGraph5_aMC@NLO project, an application which 
# automatically generates Feynman diagrams and matrix elements for arbitrary
# high-energy processes in the Standard Model and beyond.
#
# It is subject to the MadGraph5_aMC@NLO license which should accompany this 
# distribution.
#
# For more information, visit madgraph.phys.ucl.ac.be and amcatnlo.web.cern.ch
#
################################################################################

from __future__ import division
from __future__ import absolute_import
import collections
import copy
import logging
import numbers
import os
import sys
import re
import math
import six
StringIO = six
from six.moves import range
if six.PY3:
    import io
    file = io.IOBase
import itertools
import time


pjoin = os.path.join

try:
    import madgraph
except ImportError:
    MADEVENT = True
    from internal import MadGraph5Error, InvalidCmd
    import internal.file_writers as file_writers
    import internal.files as files
    import internal.check_param_card as param_card_reader
    import internal.misc as misc
    MEDIR = os.path.split(os.path.dirname(os.path.realpath( __file__ )))[0]
    MEDIR = os.path.split(MEDIR)[0]
else:
    MADEVENT = False
    import madgraph.various.misc as misc
    import madgraph.iolibs.file_writers as file_writers
    import madgraph.iolibs.files as files 
    import models.check_param_card as param_card_reader
    from madgraph import MG5DIR, MadGraph5Error, InvalidCmd



logger = logging.getLogger('madevent.cards')

# A placeholder class to store unknown parameters with undecided format
class UnknownType(str):
    pass

#dict
class Banner(dict):
    """ """

    ordered_items = ['mgversion', 'mg5proccard', 'mgproccard', 'mgruncard',
                     'slha','initrwgt','mggenerationinfo', 'mgpythiacard', 'mgpgscard',
                     'mgdelphescard', 'mgdelphestrigger','mgshowercard',
                     'ma5card_parton','ma5card_hadron','run_settings']

    capitalized_items = {
            'mgversion': 'MGVersion',
            'mg5proccard': 'MG5ProcCard',
            'mgproccard': 'MGProcCard',
            'mgruncard': 'MGRunCard',
            'ma5card_parton' : 'MA5Card_parton',
            'ma5card_hadron' : 'MA5Card_hadron',            
            'mggenerationinfo': 'MGGenerationInfo',
            'mgpythiacard': 'MGPythiaCard',
            'mgpgscard': 'MGPGSCard',
            'mgdelphescard': 'MGDelphesCard',
            'mgdelphestrigger': 'MGDelphesTrigger',
            'mgshowercard': 'MGShowerCard' }
    
    forbid_cdata = ['initrwgt']
    
    def __init__(self, banner_path=None):
        """ """

        if isinstance(banner_path, Banner):
            dict.__init__(self, banner_path)
            self.lhe_version = banner_path.lhe_version
            return     
        else:
            dict.__init__(self)
        
        #Look at the version
        if MADEVENT:
            self['mgversion'] = '#%s\n' % open(pjoin(MEDIR, 'MGMEVersion.txt')).read()
        else:
            info = misc.get_pkg_info()
            self['mgversion'] = info['version']+'\n'
        
        self.lhe_version = None

   
        if banner_path:
            self.read_banner(banner_path)

    ############################################################################
    #  READ BANNER
    ############################################################################
    pat_begin=re.compile('<(?P<name>\w*)>')
    pat_end=re.compile('</(?P<name>\w*)>')

    tag_to_file={'slha':'param_card.dat',
      'mgruncard':'run_card.dat',
      'mgpythiacard':'pythia_card.dat',
      'mgpgscard' : 'pgs_card.dat',
      'mgdelphescard':'delphes_card.dat',      
      'mgdelphestrigger':'delphes_trigger.dat',
      'mg5proccard':'proc_card_mg5.dat',
      'mgproccard': 'proc_card.dat',
      'init': '',
      'mggenerationinfo':'',
      'scalesfunctionalform':'',
      'montecarlomasses':'',
      'initrwgt':'',
      'madspin':'madspin_card.dat',
      'mgshowercard':'shower_card.dat',
      'pythia8':'pythia8_card.dat',
      'ma5card_parton':'madanalysis5_parton_card.dat',
      'ma5card_hadron':'madanalysis5_hadron_card.dat',      
      'run_settings':''
      }
    
    def read_banner(self, input_path):
        """read a banner"""

        if isinstance(input_path, str):
            if input_path.find('\n') ==-1:
                input_path = open(input_path)
            else:
                def split_iter(string):
                    return (x.groups(0)[0] for x in re.finditer(r"([^\n]*\n)", string, re.DOTALL))
                input_path = split_iter(input_path)
                
        text = ''
        store = False
        for line in input_path:
            if self.pat_begin.search(line):
                if self.pat_begin.search(line).group('name').lower() in self.tag_to_file:
                    tag = self.pat_begin.search(line).group('name').lower()
                    store = True
                    continue
            if store and self.pat_end.search(line):
                if tag == self.pat_end.search(line).group('name').lower():
                    self[tag] = text
                    text = ''
                    store = False
            if store and not line.startswith(('<![CDATA[',']]>')):
                if line.endswith('\n'):
                    text += line
                else:
                    text += '%s%s' % (line, '\n')
                
            #reaching end of the banner in a event file avoid to read full file 
            if "</init>" in line:
                break
            elif "<event>" in line:
                break
    
    def __getattribute__(self, attr):
        """allow auto-build for the run_card/param_card/... """
        try:
            return super(Banner, self).__getattribute__(attr)
        except:
            if attr not in ['run_card', 'param_card', 'slha', 'mgruncard', 'mg5proccard', 'mgshowercard', 'foanalyse']:
                raise
            return self.charge_card(attr)


    
    def change_lhe_version(self, version):
        """change the lhe version associate to the banner"""
    
        version = float(version)
        if version < 3:
            version = 1
        elif version > 3:
            raise Exception("Not Supported version")
        self.lhe_version = version
    
    def get_cross(self, witherror=False):
        """return the cross-section of the file"""

        if "init" not in self:
            raise Exception
        
        text = self["init"].split('\n')
        cross = 0
        error = 0
        for line in text:
            s = line.split()
            if len(s)==4:
                cross += float(s[0])
                if witherror:
                    error += float(s[1])**2
        if not witherror:
            return cross
        else:
            return cross, math.sqrt(error)
        

    def scale_init_cross(self, ratio):
        """modify the init information with the associate scale"""

        assert "init" in self
        
        all_lines = self["init"].split('\n')
        new_data = []
        new_data.append(all_lines[0])
        for i in range(1, len(all_lines)):
            line = all_lines[i]
            split = line.split()
            if len(split) == 4:
                xsec, xerr, xmax, pid = split 
            else:
                new_data += all_lines[i:]
                break
            pid = int(pid)
            
            line = "   %+13.7e %+13.7e %+13.7e %i" % \
                (ratio*float(xsec), ratio* float(xerr), ratio*float(xmax), pid)
            new_data.append(line)
        self['init'] = '\n'.join(new_data)
    
    def get_pdg_beam(self):
        """return the pdg of each beam"""
        
        assert "init" in self
        
        all_lines = self["init"].split('\n')
        pdg1,pdg2,_ = all_lines[0].split(None, 2)
        return int(pdg1), int(pdg2)
    
    def load_basic(self, medir):
        """ Load the proc_card /param_card and run_card """
        
        self.add(pjoin(medir,'Cards', 'param_card.dat'))
        self.add(pjoin(medir,'Cards', 'run_card.dat'))
        if os.path.exists(pjoin(medir, 'SubProcesses', 'procdef_mg5.dat')):
            self.add(pjoin(medir,'SubProcesses', 'procdef_mg5.dat'))
            self.add(pjoin(medir,'Cards', 'proc_card_mg5.dat'))
        else:
            self.add(pjoin(medir,'Cards', 'proc_card.dat'))
        
    def change_seed(self, seed):
        """Change the seed value in the banner"""
        #      0       = iseed
        p = re.compile(r'''^\s*\d+\s*=\s*iseed''', re.M)
        new_seed_str = " %s = iseed" % seed
        self['mgruncard'] = p.sub(new_seed_str, self['mgruncard'])
    
    def add_generation_info(self, cross, nb_event):
        """add info on MGGeneration"""
        
        text = """
#  Number of Events        :       %s
#  Integrated weight (pb)  :       %s
""" % (nb_event, cross)
        self['MGGenerationInfo'] = text
    
    ############################################################################
    #  SPLIT BANNER
    ############################################################################
    def split(self, me_dir, proc_card=True):
        """write the banner in the Cards directory.
        proc_card argument is present to avoid the overwrite of proc_card 
        information"""

        for tag, text in self.items():
            if tag == 'mgversion':
                continue
            if not proc_card and tag in ['mg5proccard','mgproccard']:
                continue
            if not self.tag_to_file[tag]:
                continue
            ff = open(pjoin(me_dir, 'Cards', self.tag_to_file[tag]), 'w')
            ff.write(text)
            ff.close()


    ############################################################################
    #  WRITE BANNER
    ############################################################################
    def check_pid(self, pid2label):
        """special routine removing width/mass of particles not present in the model
        This is usefull in case of loop model card, when we want to use the non
        loop model."""
        
        if not hasattr(self, 'param_card'):
            self.charge_card('slha')
            
        for tag in ['mass', 'decay']:
            block = self.param_card.get(tag)
            for data in block:
                pid = data.lhacode[0]
                if pid not in list(pid2label.keys()): 
                    block.remove((pid,))

    def get_lha_strategy(self):
        """get the lha_strategy: how the weight have to be handle by the shower"""
        
        if not self["init"]:
            raise Exception("No init block define")
        
        data = self["init"].split('\n')[0].split()
        if len(data) != 10:
            misc.sprint(len(data), self['init'])
            raise Exception("init block has a wrong format")
        return int(float(data[-2]))
        
    def set_lha_strategy(self, value):
        """set the lha_strategy: how the weight have to be handle by the shower"""
        
        if not (-4 <= int(value) <= 4):
            six.reraise(Exception, "wrong value for lha_strategy", value)
        if not self["init"]:
            raise Exception("No init block define")
        
        all_lines = self["init"].split('\n')
        data = all_lines[0].split()
        if len(data) != 10:
            misc.sprint(len(data), self['init'])
            raise Exception("init block has a wrong format")
        data[-2] = '%s' % value
        all_lines[0] = ' '.join(data)
        self['init'] = '\n'.join(all_lines)


    def modify_init_cross(self, cross):
        """modify the init information with the associate cross-section"""
        assert isinstance(cross, dict)
#        assert "all" in cross
        assert "init" in self
        
        cross = dict(cross)
        for key in cross.keys():
            if isinstance(key, str) and key.isdigit() and int(key) not in cross:
                cross[int(key)] = cross[key]
        
        
        all_lines = self["init"].split('\n')
        new_data = []
        new_data.append(all_lines[0])
        for i in range(1, len(all_lines)):
            line = all_lines[i]
            split = line.split()
            if len(split) == 4:
                xsec, xerr, xmax, pid = split 
            else:
                new_data += all_lines[i:]
                break
            if int(pid) not in cross:
                raise Exception
            pid = int(pid)
            if float(xsec):
                ratio = cross[pid]/float(xsec)
            else:
                ratio = 0
            line = "   %+13.7e %+13.7e %+13.7e %i" % \
                (float(cross[pid]), ratio* float(xerr), ratio*float(xmax), pid)
            new_data.append(line)
        self['init'] = '\n'.join(new_data)
                
    ############################################################################
    #  WRITE BANNER
    ############################################################################
    def write(self, output_path, close_tag=True, exclude=[]):
        """write the banner"""
        
        if isinstance(output_path, str):
            ff = open(output_path, 'w')
        else:
            ff = output_path
            
        if MADEVENT:
            header = open(pjoin(MEDIR, 'Source', 'banner_header.txt')).read()
        else:
            header = open(pjoin(MG5DIR,'Template', 'LO', 'Source', 'banner_header.txt')).read()
            
        if not self.lhe_version:
            self.lhe_version = self.get('run_card', 'lhe_version', default=1.0)
            if float(self.lhe_version) < 3:
                self.lhe_version = 1.0
        out = header % { 'version':float(self.lhe_version)}
        try:
            ff.write(out)
        except:
            ff.write(out.encode('utf-8'))

        for tag in [t for t in self.ordered_items if t in list(self.keys())]+ \
            [t for t in self.keys() if t not in self.ordered_items]:
            if tag in ['init'] or tag in exclude: 
                continue
            capitalized_tag = self.capitalized_items[tag] if tag in self.capitalized_items else tag
            start_data, stop_data = '', ''
            if capitalized_tag not in self.forbid_cdata and \
                                          ('<' in self[tag] or '@' in self[tag]):
                start_data = '\n<![CDATA['
                stop_data = ']]>\n'
            out = '<%(tag)s>%(start_data)s\n%(text)s\n%(stop_data)s</%(tag)s>\n' % \
                     {'tag':capitalized_tag, 'text':self[tag].strip(),
                      'start_data': start_data, 'stop_data':stop_data} 
            try:
                ff.write(out)
            except:
                ff.write(out.encode('utf-8'))
        
        
        if not '/header' in exclude:
            out = '</header>\n'
            try:
                ff.write(out)
            except:
                ff.write(out.encode('utf-8'))   

        if 'init' in self and not 'init' in exclude:
            text = self['init']
            out = '<%(tag)s>\n%(text)s\n</%(tag)s>\n' % \
                     {'tag':'init', 'text':text.strip()}
            try:
                ff.write(out)
            except:
                ff.write(out.encode('utf-8'))
                
        if close_tag:
            out = '</LesHouchesEvents>\n'          
            try:
                ff.write(out)
            except:
                ff.write(out.encode('utf-8'))            
        return ff
        
        
    ############################################################################
    # BANNER
    ############################################################################
    def add(self, path, tag=None):
        """Add the content of the file to the banner"""
        
        if not tag:
            card_name = os.path.basename(path)
            if 'param_card' in card_name:
                tag = 'slha'
            elif 'run_card' in card_name:
                tag = 'MGRunCard'
            elif 'pythia_card' in card_name:
                tag = 'MGPythiaCard'
            elif 'pythia8_card' in card_name or 'pythia8.cmd' in card_name:
                tag = 'MGPythiaCard'
            elif 'pgs_card' in card_name:
                tag = 'MGPGSCard'
            elif 'delphes_card' in card_name:
                tag = 'MGDelphesCard'
            elif 'delphes_trigger' in card_name:
                tag = 'MGDelphesTrigger'
            elif 'proc_card_mg5' in card_name:
                tag = 'MG5ProcCard'
            elif 'proc_card' in card_name:
                tag = 'MGProcCard'
            elif 'procdef_mg5' in card_name:
                tag = 'MGProcCard'
            elif 'shower_card' in card_name:
                tag = 'MGShowerCard'
            elif 'madspin_card' in card_name:
                tag = 'madspin'
            elif 'FO_analyse_card' in card_name:
                tag = 'foanalyse'
            elif 'reweight_card' in card_name:
                tag='reweight_card'
            elif 'madanalysis5_parton_card' in card_name:
                tag='MA5Card_parton'
            elif 'madanalysis5_hadron_card' in card_name:
                tag='MA5Card_hadron'
            else:
                raise Exception('Impossible to know the type of the card')

            self.add_text(tag.lower(), open(path).read())

    def add_text(self, tag, text):
        """Add the content of the file to the banner"""

        if tag == 'param_card':
            tag = 'slha'
        elif tag == 'run_card':
            tag = 'mgruncard' 
        elif tag == 'proc_card':
            tag = 'mg5proccard' 
        elif tag == 'shower_card':
            tag = 'mgshowercard'
        elif tag == 'FO_analyse_card':
            tag = 'foanalyse'
        
        self[tag.lower()] = text
    
    
    def charge_card(self, tag):
        """Build the python object associated to the card"""
        
        if tag in ['param_card', 'param']:
            tag = 'slha'
        elif tag  in ['run_card', 'run']:
            tag = 'mgruncard' 
        elif tag == 'proc_card':
            tag = 'mg5proccard' 
        elif tag == 'shower_card':
            tag = 'mgshowercard'
        elif tag == 'FO_analyse_card':
            tag = 'foanalyse'

        assert tag in ['slha', 'mgruncard', 'mg5proccard', 'mgshowercard', 'foanalyse'], 'invalid card %s' % tag
        
        if tag == 'slha':
            param_card = self[tag].split('\n')
            self.param_card = param_card_reader.ParamCard(param_card)
            return self.param_card
        elif tag == 'mgruncard':
            self.run_card = RunCard(self[tag])
            return self.run_card
        elif tag == 'mg5proccard':
            proc_card = self[tag].split('\n')
            self.proc_card = ProcCard(proc_card)
            return self.proc_card
        elif tag =='mgshowercard':
            shower_content = self[tag] 
            if MADEVENT:
                import internal.shower_card as shower_card
            else:
                import madgraph.various.shower_card as shower_card
            self.shower_card = shower_card.ShowerCard(shower_content, True)
            # set testing to false (testing = true allow to init using 
            #  the card content instead of the card path"
            self.shower_card.testing = False
            return self.shower_card
        elif tag =='foanalyse':
            analyse_content = self[tag] 
            if MADEVENT:
                import internal.FO_analyse_card as FO_analyse_card
            else:
                import madgraph.various.FO_analyse_card as FO_analyse_card
            # set testing to false (testing = true allow to init using 
            #  the card content instead of the card path"
            self.FOanalyse_card = FO_analyse_card.FOAnalyseCard(analyse_content, True)
            self.FOanalyse_card.testing = False
            return self.FOanalyse_card
        

    def get_detail(self, tag, *arg, **opt):
        """return a specific """
                
        if tag in ['param_card', 'param']:
            tag = 'slha'
            attr_tag = 'param_card'
        elif tag in ['run_card', 'run']:
            tag = 'mgruncard' 
            attr_tag = 'run_card'
        elif tag == 'proc_card':
            tag = 'mg5proccard' 
            attr_tag = 'proc_card'
        elif tag == 'model':
            tag = 'mg5proccard' 
            attr_tag = 'proc_card'
            arg = ('model',)
        elif tag == 'generate':
            tag = 'mg5proccard' 
            attr_tag = 'proc_card'
            arg = ('generate',)
        elif tag == 'shower_card':
            tag = 'mgshowercard'
            attr_tag = 'shower_card'
        assert tag in ['slha', 'mgruncard', 'mg5proccard', 'shower_card'], '%s not recognized' % tag
        
        if not hasattr(self, attr_tag):
            self.charge_card(attr_tag) 

        card = getattr(self, attr_tag)
        if len(arg) == 0:
            return card
        elif len(arg) == 1:
            if tag == 'mg5proccard':
                try:
                    return card.get(arg[0])
                except KeyError as error:
                    if 'default' in opt:
                        return opt['default']
                    else:
                        raise
            try:
                return card[arg[0]]
            except KeyError:
                if 'default' in opt:
                    return opt['default']
                else:
                    raise                
        elif len(arg) == 2 and tag == 'slha':
            try:
                return card[arg[0]].get(arg[1:])
            except KeyError:
                if 'default' in opt:
                    return opt['default']
                else:
                    raise  
        elif len(arg) == 0:
            return card
        else:
            raise Exception("Unknow command")
    
    #convenient alias
    get = get_detail
    
    def set(self, card, *args):
        """modify one of the cards"""

        if tag == 'param_card':
            tag = 'slha'
            attr_tag = 'param_card'
        elif tag == 'run_card':
            tag = 'mgruncard' 
            attr_tag = 'run_card'
        elif tag == 'proc_card':
            tag = 'mg5proccard' 
            attr_tag = 'proc_card'
        elif tag == 'model':
            tag = 'mg5proccard' 
            attr_tag = 'proc_card'
            arg = ('model',)
        elif tag == 'generate':
            tag = 'mg5proccard' 
            attr_tag = 'proc_card'
            arg = ('generate',)
        elif tag == 'shower_card':
            tag = 'mgshowercard'
            attr_tag = 'shower_card'
        assert tag in ['slha', 'mgruncard', 'mg5proccard', 'shower_card'], 'not recognized'
        
        if not hasattr(self, attr_tag):
            self.charge_card(attr_tag) 
            
        card = getattr(self, attr_tag)
        if len(args) ==2:
            if tag == 'mg5proccard':
                card.info[args[0]] = args[-1]
            else:
                card[args[0]] = args[1]
        else:
            card[args[:-1]] = args[-1]
        
    
    @misc.multiple_try()
    def add_to_file(self, path, seed=None, out=None):
        """Add the banner to a file and change the associate seed in the banner"""

        if seed is not None:
            self.set("run_card", "iseed", seed)
        
        if not out:
            path_out = "%s.tmp" % path
        else:
            path_out = out
        
        ff = self.write(path_out, close_tag=False,
                        exclude=['MGGenerationInfo', '/header', 'init'])
        ff.write("## END BANNER##\n")
        if self.lhe_version >= 3:
        #add the original content
            [ff.write(line) if not line.startswith("<generator name='MadGraph5_aMC@NLO'")
                        else ff.write("<generator name='MadGraph5_aMC@NLO' version='%s'>" % self['mgversion'][:-1])
                        for line in open(path)]
        else:
            [ff.write(line) for line in open(path)]
        ff.write("</LesHouchesEvents>\n")
        ff.close()
        if out:
            os.remove(path)
        else:
            files.mv(path_out, path)


        
def split_banner(banner_path, me_dir, proc_card=True):
    """a simple way to split a banner"""
    
    banner = Banner(banner_path)
    banner.split(me_dir, proc_card)
    
def recover_banner(results_object, level, run=None, tag=None):
    """as input we receive a gen_crossxhtml.AllResults object.
       This define the current banner and load it
    """
    
    if not run:
        try: 
            _run = results_object.current['run_name']   
            _tag = results_object.current['tag'] 
        except Exception:
            return Banner()
    else:
        _run = run
    if not tag:
        try:    
            _tag = results_object[run].tags[-1] 
        except Exception as error:
            if os.path.exists( pjoin(results_object.path,'Events','%s_banner.txt' % (run))):
                tag = None
            else:
                return Banner()      
    else:
        _tag = tag
    

    path = results_object.path    
    if tag:        
        banner_path = pjoin(path,'Events',run,'%s_%s_banner.txt' % (run, tag))
    else:
        banner_path = pjoin(results_object.path,'Events','%s_banner.txt' % (run))
      
    if not os.path.exists(banner_path):
        if level != "parton" and tag != _tag:
            return recover_banner(results_object, level, _run, results_object[_run].tags[0])
        elif level == 'parton':
            paths = [pjoin(path,'Events',run, 'unweighted_events.lhe.gz'),
                     pjoin(path,'Events',run, 'unweighted_events.lhe'),
                     pjoin(path,'Events',run, 'events.lhe.gz'),
                     pjoin(path,'Events',run, 'events.lhe')]
            for p in paths:
                if os.path.exists(p):
                    if MADEVENT:
                        import internal.lhe_parser as lhe_parser
                    else:
                        import madgraph.various.lhe_parser as lhe_parser
                    lhe = lhe_parser.EventFile(p)
                    return Banner(lhe.banner)

        # security if the banner was remove (or program canceled before created it)
        return Banner()  
    
    banner = Banner(banner_path)
    
    
    
    if level == 'pythia':
        if 'mgpythiacard' in banner:
            del banner['mgpythiacard']
    if level in ['pythia','pgs','delphes']:
        for tag in ['mgpgscard', 'mgdelphescard', 'mgdelphestrigger']:
            if tag in banner:
                del banner[tag]
    return banner
    
class InvalidRunCard(InvalidCmd):
    pass

class ProcCard(list):
    """Basic Proccard object"""
    
    history_header = \
        '#************************************************************\n' + \
        '#*                     MadGraph5_aMC@NLO                    *\n' + \
        '#*                                                          *\n' + \
        "#*                *                       *                 *\n" + \
        "#*                  *        * *        *                   *\n" + \
        "#*                    * * * * 5 * * * *                     *\n" + \
        "#*                  *        * *        *                   *\n" + \
        "#*                *                       *                 *\n" + \
        "#*                                                          *\n" + \
        "#*                                                          *\n" + \
        "%(info_line)s" +\
        "#*                                                          *\n" + \
        "#*    The MadGraph5_aMC@NLO Development Team - Find us at   *\n" + \
        "#*    https://server06.fynu.ucl.ac.be/projects/madgraph     *\n" + \
        '#*                                                          *\n' + \
        '#************************************************************\n' + \
        '#*                                                          *\n' + \
        '#*               Command File for MadGraph5_aMC@NLO         *\n' + \
        '#*                                                          *\n' + \
        '#*     run as ./bin/mg5_aMC  filename                       *\n' + \
        '#*                                                          *\n' + \
        '#************************************************************\n'
    
    
    
    
    def __init__(self, init=None):
        """ initialize a basic proc_card"""
        self.info = {'model': 'sm', 'generate':None,
                     'full_model_line':'import model sm'}
        list.__init__(self)
        if init:
            self.read(init)

            
    def read(self, init):
        """read the proc_card and save the information"""
        
        if isinstance(init, str): #path to file
            init = open(init, 'r')
        
        store_line = ''
        for line in init:
            line = line.rstrip()
            if line.endswith('\\'):
                store_line += line[:-1]
            else:
                tmp = store_line + line
                self.append(tmp.strip())
                store_line = ""
        if store_line:
            raise Exception("WRONG CARD FORMAT")
        
        
    def move_to_last(self, cmd):
        """move an element to the last history."""
        for line in self[:]:
            if line.startswith(cmd):
                self.remove(line)
                list.append(self, line)
    
    def append(self, line):
        """"add a line in the proc_card perform automatically cleaning"""
        
        line = line.strip()
        cmds = line.split()
        if len(cmds) == 0:
            return
        
        list.append(self, line)
        
        # command type:
        cmd = cmds[0]
        
        if cmd == 'output':
            # Remove previous outputs from history
            self.clean(allow_for_removal = ['output'], keep_switch=True,
                           remove_bef_last='output')
        elif cmd == 'generate':
            # Remove previous generations from history
            self.clean(remove_bef_last='generate', keep_switch=True,
                     allow_for_removal= ['generate', 'add process', 'output'])
            self.info['generate'] = ' '.join(cmds[1:])
        elif cmd == 'add' and cmds[1] == 'process' and not self.info['generate']:
            self.info['generate'] = ' '.join(cmds[2:])
        elif cmd == 'import':
            if len(cmds) < 2:
                return
            if cmds[1].startswith('model'):
                self.info['full_model_line'] = line
                self.clean(remove_bef_last='import', keep_switch=True,
                        allow_for_removal=['generate', 'add process', 'add model', 'output'])
                if cmds[1] == 'model':
                    self.info['model'] = cmds[2]
                else:
                    self.info['model'] = None # not UFO model
            elif cmds[1] == 'proc_v4':
                #full cleaning
                self[:] = []
                

    def clean(self, to_keep=['set','add','load'],
                            remove_bef_last=None,
                            to_remove=['open','display','launch', 'check','history'],
                            allow_for_removal=None,
                            keep_switch=False):
        """Remove command in arguments from history.
        All command before the last occurrence of  'remove_bef_last'
        (including it) will be removed (but if another options tells the opposite).                
        'to_keep' is a set of line to always keep.
        'to_remove' is a set of line to always remove (don't care about remove_bef_ 
        status but keep_switch acts.).
        if 'allow_for_removal' is define only the command in that list can be 
        remove of the history for older command that remove_bef_lb1. all parameter
        present in to_remove are always remove even if they are not part of this 
        list.
        keep_switch force to keep the statement remove_bef_??? which changes starts
        the removal mode.
        """

        #check consistency
        if __debug__ and allow_for_removal:
            for arg in to_keep:
                assert arg not in allow_for_removal
            
    
        nline = -1
        removal = False
        #looping backward
        while nline > -len(self):
            switch  = False # set in True when removal pass in True

            #check if we need to pass in removal mode
            if not removal and remove_bef_last:
                    if self[nline].startswith(remove_bef_last):
                        removal = True
                        switch = True  

            # if this is the switch and is protected pass to the next element
            if switch and keep_switch:
                nline -= 1
                continue

            # remove command in to_remove (whatever the status of removal)
            if any([self[nline].startswith(arg) for arg in to_remove]):
                self.pop(nline)
                continue
            
            # Only if removal mode is active!
            if removal:
                if allow_for_removal:
                    # Only a subset of command can be removed
                    if any([self[nline].startswith(arg) 
                                                 for arg in allow_for_removal]):
                        self.pop(nline)
                        continue
                elif not any([self[nline].startswith(arg) for arg in to_keep]):
                    # All command have to be remove but protected
                    self.pop(nline)
                    continue
            
            # update the counter to pass to the next element
            nline -= 1
        
    def get(self, tag, default=None):
        if isinstance(tag, int):
            list.__getattr__(self, tag)
        elif tag == 'info' or tag == "__setstate__":
            return default #for pickle
        elif tag == "multiparticles":
            out = []
            for line in self:
                if line.startswith('define'):
                    try:
                        name, content = line[7:].split('=',1)
                    except ValueError:
                        name, content = line[7:].split(None,1)
                    out.append((name, content))
            return out 
        else:
            return self.info[tag]
            
    def write(self, path):
        """write the proc_card to a given path"""
        
        fsock = open(path, 'w')
        fsock.write(self.history_header)
        for line in self:
            while len(line) > 70:
                sub, line = line[:70]+"\\" , line[70:] 
                fsock.write(sub+"\n")
            else:
                fsock.write(line+"\n")
 
class InvalidCardEdition(InvalidCmd): pass 
 
class ConfigFile(dict):
    """ a class for storing/dealing with input file.
    """     

    def __init__(self, finput=None, **opt):
        """initialize a new instance. input can be an instance of MadLoopParam,
        a file, a path to a file, or simply Nothing"""                
        
        if isinstance(finput, self.__class__):
            dict.__init__(self, finput)
            for key in finput.__dict__:
                setattr(self, key, copy.copy(getattr(finput, key)) )
            for key,value in finput.items():
                dict.__setitem__(self, key.lower(), value)
            return
        else:
            dict.__init__(self)
        
        # Initialize it with all the default value
        self.user_set = set()
        self.auto_set = set()
        self.system_only = set()
        self.lower_to_case = {}
        self.list_parameter = {} #key -> type of list (int/float/bool/str/...
        self.dict_parameter = {}
        self.comments = {} # comment associated to parameters. can be display via help message
        # store the valid options for a given parameter.
        self.allowed_value = {}
        
        self.default_setup()
        

        # if input is define read that input
        if isinstance(finput, (file, str, StringIO.StringIO)):
            self.read(finput, **opt)

    def default_setup(self):
        pass

    def __copy__(self):
        return self.__class__(self)

    def __add__(self, other):
        """define the sum"""
        assert isinstance(other, dict)
        base = self.__class__(self)
        #base = copy.copy(self)
        base.update((key.lower(),value) for key, value in other.items())
        
        return base

    def __radd__(self, other):
        """define the sum"""
        new = copy.copy(other)
        new.update((key, value) for key, value in self.items())
        return new
    
    def __contains__(self, key):
        return dict.__contains__(self, key.lower())

    def __iter__(self):
        
        for name in super(ConfigFile, self).__iter__():
            yield self.lower_to_case[name.lower()]
        
        
        #iter = super(ConfigFile, self).__iter__()
        #misc.sprint(iter)
        #return (self.lower_to_case[name] for name in iter)
    
    def keys(self):
        return [name for name in self]
    
    def items(self):
        return [(name,self[name]) for name in self]
        
    @staticmethod
    def warn(text, level, raiseerror=False):
        """convenient proxy to raiseerror/print warning"""

        if raiseerror is True:
            raise InvalidCardEdition(text)
        elif raiseerror:
            raise raiseerror(text)

        if isinstance(level,str):
            log = getattr(logger, level.lower())
        elif isinstance(level, int):
            log = lambda t: logger.log(level, t)
        elif level:
            log = level
        
        return log(text)

    def post_set(self, name, value, change_userdefine, raiseerror):
        
        if value is None:
            value = self[name]

        if hasattr(self, 'post_set_%s' % name):
            return getattr(self, 'post_set_%s' % name)(value, change_userdefine, raiseerror)
    
    def __setitem__(self, name, value, change_userdefine=False,raiseerror=False):
        """set the attribute and set correctly the type if the value is a string.
           change_userdefine on True if we have to add the parameter in user_set
        """
                       
        if  not len(self):
            #Should never happen but when deepcopy/pickle
            self.__init__()
                
        name = name.strip()
        lower_name = name.lower() 
        
        # 0. check if this parameter is a system only one
        if change_userdefine and lower_name in self.system_only:
            text='%s is a private entry which can not be modify by the user. Keep value at %s' % (name,self[name])
            self.warn(text, 'critical', raiseerror)
            return
        
        #1. check if the parameter is set to auto -> pass it to special
        if lower_name in self:
            targettype = type(dict.__getitem__(self, lower_name))
            if targettype != str and isinstance(value, str) and value.lower() == 'auto':
                self.auto_set.add(lower_name)
                if lower_name in self.user_set:
                    self.user_set.remove(lower_name)
                #keep old value.
                self.post_set(lower_name, 'auto', change_userdefine, raiseerror)
                return 
            elif lower_name in self.auto_set:
                self.auto_set.remove(lower_name)
            
        # 2. Find the type of the attribute that we want
        if lower_name in self.list_parameter:
            targettype = self.list_parameter[lower_name]
            
            
            
            if isinstance(value, str):
                # split for each comma/space
                value = value.strip()
                if value.startswith('[') and value.endswith(']'):
                    value = value[1:-1]
                #do not perform split within a " or ' block  
                data = re.split(r"((?<![\\])['\"])((?:.(?!(?<![\\])\1))*.?)\1", str(value))
                new_value = []
                i = 0
                while len(data) > i:
                    current = [_f for _f in re.split(r'(?:(?<!\\)\s)|,', data[i]) if _f]
                    i+=1
                    if len(data) > i+1:
                        if current:
                            current[-1] += '{0}{1}{0}'.format(data[i], data[i+1])
                        else:
                            current = ['{0}{1}{0}'.format(data[i], data[i+1])]
                        i+=2
                    new_value += current
 
                value = new_value                           
                
            elif not hasattr(value, '__iter__'):
                value = [value]
            elif isinstance(value, dict):
                text = "not being able to handle dictionary in card entry"
                return self.warn(text, 'critical', raiseerror)

            #format each entry    
            values =[self.format_variable(v, targettype, name=name) 
                                                                 for v in value]
            
            # ensure that each entry are in the allowed list
            if lower_name in self.allowed_value and '*' not in self.allowed_value[lower_name]:
                new_values = []
                dropped = []
                for val in values:
                    allowed = self.allowed_value[lower_name]
            
                    if val in allowed:
                        new_values.append(val)
                        continue
                    elif isinstance(val, str):
                        val = val.lower()
                        allowed = allowed.lower()
                        if value in allowed:
                            i = allowed.index(value)
                            new_values.append(self.allowed_value[i])
                            continue
                    # no continue -> bad input
                    dropped.append(val)
                    
                if not new_values:

                    text= "value '%s' for entry '%s' is not valid.  Preserving previous value: '%s'.\n" \
                               % (value, name, self[lower_name])
                    text += "allowed values are any list composed of the following entry: %s" % ', '.join([str(i) for i in self.allowed_value[lower_name]])
                    return self.warn(text, 'warning', raiseerror)                    
                elif dropped:               
                    text = "some value for entry '%s' are not valid. Invalid item are: '%s'.\n" \
                               % (value, name, dropped)
                    text += "value will be set to %s" % new_values
                    text += "allowed items in the list are: %s" % ', '.join([str(i) for i in self.allowed_value[lower_name]])        
                    self.warn(text, 'warning')

                values = new_values

            # make the assignment
            dict.__setitem__(self, lower_name, values) 
            if change_userdefine:
                self.user_set.add(lower_name)
            #check for specific action
            return self.post_set(lower_name, None, change_userdefine, raiseerror) 
        elif lower_name in self.dict_parameter:
            targettype = self.dict_parameter[lower_name] 
            full_reset = True #check if we just update the current dict or not
            
            if isinstance(value, str):
                value = value.strip()
                # allowed entry:
                #   name : value   => just add the entry
                #   name , value   => just add the entry
                #   name  value    => just add the entry
                #   {name1:value1, name2:value2}   => full reset
                
                # split for each comma/space
                if value.startswith('{') and value.endswith('}'):
                    new_value = {}
                    for pair in value[1:-1].split(','):
                        if not pair.strip():
                            break
                        x, y = pair.split(':')
                        x, y = x.strip(), y.strip()
                        if x.startswith(('"',"'")) and x.endswith(x[0]):
                            x = x[1:-1] 
                        new_value[x] = y
                    value = new_value
                elif ',' in value:
                    x,y = value.split(',')
                    value = {x.strip():y.strip()}
                    full_reset = False
                    
                elif ':' in value:
                    x,y = value.split(':')
                    value = {x.strip():y.strip()}
                    full_reset = False       
                else:
                    x,y = value.split()
                    value = {x:y}
                    full_reset = False 
            
            if isinstance(value, dict):
                for key in value:
                    value[key] = self.format_variable(value[key], targettype, name=name)
                if full_reset:
                    dict.__setitem__(self, lower_name, value)
                else:
                    dict.__getitem__(self, lower_name).update(value)
            else:
                raise Exception('%s should be of dict type'% lower_name)
            if change_userdefine:
                self.user_set.add(lower_name)
            return self.post_set(lower_name, None, change_userdefine, raiseerror)
        elif name in self:            
            targettype = type(self[name])
        else:
            logger.debug('Trying to add argument %s in %s. ' % (name, self.__class__.__name__) +\
              'This argument is not defined by default. Please consider adding it.')
            suggestions = [k for k in self.keys() if k.startswith(name[0].lower())]
            if len(suggestions)>0:
                logger.debug("Did you mean one of the following: %s"%suggestions)
            self.add_param(lower_name, self.format_variable(UnknownType(value), 
                                                             UnknownType, name))
            self.lower_to_case[lower_name] = name
            if change_userdefine:
                self.user_set.add(lower_name)
            return self.post_set(lower_name, None, change_userdefine, raiseerror)
    
        value = self.format_variable(value, targettype, name=name)
        #check that the value is allowed:
        if lower_name in self.allowed_value and '*' not in self.allowed_value[lower_name]:
            valid = False
            allowed = self.allowed_value[lower_name]
            
            # check if the current value is allowed or not (set valid to True)
            if value in allowed:
                valid=True     
            elif isinstance(value, str):
                value = value.lower().strip()
                allowed = [str(v).lower() for v in allowed]
                if value in allowed:
                    i = allowed.index(value)
                    value = self.allowed_value[lower_name][i]
                    valid=True
                    
            if not valid:
                # act if not valid:
                text = "value '%s' for entry '%s' is not valid.  Preserving previous value: '%s'.\n" \
                               % (value, name, self[lower_name])
                text += "allowed values are %s\n" % ', '.join([str(i) for i in self.allowed_value[lower_name]])
                if lower_name in self.comments:
                    text += 'type "help %s" for more information' % name
                return self.warn(text, 'warning', raiseerror)

        dict.__setitem__(self, lower_name, value)
        if change_userdefine:
            self.user_set.add(lower_name)
        self.post_set(lower_name, None, change_userdefine, raiseerror)


    def add_param(self, name, value, system=False, comment=False, typelist=None,
                  allowed=[]):
        """add a default parameter to the class"""

        lower_name = name.lower()
        if __debug__:
            if lower_name in self:
                raise Exception("Duplicate case for %s in %s" % (name,self.__class__))
        
        dict.__setitem__(self, lower_name, value)
        self.lower_to_case[lower_name] = name
        if isinstance(value, list):
            if len(value):
                targettype = type(value[0])
            else:
                targettype=typelist
                assert typelist
            if any([targettype != type(v) for v in value]):
                raise Exception("All entry should have the same type")
            self.list_parameter[lower_name] = targettype
        elif isinstance(value, dict):
            allvalues = list(value.values())
            if any([type(allvalues[0]) != type(v) for v in allvalues]):
                raise Exception("All entry should have the same type")   
            self.dict_parameter[lower_name] = type(allvalues[0])  
            if '__type__' in value:
                del value['__type__']
                dict.__setitem__(self, lower_name, value)
        
        if allowed and allowed != ['*']:
            self.allowed_value[lower_name] = allowed
            assert value in allowed or '*' in allowed
        #elif isinstance(value, bool) and allowed != ['*']:
        #    self.allowed_value[name] = [True, False]
        
             
        if system:
            self.system_only.add(lower_name)
        if comment:
            self.comments[lower_name] = comment

    def do_help(self, name):
        """return a minimal help for the parameter"""
        
        out = "## Information on parameter %s from class %s\n" % (name, self.__class__.__name__)
        if name.lower() in self:
            out += "## current value: %s (parameter should be of type %s)\n" % (self[name], type(self[name]))
            if name.lower() in self.comments:
                out += '## %s\n' % self.comments[name.lower()].replace('\n', '\n## ')
        else:
            out += "## Unknown for this class\n"
        if name.lower() in self.user_set:
            out += "## This value is considered as been set by the user\n" 
        else:
            out += "## This value is considered as been set by the system\n"
        if name.lower() in self.allowed_value:
            if '*' not in self.allowed_value[name.lower()]:
                out += "Allowed value are: %s\n" % ','.join([str(p) for p in self.allowed_value[name.lower()]])
            else:
                out += "Suggested value are : %s\n " % ','.join([str(p) for p in self.allowed_value[name.lower()] if p!='*'])
        
        logger.info(out)
        return out

    @staticmethod
    def format_variable(value, targettype, name="unknown"):
        """assign the value to the attribute for the given format"""
        
        if (six.PY2 and not isinstance(value, (str,six.text_type)) or (six.PY3 and  not isinstance(value, str))):
            # just have to check that we have the correct format
            if isinstance(value, targettype):
                pass # assignement at the end
            elif isinstance(value, numbers.Number) and issubclass(targettype, numbers.Number):
                try:
                    new_value = targettype(value)
                except TypeError:
                    if value.imag/value.real<1e-12:
                        new_value = targettype(value.real)
                    else:
                        raise
                if new_value == value:
                    value = new_value
                else:
                    raise InvalidCmd("Wrong input type for %s found %s and expecting %s for value %s" %\
                        (name, type(value), targettype, value))
            else:
                raise InvalidCmd("Wrong input type for %s found %s and expecting %s for value %s" %\
                        (name, type(value), targettype, value))                
        else:
            # We have a string we have to format the attribute from the string
            if targettype == UnknownType:
                # No formatting
                pass
            elif targettype == bool:
                value = value.strip()
                if value.lower() in ['0', '.false.', 'f', 'false', 'off']:
                    value = False
                elif value.lower() in ['1', '.true.', 't', 'true', 'on']:
                    value = True
                else:
                    raise InvalidCmd("%s can not be mapped to True/False for %s" % (repr(value),name))
            elif targettype == str:
                value = value.strip()
                if value.startswith('\'') and value.endswith('\''):
                    value = value[1:-1]
                elif value.startswith('"') and value.endswith('"'):
                    value = value[1:-1]
            elif targettype == int:
                if value.isdigit():
                    value = int(value)
                elif value[1:].isdigit() and value[0] == '-':
                    value = int(value)
                elif value.endswith(('k', 'M')) and value[:-1].isdigit():
                    convert = {'k':1000, 'M':1000000}
                    value =int(value[:-1]) * convert[value[-1]] 
                elif '/' in value or '*' in value:               
                    try:
                        split = re.split('(\*|/)',value)
                        v = float(split[0])
                        for i in range((len(split)//2)):
                            if split[2*i+1] == '*':
                                v *=  float(split[2*i+2])
                            else:
                                v /=  float(split[2*i+2])
                    except:
                        v=0
                    finally:
                        value = int(v)
                        if value != v:
                            raise InvalidCmd( "%s can not be mapped to an integer" % v)
                else:
                    try:
                        value = float(value.replace('d','e'))
                    except ValueError:
                        raise InvalidCmd("%s can not be mapped to an integer" % value)                    
                    try:
                        new_value = int(value)
                    except ValueError:
                        raise InvalidCmd( "%s can not be mapped to an integer" % value)
                    else:
                        if value == new_value:
                            value = new_value
                        else:
                            raise InvalidCmd("incorect input: %s need an integer for %s" % (value,name))
                     
            elif targettype == float:
                if value.endswith(('k', 'M')) and value[:-1].isdigit():
                    convert = {'k':1000, 'M':1000000}
                    value = 1.*int(value[:-1]) * convert[value[-1]]
                else:
                    value = value.replace('d','e') # pass from Fortran formatting
                    try:
                        value = float(value)
                    except ValueError:
                        try:
                            split = re.split('(\*|/)',value)
                            v = float(split[0])
                            for i in range((len(split)//2)):
                                if split[2*i+1] == '*':
                                    v *=  float(split[2*i+2])
                                else:
                                    v /=  float(split[2*i+2])
                        except:
                            v=0
                            raise InvalidCmd("%s can not be mapped to a float" % value)
                        finally:
                            value = v
            else:
                raise InvalidCmd("type %s is not handle by the card" % targettype)
            
        return value
            
 

    def __getitem__(self, name):
        
        lower_name = name.lower()
        if __debug__:
            if lower_name not in self:
                if lower_name in [key.lower() for key in self] :
                    raise Exception("Some key are not lower case %s. Invalid use of the class!"\
                                     % [key for key in self if key.lower() != key])
        
        if lower_name in self.auto_set:
            return 'auto'

        return dict.__getitem__(self, name.lower())

    
    def set(self, name, value, changeifuserset=True, user=False, raiseerror=False):
        """convenient way to change attribute.
        changeifuserset=False means that the value is NOT change is the value is not on default.
        user=True, means that the value will be marked as modified by the user 
        (potentially preventing future change to the value) 
        """

        # changeifuserset=False -> we need to check if the user force a value.
        if not changeifuserset:
            if name.lower() in self.user_set:
                #value modified by the user -> do nothing
                return
            
        self.__setitem__(name, value, change_userdefine=user, raiseerror=raiseerror) 
 


class ProcCharacteristic(ConfigFile):
    """A class to handle information which are passed from MadGraph to the madevent
       interface.""" 
     
    def default_setup(self):
        """initialize the directory to the default value"""
        
        self.add_param('loop_induced', False)
        self.add_param('has_isr', False)
        self.add_param('has_fsr', False)
        self.add_param('nb_channel', 0)
        self.add_param('nexternal', 0)
        self.add_param('ninitial', 0)
        self.add_param('grouped_matrix', True)
        self.add_param('has_loops', False)
        self.add_param('bias_module','None')
        self.add_param('max_n_matched_jets', 0)
        self.add_param('colored_pdgs', [1,2,3,4,5])
        self.add_param('complex_mass_scheme', False)
        self.add_param('pdg_initial1', [0])
        self.add_param('pdg_initial2', [0])
        self.add_param('limitations', [], typelist=str)        

    def read(self, finput):
        """Read the input file, this can be a path to a file, 
           a file object, a str with the content of the file."""
           
        if isinstance(finput, str):
            if "\n" in finput:
                finput = finput.split('\n')
            elif os.path.isfile(finput):
                finput = open(finput)
            else:
                raise Exception("No such file %s" % finput)
            
        for line in finput:
            if '#' in line:
                line = line.split('#',1)[0]
            if not line:
                continue
            
            if '=' in line:
                key, value = line.split('=',1)
                self[key.strip()] = value
         
    def write(self, outputpath):
        """write the file"""

        template ="#    Information about the process      #\n"
        template +="#########################################\n"
        
        fsock = open(outputpath, 'w')
        fsock.write(template)
        
        for key, value in self.items():
            fsock.write(" %s = %s \n" % (key, value))
        
        fsock.close()   
 



class GridpackCard(ConfigFile):
    """an object for the GridpackCard"""
    
    def default_setup(self):
        """default value for the GridpackCard"""
    
        self.add_param("GridRun", True)
        self.add_param("gevents", 2500)
        self.add_param("gseed", 1)
        self.add_param("ngran", -1)  
 
    def read(self, finput):
        """Read the input file, this can be a path to a file, 
           a file object, a str with the content of the file."""
           
        if isinstance(finput, str):
            if "\n" in finput:
                finput = finput.split('\n')
            elif os.path.isfile(finput):
                finput = open(finput)
            else:
                raise Exception("No such file %s" % finput)
        
        for line in finput:
            line = line.split('#')[0]
            line = line.split('!')[0]
            line = line.split('=',1)
            if len(line) != 2:
                continue
            self[line[1].strip()] = line[0].replace('\'','').strip()

    def write(self, output_file, template=None):
        """Write the run_card in output_file according to template 
           (a path to a valid run_card)"""

        if not template:
            if not MADEVENT:
                template = pjoin(MG5DIR, 'Template', 'LO', 'Cards', 
                                                        'grid_card_default.dat')
            else:
                template = pjoin(MEDIR, 'Cards', 'grid_card_default.dat')

                
        text = ""
        for line in open(template,'r'):                  
            nline = line.split('#')[0]
            nline = nline.split('!')[0]
            comment = line[len(nline):]
            nline = nline.split('=')
            if len(nline) != 2:
                text += line
            elif nline[1].strip() in self:
                text += '  %s\t= %s %s' % (self[nline[1].strip()],nline[1], comment)        
            else:
                logger.info('Adding missing parameter %s to current run_card (with default value)' % nline[1].strip())
                text += line 
        
        if isinstance(output_file, str):
            fsock =  open(output_file,'w')
        else:
            fsock = output_file
            
        fsock.write(text)
        fsock.close()
        
class PY8Card(ConfigFile):
    """ Implements the Pythia8 card."""

    def add_default_subruns(self, type):
        """ Placeholder function to allow overwriting in the PY8SubRun daughter.
        The initialization of the self.subruns attribute should of course not
        be performed in PY8SubRun."""
        if type == 'parameters':
            if "LHEFInputs:nSubruns" not in self:
                self.add_param("LHEFInputs:nSubruns", 1,
                hidden='ALWAYS_WRITTEN',
                comment="""
    ====================
    Subrun definitions
    ====================
    """)
        if type == 'attributes':
            if not(hasattr(self,'subruns')):
                first_subrun = PY8SubRun(subrun_id=0)
                self.subruns = dict([(first_subrun['Main:subrun'],first_subrun)])

    def default_setup(self):
        """ Sets up the list of available PY8 parameters."""
        
        # Visible parameters
        # ==================
        self.add_param("Main:numberOfEvents", -1)
        # for MLM merging
        # -1.0 means that it will be set automatically by MadGraph5_aMC@NLO
        self.add_param("JetMatching:qCut", -1.0, always_write_to_card=False)
        self.add_param("JetMatching:doShowerKt",False,always_write_to_card=False)
        # -1 means that it is automatically set.
        self.add_param("JetMatching:nJetMax", -1, always_write_to_card=False) 
        # for CKKWL merging
        self.add_param("Merging:TMS", -1.0, always_write_to_card=False)
        self.add_param("Merging:Process", '<set_by_user>', always_write_to_card=False)
        # -1 means that it is automatically set.   
        self.add_param("Merging:nJetMax", -1, always_write_to_card=False)
        # for both merging, chose whether to also consider different merging
        # scale values for the extra weights related to scale and PDF variations.
        self.add_param("SysCalc:fullCutVariation", False)
        # Select the HepMC output. The user can prepend 'fifo:<optional_fifo_path>'
        # to indicate that he wants to pipe the output. Or /dev/null to turn the
        # output off.
        self.add_param("HEPMCoutput:file", 'auto')

        # Hidden parameters always written out
        # ====================================
        self.add_param("Beams:frameType", 4,
            hidden=True,
            comment='Tell Pythia8 that an LHEF input is used.')
        self.add_param("HEPMCoutput:scaling", 1.0e9,
            hidden=True,
            comment='1.0 corresponds to HEPMC weight given in [mb]. We choose here the [pb] normalization.')
        self.add_param("Check:epTolErr", 1e-2,
            hidden=True,
            comment='Be more forgiving with momentum mismatches.')
        # By default it is important to disable any cut on the rapidity of the showered jets
        # during MLML merging and by default it is set to 2.5
        self.add_param("JetMatching:etaJetMax", 1000.0, hidden=True, always_write_to_card=True)

        # Hidden parameters written out only if user_set or system_set
        # ============================================================
        self.add_param("PDF:pSet", 'LHAPDF5:CT10.LHgrid', hidden=True, always_write_to_card=False,
            comment='Reminder: Parameter below is shower tune dependent.')
        self.add_param("SpaceShower:alphaSvalue", 0.118, hidden=True, always_write_to_card=False,
            comment='Reminder: Parameter below is shower tune dependent.')
        self.add_param("TimeShower:alphaSvalue", 0.118, hidden=True, always_write_to_card=False,
            comment='Reminder: Parameter below is shower tune dependent.')
        self.add_param("hadronlevel:all", True, hidden=True, always_write_to_card=False,
            comment='This allows to turn on/off hadronization alltogether.')
        self.add_param("partonlevel:mpi", True, hidden=True, always_write_to_card=False,
            comment='This allows to turn on/off MPI alltogether.')
        self.add_param("Beams:setProductionScalesFromLHEF", False, hidden=True, 
            always_write_to_card=False,
            comment='This parameter is automatically set to True by MG5aMC when doing MLM merging with PY8.')
        
        # for MLM merging
        self.add_param("JetMatching:merge", False, hidden=True, always_write_to_card=False,
          comment='Specifiy if we are merging sample of different multiplicity.')
        self.add_param("SysCalc:qCutList", [10.0,20.0], hidden=True, always_write_to_card=False)
        self['SysCalc:qCutList'] = 'auto'
        self.add_param("SysCalc:qWeed",-1.0,hidden=True, always_write_to_card=False,
          comment='Value of the merging scale below which one does not even write the HepMC event.')
        self.add_param("JetMatching:doVeto", False, hidden=True, always_write_to_card=False,
          comment='Do veto externally (e.g. in SysCalc).')
        self.add_param("JetMatching:scheme", 1, hidden=True, always_write_to_card=False) 
        self.add_param("JetMatching:setMad", False, hidden=True, always_write_to_card=False,
              comment='Specify one must read inputs from the MadGraph banner.') 
        self.add_param("JetMatching:coneRadius", 1.0, hidden=True, always_write_to_card=False)
        self.add_param("JetMatching:nQmatch",4,hidden=True, always_write_to_card=False)
        # for CKKWL merging (common with UMEPS, UNLOPS)
        self.add_param("TimeShower:pTmaxMatch", 2, hidden=True, always_write_to_card=False)
        self.add_param("SpaceShower:pTmaxMatch", 1, hidden=True, always_write_to_card=False)
        self.add_param("SysCalc:tmsList", [10.0,20.0], hidden=True, always_write_to_card=False)
        self['SysCalc:tmsList'] = 'auto'
        self.add_param("Merging:muFac", 91.188, hidden=True, always_write_to_card=False,
                        comment='Set factorisation scales of the 2->2 process.')
        self.add_param("Merging:applyVeto", False, hidden=True, always_write_to_card=False,
          comment='Do veto externally (e.g. in SysCalc).')
        self.add_param("Merging:includeWeightInXsection", True, hidden=True, always_write_to_card=False,
          comment='If turned off, then the option belows forces PY8 to keep the original weight.')                       
        self.add_param("Merging:muRen", 91.188, hidden=True, always_write_to_card=False,
                      comment='Set renormalization scales of the 2->2 process.')
        self.add_param("Merging:muFacInME", 91.188, hidden=True, always_write_to_card=False,
                 comment='Set factorisation scales of the 2->2 Matrix Element.')
        self.add_param("Merging:muRenInME", 91.188, hidden=True, always_write_to_card=False,
               comment='Set renormalization scales of the 2->2 Matrix Element.')
        self.add_param("SpaceShower:rapidityOrder", False, hidden=True, always_write_to_card=False)
        self.add_param("Merging:nQuarksMerge",4,hidden=True, always_write_to_card=False)
        # To be added in subruns for CKKWL
        self.add_param("Merging:mayRemoveDecayProducts", False, hidden=True, always_write_to_card=False)
        self.add_param("Merging:doKTMerging", False, hidden=True, always_write_to_card=False)
        self.add_param("Merging:Dparameter", 0.4, hidden=True, always_write_to_card=False)        
        self.add_param("Merging:doPTLundMerging", False, hidden=True, always_write_to_card=False)

        # Special Pythia8 paremeters useful to simplify the shower.
        self.add_param("BeamRemnants:primordialKT", True, hidden=True, always_write_to_card=False, comment="see http://home.thep.lu.se/~torbjorn/pythia82html/BeamRemnants.html")
        self.add_param("PartonLevel:Remnants", True, hidden=True, always_write_to_card=False, comment="Master switch for addition of beam remnants. Cannot be used to generate complete events")
        self.add_param("Check:event", True, hidden=True, always_write_to_card=False, comment="check physical sanity of the events")
        self.add_param("TimeShower:QEDshowerByQ", True, hidden=True, always_write_to_card=False, comment="Allow quarks to radiate photons for FSR, i.e. branchings q -> q gamma")
        self.add_param("TimeShower:QEDshowerByL", True, hidden=True, always_write_to_card=False, comment="Allow leptons to radiate photons for FSR, i.e. branchings l -> l gamma")
        self.add_param("SpaceShower:QEDshowerByQ", True, hidden=True, always_write_to_card=False, comment="Allow quarks to radiate photons for ISR, i.e. branchings q -> q gamma")
        self.add_param("SpaceShower:QEDshowerByL", True, hidden=True, always_write_to_card=False, comment="Allow leptons to radiate photonsfor ISR, i.e. branchings l -> l gamma")
        self.add_param("PartonLevel:FSRinResonances", True, hidden=True, always_write_to_card=False, comment="Do not allow shower to run from decay product of unstable particle")
        self.add_param("ProcessLevel:resonanceDecays", True, hidden=True, always_write_to_card=False, comment="Do not allow unstable particle to decay.")

        # Add parameters controlling the subruns execution flow.
        # These parameters should not be part of PY8SubRun daughter.
        self.add_default_subruns('parameters')
             
    def __init__(self, *args, **opts):
        # Parameters which are not printed in the card unless they are 
        # 'user_set' or 'system_set' or part of the 
        #  self.hidden_params_to_always_print set.
        self.hidden_param = []
        self.hidden_params_to_always_write = set()
        self.visible_params_to_always_write = set()
        # List of parameters that should never be written out given the current context.
        self.params_to_never_write = set()
        
        # Parameters which have been set by the system (i.e. MG5 itself during
        # the regular course of the shower interface)
        self.system_set = set()
        
        # Add attributes controlling the subruns execution flow.
        # These attributes should not be part of PY8SubRun daughter.
        self.add_default_subruns('attributes')
        
        # Parameters which have been set by the 
        super(PY8Card, self).__init__(*args, **opts)

    def add_param(self, name, value, hidden=False, always_write_to_card=True, 
                                                                  comment=None):
        """ add a parameter to the card. value is the default value and 
        defines the type (int/float/bool/str) of the input.
        The option 'hidden' decides whether the parameter should be visible to the user.
        The option 'always_write_to_card' decides whether it should
        always be printed or only when it is system_set or user_set.
        The option 'comment' can be used to specify a comment to write above
        hidden parameters.
        """
        super(PY8Card, self).add_param(name, value, comment=comment)
        name = name.lower()
        if hidden:
            self.hidden_param.append(name)
            if always_write_to_card:
                self.hidden_params_to_always_write.add(name)
        else:
            if always_write_to_card:
                self.visible_params_to_always_write.add(name)                
        if not comment is None:
            if not isinstance(comment, str):
                raise MadGraph5Error("Option 'comment' must be a string, not"+\
                                                          " '%s'."%str(comment))

    def add_subrun(self, py8_subrun):
        """Add a subrun to this PY8 Card."""
        assert(isinstance(py8_subrun,PY8SubRun))
        if py8_subrun['Main:subrun']==-1:
            raise MadGraph5Error("Make sure to correctly set the subrun ID"+\
                            " 'Main:subrun' *before* adding it to the PY8 Card.")
        if py8_subrun['Main:subrun'] in self.subruns:
            raise MadGraph5Error("A subrun with ID '%s'"%py8_subrun['Main:subrun']+\
                " is already present in this PY8 card. Remove it first, or "+\
                                                          " access it directly.")
        self.subruns[py8_subrun['Main:subrun']] = py8_subrun
        if not 'LHEFInputs:nSubruns' in self.user_set:
            self['LHEFInputs:nSubruns'] = max(self.subruns.keys())
        
    def userSet(self, name, value, **opts):
        """Set an attribute of this card, following a user_request"""
        self.__setitem__(name, value, change_userdefine=True, **opts)
        if name.lower() in self.system_set:
            self.system_set.remove(name.lower())

    def vetoParamWriteOut(self, name):
        """ Forbid the writeout of a specific parameter of this card when the 
        "write" function will be invoked."""
        self.params_to_never_write.add(name.lower())
    
    def systemSet(self, name, value, **opts):
        """Set an attribute of this card, independently of a specific user
        request and only if not already user_set."""
        try:
            force = opts.pop('force')
        except KeyError:
            force = False
        if force or name.lower() not in self.user_set:
            self.__setitem__(name, value, change_userdefine=False, **opts)
            self.system_set.add(name.lower())
    
    def MadGraphSet(self, name, value, **opts):
        """ Sets a card attribute, but only if it is absent or not already
        user_set."""
        try:
            force = opts.pop('force')
        except KeyError:
            force = False
        if name.lower() not in self or (force or name.lower() not in self.user_set):
            self.__setitem__(name, value, change_userdefine=False, **opts)
            self.system_set.add(name.lower())            
    
    def defaultSet(self, name, value, **opts):
            self.__setitem__(name, value, change_userdefine=False, **opts)
        
    @staticmethod
    def pythia8_formatting(value, formatv=None):
        """format the variable into pythia8 card convention.
        The type is detected by default"""
        if not formatv:
            if isinstance(value,UnknownType):
                formatv = 'unknown'                
            elif isinstance(value, bool):
                formatv = 'bool'
            elif isinstance(value, int):
                formatv = 'int'
            elif isinstance(value, float):
                formatv = 'float'
            elif isinstance(value, str):
                formatv = 'str'
            elif isinstance(value, list):
                formatv = 'list'
            else:
                logger.debug("unknow format for pythia8_formatting: %s" , value)
                formatv = 'str'
        else:
            assert formatv
            
        if formatv == 'unknown':
            # No formatting then
            return str(value)
        if formatv == 'bool':
            if str(value) in ['1','T','.true.','True','on']:
                return 'on'
            else:
                return 'off'
        elif formatv == 'int':
            try:
                return str(int(value))
            except ValueError:
                fl = float(value)
                if int(fl) == fl:
                    return str(int(fl))
                else:
                    raise
        elif formatv == 'float':
            return '%.10e' % float(value)
        elif formatv == 'shortfloat':
            return '%.3f' % float(value)        
        elif formatv == 'str':
            return "%s" % value
        elif formatv == 'list':
            if len(value) and isinstance(value[0],float):
                return ','.join([PY8Card.pythia8_formatting(arg, 'shortfloat') for arg in value])
            else:
                return ','.join([PY8Card.pythia8_formatting(arg) for arg in value])
            

    def write(self, output_file, template, read_subrun=False, 
                    print_only_visible=False, direct_pythia_input=False, add_missing=True):
        """ Write the card to output_file using a specific template.
        > 'print_only_visible' specifies whether or not the hidden parameters
            should be written out if they are in the hidden_params_to_always_write
            list and system_set.
        > If 'direct_pythia_input' is true, then visible parameters which are not
          in the self.visible_params_to_always_write list and are not user_set
          or system_set are commented.
        > If 'add_missing' is False then parameters that should be written_out but are absent
        from the template will not be written out."""

        # First list the visible parameters
        visible_param = [p for p in self if p.lower() not in self.hidden_param
                                                  or p.lower() in self.user_set]
        # Filter against list of parameters vetoed for write-out
        visible_param = [p for p in visible_param if p.lower() not in self.params_to_never_write]
        
        # Now the hidden param which must be written out
        if print_only_visible:
            hidden_output_param = []
        else:
            hidden_output_param = [p for p in self if p.lower() in self.hidden_param and
              not p.lower() in self.user_set and
              (p.lower() in self.hidden_params_to_always_write or 
                                                  p.lower() in self.system_set)]
        # Filter against list of parameters vetoed for write-out
        hidden_output_param = [p for p in hidden_output_param if p not in self.params_to_never_write]
        
        if print_only_visible:
            subruns = []
        else:
            if not read_subrun:
                subruns = sorted(self.subruns.keys())
        
        # Store the subruns to write in a dictionary, with its ID in key
        # and the corresponding stringstream in value
        subruns_to_write = {}
        
        # Sort these parameters nicely so as to put together parameters
        # belonging to the same group (i.e. prefix before the ':' in their name).
        def group_params(params):
            if len(params)==0:
                return []
            groups = {}
            for p in params:
                try:
                    groups[':'.join(p.split(':')[:-1])].append(p)
                except KeyError:
                    groups[':'.join(p.split(':')[:-1])] = [p,]
            res =  sum(list(groups.values()),[])
            # Make sure 'Main:subrun' appears first
            if 'Main:subrun' in res:
                res.insert(0,res.pop(res.index('Main:subrun')))
            # Make sure 'LHEFInputs:nSubruns' appears last
            if 'LHEFInputs:nSubruns' in res:
                res.append(res.pop(res.index('LHEFInputs:nSubruns')))
            return res

        visible_param       = group_params(visible_param)
        hidden_output_param = group_params(hidden_output_param)

        # First dump in a temporary_output (might need to have a second pass
        # at the very end to update 'LHEFInputs:nSubruns')
        output = StringIO.StringIO()
            
        # Setup template from which to read
        if isinstance(template, str):
            if os.path.isfile(template):
                tmpl = open(template, 'r')
            elif '\n' in template:
                tmpl = StringIO.StringIO(template)
            else:
                raise Exception("File input '%s' not found." % file_input)     
        elif template is None:
            # Then use a dummy empty StringIO, hence skipping the reading
            tmpl = StringIO.StringIO()
        elif isinstance(template, (StringIO.StringIO, file)):
            tmpl = template
        else:
            raise MadGraph5Error("Incorrect type for argument 'template': %s"%
                                                    template.__class__.__name__)

        # Read the template
        last_pos = tmpl.tell()
        line     = tmpl.readline()
        started_subrun_reading = False
        while line!='':
            # Skip comments
            if line.strip().startswith('!') or \
               line.strip().startswith('\n') or\
               line.strip() == '':
                output.write(line)
                # Proceed to next line
                last_pos = tmpl.tell()
                line     = tmpl.readline()
                continue
            # Read parameter
            try:
                param_entry, value_entry = line.split('=')
                param = param_entry.strip()
                value = value_entry.strip()
            except ValueError:
                line = line.replace('\n','')
                raise MadGraph5Error("Could not read line '%s' of Pythia8 card."%\
                                                                            line)
            # Read a subrun if detected:
            if param=='Main:subrun':
                if read_subrun:
                    if not started_subrun_reading:
                        # Record that the subrun reading has started and proceed
                        started_subrun_reading = True
                    else:
                        # We encountered the next subrun. rewind last line and exit
                        tmpl.seek(last_pos)
                        break
                else:
                    # Start the reading of this subrun
                    tmpl.seek(last_pos)
                    subruns_to_write[int(value)] = StringIO.StringIO()
                    if int(value) in subruns:
                        self.subruns[int(value)].write(subruns_to_write[int(value)],
                                                      tmpl,read_subrun=True)
                        # Remove this subrun ID from the list
                        subruns.pop(subruns.index(int(value)))
                    else:
                        # Unknow subrun, create a dummy one
                        DummySubrun=PY8SubRun()
                        # Remove all of its variables (so that nothing is overwritten)
                        DummySubrun.clear()
                        DummySubrun.write(subruns_to_write[int(value)],
                                tmpl, read_subrun=True, 
                                print_only_visible=print_only_visible, 
                                direct_pythia_input=direct_pythia_input)

                        logger.info('Adding new unknown subrun with ID %d.'%
                                                                     int(value))
                    # Proceed to next line
                    last_pos = tmpl.tell()
                    line     = tmpl.readline()
                    continue
            
            # Change parameters which must be output
            if param in visible_param:
                new_value = PY8Card.pythia8_formatting(self[param])
                visible_param.pop(visible_param.index(param))
            elif param in hidden_output_param:
                new_value = PY8Card.pythia8_formatting(self[param])
                hidden_output_param.pop(hidden_output_param.index(param))
            else:
                # Just copy parameters which don't need to be specified
                if param.lower() not in self.params_to_never_write:
                    output.write(line)
                else:
                    output.write('! The following parameter was forced to be commented out by MG5aMC.\n')
                    output.write('! %s'%line)
                # Proceed to next line
                last_pos = tmpl.tell()
                line     = tmpl.readline()
                continue
            
            # Substitute the value. 
            # If it is directly the pytia input, then don't write the param if it
            # is not in the list of visible_params_to_always_write and was 
            # not user_set or system_set
            if ((not direct_pythia_input) or
                  (param.lower() in self.visible_params_to_always_write) or
                  (param.lower() in self.user_set) or
                  (param.lower() in self.system_set)):
                template = '%s=%s'
            else:
                # These are parameters that the user can edit in AskEditCards
                # but if neither the user nor the system edited them,
                # then they shouldn't be passed to Pythia
                template = '!%s=%s'

            output.write(template%(param_entry,
                                  value_entry.replace(value,new_value)))
        
            # Proceed to next line
            last_pos = tmpl.tell()
            line     = tmpl.readline()
        
        # If add_missing is False, make sure to empty the list of remaining parameters
        if not add_missing:
            visible_param = []
            hidden_output_param = []
        
        # Now output the missing parameters. Warn about visible ones.
        if len(visible_param)>0 and not template is None:
            output.write(
"""!
! Additional general parameters%s.
!
"""%(' for subrun %d'%self['Main:subrun'] if 'Main:subrun' in self else ''))
        for param in visible_param:
            value = PY8Card.pythia8_formatting(self[param])
            output.write('%s=%s\n'%(param,value))
            if template is None:
                if param=='Main:subrun':
                    output.write(
"""!
!  Definition of subrun %d
!
"""%self['Main:subrun'])
            elif param.lower() not in self.hidden_param:
                logger.debug('Adding parameter %s (missing in the template) to current '+\
                                    'pythia8 card (with value %s)',param, value)

        if len(hidden_output_param)>0 and not template is None:
            output.write(
"""!
! Additional technical parameters%s set by MG5_aMC.
!
"""%(' for subrun %d'%self['Main:subrun'] if 'Main:subrun' in self else ''))
        for param in hidden_output_param:
            if param.lower() in self.comments:
                comment = '\n'.join('! %s'%c for c in 
                          self.comments[param.lower()].split('\n'))
                output.write(comment+'\n')
            output.write('%s=%s\n'%(param,PY8Card.pythia8_formatting(self[param])))
        
        # Don't close the file if we were reading a subrun, but simply write 
        # output and return now
        if read_subrun:
            output_file.write(output.getvalue())
            return

        # Now add subruns not present in the template
        for subrunID in subruns:
            new_subrun = StringIO.StringIO()
            self.subruns[subrunID].write(new_subrun,None,read_subrun=True)
            subruns_to_write[subrunID] = new_subrun

        # Add all subruns to the output, in the right order
        for subrunID in sorted(subruns_to_write):
            output.write(subruns_to_write[subrunID].getvalue())

        # If 'LHEFInputs:nSubruns' is not user_set, then make sure it is
        # updated at least larger or equal to the maximum SubRunID
        if 'LHEFInputs:nSubruns'.lower() not in self.user_set and \
             len(subruns_to_write)>0 and 'LHEFInputs:nSubruns' in self\
             and self['LHEFInputs:nSubruns']<max(subruns_to_write.keys()):
            logger.info("Updating PY8 parameter 'LHEFInputs:nSubruns' to "+
          "%d so as to cover all defined subruns."%max(subruns_to_write.keys()))
            self['LHEFInputs:nSubruns'] = max(subruns_to_write.keys())
            output = StringIO.StringIO()
            self.write(output,template,print_only_visible=print_only_visible)

        # Write output
        if isinstance(output_file, str):
            out = open(output_file,'w')
            out.write(output.getvalue())
            out.close()
        else:
            output_file.write(output.getvalue())
        
    def read(self, file_input, read_subrun=False, setter='default'):
        """Read the input file, this can be a path to a file, 
           a file object, a str with the content of the file.
           The setter option choses the authority that sets potential 
           modified/new parameters. It can be either: 
             'default' or 'user' or 'system'"""
        if isinstance(file_input, str):
            if "\n" in file_input:
                finput = StringIO.StringIO(file_input)
            elif os.path.isfile(file_input):
                finput = open(file_input)
            else:
                raise Exception("File input '%s' not found." % file_input)
        elif isinstance(file_input, (StringIO.StringIO, file)):
            finput = file_input
        else:
            raise MadGraph5Error("Incorrect type for argument 'file_input': %s"%
                                                    file_input.__class__.__name__)

        # Read the template
        last_pos = finput.tell()
        line     = finput.readline()
        started_subrun_reading = False
        while line!='':
            # Skip comments
            if line.strip().startswith('!') or line.strip()=='':
                # proceed to next line
                last_pos = finput.tell()
                line     = finput.readline()
                continue
            # Read parameter
            try:
                param, value = line.split('=',1)
                param = param.strip()
                value = value.strip()
            except ValueError:
                line = line.replace('\n','')
                raise MadGraph5Error("Could not read line '%s' of Pythia8 card."%\
                                                                          line)
            if '!' in value:
                value,_ = value.split('!',1)                                                             
                                                                          
            # Read a subrun if detected:
            if param=='Main:subrun':
                if read_subrun:
                    if not started_subrun_reading:
                        # Record that the subrun reading has started and proceed
                        started_subrun_reading = True
                    else:
                        # We encountered the next subrun. rewind last line and exit
                        finput.seek(last_pos)
                        return
                else:
                    # Start the reading of this subrun
                    finput.seek(last_pos)
                    if int(value) in self.subruns:
                        self.subruns[int(value)].read(finput,read_subrun=True,
                                                                  setter=setter)
                    else:
                        # Unknow subrun, create a dummy one
                        NewSubrun=PY8SubRun()
                        NewSubrun.read(finput,read_subrun=True, setter=setter)
                        self.add_subrun(NewSubrun)

                    # proceed to next line
                    last_pos = finput.tell()
                    line     = finput.readline()
                    continue
            
            # Read parameter. The case of a parameter not defined in the card is
            # handled directly in ConfigFile.

            # Use the appropriate authority to set the new/changed variable
            if setter == 'user':
                self.userSet(param,value)
            elif setter == 'system':
                self.systemSet(param,value)
            else:
                self.defaultSet(param,value)

            # proceed to next line
            last_pos = finput.tell()
            line     = finput.readline()

class PY8SubRun(PY8Card):
    """ Class to characterize a specific PY8 card subrun section. """

    def add_default_subruns(self, type):
        """ Overloading of the homonym function called in the __init__ of PY8Card.
        The initialization of the self.subruns attribute should of course not
        be performed in PY8SubRun."""
        pass

    def __init__(self, *args, **opts):
        """ Initialize a subrun """
        
        # Force user to set it manually.
        subrunID = -1
        if 'subrun_id' in opts:
            subrunID = opts.pop('subrun_id')

        super(PY8SubRun, self).__init__(*args, **opts)
        self['Main:subrun']=subrunID

    def default_setup(self):
        """Sets up the list of available PY8SubRun parameters."""
        
        # Add all default PY8Card parameters
        super(PY8SubRun, self).default_setup()
        # Make sure they are all hidden
        self.hidden_param = [k.lower() for k in self.keys()]
        self.hidden_params_to_always_write = set()
        self.visible_params_to_always_write = set()

        # Now add Main:subrun and Beams:LHEF. They are not hidden.
        self.add_param("Main:subrun", -1)
        self.add_param("Beams:LHEF", "events.lhe.gz")



runblock = collections.namedtuple('block', ('name', 'fields', 'template_on', 'template_off'))
class RunCard(ConfigFile):

    filename = 'run_card'
    blocks = [] 
                                   
    def __new__(cls, finput=None, **opt):
        if cls is RunCard:
            if not finput:
                target_class = RunCardLO
            elif isinstance(finput, cls):
                target_class = finput.__class__
            elif isinstance(finput, str):
                if '\n' not in finput:
                    finput = open(finput).read()
                if 'req_acc_FO' in finput:
                    target_class = RunCardNLO
                else:
                    target_class = RunCardLO
            else:
                return None
            return super(RunCard, cls).__new__(target_class, finput, **opt)
        else:
            return super(RunCard, cls).__new__(cls, finput, **opt)

    def __init__(self, *args, **opts):
        
        # The following parameter are updated in the defaultsetup stage.
        
        #parameter for which no warning should be raised if not define
        self.hidden_param = []
        # in which include file the parameer should be written
        self.includepath = collections.defaultdict(list)
        #some parameter have different name in fortran code
        self.fortran_name = {}
        #parameter which are not supported anymore. (no action on the code)
        self.legacy_parameter = {}
        #a list with all the cuts variable and which type object impacted
        # L means charged lepton (l) and neutral lepton (n)
        # d means that it is related to decay chain
        # J means both light jet (j) and heavy jet (b)
        # aj/jl/bj/bl/al are also possible (and stuff like aa/jj/llll/...
        self.cuts_parameter = {}
        # parameter added where legacy requires an older value.
        self.system_default = {}
        
        self.display_block = [] # set some block to be displayed
        self.cut_class = {} 
        self.warned=False


        super(RunCard, self).__init__(*args, **opts)

    def add_param(self, name, value, fortran_name=None, include=True, 
                  hidden=False, legacy=False, cut=False, system=False, sys_default=None, 
                  **opts):
        """ add a parameter to the card. value is the default value and 
        defines the type (int/float/bool/str) of the input.
        fortran_name defines what is the associate name in the f77 code
        include defines if we have to put the value in the include file
        hidden defines if the parameter is expected to be define by the user.
        legacy:Parameter which is not used anymore (raise a warning if not default)
        cut: defines the list of cut parameter to allow to set them all to off.
        sys_default: default used if the parameter is not in the card
        
        options of **opts:
        - allowed: list of valid options. '*' means anything else should be allowed.
                 empty list means anything possible as well. 
        - comment: add comment for writing/help
        - typelist: type of the list if default is empty
        """

        super(RunCard, self).add_param(name, value, system=system,**opts)
        name = name.lower()
        if fortran_name:
            self.fortran_name[name] = fortran_name
        if legacy:
            self.legacy_parameter[name] = value
            include = False
        self.includepath[include].append(name)
        if hidden or system:
            self.hidden_param.append(name)
        if cut:
            self.cuts_parameter[name] = cut
        if sys_default is not None:
            self.system_default[name] = sys_default

        

    def read(self, finput, consistency=True):
        """Read the input file, this can be a path to a file, 
           a file object, a str with the content of the file."""
           
        if isinstance(finput, str):
            if "\n" in finput:
                finput = finput.split('\n')
            elif os.path.isfile(finput):
                finput = open(finput)
            else:
                raise Exception("No such file %s" % finput)
        
        for line in finput:
            line = line.split('#')[0]
            line = line.split('!')[0]
            line = line.rsplit('=',1)
            if len(line) != 2:
                continue
            value, name = line
            name = name.lower().strip()
            if name not in self and ('min' in name or 'max' in name):
                #looks like an entry added by one user -> add it nicely
                self.add_param(name, float(value), hidden=True, cut=True)
            else:
                self.set( name, value, user=True)
        # parameter not set in the run_card can be set to compatiblity value
        if consistency:
                try:
                    self.check_validity()
                except InvalidRunCard as error:
                    if consistency == 'warning':
                        logger.warning(str(error))
                    else:
                        raise

    def valid_line(self, line, tmp):
        template_options = tmp
        default = template_options['default']
        if line.startswith('#IF('):
            cond = line[4:line.find(')')]
            if template_options.get(cond,  default):
                return True
            else:
                return False
        elif line.strip().startswith('%'):
            parameter = line[line.find('(')+1:line.find(')')]
            
            try:
                cond = self.cuts_parameter[parameter]
            except KeyError:
                return True
            
            
            if template_options.get(cond, default) or cond is True:
                return True
            else:
                return False 
        else:
            return True      
                    
                
    def write(self, output_file, template=None, python_template=False,
                    write_hidden=False, template_options=None):
        """Write the run_card in output_file according to template 
           (a path to a valid run_card)"""

        to_write = set(self.user_set) 
        written = set()
        if not template:
            raise Exception
        if not template_options:
            template_options = collections.defaultdict(str)

        # check which optional block to write:
        write_block= []
        for b in self.blocks:
            name = b.name
            # check if the block has to be written
            if name not in self.display_block and \
               not any(f in self.user_set for f in b.fields):
                continue
            write_block.append(b.name)
            
            
        if python_template:
            text = open(template,'r').read()
            text = text.split('\n')             
            # remove if templating
            text = [l if not l.startswith('#IF') else l[l.find(')# ')+2:] 
                    for l in text if self.valid_line(l, template_options)]
            text ='\n'.join(text)
        
        if python_template and not to_write:
            import string
            if self.blocks:
                text = string.Template(text)
                mapping = {}
                for b in self.blocks:
                    if b.name in write_block:
                        mapping[b.name] = b.template_on
                    else:
                        mapping[b.name] = b.template_off
                text = text.substitute(mapping)

            if not self.list_parameter:
                text = text % self
            else:
                data = dict((key.lower(),value) for key, value in self.items())              
                for name in self.list_parameter:
                    if self.list_parameter[name] != str:
                        data[name] = ', '.join(str(v) for v in data[name])
                    else:
                        data[name] = "['%s']" % "', '".join(str(v) for v in data[name])
                text = text % data
        else:                        
            text = ""
            for line in open(template,'r'):                  
                nline = line.split('#')[0]
                nline = nline.split('!')[0]
                comment = line[len(nline):]
                nline = nline.split('=')
                if python_template and nline[0].startswith('$'):
                    block_name = nline[0][1:].strip()
                    this_group = [b for b in self.blocks if b.name == block_name]
                    if not this_group:
                        logger.debug("block %s not defined", block_name)
                        continue
                    else:
                        this_group = this_group[0]
                    if block_name in write_block:
                        text += this_group.template_on % self
                        for name in this_group.fields:
                            written.add(name)
                            if name in to_write:
                                to_write.remove(name)
                    else:
                        text += this_group.template_off % self
                    
                elif len(nline) != 2:
                    text += line
                elif nline[1].strip() in self:
                    name = nline[1].strip().lower()
                    value = self[name]
                    if name in self.list_parameter:
                        if self.list_parameter[name] != str:
                            value = ', '.join([str(v) for v in value])
                        else:
                            value =  "['%s']" % "', '".join(str(v) for v in value)
                    if python_template:
                        text += line % {nline[1].strip():value, name:value}
                        written.add(name)
                    else:
                        if not comment or comment[-1]!='\n':
                            endline = '\n'
                        else:
                            endline = ''
                        text += '  %s\t= %s %s%s' % (value, name, comment, endline)
                        written.add(name)                        

                    if name in to_write:
                        to_write.remove(name)
                else:
                    logger.info('Adding missing parameter %s to current %s (with default value)',
                                 (name, self.filename))
                    written.add(name) 
                    text += line 

            for b in self.blocks:
                if b.name not in write_block:
                    continue
                # check if all attribute of the block have been written already
                if all(f in written for f in b.fields):
                    continue

                to_add = []
                for line in b.template_on.split('\n'):                  
                    nline = line.split('#')[0]
                    nline = nline.split('!')[0]
                    nline = nline.split('=')
                    if len(nline) != 2:
                        to_add.append(line)
                    elif nline[1].strip() in self:
                        name = nline[1].strip().lower()
                        value = self[name]
                        if name in self.list_parameter:
                            value = ', '.join([str(v) for v in value])
                        if name in written:
                            continue #already include before
                        else:
                            to_add.append(line % {nline[1].strip():value, name:value})
                            written.add(name)                        
    
                        if name in to_write:
                            to_write.remove(name)
                    else:
                        raise Exception
                
                if b.template_off in text:
                    text = text.replace(b.template_off, '\n'.join(to_add))
                else:
                    text += '\n'.join(to_add)

        if to_write or write_hidden:
            text+="""#********************************************************************* 
#  Additional hidden parameters
#*********************************************************************
"""            
            if write_hidden:
                #
                # do not write hidden parameter not hidden for this template 
                #
                if python_template:
                    written = written.union(set(re.findall('\%\((\w*)\)s', open(template,'r').read(), re.M)))
                to_write = to_write.union(set(self.hidden_param))
                to_write = to_write.difference(written)

            for key in to_write:
                if key in self.system_only:
                    continue

                comment = self.comments.get(key,'hidden_parameter').replace('\n','\n#')
                text += '  %s\t= %s # %s\n' % (self[key], key, comment)

        if isinstance(output_file, str):
            fsock = open(output_file,'w')
            fsock.write(text)
            fsock.close()
        else:
            output_file.write(text)


    def get_default(self, name, default=None, log_level=None):
        """return self[name] if exist otherwise default. log control if we 
        put a warning or not if we use the default value"""

        lower_name = name.lower()
        if lower_name not in self.user_set:
            if log_level is None:
                if lower_name in self.system_only:
                    log_level = 5
                elif lower_name in self.auto_set:
                    log_level = 5
                elif lower_name in self.hidden_param:
                    log_level = 10
                elif lower_name in self.cuts_parameter:
                    if not MADEVENT and madgraph.ADMIN_DEBUG:
                        log_level = 5
                    else:
                        log_level = 10
                else:
                    log_level = 20
            if not default:
                default = dict.__getitem__(self, name.lower())
 
            logger.log(log_level, '%s missed argument %s. Takes default: %s'
                                   % (self.filename, name, default))
            self[name] = default
            return default
        else:
            return self[name]   

    
    @staticmethod
    def f77_formatting(value, formatv=None):
        """format the variable into fortran. The type is detected by default"""

        if not formatv:
            if isinstance(value, bool):
                formatv = 'bool'
            elif isinstance(value, int):
                formatv = 'int'
            elif isinstance(value, float):
                formatv = 'float'
            elif isinstance(value, str):
                formatv = 'str'
            else:
                logger.debug("unknow format for f77_formatting: %s" , str(value))
                formatv = 'str'
        else:
            assert formatv
            
        if formatv == 'bool':
            if str(value) in ['1','T','.true.','True']:
                return '.true.'
            else:
                return '.false.'
            
        elif formatv == 'int':
            try:
                return str(int(value))
            except ValueError:
                fl = float(value)
                if int(fl) == fl:
                    return str(int(fl))
                else:
                    raise
                
        elif formatv == 'float':
            if isinstance(value, str):
                value = value.replace('d','e')
            return ('%.10e' % float(value)).replace('e','d')
        
        elif formatv == 'str':
            # Check if it is a list
            if value.strip().startswith('[') and value.strip().endswith(']'):
                elements = (value.strip()[1:-1]).split()
                return ['_length = %d'%len(elements)]+\
                       ['(%d) = %s'%(i+1, elem.strip()) for i, elem in \
                                                            enumerate(elements)]
            else:
                return "'%s'" % value
        

    
    def check_validity(self, log_level=30):
        """check that parameter missing in the card are set to the expected value"""

        for name, value in self.system_default.items():
                self.set(name, value, changeifuserset=False)
        

        for name in self.includepath[False]:
            to_bypass = self.hidden_param + list(self.legacy_parameter.keys())
            if name not in to_bypass:
                self.get_default(name, log_level=log_level) 

        for name in self.legacy_parameter:
            if self[name] != self.legacy_parameter[name]:
                logger.warning("The parameter %s is not supported anymore this parameter will be ignored." % name)
               
    default_include_file = 'run_card.inc'

    def update_system_parameter_for_include(self):
        """update hidden system only parameter for the correct writtin in the 
        include"""
        return

    def write_include_file(self, output_dir):
        """Write the various include file in output_dir.
        The entry True of self.includepath will be written in run_card.inc
        The entry False will not be written anywhere"""
        
        # ensure that all parameter are coherent and fix those if needed
        self.check_validity()
        
        #ensusre that system only parameter are correctly set
        self.update_system_parameter_for_include()
        
        for incname in self.includepath:
            if incname is True:
                pathinc = self.default_include_file
            elif incname is False:
                continue
            else:
                pathinc = incname

            fsock = file_writers.FortranWriter(pjoin(output_dir,pathinc))  
            for key in self.includepath[incname]:                
                #define the fortran name
                if key in self.fortran_name:
                    fortran_name = self.fortran_name[key]
                else:
                    fortran_name = key
                    
                #get the value with warning if the user didn't set it
                value = self.get_default(key)
                # Special treatment for strings containing a list of
                # strings. Convert it to a list of strings
                if isinstance(value, list):
                    # in case of a list, add the length of the list as 0th
                    # element in fortran. Only in case of integer or float
                    # list (not for bool nor string)
                    targettype = self.list_parameter[key]                        
                    if targettype is bool:
                        pass
                    elif targettype is int:
                        line = '%s(%s) = %s \n' % (fortran_name, 0, self.f77_formatting(len(value)))
                        fsock.writelines(line)
                    elif targettype is float:
                        line = '%s(%s) = %s \n' % (fortran_name, 0, self.f77_formatting(float(len(value))))
                        fsock.writelines(line)
                    # output the rest of the list in fortran
                    for i,v in enumerate(value):
                        line = '%s(%s) = %s \n' % (fortran_name, i+1, self.f77_formatting(v))
                        fsock.writelines(line)
                elif isinstance(value, dict):
                    for fortran_name, onevalue in value.items():
                        line = '%s = %s \n' % (fortran_name, self.f77_formatting(onevalue))
                        fsock.writelines(line)                       
                else:
                    line = '%s = %s \n' % (fortran_name, self.f77_formatting(value))
                    fsock.writelines(line)
            fsock.close()   

    @staticmethod
    def get_idbmup(lpp):
        """return the particle colliding pdg code"""
        if lpp in (1,2, -1,-2):
            return math.copysign(2212, lpp)
        elif lpp in (3,-3):
            return math.copysign(11, lpp)
        elif lpp == 0:
            #logger.critical("Fail to write correct idbmup in the lhe file. Please correct those by hand")
            return 0
        else:
            return lpp

    def get_banner_init_information(self):
        """return a dictionary with the information needed to write
        the first line of the <init> block of the lhe file."""
        
        output = {}
        output["idbmup1"] = self.get_idbmup(self['lpp1'])
        output["idbmup2"] = self.get_idbmup(self['lpp2'])
        output["ebmup1"] = self["ebeam1"]
        output["ebmup2"] = self["ebeam2"]
        output["pdfgup1"] = 0
        output["pdfgup2"] = 0
        output["pdfsup1"] = self.get_pdf_id(self["pdlabel"])
        output["pdfsup2"] = self.get_pdf_id(self["pdlabel"])
        return output
    
    def get_pdf_id(self, pdf):
        if pdf == "lhapdf":
            lhaid = self["lhaid"]
            if isinstance(lhaid, list):
                return lhaid[0]
            else:
                return lhaid
        else: 
            return {'none': 0, 
                    'cteq6_m':10000,'cteq6_l':10041,'cteq6l1':10042,
                    'nn23lo':246800,'nn23lo1':247000,'nn23nlo':244800
                    }[pdf]    
    
    def get_lhapdf_id(self):
        return self.get_pdf_id(self['pdlabel'])

    def remove_all_cut(self): 
        """remove all the cut"""

        for name in self.cuts_parameter:
            targettype = type(self[name])
            if targettype == bool:
                self[name] = False
            if targettype == dict:
                self[name] = '{}'
            elif 'min' in name:
                self[name] = 0
            elif 'max' in name:
                self[name] = -1
            elif 'eta' in name:
                self[name] = -1
            else:
                self[name] = 0       

class RunCardLO(RunCard):
    """an object to handle in a nice way the run_card information"""
    
    blocks = [
#    HEAVY ION OPTIONAL BLOCK            
        runblock(name='ion_pdf', fields=('nb_neutron1', 'nb_neutron2','nb_proton1','nb_proton2','mass_ion1', 'mass_ion2'),
            template_on=\
"""#*********************************************************************
# Heavy ion PDF / rescaling of PDF                                   *
#*********************************************************************
  %(nb_proton1)s    = nb_proton1 # number of proton for the first beam
  %(nb_neutron1)s    = nb_neutron1 # number of neutron for the first beam
  %(mass_ion1)s = mass_ion1 # mass of the heavy ion (first beam)
# Note that seting differently the two beams only work if you use 
# group_subprocess=False when generating your matrix-element
  %(nb_proton2)s    = nb_proton2 # number of proton for the second beam
  %(nb_neutron2)s    = nb_neutron2 # number of neutron for the second beam
  %(mass_ion2)s = mass_ion2 # mass of the heavy ion (second beam)  
""",
            template_off='# To see heavy ion options: type "update ion_pdf"'),
              
              
#    BEAM POLARIZATION OPTIONAL BLOCK
        runblock(name='beam_pol', fields=('polbeam1','polbeam2'),
            template_on=\
"""#*********************************************************************
# Beam polarization from -100 (left-handed) to 100 (right-handed)    *
#*********************************************************************
     %(polbeam1)s     = polbeam1 ! beam polarization for beam 1
     %(polbeam2)s     = polbeam2 ! beam polarization for beam 2
""",                                               
            template_off='# To see polarised beam options: type "update beam_pol"'),

#    SYSCALC OPTIONAL BLOCK              
        runblock(name='syscalc', fields=('sys_scalefact', 'sys_alpsfact','sys_matchscale','sys_pdf'),
              template_on=\
"""#**************************************
# Parameter below of the systematics study
#  will be used by SysCalc (if installed)
#**************************************
#
%(sys_scalefact)s = sys_scalefact  # factorization/renormalization scale factor
%(sys_alpsfact)s = sys_alpsfact  # \alpha_s emission scale factors
%(sys_matchscale)s = sys_matchscale # variation of merging scale
# PDF sets and number of members (0 or none for all members).
%(sys_pdf)s = sys_pdf # list of pdf sets. (errorset not valid for syscalc)
# MSTW2008nlo68cl.LHgrid 1  = sys_pdf
#
""", 
    template_off= '# Syscalc is deprecated but to see the associate options type\'update syscalc\''),

#    ECUT block (hidden it by default but for e+ e- collider)             
        runblock(name='ecut', fields=('ej','eb','ea','el','ejmax','ebmax','eamax','elmax','e_min_pdg','e_max_pdg'),
              template_on=\
"""#*********************************************************************
# Minimum and maximum E's (in the center of mass frame)              *
#*********************************************************************
  %(ej)s  = ej     ! minimum E for the jets
  %(eb)s  = eb     ! minimum E for the b
  %(ea)s  = ea     ! minimum E for the photons
  %(el)s  = el     ! minimum E for the charged leptons
  %(ejmax)s   = ejmax ! maximum E for the jets
 %(ebmax)s   = ebmax ! maximum E for the b
 %(eamax)s   = eamax ! maximum E for the photons
 %(elmax)s   = elmax ! maximum E for the charged leptons
 %(e_min_pdg)s = e_min_pdg ! E cut for other particles (use pdg code). Applied on particle and anti-particle
 %(e_max_pdg)s = e_max_pdg ! E cut for other particles (syntax e.g. {6: 100, 25: 50})
""", 
    template_off= '#\n# For display option for energy cut in the partonic center of mass frame type \'update ecut\'\n#'),

#    Frame for polarization
    runblock(name='frame', fields=('me_frame'),
              template_on=\
"""#*********************************************************************
# Frame where to evaluate the matrix-element (not the cut!) for polarization   
#*********************************************************************
  %(me_frame)s  = me_frame     ! list of particles to sum-up to define the rest-frame
                               ! in which to evaluate the matrix-element
                               ! [1,2] means the partonic center of mass 
""", 
    template_off= ''),        
#    MERGING BLOCK:  MLM           
        runblock(name='MLM', fields=('ickkw','alpsfact','chcluster','asrwgtflavor','auto_ptj_mjj','xqcut'),
            template_on=\
"""#*********************************************************************
# Matching parameter (MLM only)
#*********************************************************************
 %(ickkw)s = ickkw            ! 0 no matching, 1 MLM
 %(alpsfact)s = alpsfact         ! scale factor for QCD emission vx
 %(chcluster)s = chcluster        ! cluster only according to channel diag
 %(asrwgtflavor)s = asrwgtflavor     ! highest quark flavor for a_s reweight
 %(auto_ptj_mjj)s  = auto_ptj_mjj  ! Automatic setting of ptj and mjj if xqcut >0
                                   ! (turn off for VBF and single top processes)
 %(xqcut)s   = xqcut   ! minimum kt jet measure between partons
""",
            template_off='# To see MLM/CKKW  merging options: type "update MLM" or "update CKKW"'),

#    MERGING BLOCK:  CKKW         
        runblock(name='CKKW', fields=(),
            template_on=\
"""#***********************************************************************
# Turn on either the ktdurham or ptlund cut to activate                *
# CKKW(L) merging with Pythia8 [arXiv:1410.3012, arXiv:1109.4829]      *
#***********************************************************************
 %(ktdurham)s  =  ktdurham
 %(dparameter)s   =  dparameter
 %(ptlund)s  =  ptlund
 %(pdgs_for_merging_cut)s  =  pdgs_for_merging_cut ! PDGs for two cuts above
""",
            template_off=''),    
    
    ]    
    
    
    def default_setup(self):
        """default value for the run_card.dat"""
        
        self.add_param("run_tag", "tag_1", include=False)
        self.add_param("gridpack", False)
        self.add_param("time_of_flight", -1.0, include=False)
        self.add_param("nevents", 10000)        
        self.add_param("iseed", 0)
        self.add_param("python_seed", -2, include=False, hidden=True, comment="controlling python seed [handling in particular the final unweighting].\n -1 means use default from random module.\n -2 means set to same value as iseed")
        self.add_param("lpp1", 1, fortran_name="lpp(1)", allowed=[-1,1,0,2,3,9, -2,-3,4,-4],
                        comment='first beam energy distribution:\n 0: fixed energy\n 1: PDF from proton\n -1: PDF from anti-proton\n 2:photon from proton, 3:photon from electron, 4: photon from muon, 9: PLUGIN MODE')
        self.add_param("lpp2", 1, fortran_name="lpp(2)", allowed=[-1,1,0,2,3,9,4,-4],
                       comment='first beam energy distribution:\n 0: fixed energy\n 1: PDF from proton\n -1: PDF from anti-proton\n 2:photon from proton, 3:photon from electron, 4: photon from muon, 9: PLUGIN MODE')
        self.add_param("ebeam1", 6500.0, fortran_name="ebeam(1)")
        self.add_param("ebeam2", 6500.0, fortran_name="ebeam(2)")
        self.add_param("polbeam1", 0.0, fortran_name="pb1", hidden=True,
                                              comment="Beam polarization from -100 (left-handed) to 100 (right-handed) --use lpp=0 for this parameter--")
        self.add_param("polbeam2", 0.0, fortran_name="pb2", hidden=True,
                                              comment="Beam polarization from -100 (left-handed) to 100 (right-handed) --use lpp=0 for this parameter--")
        self.add_param('nb_proton1', 1, hidden=True, allowed=[1,0, 82 , '*'],fortran_name="nb_proton(1)",
                       comment='For heavy ion physics nb of proton in the ion (for both beam but if group_subprocess was False)')
        self.add_param('nb_proton2', 1, hidden=True, allowed=[1,0, 82 , '*'],fortran_name="nb_proton(2)",
                       comment='For heavy ion physics nb of proton in the ion (used for beam 2 if group_subprocess was False)')
        self.add_param('nb_neutron1', 0, hidden=True, allowed=[1,0, 126 , '*'],fortran_name="nb_neutron(1)",
                       comment='For heavy ion physics nb of neutron in the ion (for both beam but if group_subprocess was False)')
        self.add_param('nb_neutron2', 0, hidden=True, allowed=[1,0, 126 , '*'],fortran_name="nb_neutron(2)",
                       comment='For heavy ion physics nb of neutron in the ion (of beam 2 if group_subprocess was False )')        
        self.add_param('mass_ion1', -1.0, hidden=True, fortran_name="mass_ion(1)",
                       allowed=[-1,0, 0.938, 207.9766521*0.938, 0.000511, 0.105, '*'],
                       comment='For heavy ion physics mass in GeV of the ion (of beam 1)')
        self.add_param('mass_ion2', -1.0, hidden=True, fortran_name="mass_ion(2)",
                       allowed=[-1,0, 0.938, 207.9766521*0.938, 0.000511, 0.105, '*'],
                       comment='For heavy ion physics mass in GeV of the ion (of beam 2)')
        
        self.add_param("pdlabel", "nn23lo1", allowed=['lhapdf', 'cteq6_m','cteq6_l', 'cteq6l1','nn23lo', 'nn23lo1', 'nn23nlo']), 
        self.add_param("lhaid", 230000, hidden=True)
        self.add_param("fixed_ren_scale", False)
        self.add_param("fixed_fac_scale", False)
        self.add_param("scale", 91.1880)
        self.add_param("dsqrt_q2fact1", 91.1880, fortran_name="sf1")
        self.add_param("dsqrt_q2fact2", 91.1880, fortran_name="sf2")
        self.add_param("dynamical_scale_choice", -1, comment="\'-1\' is based on CKKW back clustering (following feynman diagram).\n \'1\' is the sum of transverse energy.\n '2' is HT (sum of the transverse mass)\n '3' is HT/2\n '4' is the center of mass energy",
                                                allowed=[-1,0,1,2,3,4])
        
        # Bias module options
        self.add_param("bias_module", 'None', include=False)
        self.add_param('bias_parameters', {'__type__':1.0}, include='BIAS/bias.inc')
                
        #matching
        self.add_param("scalefact", 1.0)
        self.add_param("ickkw", 0, allowed=[0,1], hidden=True,                  comment="\'0\' for standard fixed order computation.\n\'1\' for MLM merging activates alphas and pdf re-weighting according to a kt clustering of the QCD radiation.")
        self.add_param("highestmult", 1, fortran_name="nhmult", hidden=True)
        self.add_param("ktscheme", 1, hidden=True)
        self.add_param("alpsfact", 1.0, hidden=True)
        self.add_param("chcluster", False, hidden=True)
        self.add_param("pdfwgt", True, hidden=True)
        self.add_param("asrwgtflavor", 5, hidden=True,                          comment = 'highest quark flavor for a_s reweighting in MLM')
        self.add_param("clusinfo", True, hidden=True)
        #format output / boost
        self.add_param("lhe_version", 3.0, hidden=True)
        self.add_param("boost_event", "False", hidden=True, include=False,      comment="allow to boost the full event. The boost put at rest the sume of 4-momenta of the particle selected by the filter defined here. example going to the higgs rest frame: lambda p: p.pid==25")
        self.add_param("me_frame", [1,2], hidden=True, include=False, comment="choose lorentz frame where to evaluate matrix-element [for non lorentz invariant matrix-element/polarization]:\n  - 0: partonic center of mass\n - 1: Multi boson frame\n - 2 : (multi) scalar frame\n - 3 : user custom")
        self.add_param('frame_id', 6,  system=True)
        self.add_param("event_norm", "average", allowed=['sum','average', 'unity'],
                        include=False, sys_default='sum', hidden=True)
        #cut
        self.add_param("auto_ptj_mjj", True, hidden=True)
        self.add_param("bwcutoff", 15.0)
        self.add_param("cut_decays", False, cut='d')
        self.add_param("nhel", 0, include=False)
        #pt cut
        self.add_param("ptj", 20.0, cut='j')
        self.add_param("ptb", 0.0, cut='b')
        self.add_param("pta", 10.0, cut='a')
        self.add_param("ptl", 10.0, cut='l')
        self.add_param("misset", 0.0, cut='n')
        self.add_param("ptheavy", 0.0, cut='H',                                comment='this cut apply on particle heavier than 10 GeV')
        self.add_param("ptonium", 1.0, legacy=True)
        self.add_param("ptjmax", -1.0, cut='j')
        self.add_param("ptbmax", -1.0, cut='b')
        self.add_param("ptamax", -1.0, cut='a')
        self.add_param("ptlmax", -1.0, cut='l')
        self.add_param("missetmax", -1.0, cut='n')
        # E cut
        self.add_param("ej", 0.0, cut='j', hidden=True)
        self.add_param("eb", 0.0, cut='b', hidden=True)
        self.add_param("ea", 0.0, cut='a', hidden=True)
        self.add_param("el", 0.0, cut='l', hidden=True)
        self.add_param("ejmax", -1.0, cut='j', hidden=True)
        self.add_param("ebmax", -1.0, cut='b', hidden=True)
        self.add_param("eamax", -1.0, cut='a', hidden=True)
        self.add_param("elmax", -1.0, cut='l', hidden=True)
        # Eta cut
        self.add_param("etaj", 5.0, cut='j')
        self.add_param("etab", -1.0, cut='b')
        self.add_param("etaa", 2.5, cut='a')
        self.add_param("etal", 2.5, cut='l')
        self.add_param("etaonium", 0.6, legacy=True)
        self.add_param("etajmin", 0.0, cut='a')
        self.add_param("etabmin", 0.0, cut='b')
        self.add_param("etaamin", 0.0, cut='a')
        self.add_param("etalmin", 0.0, cut='l')
        # DRJJ
        self.add_param("drjj", 0.4, cut='jj')
        self.add_param("drbb", 0.0, cut='bb')
        self.add_param("drll", 0.4, cut='ll')
        self.add_param("draa", 0.4, cut='aa')
        self.add_param("drbj", 0.0, cut='bj')
        self.add_param("draj", 0.4, cut='aj')
        self.add_param("drjl", 0.4, cut='jl')
        self.add_param("drab", 0.0, cut='ab')
        self.add_param("drbl", 0.0, cut='bl')
        self.add_param("dral", 0.4, cut='al')
        self.add_param("drjjmax", -1.0, cut='jj')
        self.add_param("drbbmax", -1.0, cut='bb')
        self.add_param("drllmax", -1.0, cut='ll')
        self.add_param("draamax", -1.0, cut='aa')
        self.add_param("drbjmax", -1.0, cut='bj')
        self.add_param("drajmax", -1.0, cut='aj')
        self.add_param("drjlmax", -1.0, cut='jl')
        self.add_param("drabmax", -1.0, cut='ab')
        self.add_param("drblmax", -1.0, cut='bl')
        self.add_param("dralmax", -1.0, cut='al')
        # invariant mass
        self.add_param("mmjj", 0.0, cut='jj')
        self.add_param("mmbb", 0.0, cut='bb')
        self.add_param("mmaa", 0.0, cut='aa')
        self.add_param("mmll", 0.0, cut='ll')
        self.add_param("mmjjmax", -1.0, cut='jj')
        self.add_param("mmbbmax", -1.0, cut='bb')                
        self.add_param("mmaamax", -1.0, cut='aa')
        self.add_param("mmllmax", -1.0, cut='ll')
        self.add_param("mmnl", 0.0, cut='LL')
        self.add_param("mmnlmax", -1.0, cut='LL')
        #minimum/max pt for sum of leptons
        self.add_param("ptllmin", 0.0, cut='ll')
        self.add_param("ptllmax", -1.0, cut='ll')
        self.add_param("xptj", 0.0, cut='jj')
        self.add_param("xptb", 0.0, cut='bb')
        self.add_param("xpta", 0.0, cut='aa') 
        self.add_param("xptl", 0.0, cut='ll')
        # ordered pt jet 
        self.add_param("ptj1min", 0.0, cut='jj')
        self.add_param("ptj1max", -1.0, cut='jj')
        self.add_param("ptj2min", 0.0, cut='jj')
        self.add_param("ptj2max", -1.0, cut='jj')
        self.add_param("ptj3min", 0.0, cut='jjj')
        self.add_param("ptj3max", -1.0, cut='jjj')
        self.add_param("ptj4min", 0.0, cut='j'*4)
        self.add_param("ptj4max", -1.0, cut='j'*4)                
        self.add_param("cutuse", 0, cut='jj')
        # ordered pt lepton
        self.add_param("ptl1min", 0.0, cut='l'*2)
        self.add_param("ptl1max", -1.0, cut='l'*2)
        self.add_param("ptl2min", 0.0, cut='l'*2)
        self.add_param("ptl2max", -1.0, cut='l'*2)
        self.add_param("ptl3min", 0.0, cut='l'*3)
        self.add_param("ptl3max", -1.0, cut='l'*3)        
        self.add_param("ptl4min", 0.0, cut='l'*4)
        self.add_param("ptl4max", -1.0, cut='l'*4)
        # Ht sum of jets
        self.add_param("htjmin", 0.0, cut='j'*2)
        self.add_param("htjmax", -1.0, cut='j'*2)
        self.add_param("ihtmin", 0.0, cut='J'*2)
        self.add_param("ihtmax", -1.0, cut='J'*2)
        self.add_param("ht2min", 0.0, cut='J'*3) 
        self.add_param("ht3min", 0.0, cut='J'*3)
        self.add_param("ht4min", 0.0, cut='J'*4)
        self.add_param("ht2max", -1.0, cut='J'*3)
        self.add_param("ht3max", -1.0, cut='J'*3)
        self.add_param("ht4max", -1.0, cut='J'*4)
        # photon isolation
        self.add_param("ptgmin", 0.0, cut='aj')
        self.add_param("r0gamma", 0.4, hidden=True)
        self.add_param("xn", 1.0, hidden=True)
        self.add_param("epsgamma", 1.0, hidden=True) 
        self.add_param("isoem", True, hidden=True)
        self.add_param("xetamin", 0.0, cut='jj')
        self.add_param("deltaeta", 0.0, cut='j'*2)
        self.add_param("ktdurham", -1.0, fortran_name="kt_durham", cut='j')
        self.add_param("dparameter", 0.4, fortran_name="d_parameter", cut='j')
        self.add_param("ptlund", -1.0, fortran_name="pt_lund", cut='j')
        self.add_param("pdgs_for_merging_cut", [21, 1, 2, 3, 4, 5, 6], hidden=True)
        self.add_param("maxjetflavor", 4)
        self.add_param("xqcut", 0.0, cut=True)
        self.add_param("use_syst", True)
        self.add_param('systematics_program', 'systematics', include=False, hidden=True, comment='Choose which program to use for systematics computation: none, systematics, syscalc')
        self.add_param('systematics_arguments', ['--mur=0.5,1,2', '--muf=0.5,1,2', '--pdf=errorset'], include=False, hidden=True, comment='Choose the argment to pass to the systematics command. like --mur=0.25,1,4. Look at the help of the systematics function for more details.')
        
        self.add_param("sys_scalefact", "0.5 1 2", include=False, hidden=True)
        self.add_param("sys_alpsfact", "None", include=False, hidden=True)
        self.add_param("sys_matchscale", "auto", include=False, hidden=True)
        self.add_param("sys_pdf", "errorset", include=False, hidden=True)
        self.add_param("sys_scalecorrelation", -1, include=False, hidden=True)

        #parameter not in the run_card by default
        self.add_param('gridrun', False, hidden=True)
        self.add_param('fixed_couplings', True, hidden=True)
        self.add_param('mc_grouped_subproc', True, hidden=True)
        self.add_param('xmtcentral', 0.0, hidden=True, fortran_name="xmtc")
        self.add_param('d', 1.0, hidden=True)
        self.add_param('gseed', 0, hidden=True, include=False)
        self.add_param('issgridfile', '', hidden=True)
        #job handling of the survey/ refine
        self.add_param('job_strategy', 0, hidden=True, include=False, allowed=[0,1,2], comment='see appendix of 1507.00020 (page 26)')
        self.add_param('hard_survey', 0, hidden=True, include=False, comment='force to have better estimate of the integral at survey for difficult mode like VBF')
        self.add_param("second_refine_treshold", 0.9, hidden=True, include=False, comment="set a treshold to bypass the use of a second refine. if the ratio of cross-section after survey by the one of the first refine is above the treshold, the  second refine will not be done.")
        self.add_param('survey_splitting', -1, hidden=True, include=False, comment="for loop-induced control how many core are used at survey for the computation of a single iteration.")
        self.add_param('survey_nchannel_per_job', 2, hidden=True, include=False, comment="control how many Channel are integrated inside a single job on cluster/multicore")
        self.add_param('refine_evt_by_job', -1, hidden=True, include=False, comment="control the maximal number of events for the first iteration of the refine (larger means less jobs)")
        self.add_param('small_width_treatment', 1e-6, hidden=True, comment="generation where the width is below VALUE times mass will be replace by VALUE times mass for the computation. The cross-section will be corrected assuming NWA. Not used for loop-induced process")
        
        # parameter allowing to define simple cut via the pdg
        # Special syntax are related to those. (can not be edit directly)
        self.add_param('pt_min_pdg',{'__type__':0.}, include=False, cut=True)
        self.add_param('pt_max_pdg',{'__type__':0.}, include=False, cut=True)
        self.add_param('E_min_pdg',{'__type__':0.}, include=False, hidden=True,cut=True)
        self.add_param('E_max_pdg',{'__type__':0.}, include=False, hidden=True,cut=True)
        self.add_param('eta_min_pdg',{'__type__':0.}, include=False,cut=True)
        self.add_param('eta_max_pdg',{'__type__':0.}, include=False,cut=True)
        self.add_param('mxx_min_pdg',{'__type__':0.}, include=False,cut=True)
        self.add_param('mxx_only_part_antipart', {'default':False}, include=False)
        
        self.add_param('pdg_cut',[0],  system=True) # store which PDG are tracked
        self.add_param('ptmin4pdg',[0.], system=True) # store pt min
        self.add_param('ptmax4pdg',[-1.], system=True)
        self.add_param('Emin4pdg',[0.], system=True) # store pt min
        self.add_param('Emax4pdg',[-1.], system=True)  
        self.add_param('etamin4pdg',[0.], system=True) # store pt min
        self.add_param('etamax4pdg',[-1.], system=True)   
        self.add_param('mxxmin4pdg',[-1.], system=True)
        self.add_param('mxxpart_antipart', [False], system=True)
                     
        
             
    def check_validity(self):
        """ """
        
        super(RunCardLO, self).check_validity()
        
        #Make sure that nhel is only either 0 (i.e. no MC over hel) or
        #1 (MC over hel with importance sampling). In particular, it can
        #no longer be > 1.
        if 'nhel' not in self:
            raise InvalidRunCard("Parameter nhel is not defined in the run_card.")
        if self['nhel'] not in [1,0]:
            raise InvalidRunCard("Parameter nhel can only be '0' or '1', "+\
                                                          "not %s." % self['nhel'])
        if int(self['maxjetflavor']) > 6:
            raise InvalidRunCard('maxjetflavor should be lower than 5! (6 is partly supported)')
  
        if len(self['pdgs_for_merging_cut']) > 1000:
            raise InvalidRunCard("The number of elements in "+\
                               "'pdgs_for_merging_cut' should not exceed 1000.")
  
        # some cut need to be deactivated in presence of isolation
        if self['ptgmin'] > 0:
            if self['pta'] > 0:
                logger.warning('pta cut discarded since photon isolation is used')
                self['pta'] = 0.0
            if self['draj'] > 0:
                logger.warning('draj cut discarded since photon isolation is used')
                self['draj'] = 0.0   
        
        # special treatment for gridpack use the gseed instead of the iseed        
        if self['gridrun']:
            self['iseed'] = self['gseed']
        
        #Some parameter need to be fixed when using syscalc
        if self['use_syst']:
            if self['scalefact'] != 1.0:
                logger.warning('Since use_syst=T, We change the value of \'scalefact\' to 1')
                self['scalefact'] = 1.0
     
        # CKKW Treatment
        if self['ickkw'] > 0:
            if self['ickkw'] != 1:
                logger.critical('ickkw >1 is pure alpha and only partly implemented.')
                import madgraph.interface.extended_cmd as basic_cmd
                answer = basic_cmd.smart_input('Do you really want to continue', allow_arg=['y','n'], default='n')
                if answer !='y':
                    raise InvalidRunCard('ickkw>1 is still in alpha')
            if self['use_syst']:
                # some additional parameter need to be fixed for Syscalc + matching
                if self['alpsfact'] != 1.0:
                    logger.warning('Since use_syst=T, We change the value of \'alpsfact\' to 1')
                    self['alpsfact'] =1.0
            if self['maxjetflavor'] == 6:
                raise InvalidRunCard('maxjetflavor at 6 is NOT supported for matching!')
            if self['ickkw'] == 2:
                # add warning if ckkw selected but the associate parameter are empty
                self.get_default('highestmult', log_level=20)                   
                self.get_default('issgridfile', 'issudgrid.dat', log_level=20)
        if self['xqcut'] > 0:
            if self['ickkw'] == 0:
                logger.error('xqcut>0 but ickkw=0. Potentially not fully consistent setup. Be carefull')
                time.sleep(5)
            if self['drjj'] != 0:
                if 'drjj' in self.user_set:
                    logger.warning('Since icckw>0, We change the value of \'drjj\' to 0')
                self['drjj'] = 0
            if self['drjl'] != 0:
                if 'drjl' in self.user_set:
                    logger.warning('Since icckw>0, We change the value of \'drjl\' to 0')
                self['drjl'] = 0    
            if not self['auto_ptj_mjj']:         
                if self['mmjj'] > self['xqcut']:
                    logger.warning('mmjj > xqcut (and auto_ptj_mjj = F). MMJJ set to 0')
                    self['mmjj'] = 0.0 
    
        # check validity of the pdf set
        if self['pdlabel'] == 'lhapdf':
            #add warning if lhaid not define
            self.get_default('lhaid', log_level=20)
            
        # if heavy ion mode use for one beam, forbis lpp!=1
        if self['lpp1'] not in [1,2]:
            if self['nb_proton1'] !=1 or self['nb_neutron1'] !=0:
                raise InvalidRunCard( "Heavy ion mode is only supported for lpp1=1/2")
        if self['lpp2'] not in [1,2]:
            if self['nb_proton2'] !=1 or self['nb_neutron2'] !=0:
                raise InvalidRunCard( "Heavy ion mode is only supported for lpp2=1/2")   

        # check if lpp = 
        for i in [1,2]:
            if abs(self['lpp%s' % i ]) in [3,4] and self['dsqrt_q2fact%s'%i] == 91.188:
                logger.warning("Photon from lepton are using fixed scale value of muf [dsqrt_q2fact%s] as the cut of the EPA. Looks like you kept the default value (Mz). Is this really the cut-off of the EPA that you want to use?" % i)
                time.sleep(5)
        
            if abs(self['lpp%s' % i ]) == 2 and self['dsqrt_q2fact%s'%i] == 91.188:
                logger.warning("Since 2.7.1 Photon from proton are using fixed scale value of muf [dsqrt_q2fact%s] as the cut of the Improved Weizsaecker-Williams formula. Please edit it accordingly." % i)
                time.sleep(5)
                
        # if both lpp1/2 are on PA mode -> force fixed factorization scale
        if abs(self['lpp1']) in [2, 3,4] and abs(self['lpp2']) in [2, 3,4] and not self['fixed_fac_scale']:
            raise InvalidRunCard("Having both beam in elastic photon mode requires fixed_fac_scale to be on True [since this is use as cutoff]")

        # check that ebeam is bigger than the associated mass.
        for i in [1,2]:
            if self['lpp%s' % i ] not in [1,2]:
                continue
            if self['mass_ion%i' % i] == -1:
                if self['ebeam%i' % i] < 0.938:
                    if self['ebeam%i' %i] == 0:
                        logger.warning("At rest proton mode set: Energy beam set to 0.938")
                        self.set('ebeam%i' %i, 0.938)
                    else:
                        raise InvalidRunCard("Energy for beam %i lower than proton mass. Please fix this")    
            elif self['ebeam%i' % i] < self['mass_ion%i' % i]:    
                if self['ebeam%i' %i] == 0:
                    logger.warning("At rest ion mode set: Energy beam set to %s" % self['mass_ion%i' % i])
                    self.set('ebeam%i' %i, self['mass_ion%i' % i])

    def update_system_parameter_for_include(self):
        
        # polarization
        self['frame_id'] = sum(2**(n) for n in self['me_frame'])
        
        # set the pdg_for_cut fortran parameter
        pdg_to_cut = set(list(self['pt_min_pdg'].keys()) +list(self['pt_max_pdg'].keys()) + 
                         list(self['e_min_pdg'].keys()) +list(self['e_max_pdg'].keys()) +
                         list(self['eta_min_pdg'].keys()) +list(self['eta_max_pdg'].keys())+
                         list(self['mxx_min_pdg'].keys()) + list(self['mxx_only_part_antipart'].keys()))
        pdg_to_cut.discard('__type__')
        pdg_to_cut.discard('default')
        if len(pdg_to_cut)>25:
            raise Exception("Maximum 25 different pdgs are allowed for pdg specific cut")
        
        if any(int(pdg)<0 for pdg in pdg_to_cut):
            logger.warning('PDG specific cuts are always applied symmetrically on particle/anti-particle. Always use positve PDG codes')
            raise MadGraph5Error('Some PDG specific cuts are defined with negative pdg code')
        
        
        if any(pdg in pdg_to_cut for pdg in [1,2,3,4,5,21,22,11,13,15]):
            raise Exception("Can not use PDG related cut for light quark/b quark/lepton/gluon/photon")
        
        if pdg_to_cut:
            self['pdg_cut'] = list(pdg_to_cut)
            self['ptmin4pdg'] = []
            self['Emin4pdg'] = []
            self['etamin4pdg'] =[]
            self['ptmax4pdg'] = []
            self['Emax4pdg'] = []
            self['etamax4pdg'] =[]
            self['mxxmin4pdg'] =[]
            self['mxxpart_antipart']  = []
            for pdg in self['pdg_cut']:
                for var in ['pt','e','eta', 'Mxx']:
                    for minmax in ['min', 'max']:
                        if var in ['Mxx'] and minmax =='max':
                            continue
                        new_var = '%s%s4pdg' % (var, minmax)
                        old_var = '%s_%s_pdg' % (var, minmax)
                        default = 0. if minmax=='min' else -1.
                        self[new_var].append(self[old_var][str(pdg)] if str(pdg) in self[old_var] else default)
                #special for mxx_part_antipart
                old_var = 'mxx_only_part_antipart'
                new_var = 'mxxpart_antipart'
                if 'default' in self[old_var]:
                    default = self[old_var]['default']
                    self[new_var].append(self[old_var][str(pdg)] if str(pdg) in self[old_var] else default)
                else:
                    if str(pdg) not in self[old_var]:
                        raise Exception("no default value defined for %s and no value defined for pdg %s" % (old_var, pdg)) 
                    self[new_var].append(self[old_var][str(pdg)])
        else:
            self['pdg_cut'] = [0]
            self['ptmin4pdg'] = [0.]
            self['Emin4pdg'] = [0.]
            self['etamin4pdg'] =[0.]
            self['ptmax4pdg'] = [-1.]
            self['Emax4pdg'] = [-1.]
            self['etamax4pdg'] =[-1.]
            self['mxxmin4pdg'] =[0.] 
            self['mxxpart_antipart'] = [False]
            
                    
           
    def create_default_for_process(self, proc_characteristic, history, proc_def):
        """Rules
          process 1->N all cut set on off.
          loop_induced -> MC over helicity
          e+ e- beam -> lpp:0 ebeam:500
          p p beam -> set maxjetflavor automatically
          more than one multiplicity: ickkw=1 xqcut=30 use_syst=F
         """

        if proc_characteristic['loop_induced']:
            self['nhel'] = 1
        self['pdgs_for_merging_cut'] = proc_characteristic['colored_pdgs']
                    
        if proc_characteristic['ninitial'] == 1:
            #remove all cut
            self.remove_all_cut()
            self['use_syst'] = False
        else:
            # check for beam_id
            # check for beam_id
            beam_id = set()
            beam_id_split = [set(), set()]
            for proc in proc_def:   
                for oneproc in proc:
                    for i,leg in enumerate(oneproc['legs']):
                        if not leg['state']:
                            beam_id_split[i].add(leg['id'])
                            beam_id.add(leg['id'])

            if any(i in beam_id for i in [1,-1,2,-2,3,-3,4,-4,5,-5,21,22]):
                maxjetflavor = max([4]+[abs(i) for i in beam_id if  -7< i < 7])
                self['maxjetflavor'] = maxjetflavor
                self['asrwgtflavor'] = maxjetflavor
            
            if any(i in beam_id for i in [1,-1,2,-2,3,-3,4,-4,5,-5,21,22]):
                # check for e p collision
                if any(id  in beam_id for id in [11,-11,13,-13]):
                    self.display_block.append('beam_pol')
                    if any(id  in beam_id_split[0] for id in [11,-11,13,-13]):
                        self['lpp1'] = 0  
                        self['lpp2'] = 1 
                        self['ebeam1'] = '1k'  
                        self['ebeam2'] = '6500'  
                    else:
                        self['lpp1'] = 1  
                        self['lpp2'] = 0  
                        self['ebeam1'] = '6500'  
                        self['ebeam2'] = '1k'  
            
            elif any(id in beam_id for id in [11,-11,13,-13]):
                self['lpp1'] = 0
                self['lpp2'] = 0
                self['ebeam1'] = 500
                self['ebeam2'] = 500
                self['use_syst'] = False
                if set([ abs(i) for i in beam_id_split[0]]) == set([ abs(i) for i in beam_id_split[1]]):
                    self.display_block.append('ecut')
                self.display_block.append('beam_pol')
            else:
                self['lpp1'] = 0
                self['lpp2'] = 0    
                self['use_syst'] = False   
                self.display_block.append('beam_pol')  
                self.display_block.append('ecut')       
            
            # automatic polarisation of the beam if neutrino beam  
            if any(id  in beam_id for id in [12,-12,14,-14,16,-16]):
                self.display_block.append('beam_pol')
                if any(id  in beam_id_split[0] for id in [12,14,16]):
                    self['lpp1'] = 0   
                    self['ebeam1'] = '1k'  
                    self['polbeam1'] = -100
                    if not all(id  in [12,14,16] for id in beam_id_split[0]):
                        logger.warning('Issue with default beam setup of neutrino in the run_card. Please check it up [polbeam1]. %s')
                elif any(id  in beam_id_split[0] for id in [-12,-14,-16]):
                    self['lpp1'] = 0   
                    self['ebeam1'] = '1k'  
                    self['polbeam1'] = 100
                    if not all(id  in [-12,-14,-16] for id in beam_id_split[0]):
                        logger.warning('Issue with default beam setup of neutrino in the run_card. Please check it up [polbeam1].')                         
                if any(id  in beam_id_split[1] for id in [12,14,16]):
                    self['lpp2'] = 0   
                    self['ebeam2'] = '1k'  
                    self['polbeam2'] = -100
                    if not all(id  in [12,14,16] for id in beam_id_split[1]):
                        logger.warning('Issue with default beam setup of neutrino in the run_card. Please check it up [polbeam2].')
                if any(id  in beam_id_split[1] for id in [-12,-14,-16]):
                    self['lpp2'] = 0   
                    self['ebeam2'] = '1k'  
                    self['polbeam2'] = 100
                    if not all(id  in [-12,-14,-16] for id in beam_id_split[1]):
                        logger.warning('Issue with default beam setup of neutrino in the run_card. Please check it up [polbeam2].')
            
        # Check if need matching
        min_particle = 99
        max_particle = 0
        for proc in proc_def:
            min_particle = min(len(proc[0]['legs']), min_particle)
            max_particle = max(len(proc[0]['legs']), max_particle)
        if min_particle != max_particle:
            #take one of the process with min_particle
            for procmin in proc_def:
                if len(procmin[0]['legs']) != min_particle:
                    continue
                else:
                    idsmin = [l['id'] for l in procmin[0]['legs']]
                    break
            matching = False
            for procmax in proc_def:
                if len(procmax[0]['legs']) != max_particle:
                    continue
                idsmax =  [l['id'] for l in procmax[0]['legs']]
                for i in idsmin:
                    if i not in idsmax:
                        continue
                    else:
                        idsmax.remove(i)
                for j in idsmax:
                    if j not in [1,-1,2,-2,3,-3,4,-4,5,-5,21]:
                        break
                else:
                    # all are jet => matching is ON
                    matching=True
                    break 
            
            if matching:
                self['ickkw'] = 1
                self['xqcut'] = 30
                #self['use_syst'] = False 
                self['drjj'] = 0
                self['drjl'] = 0
                self['sys_alpsfact'] = "0.5 1 2"
                self['systematics_arguments'].append('--alps=0.5,1,2')
                self.display_block.append('MLM')
                self.display_block.append('CKKW')
                
        # For interference module, the systematics are wrong.
        # automatically set use_syst=F and set systematics_program=none
        no_systematics = False
        for proc in proc_def:
            for oneproc in proc:
                if '^2' in oneproc.nice_string():
                    no_systematics = True
                    break
            else:
                continue
            break

        
        if no_systematics:
            self['use_syst'] = False
            self['systematics_program'] = 'none'
        
        # if polarization is used, set the choice of the frame in the run_card
        # But only if polarization is used for massive particles
        for plist in proc_def:
            for proc in plist:
                for l in proc.get('legs') + proc.get('legs_with_decays'):
                    if l.get('polarization'):
                        model = proc.get('model')
                        particle = model.get_particle(l.get('id'))
                        if particle.get('mass').lower() != 'zero':
                            self.display_block.append('frame') 
                            break
                else:
                    continue
                break
            else:
                continue
            break

        if 'MLM' in proc_characteristic['limitations']:
            if self['dynamical_scale_choice'] ==  -1:
                self['dynamical_scale_choice'] = 3
            if self['ickkw']  == 1:
                logger.critical("MLM matching/merging not compatible with the model! You need to use another method to remove the double counting!")
            self['ickkw'] = 0
            
        # define class of particles present to hide all the cuts associated to 
        # not present class
        cut_class = collections.defaultdict(int)
        for proc in proc_def:
            for oneproc in proc:
                one_proc_cut = collections.defaultdict(int)
                ids = oneproc.get_final_ids_after_decay()
                if oneproc['decay_chains']:
                    cut_class['d']  = 1
                for pdg in ids:
                    if pdg == 22:
                        one_proc_cut['a'] +=1
                    elif abs(pdg) <= self['maxjetflavor']:
                        one_proc_cut['j'] += 1
                        one_proc_cut['J'] += 1
                    elif abs(pdg) <= 5:
                        one_proc_cut['b'] += 1
                        one_proc_cut['J'] += 1
                    elif abs(pdg) in [11,13,15]:
                        one_proc_cut['l'] += 1
                        one_proc_cut['L'] += 1
                    elif abs(pdg) in [12,14,16]:
                        one_proc_cut['n'] += 1
                        one_proc_cut['L'] += 1 
                    elif str(oneproc.get('model').get_particle(pdg)['mass']) != 'ZERO':
                        one_proc_cut['H'] += 1
                        
            for key, nb in one_proc_cut.items():
                cut_class[key] = max(cut_class[key], nb)
            self.cut_class = dict(cut_class)
            self.cut_class[''] = True #avoid empty
                                   
    def write(self, output_file, template=None, python_template=False,
              **opt):
        """Write the run_card in output_file according to template 
           (a path to a valid run_card)"""

        if not template:
            if not MADEVENT:
                template = pjoin(MG5DIR, 'Template', 'LO', 'Cards', 
                                                        'run_card.dat')
                python_template = True
            else:
                template = pjoin(MEDIR, 'Cards', 'run_card_default.dat')
                python_template = False
                

        hid_lines = {'default':True}#collections.defaultdict(itertools.repeat(True).next)
        if isinstance(output_file, str):
            if 'default' in output_file:
                if self.cut_class:
                    hid_lines['default'] = False
                    for key in self.cut_class:
                        nb = self.cut_class[key]
                        for i in range(1,nb+1):
                            hid_lines[key*i] = True
                    for k1,k2 in ['bj', 'bl', 'al', 'jl', 'ab', 'aj']:
                        if self.cut_class.get(k1) and self.cut_class.get(k2):
                            hid_lines[k1+k2] = True

        super(RunCardLO, self).write(output_file, template=template,
                                    python_template=python_template, 
                                    template_options=hid_lines,
                                    **opt)            


class InvalidMadAnalysis5Card(InvalidCmd):
    pass

class MadAnalysis5Card(dict):
    """ A class to store a MadAnalysis5 card. Very basic since it is basically
    free format."""
    
    _MG5aMC_escape_tag = '@MG5aMC'
    
    _default_hadron_inputs = ['*.hepmc', '*.hep', '*.stdhep', '*.lhco','*.root']
    _default_parton_inputs = ['*.lhe']
    _skip_analysis         = False
    
    @classmethod
    def events_can_be_reconstructed(cls, file_path):
        """ Checks from the type of an event file whether it can be reconstructed or not."""
        return not (file_path.endswith('.lhco') or file_path.endswith('.lhco.gz') or \
                          file_path.endswith('.root') or file_path.endswith('.root.gz'))
    
    @classmethod
    def empty_analysis(cls):
        """ A method returning the structure of an empty analysis """
        return {'commands':[],
                'reconstructions':[]}

    @classmethod
    def empty_reconstruction(cls):
        """ A method returning the structure of an empty reconstruction """
        return {'commands':[],
                'reco_output':'lhe'}

    def default_setup(self):
        """define the default value""" 
        self['mode']      = 'parton'
        self['inputs']    = []
        # None is the default stdout level, it will be set automatically by MG5aMC
        self['stdout_lvl'] = None
        # These two dictionaries are formated as follows:
        #     {'analysis_name':
        #          {'reconstructions' : ['associated_reconstructions_name']}
        #          {'commands':['analysis command lines here']}    }
        # with values being of the form of the empty_analysis() attribute
        # of this class and some other property could be added to this dictionary
        # in the future.
        self['analyses']       = {}
        # The recasting structure contains on set of commands and one set of 
        # card lines. 
        self['recasting']      = {'commands':[],'card':[]}
        # Add the default trivial reconstruction to use an lhco input
        # This is just for the structure
        self['reconstruction'] = {'lhco_input':
                                        MadAnalysis5Card.empty_reconstruction(),
                                  'root_input':
                                        MadAnalysis5Card.empty_reconstruction()}
        self['reconstruction']['lhco_input']['reco_output']='lhco'
        self['reconstruction']['root_input']['reco_output']='root'        

        # Specify in which order the analysis/recasting were specified
        self['order'] = []

    def __init__(self, finput=None,mode=None):
        if isinstance(finput, self.__class__):
            dict.__init__(self, finput)
            assert list(finput.__dict__.keys())
            for key in finput.__dict__:
                setattr(self, key, copy.copy(getattr(finput, key)) )
            return
        else:
            dict.__init__(self)
        
        # Initialize it with all the default value
        self.default_setup()
        if not mode is None:
            self['mode']=mode

        # if input is define read that input
        if isinstance(finput, (file, str, StringIO.StringIO)):
            self.read(finput, mode=mode)
    
    def read(self, input, mode=None):
        """ Read an MA5 card"""
        
        if mode not in [None,'parton','hadron']:
            raise MadGraph5Error('A MadAnalysis5Card can be read online the modes'+
                                                         "'parton' or 'hadron'")
        card_mode = mode
        
        if isinstance(input, (file, StringIO.StringIO)):
            input_stream = input
        elif isinstance(input, str):
            if not os.path.isfile(input):
                raise InvalidMadAnalysis5Card("Cannot read the MadAnalysis5 card."+\
                                                    "File '%s' not found."%input)
            if mode is None and 'hadron' in input:
                card_mode = 'hadron'
            input_stream = open(input,'r')
        else:
            raise MadGraph5Error('Incorrect input for the read function of'+\
              ' the MadAnalysis5Card card. Received argument type is: %s'%str(type(input)))

        # Reinstate default values
        self.__init__()
        current_name = 'default'
        current_type = 'analyses'
        for line in input_stream:
            # Skip comments for now
            if line.startswith('#'):
                continue
            if line.endswith('\n'):
                line = line[:-1]
            if line.strip()=='':
                continue
            if line.startswith(self._MG5aMC_escape_tag):
                try:
                    option,value = line[len(self._MG5aMC_escape_tag):].split('=')
                    value = value.strip()
                except ValueError:
                    option = line[len(self._MG5aMC_escape_tag):]
                option = option.strip()
                
                if option=='inputs':
                    self['inputs'].extend([v.strip() for v in value.split(',')])
                
                elif option == 'skip_analysis':
                    self._skip_analysis = True

                elif option=='stdout_lvl':
                    try: # It is likely an int
                        self['stdout_lvl']=int(value)
                    except ValueError:
                        try: # Maybe the user used something like 'logging.INFO'
                            self['stdout_lvl']=eval(value)
                        except:
                            try:
                               self['stdout_lvl']=eval('logging.%s'%value)
                            except:
                                raise InvalidMadAnalysis5Card(
                 "MA5 output level specification '%s' is incorrect."%str(value))
                
                elif option=='analysis_name':
                    current_type = 'analyses'
                    current_name = value
                    if current_name in self[current_type]:
                        raise InvalidMadAnalysis5Card(
               "Analysis '%s' already defined in MadAnalysis5 card"%current_name)
                    else:
                        self[current_type][current_name] = MadAnalysis5Card.empty_analysis()
                
                elif option=='set_reconstructions':
                    try:
                        reconstructions = eval(value)
                        if not isinstance(reconstructions, list):
                            raise
                    except:
                        raise InvalidMadAnalysis5Card("List of reconstructions"+\
                         " '%s' could not be parsed in MadAnalysis5 card."%value)
                    if current_type!='analyses' and current_name not in self[current_type]:
                        raise InvalidMadAnalysis5Card("A list of reconstructions"+\
                                   "can only be defined in the context of an "+\
                                             "analysis in a MadAnalysis5 card.")
                    self[current_type][current_name]['reconstructions']=reconstructions
                    continue
                
                elif option=='reconstruction_name':
                    current_type = 'reconstruction'
                    current_name = value
                    if current_name in self[current_type]:
                        raise InvalidMadAnalysis5Card(
               "Reconstruction '%s' already defined in MadAnalysis5 hadron card"%current_name)
                    else:
                        self[current_type][current_name] = MadAnalysis5Card.empty_reconstruction()

                elif option=='reco_output':
                    if current_type!='reconstruction' or current_name not in \
                                                         self['reconstruction']:
                        raise InvalidMadAnalysis5Card(
               "Option '%s' is only available within the definition of a reconstruction"%option)
                    if not value.lower() in ['lhe','root']:
                        raise InvalidMadAnalysis5Card(
                                  "Option '%s' can only take the values 'lhe' or 'root'"%option)
                    self['reconstruction'][current_name]['reco_output'] = value.lower()
                
                elif option.startswith('recasting'):
                    current_type = 'recasting'
                    try:
                        current_name = option.split('_')[1]
                    except:
                        raise InvalidMadAnalysis5Card('Malformed MA5 recasting option %s.'%option)
                    if len(self['recasting'][current_name])>0:
                        raise InvalidMadAnalysis5Card(
               "Only one recasting can be defined in MadAnalysis5 hadron card")
                
                else:
                    raise InvalidMadAnalysis5Card(
               "Unreckognized MG5aMC instruction in MadAnalysis5 card: '%s'"%option)
                
                if option in ['analysis_name','reconstruction_name'] or \
                                                 option.startswith('recasting'):
                    self['order'].append((current_type,current_name))
                continue

            # Add the default analysis if needed since the user does not need
            # to specify it.
            if current_name == 'default' and current_type == 'analyses' and\
                                          'default' not in self['analyses']:
                    self['analyses']['default'] = MadAnalysis5Card.empty_analysis()
                    self['order'].append(('analyses','default'))

            if current_type in ['recasting']:
                self[current_type][current_name].append(line)
            elif current_type in ['reconstruction']:
                self[current_type][current_name]['commands'].append(line)
            elif current_type in ['analyses']:
                self[current_type][current_name]['commands'].append(line)

        if 'reconstruction' in self['analyses'] or len(self['recasting']['card'])>0:
            if mode=='parton':
                raise InvalidMadAnalysis5Card(
      "A parton MadAnalysis5 card cannot specify a recombination or recasting.")
            card_mode = 'hadron'
        elif mode is None:
            card_mode = 'parton'

        self['mode'] = card_mode
        if self['inputs'] == []:
            if self['mode']=='hadron':
                self['inputs']  = self._default_hadron_inputs
            else:
                self['inputs']  = self._default_parton_inputs
        
        # Make sure at least one reconstruction is specified for each hadron
        # level analysis and that it exists.
        if self['mode']=='hadron':
            for analysis_name, analysis in self['analyses'].items():
                if len(analysis['reconstructions'])==0:
                    raise InvalidMadAnalysis5Card('Hadron-level analysis '+\
                      "'%s' is not specified any reconstruction(s)."%analysis_name)
                if any(reco not in self['reconstruction'] for reco in \
                                                   analysis['reconstructions']):
                    raise InvalidMadAnalysis5Card('A reconstructions specified in'+\
                                 " analysis '%s' is not defined."%analysis_name)
    
    def write(self, output):
        """ Write an MA5 card."""

        if isinstance(output, (file, StringIO.StringIO)):
            output_stream = output
        elif isinstance(output, str):
            output_stream = open(output,'w')
        else:
            raise MadGraph5Error('Incorrect input for the write function of'+\
              ' the MadAnalysis5Card card. Received argument type is: %s'%str(type(output)))
        
        output_lines = []
        if self._skip_analysis:
            output_lines.append('%s skip_analysis'%self._MG5aMC_escape_tag)
        output_lines.append('%s inputs = %s'%(self._MG5aMC_escape_tag,','.join(self['inputs'])))
        if not self['stdout_lvl'] is None:
            output_lines.append('%s stdout_lvl=%s'%(self._MG5aMC_escape_tag,self['stdout_lvl']))
        for definition_type, name in self['order']:
            
            if definition_type=='analyses':
                output_lines.append('%s analysis_name = %s'%(self._MG5aMC_escape_tag,name))
                output_lines.append('%s set_reconstructions = %s'%(self._MG5aMC_escape_tag,
                                str(self['analyses'][name]['reconstructions'])))                
            elif definition_type=='reconstruction':
                output_lines.append('%s reconstruction_name = %s'%(self._MG5aMC_escape_tag,name))
            elif definition_type=='recasting':
                output_lines.append('%s recasting_%s'%(self._MG5aMC_escape_tag,name))

            if definition_type in ['recasting']:
                output_lines.extend(self[definition_type][name])
            elif definition_type in ['reconstruction']:
                output_lines.append('%s reco_output = %s'%(self._MG5aMC_escape_tag,
                                    self[definition_type][name]['reco_output']))                
                output_lines.extend(self[definition_type][name]['commands'])
            elif definition_type in ['analyses']:
                output_lines.extend(self[definition_type][name]['commands'])                
        
        output_stream.write('\n'.join(output_lines))
        
        return
    
    def get_MA5_cmds(self, inputs_arg, submit_folder, run_dir_path=None, 
                                               UFO_model_path=None, run_tag=''):
        """ Returns a list of tuples ('AnalysisTag',['commands']) specifying 
        the commands of the MadAnalysis runs required from this card. 
        At parton-level, the number of such commands is the number of analysis 
        asked for. In the future, the idea is that the entire card can be
        processed in one go from MA5 directly."""
        
        if isinstance(inputs_arg, list):
            inputs = inputs_arg
        elif isinstance(inputs_arg, str):
            inputs = [inputs_arg]
        else:
            raise MadGraph5Error("The function 'get_MA5_cmds' can only take "+\
                            " a string or a list for the argument 'inputs_arg'")
        
        if len(inputs)==0:
            raise MadGraph5Error("The function 'get_MA5_cmds' must have "+\
                                              " at least one input specified'")
        
        if run_dir_path is None:
            run_dir_path = os.path.dirname(inputs_arg)
        
        cmds_list = []
        
        UFO_load = []
        # first import the UFO if provided
        if UFO_model_path:
            UFO_load.append('import %s'%UFO_model_path)
        
        def get_import(input, type=None):
            """ Generates the MA5 import commands for that event file. """
            dataset_name = os.path.basename(input).split('.')[0]
            res = ['import %s as %s'%(input, dataset_name)]
            if not type is None:
                res.append('set %s.type = %s'%(dataset_name, type))
            return res
        
        fifo_status = {'warned_fifo':False,'fifo_used_up':False}
        def warn_fifo(input):
            if not input.endswith('.fifo'):
                return False
            if not fifo_status['fifo_used_up']:
                fifo_status['fifo_used_up'] = True
                return False
            else:
                if not fifo_status['warned_fifo']:
                    logger.warning('Only the first MA5 analysis/reconstructions can be run on a fifo. Subsequent runs will skip fifo inputs.')
                    fifo_status['warned_fifo'] = True
                return True
            
        # Then the event file(s) input(s)
        inputs_load = []
        for input in inputs:
            inputs_load.extend(get_import(input))
        
        submit_command = 'submit %s'%submit_folder+'_%s'
        
        # Keep track of the reconstruction outpus in the MA5 workflow
        # Keys are reconstruction names and values are .lhe.gz reco file paths.
        # We put by default already the lhco/root ones present
        reconstruction_outputs = {
                'lhco_input':[f for f in inputs if 
                                 f.endswith('.lhco') or f.endswith('.lhco.gz')],
                'root_input':[f for f in inputs if 
                                 f.endswith('.root') or f.endswith('.root.gz')]}

        # If a recasting card has to be written out, chose here its path
        recasting_card_path = pjoin(run_dir_path,
       '_'.join([run_tag,os.path.basename(submit_folder),'recasting_card.dat']))

        # Make sure to only run over one analysis over each fifo.
        for definition_type, name in self['order']:
            if definition_type == 'reconstruction':   
                analysis_cmds = list(self['reconstruction'][name]['commands'])
                reco_outputs = []
                for i_input, input in enumerate(inputs):
                    # Skip lhco/root as they must not be reconstructed
                    if not MadAnalysis5Card.events_can_be_reconstructed(input):
                        continue
                    # Make sure the input is not a used up fifo.
                    if warn_fifo(input):
                        continue
                    analysis_cmds.append('import %s as reco_events'%input)
                    if self['reconstruction'][name]['reco_output']=='lhe':
                        reco_outputs.append('%s_%s.lhe.gz'%(os.path.basename(
                               input).replace('_events','').split('.')[0],name))
                        analysis_cmds.append('set main.outputfile=%s'%reco_outputs[-1])
                    elif self['reconstruction'][name]['reco_output']=='root':
                        reco_outputs.append('%s_%s.root'%(os.path.basename(
                               input).replace('_events','').split('.')[0],name))
                        analysis_cmds.append('set main.fastsim.rootfile=%s'%reco_outputs[-1])
                    analysis_cmds.append(
                                 submit_command%('reco_%s_%d'%(name,i_input+1)))
                    analysis_cmds.append('remove reco_events')
                    
                reconstruction_outputs[name]= [pjoin(run_dir_path,rec_out) 
                                                    for rec_out in reco_outputs]
                if len(reco_outputs)>0:
                    cmds_list.append(('_reco_%s'%name,analysis_cmds))

            elif definition_type == 'analyses':
                if self['mode']=='parton':
                    cmds_list.append( (name, UFO_load+inputs_load+
                      self['analyses'][name]['commands']+[submit_command%name]) )
                elif self['mode']=='hadron':
                    # Also run on the already reconstructed root/lhco files if found.
                    for reco in self['analyses'][name]['reconstructions']+\
                                                    ['lhco_input','root_input']:
                        if len(reconstruction_outputs[reco])==0:
                            continue
                        if self['reconstruction'][reco]['reco_output']=='lhe':
                            # For the reconstructed lhe output we must be in parton mode
                            analysis_cmds = ['set main.mode = parton']
                        else:
                            analysis_cmds = []
                        analysis_cmds.extend(sum([get_import(rec_out) for 
                                   rec_out in reconstruction_outputs[reco]],[]))
                        analysis_cmds.extend(self['analyses'][name]['commands'])
                        analysis_cmds.append(submit_command%('%s_%s'%(name,reco)))
                        cmds_list.append( ('%s_%s'%(name,reco),analysis_cmds)  )

            elif definition_type == 'recasting':
                if len(self['recasting']['card'])==0:
                    continue
                if name == 'card':
                    # Create the card here
                    open(recasting_card_path,'w').write('\n'.join(self['recasting']['card']))
                if name == 'commands':
                    recasting_cmds = list(self['recasting']['commands'])
                    # Exclude LHCO files here of course
                    n_inputs = 0
                    for input in inputs:
                        if not MadAnalysis5Card.events_can_be_reconstructed(input):
                            continue
                        # Make sure the input is not a used up fifo.
                        if warn_fifo(input):
                            continue
                        recasting_cmds.extend(get_import(input,'signal'))
                        n_inputs += 1

                    recasting_cmds.append('set main.recast.card_path=%s'%recasting_card_path)
                    recasting_cmds.append(submit_command%'Recasting')
                    if n_inputs>0:
                        cmds_list.append( ('Recasting',recasting_cmds))

        return cmds_list

class RunCardNLO(RunCard):
    """A class object for the run_card for a (aMC@)NLO pocess"""
    
    def default_setup(self):
        """define the default value"""
        
        self.add_param('run_tag', 'tag_1', include=False)
        self.add_param('nevents', 10000)
        self.add_param('req_acc', -1.0, include=False)
        self.add_param('nevt_job', -1, include=False)
        self.add_param('event_norm', 'average')
        #FO parameter
        self.add_param('req_acc_fo', 0.01, include=False)        
        self.add_param('npoints_fo_grid', 5000, include=False)
        self.add_param('niters_fo_grid', 4, include=False)
        self.add_param('npoints_fo', 10000, include=False)        
        self.add_param('niters_fo', 6, include=False)
        #seed and collider
        self.add_param('iseed', 0)
        self.add_param('lpp1', 1, fortran_name='lpp(1)')        
        self.add_param('lpp2', 1, fortran_name='lpp(2)')                        
        self.add_param('ebeam1', 6500.0, fortran_name='ebeam(1)')
        self.add_param('ebeam2', 6500.0, fortran_name='ebeam(2)')        
        self.add_param('pdlabel', 'nn23nlo', allowed=['lhapdf', 'cteq6_m','cteq6_d','cteq6_l','cteq6l1', 'nn23lo','nn23lo1','nn23nlo'])                
        self.add_param('lhaid', [244600],fortran_name='lhaPDFid')
        self.add_param('lhapdfsetname', ['internal_use_only'], system=True)
        #shower and scale
        self.add_param('parton_shower', 'HERWIG6', fortran_name='shower_mc')        
        self.add_param('shower_scale_factor',1.0)
        self.add_param('fixed_ren_scale', False)
        self.add_param('fixed_fac_scale', False)
        self.add_param('mur_ref_fixed', 91.118)                       
        self.add_param('muf1_ref_fixed', -1.0, hidden=True)
        self.add_param('muf_ref_fixed', 91.118)                       
        self.add_param('muf2_ref_fixed', -1.0, hidden=True)
        self.add_param("dynamical_scale_choice", [-1],fortran_name='dyn_scale', comment="\'-1\' is based on CKKW back clustering (following feynman diagram).\n \'1\' is the sum of transverse energy.\n '2' is HT (sum of the transverse mass)\n '3' is HT/2")
        self.add_param('fixed_qes_scale', False, hidden=True)
        self.add_param('qes_ref_fixed', -1.0, hidden=True)
        self.add_param('mur_over_ref', 1.0)
        self.add_param('muf_over_ref', 1.0)                       
        self.add_param('muf1_over_ref', -1.0, hidden=True)                       
        self.add_param('muf2_over_ref', -1.0, hidden=True)
        self.add_param('qes_over_ref', -1.0, hidden=True)
        self.add_param('reweight_scale', [True], fortran_name='lscalevar')
        self.add_param('rw_rscale_down', -1.0, hidden=True)        
        self.add_param('rw_rscale_up', -1.0, hidden=True)
        self.add_param('rw_fscale_down', -1.0, hidden=True)                       
        self.add_param('rw_fscale_up', -1.0, hidden=True)
        self.add_param('rw_rscale', [1.0,2.0,0.5], fortran_name='scalevarR')
        self.add_param('rw_fscale', [1.0,2.0,0.5], fortran_name='scalevarF')
        self.add_param('reweight_pdf', [False], fortran_name='lpdfvar')
        self.add_param('pdf_set_min', 244601, hidden=True)
        self.add_param('pdf_set_max', 244700, hidden=True)
        self.add_param('store_rwgt_info', False)
        self.add_param('systematics_program', 'none', include=False, hidden=True, comment='Choose which program to use for systematics computation: none, systematics')
        self.add_param('systematics_arguments', [''], include=False, hidden=True, comment='Choose the argment to pass to the systematics command. like --mur=0.25,1,4. Look at the help of the systematics function for more details.')
             
        #merging
        self.add_param('ickkw', 0, allowed=[-1,0,3,4], comment=" - 0: No merging\n - 3:  FxFx Merging :  http://amcatnlo.cern.ch/FxFx_merging.htm\n - 4: UNLOPS merging (No interface within MG5aMC)\n - -1:  NNLL+NLO jet-veto computation. See arxiv:1412.8408 [hep-ph]")
        self.add_param('bwcutoff', 15.0)
        #cuts        
        self.add_param('jetalgo', 1.0)
        self.add_param('jetradius', 0.7)         
        self.add_param('ptj', 10.0 , cut=True)
        self.add_param('etaj', -1.0, cut=True)        
        self.add_param('ptl', 0.0, cut=True)
        self.add_param('etal', -1.0, cut=True) 
        self.add_param('drll', 0.0, cut=True)
        self.add_param('drll_sf', 0.0, cut=True)        
        self.add_param('mll', 0.0, cut=True)
        self.add_param('mll_sf', 30.0, cut=True) 
        self.add_param('ptgmin', 20.0, cut=True)
        self.add_param('etagamma', -1.0)        
        self.add_param('r0gamma', 0.4)
        self.add_param('xn', 1.0)                         
        self.add_param('epsgamma', 1.0)
        self.add_param('isoem', True)        
        self.add_param('maxjetflavor', 4, hidden=True)
        self.add_param('iappl', 0)   
        self.add_param('lhe_version', 3, hidden=True, include=False)
        
        #internal variable related to FO_analyse_card
        self.add_param('FO_LHE_weight_ratio',1e-3, hidden=True, system=True)
        self.add_param('FO_LHE_postprocessing',['grouping','random'], 
                       hidden=True, system=True, include=False)
    
        # parameter allowing to define simple cut via the pdg
        self.add_param('pt_min_pdg',{'__type__':0.}, include=False,cut=True)
        self.add_param('pt_max_pdg',{'__type__':0.}, include=False,cut=True)
        self.add_param('mxx_min_pdg',{'__type__':0.}, include=False,cut=True)
        self.add_param('mxx_only_part_antipart', {'default':False}, include=False, hidden=True)
        
        #hidden parameter that are transfer to the fortran code
        self.add_param('pdg_cut',[0], hidden=True, system=True) # store which PDG are tracked
        self.add_param('ptmin4pdg',[0.], hidden=True, system=True) # store pt min
        self.add_param('ptmax4pdg',[-1.], hidden=True, system=True)
        self.add_param('mxxmin4pdg',[0.], hidden=True, system=True)
        self.add_param('mxxpart_antipart', [False], hidden=True, system=True)
        
    def check_validity(self):
        """check the validity of the various input"""
        
        super(RunCardNLO, self).check_validity()

        # for lepton-lepton collisions, ignore 'pdlabel' and 'lhaid'
        if abs(self['lpp1'])!=1 or abs(self['lpp2'])!=1:
            if self['lpp1'] == 1 or self['lpp2']==1:
                raise InvalidRunCard('Process like Deep Inelastic scattering not supported at NLO accuracy.')
            
            if self['pdlabel']!='nn23nlo' or self['reweight_pdf']:
                self['pdlabel']='nn23nlo'
                self['reweight_pdf']=[False]
                logger.info('''Lepton-lepton collisions: ignoring PDF related parameters in the run_card.dat (pdlabel, lhaid, reweight_pdf, ...)''')
        
        # For FxFx merging, make sure that the following parameters are set correctly:
        if self['ickkw'] == 3: 
            # 1. Renormalization and factorization (and ellis-sexton scales) are not fixed       
            scales=['fixed_ren_scale','fixed_fac_scale','fixed_QES_scale']
            for scale in scales:
                if self[scale]:
                    logger.warning('''For consistency in the FxFx merging, \'%s\' has been set to false'''
                                % scale,'$MG:BOLD')
                    self[scale]= False
            #and left to default dynamical scale
            if len(self["dynamical_scale_choice"]) > 1 or self["dynamical_scale_choice"][0] != -1:
                self["dynamical_scale_choice"] = [-1]
                self["reweight_scale"]=[self["reweight_scale"][0]]
                logger.warning('''For consistency in the FxFx merging, dynamical_scale_choice has been set to -1 (default)'''
                                ,'$MG:BOLD')
                
            # 2. Use kT algorithm for jets with pseudo-code size R=1.0
            jetparams=['jetradius','jetalgo']
            for jetparam in jetparams:
                if float(self[jetparam]) != 1.0:
                    logger.info('''For consistency in the FxFx merging, \'%s\' has been set to 1.0'''
                                % jetparam ,'$MG:BOLD')
                    self[jetparam] = 1.0
        elif self['ickkw'] == -1 and (self["dynamical_scale_choice"][0] != -1 or
                                      len(self["dynamical_scale_choice"]) > 1):
                self["dynamical_scale_choice"] = [-1]
                self["reweight_scale"]=[self["reweight_scale"][0]]
                logger.warning('''For consistency with the jet veto, the scale which will be used is ptj. dynamical_scale_choice will be set at -1.'''
                                ,'$MG:BOLD')            
                                
        # For interface to APPLGRID, need to use LHAPDF and reweighting to get scale uncertainties
        if self['iappl'] != 0 and self['pdlabel'].lower() != 'lhapdf':
            raise InvalidRunCard('APPLgrid generation only possible with the use of LHAPDF')
        if self['iappl'] != 0 and not self['reweight_scale']:
            raise InvalidRunCard('APPLgrid generation only possible with including' +\
                                      ' the reweighting to get scale dependence')

        # Hidden values check
        if self['qes_ref_fixed'] == -1.0:
            self['qes_ref_fixed']=self['mur_ref_fixed']
        if self['qes_over_ref'] == -1.0:
            self['qes_over_ref']=self['mur_over_ref']
        if self['muf1_over_ref'] != -1.0 and self['muf1_over_ref'] == self['muf2_over_ref']:
            self['muf_over_ref']=self['muf1_over_ref']
        if self['muf1_over_ref'] == -1.0:
            self['muf1_over_ref']=self['muf_over_ref']
        if self['muf2_over_ref'] == -1.0:
            self['muf2_over_ref']=self['muf_over_ref']
        if self['muf1_ref_fixed'] != -1.0 and self['muf1_ref_fixed'] == self['muf2_ref_fixed']:
            self['muf_ref_fixed']=self['muf1_ref_fixed']
        if self['muf1_ref_fixed'] == -1.0:
            self['muf1_ref_fixed']=self['muf_ref_fixed']
        if self['muf2_ref_fixed'] == -1.0:
            self['muf2_ref_fixed']=self['muf_ref_fixed']
        # overwrite rw_rscale and rw_fscale when rw_(r/f)scale_(down/up) are explicitly given in the run_card for backward compatibility.
        if (self['rw_rscale_down'] != -1.0 and ['rw_rscale_down'] not in self['rw_rscale']) or\
           (self['rw_rscale_up'] != -1.0 and ['rw_rscale_up'] not in self['rw_rscale']):
            self['rw_rscale']=[1.0,self['rw_rscale_up'],self['rw_rscale_down']]
        if (self['rw_fscale_down'] != -1.0 and ['rw_fscale_down'] not in self['rw_fscale']) or\
           (self['rw_fscale_up'] != -1.0 and ['rw_fscale_up'] not in self['rw_fscale']):
            self['rw_fscale']=[1.0,self['rw_fscale_up'],self['rw_fscale_down']]
    
        # PDF reweighting check
        if any(self['reweight_pdf']):
            # check that we use lhapdf if reweighting is ON
            if self['pdlabel'] != "lhapdf":
                raise InvalidRunCard('Reweight PDF option requires to use pdf sets associated to lhapdf. Please either change the pdlabel to use LHAPDF or set reweight_pdf to False.')

        # make sure set have reweight_pdf and lhaid of length 1 when not including lhapdf
        if self['pdlabel'] != "lhapdf":
            self['reweight_pdf']=[self['reweight_pdf'][0]]
            self['lhaid']=[self['lhaid'][0]]
            
        # make sure set have reweight_scale and dyn_scale_choice of length 1 when fixed scales:
        if self['fixed_ren_scale'] and self['fixed_fac_scale']:
            self['reweight_scale']=[self['reweight_scale'][0]]
            self['dynamical_scale_choice']=[0]

        # If there is only one reweight_pdf/reweight_scale, but
        # lhaid/dynamical_scale_choice are longer, expand the
        # reweight_pdf/reweight_scale list to have the same length
        if len(self['reweight_pdf']) == 1 and len(self['lhaid']) != 1:
            self['reweight_pdf']=self['reweight_pdf']*len(self['lhaid'])
            logger.warning("Setting 'reweight_pdf' for all 'lhaid' to %s" % self['reweight_pdf'][0])
        if len(self['reweight_scale']) == 1 and len(self['dynamical_scale_choice']) != 1:
            self['reweight_scale']=self['reweight_scale']*len(self['dynamical_scale_choice']) 
            logger.warning("Setting 'reweight_scale' for all 'dynamical_scale_choice' to %s" % self['reweight_pdf'][0])

        # Check that there are no identical elements in lhaid or dynamical_scale_choice
        if len(self['lhaid']) != len(set(self['lhaid'])):
                raise InvalidRunCard("'lhaid' has two or more identical entries. They have to be all different for the code to work correctly.")
        if len(self['dynamical_scale_choice']) != len(set(self['dynamical_scale_choice'])):
                raise InvalidRunCard("'dynamical_scale_choice' has two or more identical entries. They have to be all different for the code to work correctly.")
            
        # Check that lenght of lists are consistent
        if len(self['reweight_pdf']) != len(self['lhaid']):
            raise InvalidRunCard("'reweight_pdf' and 'lhaid' lists should have the same length")
        if len(self['reweight_scale']) != len(self['dynamical_scale_choice']):
            raise InvalidRunCard("'reweight_scale' and 'dynamical_scale_choice' lists should have the same length")
        if len(self['dynamical_scale_choice']) > 10 :
            raise InvalidRunCard("Length of list for 'dynamical_scale_choice' too long: max is 10.")
        if len(self['lhaid']) > 25 :
            raise InvalidRunCard("Length of list for 'lhaid' too long: max is 25.")
        if len(self['rw_rscale']) > 9 :
            raise InvalidRunCard("Length of list for 'rw_rscale' too long: max is 9.")
        if len(self['rw_fscale']) > 9 :
            raise InvalidRunCard("Length of list for 'rw_fscale' too long: max is 9.")
    # make sure that the first element of rw_rscale and rw_fscale is the 1.0
        if 1.0 not in self['rw_rscale']:
            logger.warning("'1.0' has to be part of 'rw_rscale', adding it")
            self['rw_rscale'].insert(0,1.0)
        if 1.0 not in self['rw_fscale']:
            logger.warning("'1.0' has to be part of 'rw_fscale', adding it")
            self['rw_fscale'].insert(0,1.0)
        if self['rw_rscale'][0] != 1.0 and 1.0 in self['rw_rscale']:
            a=self['rw_rscale'].index(1.0)
            self['rw_rscale'][0],self['rw_rscale'][a]=self['rw_rscale'][a],self['rw_rscale'][0]
        if self['rw_fscale'][0] != 1.0 and 1.0 in self['rw_fscale']:
            a=self['rw_fscale'].index(1.0)
            self['rw_fscale'][0],self['rw_fscale'][a]=self['rw_fscale'][a],self['rw_fscale'][0]
    # check that all elements of rw_rscale and rw_fscale are diffent.
        if len(self['rw_rscale']) != len(set(self['rw_rscale'])):
                raise InvalidRunCard("'rw_rscale' has two or more identical entries. They have to be all different for the code to work correctly.")
        if len(self['rw_fscale']) != len(set(self['rw_fscale'])):
                raise InvalidRunCard("'rw_fscale' has two or more identical entries. They have to be all different for the code to work correctly.")


        # check that ebeam is bigger than the proton mass.
        for i in [1,2]:
            if self['lpp%s' % i ] not in [1,2]:
                continue

            if self['ebeam%i' % i] < 0.938:
                if self['ebeam%i' %i] == 0:
                    logger.warning("At rest proton mode set: Energy beam set to 0.938")
                    self.set('ebeam%i' %i, 0.938)
                else:
                    raise InvalidRunCard("Energy for beam %i lower than proton mass. Please fix this")    


    def update_system_parameter_for_include(self):
        
        # set the pdg_for_cut fortran parameter
        pdg_to_cut = set(list(self['pt_min_pdg'].keys()) +list(self['pt_max_pdg'].keys())+
                         list(self['mxx_min_pdg'].keys())+ list(self['mxx_only_part_antipart'].keys()))
        pdg_to_cut.discard('__type__')
        pdg_to_cut.discard('default')
        if len(pdg_to_cut)>25:
            raise Exception("Maximum 25 different PDGs are allowed for PDG specific cut")
        
        if any(int(pdg)<0 for pdg in pdg_to_cut):
            logger.warning('PDG specific cuts are always applied symmetrically on particle/anti-particle. Always use positve PDG codes')
            raise MadGraph5Error('Some PDG specific cuts are defined with negative PDG codes')
        
        
        if any(pdg in pdg_to_cut for pdg in [21,22,11,13,15]+ list(range(self['maxjetflavor']+1))):
            # Note that this will double check in the fortran code
            raise Exception("Can not use PDG related cuts for massless SM particles/leptons")
        if pdg_to_cut:
            self['pdg_cut'] = list(pdg_to_cut)
            self['ptmin4pdg'] = []
            self['ptmax4pdg'] = []
            self['mxxmin4pdg'] = []
            self['mxxpart_antipart']  = []
            for pdg in self['pdg_cut']:
                for var in ['pt','mxx']:
                    for minmax in ['min', 'max']:
                        if var == 'mxx' and minmax == 'max':
                            continue
                        new_var = '%s%s4pdg' % (var, minmax)
                        old_var = '%s_%s_pdg' % (var, minmax)
                        default = 0. if minmax=='min' else -1.
                        self[new_var].append(self[old_var][str(pdg)] if str(pdg) in self[old_var] else default)
                #special for mxx_part_antipart
                old_var = 'mxx_only_part_antipart'
                new_var = 'mxxpart_antipart'
                if 'default' in self[old_var]:
                    default = self[old_var]['default']
                    self[new_var].append(self[old_var][str(pdg)] if str(pdg) in self[old_var] else default)
                else:
                    if str(pdg) not in self[old_var]:
                        raise Exception("no default value defined for %s and no value defined for pdg %s" % (old_var, pdg)) 
                    self[new_var].append(self[old_var][str(pdg)])
        else:
            self['pdg_cut'] = [0]
            self['ptmin4pdg'] = [0.]
            self['ptmax4pdg'] = [-1.]
            self['mxxmin4pdg'] = [0.]
            self['mxxpart_antipart'] = [False]

    def write(self, output_file, template=None, python_template=False, **opt):
        """Write the run_card in output_file according to template 
           (a path to a valid run_card)"""

        if not template:
            if not MADEVENT:
                template = pjoin(MG5DIR, 'Template', 'NLO', 'Cards', 
                                                        'run_card.dat')
                python_template = True
            else:
                template = pjoin(MEDIR, 'Cards', 'run_card_default.dat')
                python_template = False
       
        super(RunCardNLO, self).write(output_file, template=template,
                                    python_template=python_template, **opt)


    def create_default_for_process(self, proc_characteristic, history, proc_def):
        """Rules
          e+ e- beam -> lpp:0 ebeam:500  
          p p beam -> set maxjetflavor automatically
        """

        # check for beam_id
        beam_id = set()
        for proc in proc_def:
            for leg in proc['legs']:
                if not leg['state']:
                    beam_id.add(leg['id'])
        if any(i in beam_id for i in [1,-1,2,-2,3,-3,4,-4,5,-5,21,22]):
            maxjetflavor = max([4]+[abs(i) for i in beam_id if  -7< i < 7])
            self['maxjetflavor'] = maxjetflavor
            pass
        elif any(id in beam_id for id in [11,-11,13,-13]):
            self['lpp1'] = 0
            self['lpp2'] = 0
            self['ebeam1'] = 500
            self['ebeam2'] = 500
        else:
            self['lpp1'] = 0
            self['lpp2'] = 0  
            
        if proc_characteristic['ninitial'] == 1:
            #remove all cut
            self.remove_all_cut()
            
        # Check if need matching
        min_particle = 99
        max_particle = 0
        for proc in proc_def:
            min_particle = min(len(proc['legs']), min_particle)
            max_particle = max(len(proc['legs']), max_particle)
        matching = False
        if min_particle != max_particle:
            #take one of the process with min_particle
            for procmin in proc_def:
                if len(procmin['legs']) != min_particle:
                    continue
                else:
                    idsmin = [l['id'] for l in procmin['legs']]
                    break
            
            for procmax in proc_def:
                if len(procmax['legs']) != max_particle:
                    continue
                idsmax =  [l['id'] for l in procmax['legs']]
                for i in idsmin:
                    if i not in idsmax:
                        continue
                    else:
                        idsmax.remove(i)
                for j in idsmax:
                    if j not in [1,-1,2,-2,3,-3,4,-4,5,-5,21]:
                        break
                else:
                    # all are jet => matching is ON
                    matching=True
                    break 
        
        if matching: 
            self['ickkw'] = 3
            self['fixed_ren_scale'] = False
            self["fixed_fac_scale"] = False
            self["fixed_QES_scale"] = False
            self["jetalgo"] = 1
            self["jetradius"] = 1
            self["parton_shower"] = "PYTHIA8"
            
    
    
    
class MadLoopParam(ConfigFile):
    """ a class for storing/dealing with the file MadLoopParam.dat
    contains a parser to read it, facilities to write a new file,...
    """
    
    _ID_reduction_tool_map = {1:'CutTools',
                             2:'PJFry++',
                             3:'IREGI',
                             4:'Golem95',
                             5:'Samurai',
                             6:'Ninja',
                             7:'COLLIER'}
    
    def default_setup(self):
        """initialize the directory to the default value"""
        
        self.add_param("MLReductionLib", "6|7|1")
        self.add_param("IREGIMODE", 2)
        self.add_param("IREGIRECY", True)
        self.add_param("CTModeRun", -1)
        self.add_param("MLStabThres", 1e-3)
        self.add_param("NRotations_DP", 0)
        self.add_param("NRotations_QP", 0)
        self.add_param("ImprovePSPoint", 2)
        self.add_param("CTLoopLibrary", 2)
        self.add_param("CTStabThres", 1e-2)
        self.add_param("CTModeInit", 1)
        self.add_param("CheckCycle", 3)
        self.add_param("MaxAttempts", 10)
        self.add_param("ZeroThres", 1e-9)
        self.add_param("OSThres", 1.0e-8)
        self.add_param("DoubleCheckHelicityFilter", True)
        self.add_param("WriteOutFilters", True)
        self.add_param("UseLoopFilter", False)
        self.add_param("HelicityFilterLevel", 2)
        self.add_param("LoopInitStartOver", False)
        self.add_param("HelInitStartOver", False)
        self.add_param("UseQPIntegrandForNinja", True)        
        self.add_param("UseQPIntegrandForCutTools", True)
        self.add_param("COLLIERMode", 1)
        self.add_param("COLLIERComputeUVpoles", True)
        self.add_param("COLLIERComputeIRpoles", True)
        self.add_param("COLLIERRequiredAccuracy", 1.0e-8)
        self.add_param("COLLIERCanOutput",False)
        self.add_param("COLLIERGlobalCache",-1)
        self.add_param("COLLIERUseCacheForPoles",False)
        self.add_param("COLLIERUseInternalStabilityTest",True)

    def read(self, finput):
        """Read the input file, this can be a path to a file, 
           a file object, a str with the content of the file."""
           
        if isinstance(finput, str):
            if "\n" in finput:
                finput = finput.split('\n')
            elif os.path.isfile(finput):
                finput = open(finput)
            else:
                raise Exception("No such file %s" % input)
        
        previous_line= ''
        for line in finput:
            if previous_line.startswith('#'):
                name = previous_line[1:].split()[0]
                value = line.strip()
                if len(value) and value[0] not in ['#', '!']:
                    self.__setitem__(name, value, change_userdefine=True)
            previous_line = line
        
    
    def write(self, outputpath, template=None,commentdefault=False):
        
        if not template:
            if not MADEVENT:
                template = pjoin(MG5DIR, 'Template', 'loop_material', 'StandAlone', 
                                                   'Cards', 'MadLoopParams.dat')
            else:
                template = pjoin(MEDIR, 'Cards', 'MadLoopParams_default.dat')
        fsock = open(template, 'r')
        template = fsock.readlines()
        fsock.close()
        
        if isinstance(outputpath, str):
            output = open(outputpath, 'w')
        else:
            output = outputpath

        def f77format(value):
            if isinstance(value, bool):
                if value:
                    return '.true.'
                else:
                    return '.false.'
            elif isinstance(value, int):
                return value
            elif isinstance(value, float):
                tmp ='%e' % value
                return tmp.replace('e','d')
            elif isinstance(value, str):
                return value
            else:
                raise Exception("Can not format input %s" % type(value))
            
        name = ''
        done = set()
        for line in template:
            if name:
                done.add(name)
                if commentdefault and name.lower() not in self.user_set :
                    output.write('!%s\n' % f77format(self[name]))
                else:
                    output.write('%s\n' % f77format(self[name]))
                name=''
                continue
            elif line.startswith('#'):
                name = line[1:].split()[0]
            output.write(line)
        
    
            
        
        
        
        
    
