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