1 /**
2  * @file    FbcToCobraConverter.cpp
3  * @brief   Implementation of a fbc 2 cobra converter.
4  * @author  Frank T. Bergmann
5  *
6  * <!--------------------------------------------------------------------------
7  * This file is part of libSBML.  Please visit http://sbml.org for more
8  * information about SBML, and the latest version of libSBML.
9  *
10  * Copyright (C) 2020 jointly by the following organizations:
11  *     1. California Institute of Technology, Pasadena, CA, USA
12  *     2. University of Heidelberg, Heidelberg, Germany
13  *     3. University College London, London, UK
14  *
15  * Copyright (C) 2019 jointly by the following organizations:
16  *     1. California Institute of Technology, Pasadena, CA, USA
17  *     2. University of Heidelberg, Heidelberg, Germany
18  *
19  * Copyright (C) 2013-2018 jointly by the following organizations:
20  *     1. California Institute of Technology, Pasadena, CA, USA
21  *     2. EMBL European Bioinformatics Institute (EMBL-EBI), Hinxton, UK
22  *     3. University of Heidelberg, Heidelberg, Germany
23  *
24  * Copyright (C) 2009-2013 jointly by the following organizations:
25  *     1. California Institute of Technology, Pasadena, CA, USA
26  *     2. EMBL European Bioinformatics Institute (EMBL-EBI), Hinxton, UK
27  *
28  * This library is free software; you can redistribute it and/or modify it
29  * under the terms of the GNU Lesser General Public License as published by
30  * the Free Software Foundation.  A copy of the license agreement is provided
31  * in the file named "LICENSE.txt" included with this software distribution
32  * and also available online as http://sbml.org/software/libsbml/license.html
33  * ---------------------------------------------------------------------- -->
34  */
35 
36 
37 #include <sbml/packages/fbc/util/FbcToCobraConverter.h>
38 #include <sbml/conversion/SBMLConverterRegistry.h>
39 #include <sbml/conversion/SBMLConverterRegister.h>
40 #include <sbml/conversion/ConversionProperties.h>
41 
42 #include <sbml/common/sbmlfwd.h>
43 #include <sbml/extension/SBasePlugin.h>
44 #include <sbml/extension/SBMLDocumentPlugin.h>
45 #include <sbml/SBMLWriter.h>
46 #include <sbml/SBMLReader.h>
47 #include <sbml/packages/fbc/common/FbcExtensionTypes.h>
48 #include <sbml/math/FormulaParser.h>
49 
50 #ifdef __cplusplus
51 
52 #include <algorithm>
53 #include <string>
54 
55 using namespace std;
56 LIBSBML_CPP_NAMESPACE_BEGIN
57 
58 /** @cond doxygenLibsbmlInternal */
59 /*
60  * SBML Converter stuff below
61  */
init()62 void FbcToCobraConverter::init()
63 {
64   SBMLConverterRegistry::getInstance().addConverter(new FbcToCobraConverter());
65 }
66 /** @endcond */
67 
68 
FbcToCobraConverter()69 FbcToCobraConverter::FbcToCobraConverter()
70  : SBMLConverter("SBML FBC to COBRA Converter")
71 {
72 
73 }
74 
75 
FbcToCobraConverter(const FbcToCobraConverter & orig)76 FbcToCobraConverter::FbcToCobraConverter(const FbcToCobraConverter& orig) :
77   SBMLConverter(orig)
78 {
79 }
80 
81 FbcToCobraConverter*
clone() const82 FbcToCobraConverter::clone() const
83 {
84   return new FbcToCobraConverter(*this);
85 }
86 
87 
88 /*
89  * Destroy this object.
90  */
~FbcToCobraConverter()91 FbcToCobraConverter::~FbcToCobraConverter ()
92 {
93 }
94 
95 
96 ConversionProperties
getDefaultProperties() const97   FbcToCobraConverter::getDefaultProperties() const
98 {
99   static ConversionProperties prop;
100   prop.addOption("convert fbc to cobra", true, "convert FBC L3V1 to SBML L2V4 with COBRA annotation");
101   prop.addOption("overwriteReactionNotes", false, "write gene association into reaction notes, even if the reaction has notes already");
102   return prop;
103 }
104 
105 
106 bool
matchesProperties(const ConversionProperties & props) const107   FbcToCobraConverter::matchesProperties(const ConversionProperties &props) const
108 {
109   if (!props.hasOption("convert fbc to cobra"))
110     return false;
111   return true;
112 }
113 
114 
setObjectiveCoefficient(FbcModelPlugin * plugin,Model * model)115 void setObjectiveCoefficient(FbcModelPlugin* plugin, Model* model)
116 {
117   if (plugin == NULL || model == NULL)
118     return;
119 
120   Objective* obj = plugin->getActiveObjective();
121   if (obj == NULL)
122     return;
123 
124   for (unsigned int i = 0; i < obj->getNumFluxObjectives(); ++i)
125   {
126     FluxObjective* fluxObj = obj->getFluxObjective(i);
127     if (fluxObj == NULL)
128       continue;
129     Reaction* reaction = model->getReaction(fluxObj->getReaction());
130     if (reaction == NULL)
131       continue;
132     KineticLaw* law = reaction->getKineticLaw();
133     if (law == NULL)
134       continue;
135     LocalParameter* param = law->getLocalParameter("OBJECTIVE_COEFFICIENT");
136     param->setValue(fluxObj->getCoefficient());
137   }
138 }
139 
140 
createKineticLawForReaction(Reaction * reaction)141 void createKineticLawForReaction(Reaction* reaction)
142 {
143   if (reaction == NULL)
144     return;
145   reaction->unsetKineticLaw();
146   KineticLaw *law = reaction->getKineticLaw();
147   if (law == NULL)
148   {
149     law = reaction->createKineticLaw();
150     LocalParameter* fluxValue = law->createLocalParameter();
151     fluxValue->initDefaults();
152     fluxValue->setId("FLUX_VALUE");
153     fluxValue->setValue(0);
154     fluxValue->setUnits("dimensionless");
155     ASTNode* astn = SBML_parseFormula("FLUX_VALUE");
156     law->setMath(astn);
157     delete astn;
158   }
159 
160   LocalParameter* LB = law->getLocalParameter("LOWER_BOUND");
161   if (LB == NULL)
162   {
163     LB = law->createLocalParameter();
164     LB->initDefaults();
165     LB->setId("LOWER_BOUND");
166     LB->setUnits("dimensionless");
167     LB->setValue(-std::numeric_limits<double>::infinity());
168   }
169 
170   LocalParameter* UB = law->getLocalParameter("UPPER_BOUND");
171   if (UB == NULL)
172   {
173     UB = law->createLocalParameter();
174     UB->initDefaults();
175     UB->setId("UPPER_BOUND");
176     UB->setUnits("dimensionless");
177     LB->setValue(std::numeric_limits<double>::infinity());
178   }
179 
180   LocalParameter* param = law->getLocalParameter("OBJECTIVE_COEFFICIENT");
181   if (param == NULL)
182   {
183     param = law->createLocalParameter();
184     param->initDefaults();
185     param->setId("OBJECTIVE_COEFFICIENT");
186     param->setUnits("dimensionless");
187     param->setValue(0);
188   }
189 
190 }
191 
updateKineticLawFromBound(Reaction * reaction,FluxBound * current)192 void updateKineticLawFromBound(Reaction* reaction, FluxBound* current)
193 {
194   if (reaction == NULL || current == NULL)
195     return;
196   const string operation = current -> getOperation();
197 
198   KineticLaw *law = reaction->getKineticLaw();
199   LocalParameter* LB = law->getLocalParameter("LOWER_BOUND");
200   LocalParameter* UB = law->getLocalParameter("UPPER_BOUND");
201 
202   if (operation == "less" || operation == "lessEqual" || operation == "equal")
203   {
204     UB->setValue(current->getValue());
205   }
206   if (operation == "greater" || operation == "greaterEqual" || operation == "equal")
207   {
208     LB->setValue(current->getValue());
209   }
210 
211 }
212 
getNotesForFormula(const string & formula)213 string getNotesForFormula(const string& formula)
214 {
215   stringstream str;
216 
217   str
218     << "<html xmlns=\"http://www.w3.org/1999/xhtml\">\n\t<p>FORMULA: "
219     << formula
220     << "</p>\n</html>";
221 
222   return str.str();
223 }
224 
getGeneAssociationForReaction(const FbcModelPlugin * plugin,const std::string & id)225 const GeneAssociation* getGeneAssociationForReaction(const FbcModelPlugin* plugin, const std::string& id)
226 {
227   if (plugin == NULL) return NULL;
228 
229   for (int i = 0; i < plugin->getNumGeneAssociations(); ++i)
230   {
231     const GeneAssociation* association = plugin->getGeneAssociation((unsigned int)i);
232     if (association == NULL) continue;
233 
234     if (association->isSetReaction() && association->getReaction() == id)
235       return association;
236   }
237   return NULL;
238 }
239 
240 int
convert()241   FbcToCobraConverter::convert()
242 {
243   int result = LIBSBML_OPERATION_FAILED;
244 
245   if (mDocument == NULL)
246   {
247     return LIBSBML_INVALID_OBJECT;
248   }
249 
250   Model* mModel = mDocument->getModel();
251   if (mModel == NULL)
252   {
253     return LIBSBML_INVALID_OBJECT;
254   }
255 
256   FbcModelPlugin *plugin =
257     static_cast<FbcModelPlugin*>(mDocument->getModel()->getPlugin("fbc"));
258 
259   // if we have don't have a fbc model we cannot do the conversion
260   if (plugin == NULL || mDocument->getLevel() != 3)
261   {
262     return LIBSBML_OPERATION_FAILED;
263   }
264 
265   plugin->unsetStrict();
266   // collect information
267 
268   Model* model = mDocument->getModel();
269   map<const string, int> chargeMap;
270   map<const string, string> formulaMap;
271 
272   for (unsigned int i = 0; i < model->getNumSpecies(); ++i)
273   {
274     Species* current = model->getSpecies(i);
275     const string& currentId = current->getId();
276     FbcSpeciesPlugin *splugin = static_cast<FbcSpeciesPlugin*>(current->getPlugin("fbc"));
277     if (splugin == NULL)
278       continue;
279     if (splugin->isSetCharge())
280     {
281       chargeMap[currentId] = splugin->getCharge();
282     }
283     if (splugin->isSetChemicalFormula())
284     {
285       formulaMap[currentId] = splugin->getChemicalFormula();
286     }
287   }
288 
289 
290   // overwrite reactionNotes
291   bool overwriteReactionNotes = getProperties() != NULL &&
292     getProperties()->hasOption("overwriteReactionNotes") &&
293     getProperties()->getBoolValue("overwriteReactionNotes");
294 
295   // create KineticLaw
296   for (unsigned int i = 0; i < model->getNumReactions(); ++i)
297   {
298     Reaction* reaction = model->getReaction(i);
299     if (reaction == NULL)
300       continue;
301 
302     createKineticLawForReaction(reaction);
303 
304     FbcReactionPlugin* rplug = dynamic_cast<FbcReactionPlugin*>(reaction->getPlugin("fbc"));
305 
306     // get gene association for reaction
307     const GeneAssociation* association = getGeneAssociationForReaction(plugin, reaction->getId());
308     std::string infix;
309 
310     if (association != NULL && association->getAssociation() != NULL)
311     {
312       infix = association->getAssociation()->toInfix();
313     }
314     else if (rplug != NULL && rplug->isSetGeneProductAssociation())
315     {
316       infix = rplug->getGeneProductAssociation()->getAssociation()->toInfix();
317       rplug->unsetGeneProductAssociation();
318     }
319 
320     if (infix.empty()) continue;
321 
322     //unset notes if requested
323     if (reaction->isSetNotes() && overwriteReactionNotes)
324       reaction->unsetNotes();
325 
326     // skip adding gene association if we already have notes
327     if (reaction->isSetNotes()) continue;
328 
329 
330 
331     // write new notes
332     reaction->setNotes(
333       "<body xmlns='http://www.w3.org/1999/xhtml'>\n"
334       "  <p>GENE_ASSOCIATION : " + infix +"</p>\n"
335       "</body>"
336     );
337 
338     if (rplug != NULL)
339     {
340       rplug->unsetLowerFluxBound();
341       rplug->unsetUpperFluxBound();
342     }
343 
344   }
345 
346   // update kinetic law from bounds
347   for (unsigned int i = 0; i < plugin->getNumFluxBounds(); ++i)
348   {
349     FluxBound *current = plugin->getFluxBound(i);
350     if (current == NULL)
351       continue;
352     Reaction* reaction = model->getReaction(current->getReaction());
353     if (reaction == NULL)
354       continue;
355 
356     updateKineticLawFromBound(reaction, current);
357 
358   }
359 
360   setObjectiveCoefficient(plugin, model);
361 
362   // disable package
363   mDocument->enablePackage("http://www.sbml.org/sbml/level3/version1/fbc/version1", "fbc",false);
364   mDocument->enablePackage("http://www.sbml.org/sbml/level3/version1/fbc/version2", "fbc",false);
365 
366   // convert model to L2V1 (as L2V2 is the last model that had charge)
367   mDocument->setConversionValidators(AllChecksON & UnitsCheckOFF);
368 
369   SBMLNamespaces sbmlns21(2,1);
370   ConversionProperties prop(&sbmlns21);
371   prop.addOption("strict", false, "should validity be preserved");
372   prop.addOption("ignorePackages", true, "convert even if packages are used");
373   prop.addOption("setLevelAndVersion", true, "convert the document to the given level and version");
374   int conversionResult = mDocument->convert(prop);
375   if (conversionResult != LIBSBML_OPERATION_SUCCESS)
376     return conversionResult;
377 
378   // set charge on species
379   for (unsigned int i = 0; i < model->getNumSpecies(); ++i)
380   {
381     Species* current = model->getSpecies(i);
382     const string currentId = current->getId();
383     int charge = chargeMap[currentId];
384 
385     if (charge != 0)
386       current->setCharge(charge);
387 
388     const string formula = formulaMap[currentId];
389     if (!formula.empty())
390     {
391       current->setNotes( getNotesForFormula(formula) );
392     }
393   }
394 
395 
396   result = LIBSBML_OPERATION_SUCCESS;
397   return result;
398 }
399 
400 
401 /** @cond doxygenIgnored */
402 /** @endcond */
403 
404 LIBSBML_CPP_NAMESPACE_END
405 
406 #endif  /* __cplusplus */
407 
408 
409