1 /**
2 * @cond doxygenLibsbmlInternal
3 *
4 * @file PowerUnitsCheck.cpp
5 * @brief Ensures math units are consistent.
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/Species.h>
47 #include <sbml/Parameter.h>
48 #include <sbml/UnitDefinition.h>
49 #include <sbml/Event.h>
50 #include <sbml/Reaction.h>
51 #include <sbml/EventAssignment.h>
52 #include <sbml/SpeciesReference.h>
53 #include <sbml/Rule.h>
54 #include <sbml/SBMLTransforms.h>
55 #include <sbml/math/FormulaFormatter.h>
56 #include <sbml/util/IdList.h>
57
58 #include <sbml/units/UnitFormulaFormatter.h>
59
60 #include <sbml/util/List.h>
61 #include <sbml/util/util.h>
62
63 #include "PowerUnitsCheck.h"
64
65
66 /** @cond doxygenIgnored */
67 using namespace std;
68 /** @endcond */
69
70 LIBSBML_CPP_NAMESPACE_BEGIN
71
72 static const char* PREAMBLE =
73 "A math expression using power with non-integer units may result in incorrect units.";
74
75
76 /*
77 * Creates a new Constraint with the given @p id.
78 */
PowerUnitsCheck(unsigned int id,Validator & v)79 PowerUnitsCheck::PowerUnitsCheck (unsigned int id, Validator& v) : UnitsBase(id, v)
80 {
81 }
82
83
84 /*
85 * Destroys this Constraint.
86 */
~PowerUnitsCheck()87 PowerUnitsCheck::~PowerUnitsCheck ()
88 {
89 }
90
91 /*
92 * @return the preamble to use when logging constraint violations.
93 */
94 const char*
getPreamble()95 PowerUnitsCheck::getPreamble ()
96 {
97 return PREAMBLE;
98 }
99
100
101
102
103 /*
104 * Checks that the units of the result of the assignment rule
105 * are consistent with variable being assigned
106 *
107 * If an inconsistent variable is found, an error message is logged.
108 */
109 void
checkUnits(const Model & m,const ASTNode & node,const SBase & sb,bool inKL,int reactNo)110 PowerUnitsCheck::checkUnits (const Model& m, const ASTNode& node, const SBase & sb,
111 bool inKL, int reactNo)
112 {
113 ASTNodeType_t type = node.getType();
114
115 switch (type)
116 {
117 //case AST_DIVIDE:
118 // checkForPowersBeingDivided(m, node, sb);
119 // break;
120 case AST_POWER:
121 case AST_FUNCTION_POWER:
122
123 checkUnitsFromPower(m, node, sb, inKL, reactNo);
124 break;
125
126 case AST_FUNCTION:
127
128 checkFunction(m, node, sb, inKL, reactNo);
129 break;
130
131 default:
132
133 checkChildren(m, node, sb, inKL, reactNo);
134 break;
135
136 }
137 }
138
139
140 /*
141 * Checks that the units of the power function are consistent
142 *
143 * If inconsistent units are found, an error message is logged.
144 *
145 * The two arguments to power, which are of the form power(a, b)
146 * with the meaning a^b, should be as follows:
147 * (1) if the second argument is an integer,
148 * then the first argument can have any units;
149 * (2) if the second argument b is a rational number n/m,
150 * it must be possible to derive the m-th root of (a{unit})n,
151 * where {unit} signifies the units associated with a;
152 * otherwise, (3) the units of the first argument must be 'dimensionless'.
153 * The second argument (b) should always have units of 'dimensionless'.
154 *
155 */
156 void
checkUnitsFromPower(const Model & m,const ASTNode & node,const SBase & sb,bool inKL,int reactNo)157 PowerUnitsCheck::checkUnitsFromPower (const Model& m,
158 const ASTNode& node,
159 const SBase & sb, bool inKL, int reactNo)
160 {
161
162 /* check that node has 2 children */
163 if (node.getNumChildren() != 2)
164 {
165 return;
166 }
167
168 double value;
169 UnitDefinition dim(m.getSBMLNamespaces());
170 Unit unit(m.getSBMLNamespaces());
171 unit.setKind(UNIT_KIND_DIMENSIONLESS);
172 unit.initDefaults();
173 dim.addUnit(&unit);
174
175 UnitFormulaFormatter *unitFormat = new UnitFormulaFormatter(&m);
176
177 UnitDefinition *tempUD = NULL;
178 UnitDefinition *unitsArg1, *unitsArgPower;
179 unitsArg1 = unitFormat->getUnitDefinition(node.getLeftChild(), inKL, reactNo);
180 unsigned int undeclaredUnits =
181 unitFormat->getContainsUndeclaredUnits();
182
183 ASTNode *child = node.getRightChild();
184 unitFormat->resetFlags();
185 unitsArgPower = unitFormat->getUnitDefinition(child, inKL, reactNo);
186
187 unsigned int undeclaredUnitsPower =
188 unitFormat->getContainsUndeclaredUnits();
189
190 // The second argument (b) should always have units of 'dimensionless'.
191 // or it has undeclared units that we assume are correct
192
193 if (undeclaredUnitsPower == 0 && !UnitDefinition::areEquivalent(&dim, unitsArgPower))
194 {
195 logNonDimensionlessPowerConflict(node, sb);
196 }
197
198 // The first argument is dimensionless then it doesnt matter
199 // what the power is
200
201 if (undeclaredUnits == 0 && !UnitDefinition::areEquivalent(&dim, unitsArg1))
202 {
203 // if not argument needs to be an integer or a rational
204 unsigned int isInteger = 0;
205 unsigned int isRational = 0;
206 unsigned int isExpression = 0;
207 /* power must be an integer
208 * but need to check that it is not a real
209 * number that is integral
210 * i.e. mathml <cn> 2 </cn> will record a "real"
211 */
212 if (child->isRational())
213 {
214 isRational = 1;
215 }
216 else if (child->isInteger())
217 {
218 isInteger = 1;
219 }
220 else if (child->isReal())
221 {
222 if (ceil(child->getReal()) == child->getReal())
223 {
224 isInteger = 1;
225 }
226 }
227 else if (child->getNumChildren() > 0)
228
229 {
230 // power might itself be an expression
231 tempUD = unitFormat->getUnitDefinition(child, inKL, reactNo);
232 UnitDefinition::simplify(tempUD);
233
234 if (tempUD->isVariantOfDimensionless())
235 {
236 SBMLTransforms::mapComponentValues(&m);
237 double value1 = SBMLTransforms::evaluateASTNode(child);
238 SBMLTransforms::clearComponentValues();
239 if (!util_isNaN(value1))
240 {
241 if (floor(value1) != value1)
242 isExpression = 1;
243 else
244 isInteger = 1;
245 }
246 else
247 {
248 isExpression = 1;
249 }
250 }
251 else
252 {
253 /* here the child is an expression with units
254 * flag the expression as not checked
255 */
256 isExpression = 1;
257 }
258 }
259 else
260 {
261 // power could be a parameter or a speciesReference in l3
262 const Parameter *param = NULL;
263 const SpeciesReference *sr = NULL;
264
265 if (child->isName())
266 {
267 /* Parameters may be declared in two places (the model and the
268 * kinetic law's local parameter list), so we have to check both.
269 */
270
271 if (sb.getTypeCode() == SBML_KINETIC_LAW)
272 {
273 const KineticLaw* kl = dynamic_cast<const KineticLaw*>(&sb);
274
275 /* First try local parameters and if null is returned, try
276 * the global parameters */
277 if (kl != NULL)
278 {
279 param = kl->getParameter(child->getName());
280 }
281 }
282
283 if (param == NULL)
284 {
285 param = m.getParameter(child->getName());
286 }
287
288 if (param == NULL && m.getLevel() > 2)
289 {
290 // could be a species reference
291 sr = m.getSpeciesReference(child->getName());
292 }
293
294 }
295
296 if (param != NULL)
297 {
298 /* We found a parameter with this identifier. */
299
300 if (UnitDefinition::areEquivalent(&dim, unitsArgPower) || undeclaredUnitsPower)
301 {
302 value = param->getValue();
303 if (value != 0)
304 {
305 if (ceil(value) == value)
306 {
307 isInteger = 1;
308 }
309 }
310
311 }
312 else
313 {
314 /* No parameter definition found for child->getName() */
315 logUnitConflict(node, sb);
316 }
317 }
318 else if (sr != NULL)
319 {
320 // technically here there is an issue
321 // stoichiometry is dimensionless
322 SBMLTransforms::mapComponentValues(&m);
323 double value1 = SBMLTransforms::evaluateASTNode(child, &m);
324 SBMLTransforms::clearComponentValues();
325 // but it may not be an integer
326 if (util_isNaN(value1))
327 // we cant check
328 {
329 isExpression = 1;
330 }
331 else
332 {
333 if (ceil(value1) == value1)
334 {
335 isInteger = 1;
336 }
337 }
338 }
339 }
340
341 if (isRational == 1)
342 {
343 //FIX-ME will need sorting for double exponents
344
345 //* (2) if the second argument b is a rational number n/m,
346 //* it must be possible to derive the m-th root of (a{unit})n,
347 //* where {unit} signifies the units associated with a;
348 unsigned int impossible = 0;
349 for (unsigned int n = 0; impossible == 0 && n < unitsArg1->getNumUnits(); n++)
350 {
351 if ((int)(unitsArg1->getUnit(n)->getExponent()) * child->getInteger() %
352 child->getDenominator() != 0)
353 impossible = 1;
354 }
355
356 if (impossible)
357 logRationalPowerConflict(node, sb);
358
359 }
360 else if (isExpression == 1)
361 {
362 logExpressionPowerConflict(node, sb);
363 }
364 else if (isInteger == 0)
365 {
366 // in level 3 this would be allowed
367 if (m.getLevel() < 3)
368 {
369 logNonIntegerPowerConflict(node, sb);
370 }
371 }
372
373 }
374
375
376
377
378 // if (!areEquivalent(dim, unitsPower))
379 // {
380 // /* 'v' does not have units of dimensionless. */
381
382 // /* If the power 'n' is a parameter, check if its units are either
383 // * undeclared or declared as dimensionless. If either is the case,
384 // * the value of 'n' must be an integer.
385 // */
386
387 // const Parameter *param = NULL;
388
389 // if (child->isName())
390 // {
391 // /* Parameters may be declared in two places (the model and the
392 // * kinetic law's local parameter list), so we have to check both.
393 // */
394
395 // if (sb.getTypeCode() == SBML_KINETIC_LAW)
396 // {
397 // const KineticLaw* kl = dynamic_cast<const KineticLaw*>(&sb);
398
399 // /* First try local parameters and if null is returned, try
400 // * the global parameters */
401 // if (kl != NULL)
402 // {
403 // param = kl->getParameter(child->getName());
404 // }
405 // }
406
407 // if (param == NULL)
408 // {
409 // param = m.getParameter(child->getName());
410 // }
411 //
412 // }
413
414 // if (param != NULL)
415 // {
416 // /* We found a parameter with this identifier. */
417
418 // if (areEquivalent(dim, unitsArgPower) || unitFormat->hasUndeclaredUnits(child))
419 // {
420 // value = param->getValue();
421 // if (value != 0)
422 // {
423 // if (ceil(value) != value)
424 // {
425 // logUnitConflict(node, sb);
426 // }
427 // }
428
429 // }
430 // else
431 // {
432 ///* No parameter definition found for child->getName() */
433 // logUnitConflict(node, sb);
434 // }
435 // }
436 // else if (child->isFunction() || child->isOperator())
437 // {
438 // /* cannot test whether the value will be appropriate */
439 // if (!areEquivalent(dim, unitsArgPower))
440 // {
441 // logUnitConflict(node, sb);
442 // }
443 // }
444 // /* power must be an integer
445 // * but need to check that it is not a real
446 // * number that is integral
447 // * i.e. mathml <cn> 2 </cn> will record a "real"
448 // */
449 // else if (!child->isInteger())
450 // {
451 // if (!child->isReal())
452 // {
453 // logUnitConflict(node, sb);
454 // }
455 // else if (ceil(child->getReal()) != child->getReal())
456 // {
457 // logUnitConflict(node, sb);
458 // }
459 // }
460 // }
461 // else if (!areEquivalent(dim, unitsArgPower))
462 // {
463 // /* power (3, k) */
464 // logUnitConflict(node, sb);
465 // }
466
467 checkUnits(m, *node.getLeftChild(), sb, inKL, reactNo);
468
469 if (tempUD != NULL) delete tempUD;
470 delete unitFormat;
471 delete unitsArg1;
472 delete unitsArgPower;
473 }
474
475
476 /*
477 * @return the error message to use when logging constraint violations.
478 * This method is called by logFailure.
479 *
480 * Returns a message that the given @p id and its corresponding object are
481 * in conflict with an object previously defined.
482 */
483 const string
getMessage(const ASTNode & node,const SBase & object)484 PowerUnitsCheck::getMessage (const ASTNode& node, const SBase& object)
485 {
486
487 ostringstream oss_msg;
488
489 //oss_msg << getPreamble();
490
491 char * formula = SBML_formulaToString(&node);
492 oss_msg << "The formula '" << formula;
493 oss_msg << "' in the " << getFieldname() << " element of the <" << object.getElementName();
494 oss_msg << "> ";
495 switch(object.getTypeCode()) {
496 case SBML_INITIAL_ASSIGNMENT:
497 case SBML_EVENT_ASSIGNMENT:
498 case SBML_ASSIGNMENT_RULE:
499 case SBML_RATE_RULE:
500 //LS DEBUG: could use other attribute values, or 'isSetActualId'.
501 break;
502 default:
503 if (object.isSetId()) {
504 oss_msg << "with id '" << object.getId() << "' ";
505 }
506 break;
507 }
508 oss_msg << "contains a power that is not an integer and thus may produce ";
509 oss_msg << "invalid units.";
510 safe_free(formula);
511
512 return oss_msg.str();
513 }
514
515
516 //void
517 //PowerUnitsCheck::checkForPowersBeingDivided (const Model& m, const ASTNode& node,
518 // const SBase & sb)
519 //{
520 // ASTNode* left = node.getLeftChild();
521 // ASTNode* right = node.getRightChild();
522 //
523 // if (left->getType() == AST_POWER || left->getType() == AST_FUNCTION_POWER)
524 // {
525 // if (right->getType() == AST_POWER || right->getType() == AST_FUNCTION_POWER)
526 // {
527 // /* have a power divided by a power */
528 // /* check whether objects have same units */
529 // UnitFormulaFormatter *unitFormat = new UnitFormulaFormatter(&m);
530 //
531 // UnitDefinition *tempUD, *tempUD1, *tempUD2, *tempUD3;
532 // tempUD = unitFormat->getUnitDefinition(left->getLeftChild());
533 // tempUD1 = unitFormat->getUnitDefinition(right->getLeftChild());
534 // tempUD2 = unitFormat->getUnitDefinition(right->getRightChild());
535 // tempUD3 = unitFormat->getUnitDefinition(left->getRightChild());
536 //
537 // if (!areEquivalent(tempUD, tempUD1))
538 // {
539 // checkChildren(m, node, sb);
540 // }
541 // else
542 // {
543 // if(!areEquivalent(tempUD2, tempUD3))
544 // {
545 // logUnitConflict(node, sb);
546 // }
547 // else
548 // {
549 // /* create an ASTNode with pow(object, left_exp - right_exp) */
550 // ASTNode *newPower = new ASTNode(AST_POWER);
551 // ASTNode * newMinus = new ASTNode(AST_MINUS);
552 // newMinus->addChild(left->getRightChild()->deepCopy());
553 // newMinus->addChild(right->getRightChild()->deepCopy());
554 // newPower->addChild(left->getLeftChild()->deepCopy());
555 // newPower->addChild(newMinus);
556 //
557 // checkUnitsFromPower(m, *newPower, sb);
558 //
559 // delete newPower;
560 // }
561 // }
562 //
563 // delete unitFormat;
564 // delete tempUD;
565 // delete tempUD1;
566 // delete tempUD2;
567 // delete tempUD3;
568 // }
569 // else
570 // {
571 // checkChildren(m, node, sb);
572 // }
573 // }
574 // else
575 // {
576 // checkChildren(m, node, sb);
577 // }
578 //}
579 /*
580 * Logs a message about a function that should return same units
581 * as the arguments
582 */
583 void
logNonDimensionlessPowerConflict(const ASTNode & node,const SBase & sb)584 PowerUnitsCheck::logNonDimensionlessPowerConflict (const ASTNode & node,
585 const SBase & sb)
586 {
587 char * formula = SBML_formulaToString(&node);
588 msg = "The formula '";
589 msg += formula;
590 msg += "' in the ";
591 msg += getFieldname();
592 msg += " element of the <" + sb.getElementName();
593 msg += "> ";
594 switch(sb.getTypeCode()) {
595 case SBML_INITIAL_ASSIGNMENT:
596 case SBML_EVENT_ASSIGNMENT:
597 case SBML_ASSIGNMENT_RULE:
598 case SBML_RATE_RULE:
599 //LS DEBUG: could use other attribute values, or 'isSetActualId'.
600 break;
601 default:
602 if (sb.isSetId()) {
603 msg += "with id '";
604 msg += sb.getId() + "' ";
605 }
606 break;
607 }
608 msg += "contains a power that is not dimensionless and thus may produce ";
609 msg += "invalid units.";
610 safe_free(formula);
611
612 logFailure(sb, msg);
613
614 }
615
616
617 void
logNonIntegerPowerConflict(const ASTNode & node,const SBase & sb)618 PowerUnitsCheck::logNonIntegerPowerConflict (const ASTNode & node,
619 const SBase & sb)
620 {
621 char * formula = SBML_formulaToString(&node);
622 msg = "The formula '";
623 msg += formula;
624 msg += "' in the ";
625 msg += getFieldname();
626 msg += " element of the <" + sb.getElementName();
627 msg += "> ";
628 switch(sb.getTypeCode()) {
629 case SBML_INITIAL_ASSIGNMENT:
630 case SBML_EVENT_ASSIGNMENT:
631 case SBML_ASSIGNMENT_RULE:
632 case SBML_RATE_RULE:
633 //LS DEBUG: could use other attribute values, or 'isSetActualId'.
634 break;
635 default:
636 if (sb.isSetId()) {
637 msg += "with id '";
638 msg += sb.getId() + "' ";
639 }
640 break;
641 }
642 msg += "contains a power that is not an integer and thus may produce ";
643 msg += "invalid units.";
644 safe_free(formula);
645
646 logFailure(sb, msg);
647
648 }
649
650 void
logRationalPowerConflict(const ASTNode & node,const SBase & sb)651 PowerUnitsCheck::logRationalPowerConflict (const ASTNode & node,
652 const SBase & sb)
653 {
654 char * formula = SBML_formulaToString(&node);
655 msg = "The formula '";
656 msg += formula;
657 msg += "' in the ";
658 msg += getFieldname();
659 msg += " element of the <" + sb.getElementName();
660 msg += "> ";
661 switch(sb.getTypeCode()) {
662 case SBML_INITIAL_ASSIGNMENT:
663 case SBML_EVENT_ASSIGNMENT:
664 case SBML_ASSIGNMENT_RULE:
665 case SBML_RATE_RULE:
666 //LS DEBUG: could use other attribute values, or 'isSetActualId'.
667 break;
668 default:
669 if (sb.isSetId()) {
670 msg += "with id '";
671 msg += sb.getId() + "' ";
672 }
673 break;
674 }
675 msg += "contains a rational power that is inconsistent and thus may produce ";
676 msg += "invalid units.";
677 safe_free(formula);
678
679 logFailure(sb, msg);
680
681 }
682
683
684 void
logExpressionPowerConflict(const ASTNode & node,const SBase & sb)685 PowerUnitsCheck::logExpressionPowerConflict (const ASTNode & node,
686 const SBase & sb)
687 {
688 char * formula = SBML_formulaToString(&node);
689 msg = "The formula '";
690 msg += formula;
691 msg += "' in the ";
692 msg += getFieldname();
693 msg += " element of the <" + sb.getElementName();
694 msg += "> ";
695 switch(sb.getTypeCode()) {
696 case SBML_INITIAL_ASSIGNMENT:
697 case SBML_EVENT_ASSIGNMENT:
698 case SBML_ASSIGNMENT_RULE:
699 case SBML_RATE_RULE:
700 //LS DEBUG: could use other attribute values, or 'isSetActualId'.
701 break;
702 default:
703 if (sb.isSetId()) {
704 msg += "with id '";
705 msg += sb.getId() + "' ";
706 }
707 break;
708 }
709 msg += "contains an expression for the exponent of the power function ";
710 msg += "and thus cannot be checked for unit validity.";
711
712
713 safe_free(formula);
714
715 logFailure(sb, msg);
716
717 }
718
719
720 LIBSBML_CPP_NAMESPACE_END
721 /** @endcond */
722
723