# -*- coding: utf-8 -*-
from __future__ import print_function, division, absolute_import
# Author: Subhasis Ray
# Maintainer: Dilawar Singh, Harsha Rani, Upi Bhalla
import warnings
import os
import pydoc
from io import StringIO
from contextlib import closing
# Import function from C++ module into moose namespace.
import moose._moose as _moose
import moose.utils as mu
# sbml import.
sbmlImport_, sbmlError_ = True, ''
try:
import moose.SBML.readSBML as _readSBML
import moose.SBML.writeSBML as _writeSBML
except Exception as e:
sbmlImport_ = False
sbmlError_ = '%s' % e
# NeuroML2 import.
nml2Import_, nml2ImportError_ = True, ''
try:
import moose.neuroml2 as _neuroml2
except Exception as e:
nml2Import_ = False
nml2ImportError_ = ' '.join( [
"NML2 support is disabled because `libneuroml` and "
, "`pyneuroml` modules are not found.\n"
, " $ pip install pyneuroml libneuroml \n"
, " should fix it."
, " Actual error: %s " % e ]
)
chemImport_, chemError_ = True, ''
try:
import moose.chemUtil as _chemUtil
except Exception as e:
chemImport_ = False
chemError_ = '%s' % e
kkitImport_, kkitImport_error_ = True, ''
try:
import moose.genesis.writeKkit as _writeKkit
except ImportError as e:
kkitImport_ = False
kkitImport_err_ = '%s' % e
mergechemImport_, mergechemError_ = True, ''
try:
import moose.chemMerge as _chemMerge
except Exception as e:
mergechemImport_ = False
mergechemError_ = '%s' % e
def loadModel(filename, modelpath, solverclass="gsl"):
"""loadModel: Load model from a file to a specified path.
Parameters
----------
filename: str
model description file.
modelpath: str
moose path for the top level element of the model to be created.
method: str
solver type to be used for simulating the model.
TODO: Link to detailed description of solvers?
Returns
-------
object
moose.element if succcessful else None.
"""
if not os.path.isfile( os.path.realpath(filename) ):
mu.warn( "Model file '%s' does not exists or is not readable." % filename )
return None
extension = os.path.splitext(filename)[1]
if extension in [".swc", ".p"]:
return _moose.loadModelInternal(filename, modelpath, "Neutral" )
if extension in [".g", ".cspace"]:
# only if genesis or cspace file and method != ee then only
# mooseAddChemSolver is called.
ret = _moose.loadModelInternal(filename, modelpath, "ee")
sc = solverclass.lower()
if sc in ["gssa","gillespie","stochastic","gsolve"]:
method = "gssa"
elif sc in ["gsl","runge kutta","deterministic","ksolve","rungekutta","rk5","rkf","rk"]:
method = "gsl"
elif sc in ["exponential euler","exponentialeuler","neutral"]:
method = "ee"
else:
method = "ee"
if method != 'ee':
chemError = _chemUtil.add_Delete_ChemicalSolver.mooseAddChemSolver(modelpath, method)
return ret
else:
mu.error( "Unknown model extenstion '%s'" % extension)
return None
# Version
def version( ):
# Show user version.
return _moose.VERSION
# Tests
from moose.moose_test import test
sequence_types = ['vector<double>',
'vector<int>',
'vector<long>',
'vector<unsigned int>',
'vector<float>',
'vector<unsigned long>',
'vector<short>',
'vector<Id>',
'vector<ObjId>']
known_types = ['void',
'char',
'short',
'int',
'unsigned int',
'double',
'float',
'long',
'unsigned long',
'string',
'vec',
'melement'] + sequence_types
# SBML related functions.
def mooseReadSBML(filepath, loadpath, solver='ee',validate="on"):
"""Load SBML model.
Parameter
--------
filepath: str
filepath to be loaded.
loadpath : str
Root path for this model e.g. /model/mymodel
solver : str
Solver to use (default 'ee').
Available options are "ee", "gsl", "stochastic", "gillespie"
"rk", "deterministic"
For full list see ??
"""
global sbmlImport_
if sbmlImport_:
modelpath = _readSBML.mooseReadSBML(filepath, loadpath, solver, validate)
sc = solver.lower()
if sc in ["gssa","gillespie","stochastic","gsolve"]:
method = "gssa"
elif sc in ["gsl","runge kutta","deterministic","ksolve","rungekutta","rk5","rkf","rk"]:
method = "gsl"
elif sc in ["exponential euler","exponentialeuler","neutral"]:
method = "ee"
else:
method = "ee"
if method != 'ee':
chemError = _chemUtil.add_Delete_ChemicalSolver.mooseAddChemSolver(modelpath[0].path, method)
return modelpath
else:
print( sbmlError_ )
return False
def mooseWriteSBML(modelpath, filepath, sceneitems={}):
"""mooseWriteSBML: Writes loaded model under modelpath to a file in SBML format.
Parameters
----------
modelpath : str
model path in moose e.g /model/mymodel \n
filepath : str
Path of output file. \n
sceneitems : dict
UserWarning: user need not worry about this layout position is saved in
Annotation field of all the moose Object (pool,Reaction,enzyme).
If this function is called from
* GUI - the layout position of moose object is passed
* command line - NA
* if genesis/kkit model is loaded then layout position is taken from the file
* otherwise auto-coordinates is used for layout position.
"""
if sbmlImport_:
return _writeSBML.mooseWriteSBML(modelpath, filepath, sceneitems)
else:
print( sbmlError_ )
return False
def mooseWriteKkit(modelpath, filepath, sceneitems={}):
"""Writes loded model under modelpath to a file in Kkit format.
Parameters
----------
modelpath : str
Model path in moose.
filepath : str
Path of output file.
"""
global kkitImport_, kkitImport_err_
if not kkitImport_:
print( '[WARN] Could not import module to enable this function' )
print( '\tError was %s' % kkitImport_error_ )
return False
return _writeKkit.mooseWriteKkit(modelpath, filepath,sceneitems)
def mooseDeleteChemSolver(modelpath):
"""mooseDeleteChemSolver
deletes solver on all the compartment and its children.
Notes
-----
This is neccesary while created a new moose object on a pre-existing modelpath,
this should be followed by mooseAddChemSolver for add solvers on to compartment
to simulate else default is Exponential Euler (ee)
"""
if chemImport_:
return _chemUtil.add_Delete_ChemicalSolver.mooseDeleteChemSolver(modelpath)
else:
print( chemError_ )
return False
def mooseAddChemSolver(modelpath, solver):
"""mooseAddChemSolver:
Add solver on chemical compartment and its children for calculation
Parameters
----------
modelpath : str
Model path that is loaded into moose.
solver : str
Exponential Euler "ee" is default. Other options are Gillespie ("gssa"),
Runge Kutta ("gsl") etc. Link to documentation?
"""
if chemImport_:
chemError_ = _chemUtil.add_Delete_ChemicalSolver.mooseAddChemSolver(modelpath, solver)
return chemError_
else:
print( chemError_ )
return False
def mergeChemModel(src, des):
"""mergeChemModel: Merges two chemical model.
File or filepath can be passed source is merged to destination
"""
#global mergechemImport_
if mergechemImport_:
return _chemMerge.merge.mergeChemModel(src,des)
else:
return False
# NML2 reader and writer function.
def mooseReadNML2( modelpath, verbose = False ):
"""Read NeuroML model (version 2) and return reader object.
"""
global nml2Import_
if not nml2Import_:
mu.warn( nml2ImportError_ )
raise RuntimeError( "Could not load NML2 support." )
reader = _neuroml2.NML2Reader( verbose = verbose )
reader.read( modelpath )
return reader
def mooseWriteNML2( outfile ):
raise NotImplementedError( "Writing to NML2 is not supported yet" )
################################################################
# Wrappers for global functions
################################################################
def pwe():
"""Print present working element. Convenience function for GENESIS
users. If you want to retrieve the element in stead of printing
the path, use moose.getCwe()
"""
pwe_ = _moose.getCwe()
print(pwe_.getPath())
return pwe_
def le(el=None):
"""List elements under `el` or current element if no argument
specified.
Parameters
----------
el : str/melement/vec/None
The element or the path under which to look. If `None`, children
of current working element are displayed.
Returns
-------
List of path of child elements
"""
if el is None:
el = _moose.getCwe()
elif isinstance(el, str):
if not _moose.exists(el):
raise ValueError('no such element')
el = _moose.element(el)
elif isinstance(el, _moose.vec):
el = el[0]
print('Elements under', el.path)
for ch in el.children:
print(ch.path)
return [child.path for child in el.children]
ce = _moose.setCwe # ce is a GENESIS shorthand for change element.
def syncDataHandler(target):
"""Synchronize data handlers for target.
Parameters
----------
target : melement/vec/str
Target element or vec or path string.
Raises
------
NotImplementedError
The call to the underlying C++ function does not work.
Notes
-----
This function is defined for completeness, but currently it does not work.
"""
raise NotImplementedError('The implementation is not working for IntFire - goes to invalid objects. \
First fix that issue with SynBase or something in that line.')
if isinstance(target, str):
if not _moose.exists(target):
raise ValueError('%s: element does not exist.' % (target))
target = _moose.vec(target)
_moose.syncDataHandler(target)
def showfield(el, field='*', showtype=False):
"""Show the fields of the element `el`, their data types and
values in human readable format. Convenience function for GENESIS
users.
Parameters
----------
el : melement/str
Element or path of an existing element.
field : str
Field to be displayed. If '*' (default), all fields are displayed.
showtype : bool
If True show the data type of each field. False by default.
Returns
-------
None
"""
if isinstance(el, str):
if not _moose.exists(el):
raise ValueError('no such element')
el = _moose.element(el)
if field == '*':
value_field_dict = _moose.getFieldDict(el.className, 'valueFinfo')
max_type_len = max(len(dtype) for dtype in value_field_dict.values())
max_field_len = max(len(dtype) for dtype in value_field_dict.keys())
print('\n[', el.path, ']')
for key, dtype in sorted(value_field_dict.items()):
if dtype == 'bad' or key == 'this' or key == 'dummy' or key == 'me' or dtype.startswith(
'vector') or 'ObjId' in dtype:
continue
value = el.getField(key)
if showtype:
typestr = dtype.ljust(max_type_len + 4)
# The following hack is for handling both Python 2 and
# 3. Directly putting the print command in the if/else
# clause causes syntax error in both systems.
print(typestr, end=' ')
print(key.ljust(max_field_len + 4), '=', value)
else:
try:
print(field, '=', el.getField(field))
except AttributeError:
pass # Genesis silently ignores non existent fields
def showfields(el, showtype=False):
"""Convenience function. Should be deprecated if nobody uses it.
"""
warnings.warn(
'Deprecated. Use showfield(element, field="*", showtype=True) instead.',
DeprecationWarning)
showfield(el, field='*', showtype=showtype)
# Predefined field types and their human readable names
finfotypes = [('valueFinfo', 'value field'),
('srcFinfo', 'source message field'),
('destFinfo', 'destination message field'),
('sharedFinfo', 'shared message field'),
('lookupFinfo', 'lookup field')]
def listmsg(el):
"""Return a list containing the incoming and outgoing messages of
`el`.
Parameters
----------
el : melement/vec/str
MOOSE object or path of the object to look into.
Returns
-------
msg : list
List of Msg objects corresponding to incoming and outgoing
connections of `el`.
"""
obj = _moose.element(el)
ret = []
for msg in obj.inMsg:
ret.append(msg)
for msg in obj.outMsg:
ret.append(msg)
return ret
def showmsg(el):
"""Print the incoming and outgoing messages of `el`.
Parameters
----------
el : melement/vec/str
Object whose messages are to be displayed.
Returns
-------
None
"""
obj = _moose.element(el)
print('INCOMING:')
for msg in obj.msgIn:
print(
msg.e2.path,
msg.destFieldsOnE2,
'<---',
msg.e1.path,
msg.srcFieldsOnE1)
print('OUTGOING:')
for msg in obj.msgOut:
print(
msg.e1.path,
msg.srcFieldsOnE1,
'--->',
msg.e2.path,
msg.destFieldsOnE2)
def getfielddoc(tokens, indent=''):
"""Return the documentation for field specified by `tokens`.
Parameters
----------
tokens : (className, fieldName) str
A sequence whose first element is a MOOSE class name and second
is the field name.
indent : str
indentation (default: empty string) prepended to builtin
documentation string.
Returns
-------
docstring : str
string of the form
`{indent}{className}.{fieldName}: {datatype} - {finfoType}\n{Description}\n`
Raises
------
NameError
If the specified fieldName is not present in the specified class.
"""
assert(len(tokens) > 1)
classname = tokens[0]
fieldname = tokens[1]
while True:
try:
classelement = _moose.element('/classes/' + classname)
for finfo in classelement.children:
for fieldelement in finfo:
baseinfo = ''
if classname != tokens[0]:
baseinfo = ' (inherited from {})'.format(classname)
if fieldelement.fieldName == fieldname:
# The field elements are
# /classes/{ParentClass}[0]/{fieldElementType}[N].
finfotype = fieldelement.name
return '{indent}{classname}.{fieldname}: type={type}, finfotype={finfotype}{baseinfo}\n\t{docs}\n'.format(
indent=indent, classname=tokens[0],
fieldname=fieldname,
type=fieldelement.type,
finfotype=finfotype,
baseinfo=baseinfo,
docs=fieldelement.docs)
classname = classelement.baseClass
except ValueError:
raise NameError('`%s` has no field called `%s`'
% (tokens[0], tokens[1]))
def toUnicode(v, encoding='utf8'):
# if isinstance(v, str):
# return v
try:
return v.decode(encoding)
except (AttributeError, UnicodeEncodeError):
return str(v)
def getmoosedoc(tokens, inherited=False):
"""Return MOOSE builtin documentation.
Parameters
----------
tokens : (className, [fieldName])
tuple containing one or two strings specifying class name
and field name (optional) to get documentation for.
inherited: bool (default: False)
include inherited fields.
Returns
-------
docstring : str
Documentation string for class `className`.`fieldName` if both
are specified, for the class `className` if fieldName is not
specified. In the latter case, the fields and their data types
and finfo types are listed.
Raises
------
NameError
If class or field does not exist.
"""
indent = ' '
docstring = StringIO()
with closing(docstring):
if not tokens:
return ""
try:
class_element = _moose.element('/classes/%s' % (tokens[0]))
except ValueError as e:
raise NameError('name \'%s\' not defined.' % (tokens[0]))
if len(tokens) > 1:
docstring.write(toUnicode(getfielddoc(tokens)))
else:
docstring.write(toUnicode('%s\n' % (class_element.docs)))
append_finfodocs(tokens[0], docstring, indent)
if inherited:
mro = eval('_moose.%s' % (tokens[0])).mro()
for class_ in mro[1:]:
if class_ == _moose.melement:
break
docstring.write(toUnicode(
'\n\n#Inherited from %s#\n' % (class_.__name__)))
append_finfodocs(class_.__name__, docstring, indent)
if class_ == _moose.Neutral: # Neutral is the toplevel moose class
break
return docstring.getvalue()
def append_finfodocs(classname, docstring, indent):
"""Append list of finfos in class name to docstring"""
try:
class_element = _moose.element('/classes/%s' % (classname))
except ValueError:
raise NameError('class \'%s\' not defined.' % (classname))
for ftype, rname in finfotypes:
docstring.write(toUnicode('\n*%s*\n' % (rname.capitalize())))
try:
finfo = _moose.element('%s/%s' % (class_element.path, ftype))
for field in finfo.vec:
docstring.write(toUnicode(
'%s%s: %s\n' % (indent, field.fieldName, field.type)))
except ValueError:
docstring.write(toUnicode('%sNone\n' % (indent)))
# the global pager is set from pydoc even if the user asks for paged
# help once. this is to strike a balance between GENESIS user's
# expectation of control returning to command line after printing the
# help and python user's expectation of seeing the help via more/less.
pager = None
def doc(arg, inherited=True, paged=True):
"""Display the documentation for class or field in a class.
Parameters
----------
arg : str/class/melement/vec
A string specifying a moose class name and a field name
separated by a dot. e.g., 'Neutral.name'. Prepending `moose.`
is allowed. Thus moose.doc('moose.Neutral.name') is equivalent
to the above.
It can also be string specifying just a moose class name or a
moose class or a moose object (instance of melement or vec
or there subclasses). In that case, the builtin documentation
for the corresponding moose class is displayed.
paged: bool
Whether to display the docs via builtin pager or print and
exit. If not specified, it defaults to False and
moose.doc(xyz) will print help on xyz and return control to
command line.
Returns
-------
None
Raises
------
NameError
If class or field does not exist.
"""
# There is no way to dynamically access the MOOSE docs using
# pydoc. (using properties requires copying all the docs strings
# from MOOSE increasing the loading time by ~3x). Hence we provide a
# separate function.
global pager
if paged and pager is None:
pager = pydoc.pager
tokens = []
text = ''
if isinstance(arg, str):
tokens = arg.split('.')
if tokens[0] == 'moose':
tokens = tokens[1:]
elif isinstance(arg, type):
tokens = [arg.__name__]
elif isinstance(arg, _moose.melement) or isinstance(arg, _moose.vec):
text = '%s: %s\n\n' % (arg.path, arg.className)
tokens = [arg.className]
if tokens:
text += getmoosedoc(tokens, inherited=inherited)
else:
text += pydoc.getdoc(arg)
if pager:
pager(text)
else:
print(text)
#
# moose.py ends here