1 /**
2  * @cond doxygenLibsbmlInternal
3  *
4  * @file    ExponentUnitsCheck.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/math/FormulaFormatter.h>
55 
56 #include <sbml/units/UnitFormulaFormatter.h>
57 
58 #include "ExponentUnitsCheck.h"
59 
60 /** @cond doxygenIgnored */
61 using namespace std;
62 /** @endcond */
63 
64 LIBSBML_CPP_NAMESPACE_BEGIN
65 
66 static const char* PREAMBLE =
67   "The use of non-integral exponents may result in incorrect units.";
68 
69 
70 /*
71  * Creates a new Constraint with the given @p id.
72  */
ExponentUnitsCheck(unsigned int id,Validator & v)73 ExponentUnitsCheck::ExponentUnitsCheck (unsigned int id, Validator& v) : UnitsBase(id, v)
74 {
75 }
76 
77 
78 /*
79  * Destroys this Constraint.
80  */
~ExponentUnitsCheck()81 ExponentUnitsCheck::~ExponentUnitsCheck ()
82 {
83 }
84 
85 /*
86  * @return the preamble to use when logging constraint violations.
87  */
88 const char*
getPreamble()89 ExponentUnitsCheck::getPreamble ()
90 {
91   return PREAMBLE;
92 }
93 
94 
95 
96 
97 /*
98   * Checks that the units of the result of the assignment rule
99   * are consistent with variable being assigned
100   *
101   * If an inconsistent variable is found, an error message is logged.
102   */
103 void
checkUnits(const Model & m,const ASTNode & node,const SBase & sb,bool inKL,int reactNo)104 ExponentUnitsCheck::checkUnits (const Model& m, const ASTNode& node, const SBase & sb,
105                                  bool inKL, int reactNo)
106 {
107   ASTNodeType_t type = node.getType();
108 
109   switch (type)
110   {
111     case AST_FUNCTION_ROOT:
112 
113       checkUnitsFromRoot(m, node, sb, inKL, reactNo);
114       break;
115 
116 
117     case AST_FUNCTION:
118 
119       checkFunction(m, node, sb, inKL, reactNo);
120       break;
121 
122     default:
123 
124       checkChildren(m, node, sb, inKL, reactNo);
125       break;
126 
127   }
128 }
129 
130 
131 /*
132   * Checks that the units of the power function are consistent
133   *
134   * If inconsistent units are found, an error message is logged.
135   *
136   * The two arguments to root, which are of the form root(n, a)
137   * where the degree n is optional (defaulting to '2'), should be as follows:
138   * (1) if the optional degree qualifier n is an integer,
139   * then it must be possible to derive the n-th root of a;
140   * (2) if the optional degree qualifier n is a rational n/m
141   * then it must be possible to derive the n-th root of (a{unit})m,
142   * where {unit} signifies the units associated with a;
143   * otherwise, (3) the units of a must be 'dimensionless'.
144   */
145 void
checkUnitsFromRoot(const Model & m,const ASTNode & node,const SBase & sb,bool inKL,int reactNo)146 ExponentUnitsCheck::checkUnitsFromRoot (const Model& m,
147                                         const ASTNode& node,
148                                         const SBase & sb, bool inKL, int reactNo)
149 {
150   /* check that node has 2 children */
151   if (node.getNumChildren() != 2)
152   {
153     return;
154   }
155 
156   UnitDefinition dim(m.getSBMLNamespaces());
157   Unit unit(m.getSBMLNamespaces());
158   unit.setKind(UNIT_KIND_DIMENSIONLESS);
159   unit.initDefaults();
160   dim.addUnit(&unit);
161   /* root (v, n) = v^1/n
162    * the exponent of the resulting unit must be integral
163    */
164 
165   int root = 1;
166   UnitDefinition * unitsArg1;
167   UnitFormulaFormatter *unitFormat = new UnitFormulaFormatter(&m);
168 
169   unitsArg1 = unitFormat->getUnitDefinition(node.getLeftChild(), inKL, reactNo);
170   unsigned int undeclaredUnits =
171     unitFormat->getContainsUndeclaredUnits();
172   ASTNode * child = node.getRightChild();
173 
174   // The first argument is dimensionless then it doesnt matter
175   // what the root is
176 
177   if (undeclaredUnits == 0 && !UnitDefinition::areEquivalent(&dim, unitsArg1))
178   {
179     // if not argument needs to be an integer or a rational
180     unsigned int isInteger = 0;
181     unsigned int isRational = 0;
182 
183     if (child->isRational())
184     {
185       isRational = 1;
186     }
187     else if (child->isInteger())
188     {
189       isInteger = 1;
190       root = (int)child->getInteger();
191     }
192     else if (child->isReal())
193     {
194       if (ceil(child->getReal()) == child->getReal())
195       {
196         isInteger = 1;
197         root = (int) child->getReal();
198       }
199       else
200       {
201         logNonIntegerPowerConflict(node, sb);
202       }
203     }
204     else
205     {
206       logUnitConflict(node, sb);
207     }
208 
209     if (isRational == 1)
210     {
211       //* (2) if the second argument b is a rational number n/m,
212       //* it must be possible to derive the m-th root of (a{unit})n,
213       //* where {unit} signifies the units associated with a;
214       unsigned int impossible = 0;
215       for (unsigned int n = 0; impossible == 0 && n < unitsArg1->getNumUnits(); n++)
216       {
217         if ((int)(unitsArg1->getUnit(n)->getExponent()) * child->getInteger() %
218           child->getDenominator() != 0)
219           impossible = 1;
220       }
221 
222       if (impossible)
223         logRationalPowerConflict(node, sb);
224 
225     }
226     else if (isInteger == 1)
227     {
228       unsigned int impossible = 0;
229       for (unsigned int n = 0; impossible == 0 && n < unitsArg1->getNumUnits(); n++)
230       {
231         if ((int)(unitsArg1->getUnit(n)->getExponent()) % root != 0)
232           impossible = 1;
233       }
234 
235       if (impossible)
236         logNonIntegerPowerConflict(node, sb);
237     }
238 
239   }
240 
241   ///* exponent must have integral form */
242   //if (!child->isInteger())
243   //{
244   //  if (!child->isReal())
245   //  {
246   //    logUnitConflict(node, sb);
247   //  }
248   //  else if (ceil(child->getReal()) != child->getReal())
249   //  {
250   //    logUnitConflict(node, sb);
251   //  }
252   //  else
253   //  {
254   //    root = (int) child->getReal();
255   //  }
256   //}
257   //else
258   //{
259   //  root = child->getInteger();
260   //}
261   //
262   //for (n = 0; n < tempUD->getNumUnits(); n++)
263   //{
264   //  if (tempUD->getUnit(n)->getExponent() % root != 0)
265   //  {
266   //    logUnitConflict(node, sb);
267   //  }
268   //}
269 
270   checkUnits(m, *node.getLeftChild(), sb);
271 
272   delete unitFormat;
273   delete unitsArg1;
274 }
275 
276 
277 /*
278  * @return the error message to use when logging constraint violations.
279  * This method is called by logFailure.
280  *
281  * Returns a message that the given @p id and its corresponding object are
282  * in  conflict with an object previously defined.
283  */
284 const string
getMessage(const ASTNode & node,const SBase & object)285 ExponentUnitsCheck::getMessage (const ASTNode& node, const SBase& object)
286 {
287 
288   ostringstream oss_msg;
289 
290   //oss_msg << getPreamble();
291 
292   char * formula = SBML_formulaToString(&node);
293   oss_msg << "The formula '" << formula;
294   oss_msg << "' in the " << getFieldname() << " element of the <" << object.getElementName();
295   oss_msg << "> ";
296   switch(object.getTypeCode()) {
297   case SBML_INITIAL_ASSIGNMENT:
298   case SBML_EVENT_ASSIGNMENT:
299   case SBML_ASSIGNMENT_RULE:
300   case SBML_RATE_RULE:
301     //LS DEBUG:  could use other attribute values, or 'isSetActualId'.
302     break;
303   default:
304     if (object.isSetId()) {
305       oss_msg << "with id '" << object.getId() << "' ";
306     }
307     break;
308   }
309   oss_msg << "produces an exponent that is not an integer and thus may produce ";
310   oss_msg << "invalid units.";
311   safe_free(formula);
312 
313   return oss_msg.str();
314 }
315 
316 void
logRationalPowerConflict(const ASTNode & node,const SBase & sb)317 ExponentUnitsCheck::logRationalPowerConflict (const ASTNode & node,
318                                              const SBase & sb)
319 {
320   char * formula = SBML_formulaToString(&node);
321   msg = "The formula '";
322   msg += formula;
323   msg += "' in the ";
324   msg += getFieldname();
325   msg += " element of the <" + sb.getElementName();
326   msg += "> ";
327   switch(sb.getTypeCode()) {
328   case SBML_INITIAL_ASSIGNMENT:
329   case SBML_EVENT_ASSIGNMENT:
330   case SBML_ASSIGNMENT_RULE:
331   case SBML_RATE_RULE:
332     //LS DEBUG:  could use other attribute values, or 'isSetActualId'.
333     break;
334   default:
335     if (sb.isSetId()) {
336       msg += "with id '";
337       msg += sb.getId() + "' ";
338     }
339     break;
340   }
341   msg += "contains a rational power that is inconsistent and thus may produce ";
342   msg += "invalid units.";
343   safe_free(formula);
344 
345   logFailure(sb, msg);
346 
347 }
348 
349 void
logNonIntegerPowerConflict(const ASTNode & node,const SBase & sb)350 ExponentUnitsCheck::logNonIntegerPowerConflict (const ASTNode & node,
351                                              const SBase & sb)
352 {
353   char * formula = SBML_formulaToString(&node);
354   msg = "The formula '";
355   msg += formula;
356   msg += "' in the ";
357   msg += getFieldname();
358   msg += " element of the <" + sb.getElementName();
359   msg += "> ";
360   switch(sb.getTypeCode()) {
361   case SBML_INITIAL_ASSIGNMENT:
362   case SBML_EVENT_ASSIGNMENT:
363   case SBML_ASSIGNMENT_RULE:
364   case SBML_RATE_RULE:
365     //LS DEBUG:  could use other attribute values, or 'isSetActualId'.
366     break;
367   default:
368     if (sb.isSetId()) {
369       msg += "with id '";
370       msg += sb.getId() + "' ";
371     }
372     break;
373   }
374   msg += "contains a root that is not an integer and thus may produce ";
375   msg += "invalid units.";
376   safe_free(formula);
377 
378   logFailure(sb, msg);
379 
380 }
381 
382 LIBSBML_CPP_NAMESPACE_END
383 /** @endcond */
384 
385