1 /**
2 * @cond doxygenLibsbmlInternal
3 *
4 * @file OverDeterminedCheck.cpp
5 * @brief Checks for over determined models.
6 * @author Sarah Keating
7 *
8 * <!--------------------------------------------------------------------------
9 * This file is part of libSBML. Please visit http://sbml.org for more
10 * information about SBML, and the latest version of libSBML.
11 *
12 * Copyright (C) 2020 jointly by the following organizations:
13 * 1. California Institute of Technology, Pasadena, CA, USA
14 * 2. University of Heidelberg, Heidelberg, Germany
15 * 3. University College London, London, UK
16 *
17 * Copyright (C) 2019 jointly by the following organizations:
18 * 1. California Institute of Technology, Pasadena, CA, USA
19 * 2. University of Heidelberg, Heidelberg, Germany
20 *
21 * Copyright (C) 2013-2018 jointly by the following organizations:
22 * 1. California Institute of Technology, Pasadena, CA, USA
23 * 2. EMBL European Bioinformatics Institute (EMBL-EBI), Hinxton, UK
24 * 3. University of Heidelberg, Heidelberg, Germany
25 *
26 * Copyright (C) 2009-2013 jointly by the following organizations:
27 * 1. California Institute of Technology, Pasadena, CA, USA
28 * 2. EMBL European Bioinformatics Institute (EMBL-EBI), Hinxton, UK
29 *
30 * Copyright (C) 2006-2008 by the California Institute of Technology,
31 * Pasadena, CA, USA
32 *
33 * Copyright (C) 2002-2005 jointly by the following organizations:
34 * 1. California Institute of Technology, Pasadena, CA, USA
35 * 2. Japan Science and Technology Agency, Japan
36 *
37 * This library is free software; you can redistribute it and/or modify it
38 * under the terms of the GNU Lesser General Public License as published by
39 * the Free Software Foundation. A copy of the license agreement is provided
40 * in the file named "LICENSE.txt" included with this software distribution
41 * and also available online as http://sbml.org/software/libsbml/license.html
42 * ---------------------------------------------------------------------- -->*/
43
44 #include <sbml/Model.h>
45 #include <sbml/Compartment.h>
46 #include <sbml/util/List.h>
47
48 #include <sbml/util/IdList.h>
49 #include "OverDeterminedCheck.h"
50
51 #include <iostream>
52 #include <cstring>
53
54 /** @cond doxygenIgnored */
55 using namespace std;
56 /** @endcond */
57
58 LIBSBML_CPP_NAMESPACE_BEGIN
59
60 #define SET_NAME(name,prefix,count)\
61 {\
62 std::stringstream str; str << prefix << count;\
63 name = str.str();\
64 }
65
66 /*
67 * Creates a new Constraint with the given @p id.
68 */
OverDeterminedCheck(unsigned int id,Validator & v)69 OverDeterminedCheck::OverDeterminedCheck ( unsigned int id,
70 Validator& v ) :
71 TConstraint<Model>(id, v)
72 {
73 }
74
75
76 /*
77 * Destroys this Constraint.
78 */
~OverDeterminedCheck()79 OverDeterminedCheck::~OverDeterminedCheck ()
80 {
81 //mEquations.clear(); // list of equation vertexes
82 //mVariables.clear(); // list of variable vertexes
83 //mGraph.clear();
84
85 ///* these are to enable the bipartite matching without passing variables */
86 //mMatching.clear();
87 //mVarNeighInPrev.clear();
88 //mEqnNeighInPrev.clear();
89
90 //revisited.clear();
91 //visited.clear();
92
93 }
94
95
96 /*
97 * Checks that a model is not over determined
98 */
99 void
check_(const Model & m,const Model &)100 OverDeterminedCheck::check_ (const Model& m, const Model&)
101 {
102 unsigned int n;
103 unsigned int NumAlgRules = 0;
104 IdList unmatchedEqns;
105
106 for (n = 0; n < m.getNumRules(); n++)
107 {
108 if (m.getRule(n)->isAlgebraic() && m.getRule(n)->isSetMath())
109 {
110 NumAlgRules++;
111 }
112 }
113
114 if (NumAlgRules > 0)
115 {
116 EquationMatching* eqn = new EquationMatching();
117
118 eqn->createGraph(m);
119
120 /* short check - if number equations exceeds number of variables
121 * then maximal matching MUST leave one or more equations unconnected
122 */
123 if (eqn->getNumEquations() > eqn->getNumVariables())
124 {
125 logOverDetermined(m);
126 }
127 else
128 {
129 unmatchedEqns = eqn->findMatching();
130
131 if (unmatchedEqns.size() > 0)
132 {
133 logOverDetermined(m);
134 }
135 }
136
137 delete eqn;
138 }
139 }
140
141 /*
142 * Logs a message about overdetermined model.
143 * As yet this only reports the problem - it doesnt really give
144 * any additional information
145 */
146 void
logOverDetermined(const Model & m)147 OverDeterminedCheck::logOverDetermined (const Model& m)
148 {
149 //msg =
150 // "The system of equations created from an SBML model must not be "
151 // "overdetermined. (References: L2V2 Section 4.11.5.)";
152
153 logFailure(m);
154 }
155
EquationMatching()156 EquationMatching::EquationMatching()
157 {
158 }
159
~EquationMatching()160 EquationMatching::~EquationMatching ()
161 {
162 mEquations.clear(); // list of equation vertexes
163 mVariables.clear(); // list of variable vertexes
164 mGraph.clear();
165
166 /* these are to enable the bipartite matching without passing variables */
167 mMatching.clear();
168 mVarNeighInPrev.clear();
169 mEqnNeighInPrev.clear();
170
171 revisited.clear();
172 visited.clear();
173
174 }
175
176
177
178 /*
179 * creates equation vertexes according to the L2V2 spec 4.11.5 for every
180 * 1. a Species that has the boundaryCondition field set to false
181 * and constant field set to false and which is referenced by one or more
182 * reactant or product lists of a Reaction containing a KineticLaw
183 * 2. a Rule
184 * 3. a KineticLaw
185 */
186 void
writeEquationVertexes(const Model & m)187 EquationMatching::writeEquationVertexes(const Model& m)
188 {
189 const Reaction *r;
190 const Species* s;
191 string rule;
192 string react;
193
194 unsigned int n, sr;
195
196 /* a Species that has the boundaryCondition field set to false
197 * and constant field set to false and which is referenced by one or
198 * more reactant or product lists of a Reaction containing
199 * a KineticLaw
200 */
201 for (n = 0; n < m.getNumReactions(); n++)
202 {
203 if (m.getReaction(n)->isSetKineticLaw())
204 {
205 r = m.getReaction(n);
206 for (sr = 0; sr < r->getNumReactants(); sr++)
207 {
208 s = m.getSpecies(r->getReactant(sr)->getSpecies());
209 if (!s->getBoundaryCondition() && !s->getConstant())
210 {
211 if (!mEquations.contains(s->getId()))
212 mEquations.append(s->getId());
213 }
214 }
215
216 for (sr = 0; sr < r->getNumProducts(); sr++)
217 {
218 s = m.getSpecies(r->getProduct(sr)->getSpecies());
219 if (!s->getBoundaryCondition() && !s->getConstant())
220 {
221 if (!mEquations.contains(s->getId()))
222 mEquations.append(s->getId());
223 }
224 }
225 }
226 }
227
228 /* a Rule structure */
229 for (n = 0; n < m.getNumRules(); n++)
230 {
231 SET_NAME(rule, "rule_", n);
232 mEquations.append(rule);
233 }
234
235 /* a Kinetic Law structure */
236 for (n = 0; n < m.getNumReactions(); n++)
237 {
238 if (m.getReaction(n)->isSetKineticLaw())
239 {
240 SET_NAME(react, "KL_", n);
241 mEquations.append(react);
242 }
243 }
244 }
245
246
247 /*
248 * creates variable vertexes according to the L2V2 spec 4.11.5 for
249 * (a) every Species, Compartment and Parameter structure which has the
250 * Constant field set to false; and
251 * (b) for every Reaction structure.
252 */
253 void
writeVariableVertexes(const Model & m)254 EquationMatching::writeVariableVertexes(const Model& m)
255 {
256 unsigned int n, k;
257
258 for (n = 0; n < m.getNumCompartments(); n++)
259 {
260 if (!m.getCompartment(n)->getConstant())
261 {
262 mVariables.append(m.getCompartment(n)->getId());
263 }
264 else if (m.getLevel() == 1)
265 {
266 mVariables.append(m.getCompartment(n)->getId());
267 }
268 }
269
270 for (n = 0; n < m.getNumSpecies(); n++)
271 {
272 if (!m.getSpecies(n)->getConstant())
273 {
274 mVariables.append(m.getSpecies(n)->getId());
275 }
276 else if (m.getLevel() == 1)
277 {
278 mVariables.append(m.getSpecies(n)->getId());
279 }
280 }
281
282 for (n = 0; n < m.getNumParameters(); n++)
283 {
284 if (!m.getParameter(n)->getConstant())
285 {
286 mVariables.append(m.getParameter(n)->getId());
287 }
288 else if (m.getLevel() == 1)
289 {
290 mVariables.append(m.getParameter(n)->getId());
291 }
292 }
293
294 for (n = 0; n < m.getNumReactions(); n++)
295 {
296 if (m.getReaction(n)->isSetKineticLaw())
297 {
298 mVariables.append(m.getReaction(n)->getId());
299 }
300 if (m.getLevel() > 2)
301 {
302 /* in L3 stoichiometry could be altered by rules
303 * so these need to be included
304 * where a speciesReference is marked as false
305 */
306 for (k = 0; k < m.getReaction(n)->getNumReactants(); k++)
307 {
308 if (m.getReaction(n)->getReactant(k)->getConstant() == false )
309 {
310 mVariables.append(m.getReaction(n)->getReactant(k)->getId());
311 }
312 }
313 for (k = 0; k < m.getReaction(n)->getNumProducts(); k++)
314 {
315 if (m.getReaction(n)->getProduct(k)->getConstant() == false )
316 {
317 mVariables.append(m.getReaction(n)->getProduct(k)->getId());
318 }
319 }
320 }
321 }
322 }
323
324 unsigned int
getNumEquations()325 EquationMatching::getNumEquations()
326 {
327 return mEquations.size();
328 }
329 unsigned int
getNumVariables()330 EquationMatching::getNumVariables()
331 {
332 return mVariables.size();
333 }
334 /*
335 * creates a bipartite graph according to the L2V2 spec 4.11.5
336 * creates edges between the equation vertexes and the variable vertexes
337 * graph produced is an id representimg the equation and an IdList
338 * listing the edges the equation vertex is connected to
339 */
340 void
createGraph(const Model & m)341 EquationMatching::createGraph(const Model& m)
342 {
343 IdList joined;
344 IdList speciesAdded;
345 unsigned int n, sr;
346 const Reaction *r;
347 const Species *s;
348 const char * sId;
349 const Rule *rule;
350 const ASTNode *math;
351 const KineticLaw * kl;
352 List * names;
353 ASTNode * node;
354 string name;
355
356 /* create a list of ids relating to
357 * 1. species
358 * 2. rules
359 * 3. kinetic laws
360 */
361 writeEquationVertexes(m);
362
363 /* create a list relating to variables
364 * 1. compartments
365 * 2. species
366 * 3. parameters
367 * 4. reactions
368 */
369 writeVariableVertexes(m);
370
371 /* create the edges for the graph */
372
373 /*
374 * a Species structure that has the boundaryCondition field set to false
375 * and constant field set to false and which is referenced by the reactant
376 * or product lists of a Reaction structure containing a KineticLaw structure.
377 * The edge connects the vertex representing the species
378 * to the vertex representing the species' equation
379 */
380
381 speciesAdded.clear();
382 unsigned int eqnCount = 0;
383 for (n = 0; n < m.getNumReactions(); n++)
384 {
385 if (m.getReaction(n)->isSetKineticLaw())
386 {
387 r = m.getReaction(n);
388 for (sr = 0; sr < r->getNumReactants(); sr++)
389 {
390 s = m.getSpecies(r->getReactant(sr)->getSpecies());
391 sId = s->getId().c_str();
392
393 if (mEquations.contains(sId)
394 && mVariables.contains(sId)
395 && !speciesAdded.contains(sId))
396 {
397 joined.append(sId);
398 speciesAdded.append(sId);
399 }
400 if (joined.size() > 0)
401 {
402 mGraph[mEquations.at((int)eqnCount)] = joined;
403 joined.clear();
404 eqnCount++;
405 }
406 }
407
408 for (sr = 0; sr < r->getNumProducts(); sr++)
409 {
410 s = m.getSpecies(r->getProduct(sr)->getSpecies());
411 sId = s->getId().c_str();
412
413 if (mEquations.contains(sId)
414 && mVariables.contains(sId)
415 && !speciesAdded.contains(sId))
416 {
417 joined.append(sId);
418 speciesAdded.append(sId);
419 }
420 if (joined.size() > 0)
421 {
422 mGraph[mEquations.at((int)eqnCount)] = joined;
423 joined.clear();
424 eqnCount++;
425 }
426 }
427 }
428 }
429
430 /* rules */
431 for (n = 0; n < m.getNumRules(); n++)
432 {
433 rule = m.getRule(n);
434
435 /*
436 * an AssignmentRule or RateRule.
437 * The edge connects the vertex representing the Rule to the vertex
438 * representing the variable referenced by the variable field of the rule.
439 */
440 if (rule->isAssignment() || rule->isRate())
441 {
442 if (mVariables.contains(rule->getVariable()))
443 {
444 joined.append(rule->getVariable());
445 }
446 }
447
448 /*
449 * the occurrence of a MathML ci symbol referencing a variable within an
450 * AssignmentRule or AlgebraicRule.
451 * The ci element must either reference: (a) a Species, compartment or
452 * parameter structure which has the constant field set to false; or
453 * (b) reference a Reaction structure
454 * The edge connects the vertex representing the rule to the vertex
455 * representing the variable.
456 */
457 if (rule->isSetMath())
458 {
459 math = rule->getMath();
460 names = math->getListOfNodes( ASTNode_isName );
461
462 for (sr = 0; sr < names->getSize(); sr++)
463 {
464 node = static_cast<ASTNode*>( names->get(sr) );
465 name = node->getName() ? node->getName() : "";
466 if (mVariables.contains(name))
467 {
468 joined.append(name);
469 }
470 }
471
472 delete names;
473
474 }
475
476 mGraph[mEquations.at((int)eqnCount)] = joined;
477 joined.clear();
478 eqnCount++;
479 }
480
481 /* kineticlaws */
482 for (n = 0; n < m.getNumReactions(); n++)
483 {
484 if (m.getReaction(n)->isSetKineticLaw())
485 {
486 /*
487 * a KineticLaw.
488 * The edge connects the vertex representing the KineticLaw equation
489 * to the variable vertex representing the Reaction containing the
490 * KineticLaw.
491 */
492 if (mVariables.contains(m.getReaction(n)->getId()))
493 {
494 joined.append(m.getReaction(n)->getId());
495 }
496
497 /*
498 * the occurrence of a MathML ci symbol referencing a variable within
499 * an KineticLaw.
500 * The ci element must either reference a Species, compartment or
501 * parameter structure which has the constant field set to false.
502 * In this context a ci element cannot refer to a Reaction structure.
503 * The edge connects the vertex representing the kinetic law to the
504 * vertex representing the variable.
505 */
506 kl = m.getReaction(n)->getKineticLaw();
507
508 if (kl->isSetMath())
509 {
510 math = kl->getMath();
511 names = math->getListOfNodes( ASTNode_isName );
512
513 for (sr = 0; sr < names->getSize(); sr++)
514 {
515 node = static_cast<ASTNode*>( names->get(sr) );
516 name = node->getName() ? node->getName() : "";
517 if (mVariables.contains(name))
518 {
519 joined.append(name);
520 }
521 }
522
523 delete names;
524
525 }
526 mGraph[mEquations.at((int)eqnCount)] = joined;
527 joined.clear();
528 eqnCount++;
529 }
530 }
531 }
532
533 /*
534 * finds a maximal matching of the bipartite graph
535 * adapted from the only implementation I could find:
536 * # Hopcroft-Karp bipartite max-cardinality mMatching and max independent set
537 * # David Eppstein, UC Irvine, 27 Apr 2002 - Python Cookbook
538 *
539 * returns an IdList of any equation vertexes that are unconnected
540 * in the maximal matching
541 */
542 IdList
findMatching()543 EquationMatching::findMatching()
544 {
545 IdList unmatchedEquations;
546
547
548 unsigned int n, p;
549 IdList temp;
550 IdList tempVarsInMatching;
551 IdList unmatch;
552 IdList layer;
553 IdList unmatchFlag;
554 unmatchFlag.append("unmatched");
555 graph newLayer;
556
557 /* create greedy mMatching */
558 for (n = 0; n < mEquations.size(); n++)
559 {
560 for (p = 0; p < mGraph[mEquations.at((int)n)].size(); p++)
561 {
562 if (mMatching.count(mGraph[mEquations.at((int)n)].at((int)p)) == 0)
563 {
564 temp.append(mEquations.at((int)n));
565 mMatching[mGraph[mEquations.at((int)n)].at((int)p)] = temp;
566 temp.clear();
567 break;
568 }
569 }
570 }
571
572 unsigned int maximal = 1;
573 while (maximal == 1)
574 {
575 unmatch.clear();
576 mVarNeighInPrev.clear();
577
578 /* create mEqnNeighInPrev - graph giving neighbour in previous layer */
579
580 /* list of variables in mMatching */
581 tempVarsInMatching.clear();
582 for (graph::iterator iter = mMatching.begin(); iter != mMatching.end(); iter++)
583 {
584 tempVarsInMatching.append((*iter).second.at(0));
585 }
586
587
588 for (n = 0; n < mEquations.size(); n++)
589 {
590 if (!tempVarsInMatching.contains(mEquations.at((int)n)))
591 {
592 mEqnNeighInPrev[mEquations.at((int)n)] = unmatchFlag;
593 layer.append(mEquations.at((int)n));
594 }
595 }
596
597 /* extend layering structure */
598 while (layer.size() > 0 && unmatch.size() == 0)
599 {
600 graph::iterator iter;
601
602 newLayer.clear();
603
604 temp.clear();
605 for (iter = mVarNeighInPrev.begin();
606 iter != mVarNeighInPrev.end(); iter++)
607 {
608 temp.append((*iter).first);
609 }
610 for (n = 0; n < layer.size(); n++)
611 {
612 for (p = 0; p < mGraph[layer.at((int)n)].size(); p++)
613 {
614 if (!temp.contains(mGraph[layer.at((int)n)].at((int)p)))
615 {
616 newLayer[mGraph[layer.at((int)n)].at((int)p)].append(layer.at((int)n));
617 }
618 }
619 }
620
621 layer.clear();
622 temp.clear();
623 for (iter = newLayer.begin();
624 iter != newLayer.end(); iter++)
625 {
626 mVarNeighInPrev[(*iter).first] = (*iter).second;
627 if (tempVarsInMatching.contains((*iter).first))
628 {
629 layer.append(mMatching[(*iter).first].at(0));
630 temp.append((*iter).first);
631 mEqnNeighInPrev[mMatching[(*iter).first].at(0)] = temp;
632 }
633 else
634 {
635 unmatch.append((*iter).first);
636 }
637 }
638 } // end of extending layers while statement
639
640 /* finished without needing alternative paths */
641 if (unmatch.size() == 0)
642 {
643 /* list any equations that are not matched */
644 temp.clear();
645 for (graph::iterator iter = mMatching.begin();
646 iter != mMatching.end(); iter++)
647 {
648 temp.append(mMatching[(*iter).first].at(0));
649 }
650
651 for (n = 0; n < mEquations.size(); n++)
652 {
653 if (!temp.contains(mEquations.at((int)n)))
654 {
655 unmatchedEquations.append(mEquations.at((int)n));
656 }
657 }
658 maximal = 0;
659 }
660 else
661 {
662 for (n = 0; n < unmatch.size(); n++)
663 {
664 maximal = Recurse(unmatch.at((int)n));
665 if (maximal == 2) break;
666 }
667 }
668 }
669
670 if (maximal == 2)
671 {
672 // we have a flip flopping set of matches
673 unmatchedEquations.append(mMatching[unmatch.at((int)n)].at(0));
674 }
675 return unmatchedEquations;
676
677 }
678
679
680 /*
681 * function that looks for alternative paths and adds these to the matching
682 * where necessary
683 */
684 unsigned int
Recurse(std::string v)685 EquationMatching::Recurse(std::string v)
686 {
687 //static graph revisited;
688 //static IdList visited;
689 unsigned int rec = 0;
690 unsigned int n;
691 IdList tempVarNeigh;
692 IdList tempEqnNeigh;
693 IdList L;
694 IdList pu;
695 IdList prev;
696
697 graph::iterator iter;
698
699 tempVarNeigh.clear();
700 for (iter = mVarNeighInPrev.begin();
701 iter != mVarNeighInPrev.end(); iter++)
702 {
703 tempVarNeigh.append((*iter).first);
704 }
705
706 tempEqnNeigh.clear();
707 for (iter = mEqnNeighInPrev.begin();
708 iter != mEqnNeighInPrev.end(); iter++)
709 {
710 tempEqnNeigh.append((*iter).first);
711 }
712
713 if (tempVarNeigh.contains(v))
714 {
715 L = mVarNeighInPrev[v];
716 mVarNeighInPrev.erase(v);
717
718 for (n = 0; n < L.size(); n++)
719 {
720 if (tempEqnNeigh.contains(L.at((int)n)))
721 {
722 pu = mEqnNeighInPrev[L.at((int)n)];
723 mEqnNeighInPrev.erase(L.at((int)n));
724
725 if (pu.size() == 0)
726 break;
727
728 if (pu.size() == 1 && !strcmp(pu.at(0).c_str(), "unmatched"))
729 {
730 /* look to see if variable has been here before
731 * in order t catch variables that are flip flopping
732 * between equations
733 */
734 bool repeat = false;
735 if (!visited.contains(v))
736 {
737 visited.append(v);
738 revisited[v] = L;
739 }
740 else
741 {
742 prev = revisited[v];
743 unsigned int i;
744 for (i = 0; i < L.size(); i++)
745 {
746 if (prev.contains(L.at((int)i)))
747 {
748 repeat = true;
749 }
750 else
751 {
752 prev.append(L.at((int)i));
753 }
754 if (repeat) break;
755 }
756 }
757 if (!repeat)
758 {
759 mMatching[v] = L;
760 rec = 1;
761 }
762 else
763 {
764 rec = 2;
765 return rec;
766 }
767 }
768 else if (Recurse(pu.at(0)))
769 {
770 mMatching[v] = L;
771 rec = 1;
772 }
773 }
774 }
775 }
776 return rec;
777 }
778
779
780 /* return true if the pair are matched
781 */
782 bool
match_dependency(const std::string & var,const std::string & eq)783 EquationMatching::match_dependency(const std::string& var, const std::string& eq)
784 {
785 bool match = false;
786 IdList matches = mMatching[var];
787
788 if (matches.size() == 1 && matches.at(0) == eq)
789 match = true;
790
791
792 return match;
793 }
794 LIBSBML_CPP_NAMESPACE_END
795 /** @endcond */
796