"""
CliMAF macros module :
- define macros from CliMAF objects,
- find macro patterns in CRS expressions,
- rewrite CRS expressions,
- read/write macro set
"""
import sys, os
from classes import cobject, cdataset, ctree, scriptChild, cpage, allow_error_on_ds, cens, cdummy
from clogging import clogger, dedent
#: Dictionary of macros
cmacros=dict()
[docs]def macro(name,cobj,lobjects=[]):
"""
Define a CliMAF macro from a CliMAF compound object.
Transform a Climaf object in a macro, replacing all datasets,
and the objects of lobjects, by a dummy argument. Register it in
dict cmacros, if name is not None
Args:
name (string) : the name you want to give to the macro; a Python
function with the same name will be defined
cobj (CliMAF object, or string) : any CliMAF object, usually
the result of a series of operators, that you would like to
repeat using other input datasets; alternatively, you can provide
the macro formula as a string (when accustomed to the syntax)
lobjects (list, optional): for expert use- a list of objects,
which are sub-objects of cobject, and which should become arguments
of the macro
Returns:
a macro; the returned value is usualy not used 'as is' : a
python function is also defined in module cmacros and in main
namespace, and you may use it in the same way as a CliMAF
operator. All the datasets involved in ``cobj`` become arguments
of the macro, which allows you to re-do the same computations
and easily define objects similar to ``cobjs``
Example::
>>> # First use and combine CliMAF operators to get some interesting result using some dataset(s)
>>> january_ta=ds(project='example',simulation='AMIPV6ALB2G',variable='ta',frequency='monthly',period='198001')
>>> ta_europe=llbox(january_ta,latmin=40,latmax=60,lonmin=-15,lonmax=25)
>>> ta_ezm=ccdo(ta_europe,operator='zonmean')
>>> fig_ezm=plot(ta_ezm)
>>> #
>>> # Using this result as an example, define a macro named 'eu_cross_section',
>>> # which arguments will be the datasets involved in this result
>>> cmacro('eu_cross_section',fig_ezm)
>>> #
>>> # You can of course apply a macro to another dataset(s) (even here to a 2D variable)
>>> pr=ds(project='example',simulation='AMIPV6ALB2G', variable='pr', frequency='monthly', period='198001')
>>> pr_ezm=eu_cross_section(pr)
>>> #
>>> # All macros are registered in dictionary climaf.cmacro.cmacros,
>>> # which is imported by climaf.api; you can list it by :
>>> cmacros
Note : macros are automatically saved in file ~/.climaf.macros, and can be edited
See also much more explanations in the example at :download:`macro.py <../examples/macro.py>`
"""
if isinstance(cobj,str) :
s=cobj
# Next line used for interpreting macros's CRS
exec("from climaf.cmacro import cdummy; ARG=cdummy()", sys.modules['__main__'].__dict__)
try :
cobj=eval(cobj, sys.modules['__main__'].__dict__)
except :
# usually case of a CRS which project is not currently defined
clogger.error("Cannot interpret %s with the projects currently define"%s)
return None
#print "string %s was interpreted as %s"%(s,cobj)
domatch=False
for o in lobjects :
domatch = domatch or cobj==o or \
( isinstance(cobj,cobject) and cobj.buildcrs() == o.buildcrs())
if isinstance(cobj,cdataset) or isinstance(cobj,cdummy) or domatch :
return cdummy()
elif isinstance(cobj,ctree) :
rep=ctree(cobj.operator, cobj.script,
*cobj.operands, **cobj.parameters)
rep.operands = map(macro,[ None for o in rep.operands],rep.operands)
elif isinstance(cobj,scriptChild) :
rep=scriptChild(macro(None,cobj.father),cobj.varname)
elif isinstance(cobj,cpage) :
rep=cpage([ map(macro, [ None for fig in line ], line) for line in cobj.fig_lines ], cobj.widths, cobj.heights)
elif isinstance(cobj,cens) :
d=dict()
for k,v in zip(cobj.keys(),map(macro,[ None for o in cobj.values()],cobj.values())) : d[k]=v
rep=cens(d)
elif cobj is None : return None
else :
clogger.error("Cannot yet handle object :%s", `cobj`)
rep=None
if name and rep :
cmacros[name]=rep
doc="A CliMAF macro, which text is "+`rep`
defs='def %s(*args) :\n """%s"""\n return instantiate(cmacros["%s"],[ x for x in args])\n'\
% (name,doc,name)
exec defs in globals()
exec "from climaf.cmacro import %s"%name in sys.modules['__main__'].__dict__
clogger.debug("Macro %s has been declared"%name)
return rep
def crewrite(crs,alsoAtTop=True):
"""
Return the crs expression with sub-trees replaced by macro equivalent
when applicable
Search order is : from CRS tree root try all macros, then do the
same for first subtree, and recursively in depth, and then go
to second subtreesecond
"""
# Next line used for interpreting macros's CRS
exec("ARG=climaf.cmacro.cdummy()", sys.modules['__main__'].__dict__)
#
allow_error_on_ds()
try :
co=eval(crs, sys.modules['__main__'].__dict__)
except:
clogger.debug("Issue when rewriting %s"%crs)
return(crs)
allow_error_on_ds(False)
if isinstance(co,ctree) or isinstance(co,scriptChild) or isinstance(co,cpage) :
if alsoAtTop :
for m in cmacros :
clogger.debug("looking at macro : "+m+"="+`cmacros[m]`+\
" \ncompared to : "+`macro(None,co)`)
argl=cmatch(cmacros[m],co)
if len(argl) > 0 :
rep=m+"("
for arg in argl :
rep+=crewrite(arg.buildcrs(crsrewrite=crewrite))+","
rep+=")"; rep=rep.replace(",)",")")
return rep
# No macro matches at top level, or top level not wished.
# Let us dig a bit
return(co.buildcrs(crsrewrite=crewrite))
else :
return(crs)
def cmatch(macro,cobj) :
"""
Analyze if macro does match cobj, and return the list of objects
matching macro arguments, ordered by depth-first traversal
"""
clogger.debug("matching "+`macro`+" and "+`cobj`)
if isinstance(cobj,ctree) and isinstance(macro,ctree) and \
macro.operator==cobj.operator :
nok=False
for mpara,para in zip(macro.parameters,cobj.parameters) :
if mpara != para or \
macro.parameters[para] != cobj.parameters[para]:
nok=True
if nok : return []
argsub=[]
for mop,op in zip(macro.operands,cobj.operands) :
if isinstance(mop,cdummy) :
argsub.append(op)
else :
argsub+=cmatch(mop,op)
return(argsub)
elif isinstance(cobj,scriptChild) and isinstance(macro,scriptChild) and \
macro.varname==cobj.varname :
return(cmatch(macro.father,cobj.father,argslist))
elif isinstance(cobj,cpage) and isinstance(macro,cpage) :
argsub=[]
if cobj.heights == macro.heights and \
cobj.widths == macro.widths and \
cobj.orientation == macro.orientation :
for mlines,lines in zip(macro.fig_lines,cobj.fig_lines) :
for mfig,fig in zip(mlines,lines) :
if isinstance(mfig,cdummy) :
argsub.append(fig)
else:
argsub+=cmatch(mfig,fig)
return argsub
else : return []
def read(filename):
"""
Read macro dictionary from filename, and add it to cmacros[]
"""
import json
global cmacros
macros_texts=None
try :
macrofile=file(os.path.expanduser(filename), "r")
clogger.debug("Macrofile %s read"%(macrofile))
macros_texts=json.load(macrofile)
clogger.debug("After reading file %s, macros=%s"%(macrofile,`macros_texts`))
macrofile.close()
except:
clogger.info("Issue reading macro file %s ", filename)
if macros_texts :
for m in macros_texts :
clogger.debug("loading macro %s=%s"%(m,macros_texts[m]))
macro(str(m),str(macros_texts[m]))
def write(filename) :
"""
Writes macros dictionary to disk ; should be called before exit
"""
import json
filen=os.path.expanduser(filename)
try :
os.remove(filen)
except :
pass
macrofile=file(filen, "w")
dcrs=cmacros.copy()
for m in dcrs : dcrs[m]=dcrs[m].buildcrs()
json.dump(dcrs,macrofile,sort_keys=True,indent=4)
macrofile.close()
def show(interp=True) :
"""
List the macros, searching also for macro usage in macros (except if arg ``interp`` is True)
"""
for m in cmacros :
if interp :
print "% 15s : %s"%(m,crewrite(cmacros[m].buildcrs(),alsoAtTop=False))
else :
print "% 15s : %s"%(m,cmacros[m])
def instantiate(mac,operands, toplevel=True) :
"""
Return a copy of macro cobject ``mac`` where arguments are instantiated by the list
`operands', used in the order of depth-first tree traversal of ``mac``.
Check that the number of operands is OK vs mac
"""
if isinstance(mac,cdummy) :
if len(operands) > 0 :
rep=operands.pop(0)
else :
raise Climaf_Macro_Error('no operand left')
elif isinstance(mac,ctree) :
opers=[]
for o in mac.operands : opers.append(instantiate(o,operands,toplevel=False))
rep=ctree(mac.operator,mac.script,*opers,**mac.parameters)
elif isinstance(mac,scriptChild) :
father=instantiate(mac.father,operands,toplevel=False)
rep=scriptChild(father,mac.variable)
elif isinstance(mac,cdataset) :
rep=cdataset
if toplevel and len(operands) != 0 :
raise Climaf_Macro_Error('too many operands; left operands are : '+`operands`)
return(rep)
class Climaf_Macro_Error(Exception):
def __init__(self, valeur):
self.valeur = valeur
clogger.error(self.__str__())
dedent(100)
def __str__(self):
return `self.valeur`