[go: up one dir, main page]

Menu

[fb6a16]: / src / unit.cpp  Maximize  Restore  History

Download this file

417 lines (365 with data), 17.9 kB

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
/*******************************************************
unit.cpp - Class for extending GiNaC to handle physical units
-------------------
begin : Mon Oct 22 2001
copyright : (C) 2001 by Jan Rheinlaender
email : jrheinlaender@users.sourceforge.net
***************************************************************************/
/***************************************************************************
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
* *
***************************************************************************/
#include "unit.h"
#include "msgdriver.h" // *** added in 1.3.1
#include "optstack.h" // *** added in 1.1
#include "printing.h" // *** added in 1.4.1
// *** added print_func in 0.8
GINAC_IMPLEMENT_REGISTERED_CLASS_OPT(Unit, basic, print_func<print_context>(&Unit::do_print).print_func<imathprint>(&Unit::do_print));
// Define static members
bool Unit::initialized = false;
/* Unit* Unit::metre; *** removed in 1.4.1
Unit* Unit::kilogram;
Unit* Unit::second;
Unit* Unit::ampere;
Unit* Unit::kelvin;
Unit* Unit::celsius;
Unit* Unit::mole;
Unit* Unit::candela;*/
std::map <std::string, Unit*> Unit::factory; // *** added in 1.4.1
std::map <std::string, expression> Unit::conversions; // *** changed ex to expression in 0.4
// *** moved initialization of the sets to Unit::init in 1.3.1
// std::set<const char*, ltstr> Unit::base_units; *** removed in 1.4.1
//unitvec *Unit::preferred_unit_conversions = new unitvec; *** removed in 1.1
std::set<const char*, ltstr> Unit::unitnames;
unit_init unit_init::unit_initializer; // *** added in 0.5
bool unit_init::called = false; // *** added in 0.5
unit_init::unit_init() { // *** added in 0.5
if (called) {
// Removed in 1.3.1, msg_error might not be initialized yet
// msg::error(0) << "Attempt to call Unit::init() more than one time!" << endline;
return;
} else
called = true;
Unit::init();
}
unit_init::~unit_init() { // *** added in 0.5
Unit::clear();
}
// Required constructors and destructors and other GiNaC-specific methods
Unit::Unit() {}; // *** changed in 1.2.1, changed in 1.3.1
// TODO: Make this private, outside callers should use Unit::get()
Unit::Unit(const std::string &n) { // *** changed in 1.3.1
name = n;
}
Unit Unit::get(const std::string &n) { // *** added in 1.4.1
if (factory[n] == NULL)
(factory[n] = new Unit(n))->setflag(status_flags::dynallocated); // *** added dynallocated in 1.4.1
return *factory[n];
} // Unit::create()
Unit* Unit::getp(const std::string &n) { // *** added in 1.4.1
if (factory[n] == NULL)
(factory[n] = new Unit(n))->setflag(status_flags::dynallocated); // *** added dynallocated in 1.4.1
return factory[n];
} // Unit::create()
void Unit::archive(archive_node &n) const
{
inherited::archive(n);
n.add_string("Unit", name);
n.add_string("Unit-printname", printname); // *** added in 1.4.1
}
/* *** removed in 1.3.1
Unit::Unit(const archive_node &n, lst &sym_lst) : inherited(n, sym_lst)
{
n.find_string("Unit", name);
}
*/
// *** changed to read_archive format in 1.3.1
void Unit::read_archive(const archive_node &n, lst &sym_lst)
{
inherited::read_archive(n, sym_lst);
n.find_string("Unit", name);
n.find_string("Unit-printname", printname);
// return (new Unit(n, sym_lst))->setflag(status_flags::dynallocated);//
}
GINAC_BIND_UNARCHIVER(Unit);
int Unit::compare_same_type(const basic &other) const {
const Unit &o = static_cast<const Unit &>(other);
if (name == o.name)
return 0;
else if (name < o.name)
return -1;
else
return 1;
}
// Makes GiNaC more efficient
bool Unit::is_equal_same_type(const basic & other) const { // *** added in 1.4.6
const Unit & o = static_cast<const Unit &>(other);
return (name == o.name);
} // Unit::is_equal_same_type()
void Unit::do_print(const print_context &c, unsigned level) const {
// *** changed name to do_print in 0.8
std::string thename = (printname == "" ? name : printname); // added in 1.4.1
if (is_a<print_tree>(c)) {
c.s << thename;
} else if (is_a<print_csrc>(c)) {
c.s << thename;
} else if (is_a<print_latex>(c)) {
c.s << ((printname == "") ? (std::string("\\") + thename) : thename);
} else if (is_a<imathprint>(c)) { // added in 1.4.1 for iMath
if (thename[0] == '\\') thename.erase(0,1); // Handle broken names, should not occur at all
if (thename[0] == '{') { // smath unit definition
c.s << thename;
} else if (thename[0] == '%') {
if (thename.length() == 1)
c.s << "\"%\""; // Percent as a unit
else
c.s << thename; // Beginning of a unit name
} else { // protect the name with quotes
c.s << "\"" + thename + "\"";
}
} else {
c.s << "[" << thename << "]";
}
} // Unit::do_print()
ex Unit::eval(int level) const
// It is possible to define simplification or canonicalization rules here, like turning
// mm to m*10^(-3) etc.
{
return this->hold();
}
void Unit::set_printname(const std::string& pn) {
printname = pn;
// *** added in 1.4.3
if (pn != name) {
add_conversion(pn, get(name));
add_unitname(pn);
}
} // set_printname()
void Unit::add_conversion(const std::string &uname, const expression& other_units) {
if (conversions[uname] != numeric(0))
MSG_WARN(0, "Warning: Overwriting unit definition of " << uname << endline);
conversions[uname] = Unit::canonicalize(other_units);
MSG_INFO(2, "Adding conversion [" << uname << "] = " << Unit::canonicalize(other_units) << endline);
}
void Unit::add_unitname(const std::string &unitname) {
MSG_INFO(2, "Adding unitname: " << unitname << endline);
unitnames = set_add(unitnames, unitname);
} // Unit::add_unitname()
bool Unit::is_unitname(const std::string &uname) {
MSG_INFO(4, "Checking whether " << uname << " is a unit." << endline);
MSG_INFO(5, unitnames << endline);
return (set_has(unitnames, uname));
} // Unit::is_unitname()
bool Unit::is_unitnamep(const std::string &uname) {
MSG_INFO(4, "Checking whether %" << uname << " is a unit." << endline);
MSG_INFO(5, unitnames << endline);
return (set_has(unitnames, std::string("%") + uname));
} // Unit::is_unitnamep()
unitvec *Unit::create_conversions(const exprvector &units, const bool always) {
// *** added in 0.3, changed to use vector in 0.9, changed argument 2 to vector in 1.0
unitvec *result = new unitvec;
unitvec convs; // *** added in 0.9, changed to vector in 1.0
for (exprvector::const_iterator i = units.begin(); i < units.end(); i++) {
MSG_INFO(2, "Creating conversion for " << *i << endline);
// *** changed to const_iterator in 0.7, to std::vector in 1.0
expression pref_unit = *i;
expression conversion = conversions[ex_to<Unit>(pref_unit).get_name()];
if (conversion.is_zero()) { // *** Added in 1.4.1 because subst_units will crash with this conversion
MSG_WARN(0, "Warning: No conversion exists for " << *i << ". Ignoring" << endline);
continue;
}
// *** changed in 0.6 to avoid unnecessary substitutions, added algebraic in 0.8
if (!result->empty()) conversion = conversion.csubs(convs, subs_options::algebraic);
// Any numerics on the left hand side of the relation need to be moved to the
// right hand side because 3\mm will not match "1000\mm == \m"
if (is_a<mul>(conversion)) {
// Other cases are either impossible or need not be handled
mul conv = ex_to<mul>(conversion);
conversion = (conversion / expression(conv.op(conv.nops())));
pref_unit = pref_unit / expression(conv.op(conv.nops()));
//TODO: if the numeric is a float, the above code will probably leave a coefficient of
//0.99999999999995 or 1.00000000000005 on the left side!
}
if (always || (conversion != pref_unit)) { // *** added in 0.5
// If someone says \preferred_units{\metre} no conversion is necessary!
// But maybe he wants to override a global \preferred_units{\m} ?
MSG_INFO(2, "Adding " << conversion << "==" << pref_unit << " to list of preferred units." << endline);
result->push_back(conversion == pref_unit);
convs.push_back(conversion == pref_unit); // *** changed to push_back() in 1.0
}
}
return result;
} // create_conversions()
expression Unit::subst_units(const expression &e, const unitvec& unitlist) { // *** changed in 1.1, added unitlist in 1.4.1
MSG_INFO(1, "Substituting units in " << e << endline);
expression result = e;
// Note that we can NOT substitute all units at once because there might be inter-dependencies
// between the preferred units (e.g., \mm and N/mm^2)
// Ginac documentation says all substitutions are done in parallel, but it doesn't work
for (unitvec::const_iterator i = unitlist.begin(); i < unitlist.end(); i++) {
MSG_INFO(2, "Substituting unit " << *i << endline);
if (is_a<constant>(i->lhs()) && e.has(ex_to<constant>(i->lhs()))) {
// *** handles Pi = 180\degree, added in 1.4.5
result = result.subs(*i, subs_options::algebraic);
} else if (is_a<numeric>(expression(i->lhs()).evalf())) { // *** added in 0.9, handles evalf()'ed cases for Pi = 180\degree
if (is_a<equation>(e)) { // *** added in 1.4.1, handle special cases. Note equation is a subclass of relational
// TODO: Add more intelligence to not do the substitution except on expressions containing Pi or a numeric
// TODO: What about relationals? But relational does not have get_op()
equation eq = ex_to<equation>(e);
expression l = eq.lhs();
expression r = eq.rhs();
if (!is_a<symbol>(l)) {
l = l / expression(expression(i->lhs()).evalf() / expression(i->rhs()));
}
if (!is_a<symbol>(r)) {
r = r / expression(expression(i->lhs()).evalf() / expression(i->rhs()));
}
result = relational(l, r, eq.getop());
} else {
// TODO: This of course does not work if the number given in radians is hidden somewhere
// inside an expression
result = result / expression(expression(i->lhs()).evalf() / expression(i->rhs())); // *** bug removed in 1.3.1, was lhs * rhs !!!
}
} else {
result = result.subs(*i, subs_options::algebraic);
}
MSG_INFO(2, "Current result: " << result << endline);
}
return result;
} // subst_units()
expression Unit::canonicalize_from(const std::string &unit) { // *** changed ex to expression in 0.4
if (conversions[unit] == numeric(0)) {
MSG_ERROR(0, "Unknown unit " << unit << endline);
return expression(Unit::get("unknown")); // *** changed in 1.4.1
} else {
MSG_INFO(3, "Unit conversion table: " << unit << " = " << conversions[unit] << endline);
return conversions[unit];
}
}
expression Unit::canonicalize(const expression &units) { // *** changed ex to expression in 0.4
// This method only works on expressions which are units or a multiplication of units
MSG_INFO(3, "Canonicalizing " << units << endline);
expression u = units.expand(); // added in 0.5 to expand functions in unit definitions
if (is_a<Unit>(u)) {
return conversions[ex_to<Unit>(u).name];
} else if (is_a<mul>(u)) {
expression result(numeric(1));
//mul m = ex_to_mul(u);
for (const_iterator i = u.begin(); i != u.end(); i++) {
// *** changed to const_iterator in 0.7
result = result * Unit::canonicalize(*i);
}
return result;
} else if (is_a<power>(u)) {
power p = ex_to<power>(u);
return pow(Unit::canonicalize(get_basis(p)), get_exp(p)); // *** changed to use get_... in 1.2
} else if (is_a<symbol>(u)) {
// This exception exists to allow for symbols that are constants, i.e. \pi. Of course, it is
// impossible to avoid allowing other symbols that should not occur in a unit definition!
return u;
} else if (!is_a<numeric>(u) && !is_a<constant>(u)) { // *** added constant in 0.9
MSG_WARN(0, "Warning: Cannot canonicalize " << u << "!" << endline);
}
return u;
} // Unit::canonicalize
void Unit::init() {
if (initialized) return;
MSG_INFO(2, "Initializing units" << endline);
// *** Moved here in 1.3.1
// const char *bname[8] = {"m", "kg", "s", "A", "K", "XC", "mol", "cd"}; *** removed in 1.4.1
// base_units = std::set<const char*, ltstr>(bname,bname+8);
const char *unames[16] = {"\\metre", "\\kilogram", "\\second", "\\ampere",
"\\kelvin", "\\celsius", "\\mole", "\\candela",
"%metre", "%kilogram", "%second", "%ampere", // *** added in 1.3.1 for iMath
"%kelvin", "%celsius", "%mole", "%candela"};
unitnames = std::set<const char*, ltstr>(unames,unames+16);
#ifdef CPPU_ENV
conversions = std::map <std::string, expression>();
#endif
// Initialize the map of conversions
// Called during static initialization of class unit_initializer *** changed in 0.5
MSG_INFO(3, "Initializing unit conversion table..." << endline);
// base SI units
// Note that °C must be a base unit because otherwise \eq{\theta = 20\celsius} will print as \theta = 20\K !
// *** change Unit() to Unit::get() in 1.4.1
/* metre = Unit::getp("metre"); *** removed in 1.4.1
kilogram = Unit::getp("kilogram");
second = Unit::getp("second");
ampere = Unit::getp("ampere");
kelvin = Unit::getp("kelvin");
celsius = Unit::getp("celsius");
mole = Unit::getp("mole");
candela = Unit::getp("candela"); */
// added in 1.3.1 for iMath
// *** changed to Unit::getp in 1.4.1
Unit::getp("metre")->printname = "m"; // set_printname() is not what we want because "m" is added as a unit name and then we can't use a variable "m" any more
Unit::getp("kilogram")->printname = "kg"; // two-letter variable names can't occur in latex so this is not a problem
Unit::getp("second")->printname = "s";
Unit::getp("ampere")->printname = "A";
Unit::getp("kelvin")->printname = "K";
Unit::getp("celsius")->printname = "\\text{\\textdegree{C}}"; // TODO: This is not UTF-8 - safe ?!
Unit::getp("mole")->printname = "mol";
Unit::getp("candela")->printname = "cd";
// *** changed to Unit::get in 1.4.1
conversions["metre"] = expression(Unit::get("metre"));
conversions["kilogram"] = expression(Unit::get("kilogram"));
conversions["second"] = expression(Unit::get("second"));
conversions["ampere"] = expression(Unit::get("ampere"));
conversions["kelvin"] = expression(Unit::get("kelvin"));
conversions["celsius"] = expression(Unit::get("celsius")); // we can't have a Latex macro \°C !
conversions["mole"]= expression(Unit::get("mole"));
conversions["candela"] = expression(Unit::get("candela"));
MSG_INFO(2, "Finished initializing units" << endline);
initialized = true;
} // Unit::init()
void Unit::print(std::ostream &os) {
// *** changed ex to expression in 0.4
std::map<std::string, expression>::iterator conv = conversions.begin();
os << "List of unit conversions:" << std::endl;
while (conv != conversions.end()) {
os << conv->first << ": " << conv->second << std::endl;
conv++;
}
os << std::endl;
}
/*
void Unit::print(message &ms) {
std::map<std::string, expression>::iterator conv = conversions.begin();
ms << "List of unit conversions:" << endline;
while (conv != conversions.end()) {
ms << conv->first << ": " << conv->second << endline;
conv++;
}
ms << endline;
}
*/
void Unit::clear() {
conversions.clear();
// *** changed to Unit::get in 1.4.1
conversions["metre"] = expression(Unit::get("metre"));
conversions["kilogram"] = expression(Unit::get("kilogram"));
conversions["second"] = expression(Unit::get("second"));
conversions["ampere"] = expression(Unit::get("ampere"));
conversions["kelvin"] = expression(Unit::get("kelvin"));
conversions["celsius"] = expression(Unit::get("celsius")); // we can't have a Latex macro \°C !
conversions["mole"]= expression(Unit::get("mole"));
conversions["candela"] = expression(Unit::get("candela"));
/* conversions["metre"] = *metre;
conversions["kilogram"] = *kilogram;
conversions["second"] = *second;
conversions["ampere"] = *ampere;
conversions["kelvin"] = *kelvin;
conversions["celsius"] = *celsius;
conversions["mole"]= *mole;
conversions["candela"] = *candela; */
unitnames.clear();
const char *unames[16] = {"\\metre", "\\kilogram", "\\second", "\\ampere", // *** changed in 1.3.1
"\\kelvin", "\\celsius", "\\mole", "\\candela",
"%metre", "%kilogram", "%second", "%ampere", // *** added in 1.3.1
"%kelvin", "%celsius", "%mole", "%candela"};
unitnames = std::set<const char*, ltstr>(unames,unames+16);
}