Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add LibSBML conversion option #67

Draft
wants to merge 8 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -17,3 +17,4 @@ README.html
ABOUT.html
NEWS.html
*.~is
*.bak
1 change: 1 addition & 0 deletions moccasin/converter/cleaner.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
# ------------------------------------------------------------------------- -->

import sys
import os

try:
thisdir = os.path.dirname(os.path.abspath(__file__))
Expand Down
9 changes: 8 additions & 1 deletion moccasin/converter/converter.py
Original file line number Diff line number Diff line change
Expand Up @@ -898,7 +898,7 @@ def valid_id(text, search=re.compile(r'\A[a-zA-Z_][a-zA-Z0-9_]*\Z').search):
# -----------------------------------------------------------------------------

def create_raterule_model(parse_results, use_species=True, output_format="sbml",
name_vars_after_param=False, add_comments=True):
name_vars_after_param=False, add_comments=True, convert_to_reactions=False):

# First, gather some initial information.
working_context = first_function_context(parse_results)
Expand Down Expand Up @@ -1022,6 +1022,13 @@ def create_raterule_model(parse_results, use_species=True, output_format="sbml",
# (in poss-processing) a definition to the SBML produced by Biocham.
document.post_add.append(('t', 'time'))

# convert to reactions
if convert_to_reactions:
prop = ConversionProperties()
prop.addOption("inferReactions", True,
"Infer reactions from rateRules in the model");
check(document.convert(prop), 'infering reactions failed')

# Finally, return the model, with extra info about things that may need
# to be added back in post-processing if we are hand this to BIOCHAM.
output = generate_output(document, add_comments)
Expand Down
6 changes: 5 additions & 1 deletion moccasin/converter/evaluate_formula.py
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,9 @@ def __init__(self):
"/": operator.truediv,
"^": operator.pow
}
self.int_fn = {
"factorial": math.factorial,
}
self.fn = {
"abs": abs,
"acos": math.acos,
Expand All @@ -131,7 +134,6 @@ def __init__(self):
"cos": math.cos,
"cosh": math.cosh,
"exp": math.exp, # not working
"factorial": math.factorial,
"floor": math.floor,
"fix": self.fix,
"ln": math.log,
Expand Down Expand Up @@ -174,6 +176,8 @@ def evaluate_stack(self, s):
return Decimal(math.e) # 2.718281828
elif op in self.fn:
return Decimal(self.fn[op](self.evaluate_stack(s)))
elif op in self.int_fn:
return Decimal(self.int_fn[op](int(self.evaluate_stack(s))))
elif op in self.bin_fn:
op2 = Decimal(self.evaluate_stack(s))
op1 = Decimal(self.evaluate_stack(s))
Expand Down
1 change: 1 addition & 0 deletions moccasin/converter/expr_tester.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@

import sys
from collections import defaultdict
import os

try:
thisdir = os.path.dirname(os.path.abspath(__file__))
Expand Down
1 change: 1 addition & 0 deletions moccasin/converter/finder.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
# ------------------------------------------------------------------------- -->

import sys
import os

try:
thisdir = os.path.dirname(os.path.abspath(__file__))
Expand Down
1 change: 1 addition & 0 deletions moccasin/converter/recognizer.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@

import sys
from collections import defaultdict
import os

try:
thisdir = os.path.dirname(os.path.abspath(__file__))
Expand Down
1 change: 1 addition & 0 deletions moccasin/converter/rewriter.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@

import sys
from decimal import *
import os

try:
thisdir = os.path.dirname(os.path.abspath(__file__))
Expand Down
8 changes: 8 additions & 0 deletions moccasin/interfaces/controller.py
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,14 @@ def build_model(self, use_species, output_format, name_after_param, add_comments
add_comments)
return output

def build_reaction_model_libsbml(self, use_species, name_after_param, add_comments):
'''Converts a parsed file into reaction-based SBML.'''

(output, _, _) = create_raterule_model(self.parse_results, use_species,
'sbml', name_after_param,
add_comments, convert_to_reactions=True)
return output


def build_reaction_model(self, use_species, name_after_param, add_comments):
'''Converts a parsed file into reaction-based SBML.'''
Expand Down
27 changes: 20 additions & 7 deletions moccasin/interfaces/moccasin_CLI.py
Original file line number Diff line number Diff line change
Expand Up @@ -61,11 +61,12 @@
debug_parser = ('print debug information about the parsed MATLAB', 'flag', 'D'),
version = ('print MOCCASIN version info and exit', 'flag', 'V'),
no_comments = ('do not insert version comments into SBML output', 'flag', 'X'),
use_libsbml = ('use libsbml converter for reaction-based SBML (default=False)', 'flag', 'l'),
paths = 'paths to MATLAB input files to convert'
)

def cli_main(gui, use_equations, use_params, quiet, relaxed, xpp_output,
no_color, debug_parser, version, no_comments, *paths):
no_color, debug_parser, version, no_comments, use_libsbml=False, *paths):
'''Interface for controlling MOCCASIN, the MATLAB ODE converter for SBML.
MOCCASIN can take certain forms of ODE (ordinary differential equation) models
written in MATLAB and Octave and export them as SBML files. MOCCASIN does not
Expand Down Expand Up @@ -128,6 +129,9 @@ def cli_main(gui, use_equations, use_params, quiet, relaxed, xpp_output,
-e (/e on Windows) makes the translator create equation-based SBML output,
instead of converting the model to reaction-based form (the default)

-l (/l on Windows) makes the translator use libsbml for the reaction-based
SBML output, instead of biocham

-p (/p on windows) makes the translator encode variables as SBML
parameters, instead of the default, which is to use SBML species

Expand All @@ -153,7 +157,7 @@ def cli_main(gui, use_equations, use_params, quiet, relaxed, xpp_output,
add_comments = not no_comments

if gui or not any([paths, use_equations, use_params, xpp_output,
version, debug_parser, quiet, no_comments]):
version, debug_parser, quiet, no_comments, use_libsbml]):
moccasin_GUI.gui_main()
sys.exit()
if version:
Expand All @@ -166,7 +170,7 @@ def cli_main(gui, use_equations, use_params, quiet, relaxed, xpp_output,
sys.exit()
if not paths:
raise SystemExit(color('Must provide a path to a file.', 'error', colorize))
if not xpp_output and not use_equations and not have_network():
if not xpp_output and not use_equations and not use_libsbml and not have_network():
raise SystemExit(color('No network connection.', 'error', colorize))
if not quiet:
from halo import Halo
Expand Down Expand Up @@ -204,10 +208,16 @@ def convert(path):
name_after_param = False,
add_comments = add_comments)
else:
text = 'Reaction-based SBML output'
output = controller.build_reaction_model(use_species = (not use_params),
name_after_param = False,
add_comments = add_comments)
if use_libsbml:
text = 'Reaction-based SBML output (libsbml)'
output = controller.build_reaction_model_libsbml(use_species = (not use_params),
name_after_param = False,
add_comments = add_comments)
else:
text = 'Reaction-based SBML output (biocham)'
output = controller.build_reaction_model(use_species = (not use_params),
name_after_param = False,
add_comments = add_comments)
if debug_parser:
print_header(text, 'info', quiet, colorize)
msg(output)
Expand All @@ -222,6 +232,9 @@ def convert(path):
'warning', colorize)
msg('Renaming to "{}".'.format(backup_path),
'warning', colorize)
# remove backup if it exists (otherwise rename will fail)
if os.path.exists(backup_path):
os.remove(backup_path)
os.rename(output_path, backup_path)
with open(output_path, 'w') as output_file:
output_file.write(output)
Expand Down
66 changes: 44 additions & 22 deletions moccasin/interfaces/moccasin_GUI.py
Original file line number Diff line number Diff line change
Expand Up @@ -303,7 +303,7 @@ def __init__(self, parent):
sbSizer9.Add(gSizer7, 0, wx.EXPAND, 5)

optionInset = wx.StaticBoxSizer(wx.StaticBox(self, wx.ID_ANY, "Conversion options"), wx.VERTICAL)
optionGrid = wx.GridSizer(1, 2, 10, 10)
optionGrid = wx.GridSizer(1, 3, 10, 10)

self.assumeTranslatable = wx.CheckBox(self, id = wx.ID_ANY,
label = 'Assume array operations are not used')
Expand All @@ -318,6 +318,15 @@ def __init__(self, parent):
self.addMoccasinComments.Value = True
self.addMoccasinComments.SetToolTip("Write a comment into the output file with the MOCCASIN version, time stamp, and other information.")
optionGrid.Add(self.addMoccasinComments, flag = wx.ALL, border = 6)

self.useLibSBML = wx.CheckBox(self, id = wx.ID_ANY,
label = 'Use LibSBML Conveter')
self.useLibSBML.SetToolTip("Use LibSBML to convert from raterules to reactions (if false, it will use BIOCHAM)")
self.useLibSBML.SetFont(labelFont)
self.useLibSBML.Value = True
self.useLibSBML.SetToolTip("Use LibSBML to convert from raterules to reactions (if false, it will use BIOCHAM)")
optionGrid.Add(self.useLibSBML, flag = wx.ALL, border = 6)

optionInset.Add(optionGrid, 0, wx.EXPAND, 0)

self.convertButton = wx.Button(self, wx.ID_ANY, "Convert",
Expand All @@ -339,29 +348,32 @@ def __init__(self, parent):
panelTextFont = wx.Font(wx.NORMAL_FONT.GetPointSize() -1, 70, 90,
wx.FONTWEIGHT_NORMAL, False, wx.EmptyString)

midPanelSizer = wx.StaticBoxSizer(wx.StaticBox(self, wx.ID_ANY, "MATLAB File"),
wx.VERTICAL)
self.matlabWebView = wx.html2.WebView.New(self, wx.ALIGN_BOTTOM|wx.ALL|wx.EXPAND )
#midPanelSizer = wx.StaticBoxSizer(wx.StaticBox(self, wx.ID_ANY, "MATLAB File"),
# wx.VERTICAL)
midPanelSizer = wx.BoxSizer(wx.VERTICAL)
self.matlabWebView = wx.html2.WebView.New(self, wx.ALL|wx.EXPAND )
self.matlabWebView.SetForegroundColour(wx.SystemSettings.GetColour(wx.SYS_COLOUR_WINDOWTEXT))
wxSetToolTip(self.matlabWebView, "Input file for conversion")
self.matlabWebView.SetFont(panelTextFont)
self.matlabWebView.SetPage(_EMPTY_PAGE, "")
midPanelSizer.Add(self.matlabWebView, 1, wx.ALIGN_BOTTOM|wx.ALL|wx.EXPAND, 5)
midPanelSizer.Add(self.matlabWebView, 1, wx.ALL|wx.EXPAND, 5)
mainSizer.Add(midPanelSizer, 2, wx.ALL|wx.EXPAND, 5)
mainSizer.Add(buttonVSizer, 0, wx.ALL|wx.EXPAND, 0)

# Bottom sizer
bottomPanelSizer = wx.StaticBoxSizer(
wx.StaticBox(self, wx.ID_ANY, "Converted File"), wx.VERTICAL)
#bottomPanelSizer = wx.StaticBoxSizer(
# wx.StaticBox(self, wx.ID_ANY, "Converted File"), wx.VERTICAL)
bottomPanelSizer = wx.BoxSizer(wx.VERTICAL)
self.convertedWebView = wx.html2.WebView.New(
self, wx.ALIGN_BOTTOM|wx.ALL|wx.EXPAND)
self, wx.ALL|wx.EXPAND)
self.convertedWebView.SetForegroundColour(
wx.SystemSettings.GetColour(wx.SYS_COLOUR_WINDOWTEXT))
self.convertedWebView.SetFont(panelTextFont)
self.convertedWebView.SetZoomType(wx.html2.WEBVIEW_ZOOM_TYPE_LAYOUT)
wxSetToolTip(self.convertedWebView, "Output file after conversion")
self.convertedWebView.SetPage(_EMPTY_PAGE, "")
bottomPanelSizer.Add(
self.convertedWebView, 1, wx.ALIGN_BOTTOM|wx.ALL|wx.EXPAND, 5)
self.convertedWebView, 1, wx.ALL|wx.EXPAND, 5)
mainSizer.Add(bottomPanelSizer, 2, wx.ALL|wx.EXPAND, 5)

# Set frame sizer
Expand All @@ -384,8 +396,7 @@ def __init__(self, parent):
self.Bind(wx.EVT_MENU, self.OnPageSetup, id= self.pageSetup.GetId())
self.Bind(wx.EVT_MENU, self.OnPrint, id=self.printOption.GetId())
self.Bind(wx.EVT_MENU_RANGE, self.onFileHistory, id=wx.ID_FILE1, id2=wx.ID_FILE9)
self.Bind(wx.EVT_CLOSE, self.onClose)

self.Bind(wx.EVT_CLOSE, self.onClose)

def __del__(self):
pass
Expand Down Expand Up @@ -494,20 +505,31 @@ def onConvert(self, event):
self.statusBar.SetStatusText("SBML format - equations", 2)
# Output reaction-based SBML
else:
if not have_network():
msg = "A network connection is needed for this feature, but the network appears to be unavailable."
dlg = wx.MessageDialog(self, msg, "Warning", wx.OK | wx.ICON_WARNING)
dlg.ShowModal()
dlg.Destroy()
else:
sbml = self.controller.build_reaction_model(
use_species = self.varsAsSpecies.GetValue(),
name_after_param = False,
add_comments = self.addMoccasinComments.Value)
use_libSBML = self.useLibSBML.GetValue()
if use_libSBML:
sbml = self.controller.build_reaction_model_libsbml(
use_species = self.varsAsSpecies.GetValue(),
name_after_param = False,
add_comments = self.addMoccasinComments.Value)

self.convertedWebView.SetPage(tokenize(sbml, "xml", "borland"), "")
self.statusBar.SetStatusText("SBML format - reactions", 2)
self.statusBar.SetStatusText("SBML format - reactions (libsbml)", 2)
self._output_saved = False
else:
if not have_network():
msg = "A network connection is needed for this feature, but the network appears to be unavailable."
dlg = wx.MessageDialog(self, msg, "Warning", wx.OK | wx.ICON_WARNING)
dlg.ShowModal()
dlg.Destroy()
else:
sbml = self.controller.build_reaction_model(
use_species = self.varsAsSpecies.GetValue(),
name_after_param = False,
add_comments = self.addMoccasinComments.Value)

self.convertedWebView.SetPage(tokenize(sbml, "xml", "borland"), "")
self.statusBar.SetStatusText("SBML format - reactions (BIOCHAM)", 2)
self._output_saved = False

except IOError as err:
wx.EndBusyCursor()
Expand Down
3 changes: 2 additions & 1 deletion moccasin/matlab_parser/context.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@

from __future__ import print_function
import collections
from collections.abc import MutableMapping
from pyparsing import ParseResults


Expand All @@ -34,7 +35,7 @@
#
# This next class def is based on http://stackoverflow.com/a/7760938/743730

class ContextDict(collections.MutableMapping, dict):
class ContextDict(MutableMapping, dict):
"""Class used to implement MatlabContext properties that are dictionaries."""

def __getitem__(self, key):
Expand Down
11 changes: 6 additions & 5 deletions moccasin/matlab_parser/matlab.py
Original file line number Diff line number Diff line change
Expand Up @@ -229,7 +229,7 @@ def row_to_string(row):
body = MatlabNode.as_string(thing.body)
return '@(' + arg_list + ')' + body
elif isinstance(thing, Comment) or isinstance(thing, FunDef) \
or isinstance(thing, Command):
or isinstance(thing, ShellCommand):
# No reason for as_string called for these things, but must catch
# random mayhem before falling through to the final case.
return None
Expand Down Expand Up @@ -853,10 +853,11 @@ def visit(self, node):

def default_visit(self, node):
"""Default visitor. Users can redefine this if desired."""
for a in type(node)._visitable_attr:
value = getattr(node, a, None)
if value:
setattr(node, a, self.visit(value))
if isinstance(node, MatlabNode):
for a in type(node)._visitable_attr:
value = getattr(node, a, None)
if value:
setattr(node, a, self.visit(value))
return node


Expand Down
Loading