2 infix ("``", 117, 117);
4 declare (constvalue, evfun);
10 then (get (e, 'constvalue), if %% # false then %% else e)
12 else map (constvalue, e);
14 declare_constvalue (a, x) ::= buildq ([a, x], put (a, x, 'constvalue));
16 remove_constvalue (a) ::= buildq ([a], rem (a, 'constvalue));
18 /* Hold off on this display symbol business.
19 * :lisp (put '$\` '(#\space #\space) 'dissym)
22 declare (dimensional, feature);
23 declare (nondimensional, feature);
25 declare_units (a%, u%) :=
26 block (local (a%, u%),
28 then map (lambda ([a1%], local(a1%), declare1_units (a1%, u%)), a%)
29 else declare1_units (a%, u%), u%);
31 declare1_units (a%, u%) :=
32 block (local (a%, u%),
33 apply ('define_variable, [a%, a%, any_check]),
34 put (a%, units_check (a%), value_check),
36 if featurep (a%, 'dimensional) = false
37 then apply (declare, [a%, 'dimensional]));
39 declare_qty (a%, q%) :=
40 block (local (a%, q%),
42 then map (lambda ([a1%], local(a1%), declare1_qty (a1%, q%)), a%)
43 else declare1_qty (a%, q%), q%);
45 /* Maybe we want to work with constvalue in declare1_qty
46 * (instead of the indicator qty).
48 declare1_qty (a%, q%) :=
49 block (local (a%, q%),
50 if featurep (a%, 'dimensional) = false
51 then apply (declare, [a%, 'dimensional]), put (a%, q%, qty));
55 buildq ([zz%: zz%], lambda([yy%],
56 if units('zz%) # 1 and units(yy%) # 1 and units(yy%) # units('zz%)
57 then throw (oops ('zz%, yy%)))));
59 /* PROBABLY QTY SHOULD BE A SIMPLIFYING FUNCTION !! OH WELL. KEEP STUMBLING AHEAD. */
64 if featurep (e%, 'dimensional) then
65 if get (e%, qty) = false then 'qty(e%) else get (e%, qty)
67 else if unitop_p(e%) then first(e%)
68 else if featurep (op(e%), 'dimensional) then 'qty(e%)
69 else if op(e%) = 'qty or op(e%) = nounify('qty) then e%
70 else apply (op(e%), map (qty, args(e%))));
75 if symbolp (e%) and get (e%, 'constvalue) # false
76 then units (get (e%, 'constvalue))
77 elseif featurep (e%, 'dimensional) then
78 if get (e%, units) = false then 'units(e%) else get (e%, units)
80 else if mapatom (e%) then units (op (e%)) /* mapatom but not atom => subscripted */
81 else if unitop_p(e%) then second(e%)
82 else if featurep (op(e%), 'dimensional) then units(op(e%))
83 else if op(e%) = "*" or op(e%) = "/" then map (units, e%)
84 else if op(e%) = "^" then units (first (e%)) ^ second (e%)
85 else if op(e%) = 'sqrt then sqrt (units (e%))
86 else if op(e%) = "+" or op(e%) = "-" then apply ("+", unique (map (units, args (e%))))
87 else if member (op(e%), ["=", 'equal, "[", 'matrix, "{"]) then map (units, e%)
88 else if op(e%) = "." then apply ("*", map (units, args (e%)))
89 else if op(e%) = nounify(diff)
91 else if op(e%) = nounify(integrate)
92 then units_integrate(e%)
93 else if op(e%) = nounify(at)
95 else if nondimensionalp (e%) then 1
99 block ([yy: first(e%), xnpairs: by_twos (rest (args (e%)))],
100 lproduct (units(xn[1])^xn[2], xn, xnpairs),
103 /* by_twos might want to be a built-in function ... */
105 block ([n: length(l)],
107 then makelist ([l[2*k - 1], l[2*k]], k, 1, n/2)
108 else error ("by_twos: length of list must be a multiple of 2, not", n));
110 /* define lproduct as a macro to work around funny evaluation of makelist ...
111 * lproduct might want to be a built-in function ...
113 lproduct (ee, xx, l) ::=
114 buildq ([ee, xx, l], apply ("*", makelist (ee, xx, l)));
116 units_integrate(e%) :=
117 block ([ii: first(e%), xx: second(e%)],
118 units(ii) * units(xx));
120 units_at(e%) := units (first (e%));
122 d(e%) := dimension (units (e%));
127 dd%, lambda ([e], atom(e) and featurep (e, 'dimensional)),
128 ccn0%, constantp_not0,
129 ccn1%, constantp_not1,
130 /* dl = "dimensionless" */
131 dl%, lambda ([e], dimensions (e) = 1),
132 mm%, mult_expr_nontrivialconstfactorsp,
133 nd%, nondimensionalp,
134 pp%, lambda ([e], not atom(e) and op(e) = "+"),
135 qq%, lambda ([e], not atom(e) and (op(e) = "=" or listp(e) or matrixp(e) or setp(e))),
139 fooexpr : nd% / pp%; /* simplify this, to be used later */
141 /* tellsimpafter rules are applied in order of declaration; tellsimp, the other way around */
142 tellsimp (nd% `` dl%, apply_units_conversion (nd%, dl%));
143 tellsimp (uu% `` aa%, apply_units_conversion (uu%, aa%));
144 tellsimp (aa%*pp% `` bb% , expand (aa%*pp%) `` bb%); /* HOW ABOUT THIS ONE ?? */
145 tellsimp (pp% `` aa%, apply ("+", map (buildq ([aa%], lambda ([e], e `` aa%)), args (pp%))));
146 tellsimp (dd% `` aa%, (qty(dd%) ` units(dd%)) `` aa%);
147 tellsimp (aa%*dd% `` bb%, aa% * (qty(dd%) ` units(dd%)) `` bb%);
148 tellsimp (qq% `` aa%, map (lambda ([e], e `` aa%), qq%));
149 tellsimp (''fooexpr `` aa%, nd%/(pp% `` 1/aa%));
150 tellsimpafter (uu%^ccn1%, (qty(uu%)^ccn1%) ` simplify_units (units(uu%)^ccn1%));
151 tellsimpafter (aa% * nd% * uu%, aa% * multiply_with_units (nd%, uu%));
152 tellsimpafter (aa% + uu%, add_with_units (aa%, uu%));
154 tellsimpafter (aa% ` (xx% + ccn0%), (aa% - ccn0%)`xx%);
155 tellsimpafter (aa% ` mm%, (constant_factors(mm%) * aa%) ` everything_else(mm%));
156 tellsimpafter (aa% ` cc%, aa%*cc%);
157 tellsimpafter (uu% ` nd%, qty (uu%) ` (units (uu%) * nd%));
158 tellsimpafter (abs (aa% ` bb%), abs (aa%) ` bb%);
159 tellsimpafter (aa% ` abs (bb%), aa% ` bb%);
161 /* RULES FOR RELATIONAL EXPRESSIONS HERE !! NOTIFY GUNTER KOENIGSMANN */
162 matchdeclare (uu%, unitp, vv%, lambda ([e], unitp(e) and dimensions(e) = dimensions(uu%)));
163 tellsimpafter (uu% < vv%, qty (uu%) < qty (vv% `` units (uu%)));
164 tellsimpafter (uu% <= vv%, qty (uu%) <= qty (vv% `` units (uu%)));
165 tellsimpafter (uu% >= vv%, qty (uu%) >= qty (vv% `` units (uu%)));
166 tellsimpafter (uu% > vv%, qty (uu%) > qty (vv% `` units (uu%)));
167 /* SIMPLIFICATION OF DIMENSIONAL "=" CAUSES TROUBLE FOR EXISTING FUNCTIONALITY !!
168 * ALSO IT SEEMS REALLY HEAVYHANDED TO RULE OUT FOO`M = BAR`FOOT !!
169 tellsimpafter (uu% = vv%, qty (uu%) = qty (vv% `` units (uu%)));
170 tellsimpafter (uu% # vv%, qty (uu%) # qty (vv% `` units (uu%)));
172 tellsimpafter (equal (uu%, vv%), equal (qty (uu%), qty (vv% `` units (uu%))));
173 tellsimpafter (notequal (uu%, vv%), notequal (qty (uu%), qty (vv% `` units (uu%))));
174 /* I'M NOT COMFORTABLE WITH IMPLICIT CONVERSION OF 0 TO DIMENSIONAL !!
175 * I REMEMBER NOW THAT THE JUSTIFICATION IS THAT 0 IS AN ELEMENT OF EVERY DIMENSIONAL LINE !!
176 * IS THAT ENOUGH TO GO ON ??
177 tellsimpafter (uu% < 0, qty (uu%) < 0);
178 tellsimpafter (0 < uu%, 0 < qty (uu%));
179 tellsimpafter (uu% <= 0, qty (uu%) <= 0);
180 tellsimpafter (0 <= uu%, 0 <= qty (uu%));
181 tellsimpafter (uu% >= 0, qty (uu%) >= 0);
182 tellsimpafter (0 >= uu%, 0 >= qty (uu%));
183 tellsimpafter (uu% > 0, qty (uu%) > 0);
184 tellsimpafter (0 > uu%, 0 > qty (uu%));
185 tellsimpafter (uu% = 0, qty (uu%) = 0);
186 tellsimpafter (0 = uu%, 0 = qty (uu%));
187 tellsimpafter (uu% # 0, qty (uu%) # 0);
188 tellsimpafter (0 # uu%, 0 # qty (uu%));
189 tellsimpafter (equal (uu%, 0), equal (qty (uu%), 0));
190 tellsimpafter (equal (0, uu%), equal (0, qty (uu%)));
191 tellsimpafter (notequal (uu%, 0), notequal (qty (uu%), 0));
192 tellsimpafter (notequal (0, uu%), notequal (0, qty (uu%)));
197 matchdeclare (uu%, lambda ([e], unitp (e) and not unitop_p (e)));
198 defrule (rexpand_dimensional, uu%, qty (uu%) ` units (uu%));
199 expand_dimensional (e) := applyb1 (e, rexpand_dimensional);
201 /* expand_dimensional('qty(xyz)) => 'qty('qty(xyz) ` units(xyz)) without this rule ... sigh !!
202 * does the need for it go away if qty is a simplifying function ??
204 matchdeclare (uu%, unitop_p);
205 tellsimpafter ('qty (uu%), qty (uu%));
207 unitop_p (e%) := not atom(e%) and atom (op(e%)) and is (nounify (op(e%)) = nounify ("`"));
209 if atom(e%) then featurep (e%, 'dimensional)
210 else if unitop_p (e%) then true
211 else featurep (op(e%), 'dimensional);
213 constantp_not1 (x%) := constantp(x%) and x% # 1;
214 constantp_not0 (x%) := constantp(x%) and x% # 0;
215 nonconstantp (x%) := not constantp (x%);
216 mult_exprp (e%) := not atom(e%) and atom (op(e%)) and (nounify (op(e%)) = nounify ("*") or nounify (op(e%)) = nounify ("/"));
217 mult_expr_nontrivialconstfactorsp (e%) := mult_exprp (e%) and constant_factors (e%) # 1;
218 nondimensionalp (e%) :=
219 if atom (e%) then (not symbolp (e%) or not featurep (e%, 'dimensional))
220 elseif featurep (nounify (op (e%)), 'nondimensional) then true
222 (get (nounify (op (e%)), 'nondimensionalp),
223 if %% # false then apply (%%, args (e%)));
224 nondimensional_not1 (e%) := e% # 1 and nondimensionalp (e%);
225 nondimensional_not0 (e%) := e% # 0 and nondimensionalp (e%);
227 (map (nounify, [qty, sin, cos, tan, asin, acos, atan, atan2, log, exp, sinh, cosh, tanh, asinh, acosh, atanh]),
228 apply (declare, [%%, 'nondimensional]));
229 /* HMM. MAYBE THESE RULES FOR "^" AND "/" ARE TOO STRICT. LET IT STAND FOR NOW. */
230 put (nounify ("^"), lambda ([a%, b%], nondimensionalp (a%) and nondimensionalp (b%)), 'nondimensionalp);
231 put (nounify ("/"), lambda ([a%, b%], nondimensionalp (a%) and nondimensionalp (b%)), 'nondimensionalp);
232 put (nounify (sqrt), lambda ([a%], nondimensionalp (a%)), 'nondimensionalp);
233 put (nounify ("+"), lambda ([[a%]], every (nondimensionalp, a%)), 'nondimensionalp);
234 put (nounify ("*"), lambda ([[a%]], every (nondimensionalp, a%)), 'nondimensionalp);
235 put (nounify ("-"), lambda ([[a%]], every (nondimensionalp, a%)), 'nondimensionalp);
237 add_with_units (x%, u%) :=
241 /* identify terms with like units and add them up. */
242 block ([r%: [], a%: args(u%)],
244 block ([s%: qty(a%[1]), b%: []],
245 for i%:2 thru length(a%) do
246 if units (a%[i%]) = units (a%[1]) then
247 s%: s% + qty (a%[i%])
249 b%: cons (a%[i%], b%),
250 r%: cons (s%`units(a%[1]), r%),
252 if length (r%) = 1 then
255 cons (x%, apply ("+", r%)));
257 multiply_with_units(x%, u%) :=
259 (x%*qty(u%))`units(u%)
261 /* Work around effect of *AFTERFLAG in tellsimpafter rules:
262 * if units = 1, return foo instead of foo ` 1.
265 qq : x%*apply ("*", map (qty, args (u%))),
266 uu : simplify_units (apply ("*", map (units, args (u%)))),
267 if uu = 1 then qq else qq ` uu);
269 matchdeclare (a%, all);
270 defrule (runits1, 'units (a%), 1);
272 constant_factors (e%) := block ([L%, eqns%],
273 e% : apply1 (e%, runits1),
275 eqns%: map ("=", L%, L%*0+1),
278 everything_else(e%) := e%/constant_factors(e%);
280 simplify_units (e%) := block
281 ([L% : listofvars (e%), mycontext],
282 L% : makelist (x% > 0, x%, L%),
283 mycontext : newcontext (),
285 e% : expand (e%, 0, 0),
287 killcontext (mycontext),
290 known_unit_conversions : {};
292 get_unit_conversion (u) := assoc (u, known_unit_conversions);
294 declare_unit_conversion ([L]) := block ([S, ctxt],
295 if not every (equationp, L)
296 then error ("declare_unit_conversion: all arguments must be equations."),
297 setify (ratsimp (L)),
298 S : map (flatten_conversion_equation, %%),
300 context : ezunits_context,
301 apply (assume, map (lambda ([x], x > 0), maplist (rhs, S))),
302 known_unit_conversions : union (known_unit_conversions, S),
303 kill (conversions_to_base_units), /* simple, heavy-handed */
304 reset_convert_units (), /* simple, heavy-handed */
308 flatten_conversion_equation (e%) := block
309 ([l% : lhs (e%), r% : rhs (e%)],
310 if unitop_p (l%) and unitop_p (r%)
311 then qty (l%) * units (l%) = qty (r%) * units (r%)
312 elseif not unitop_p (l%) and not unitop_p (r%) then e%
313 else error ("declare_unit_conversion: both sides must be unit expressions or both not unit expressions; found: ", e%));
315 equationp (e) := not atom (e) and op (e) = "=";
317 compute_conversion_factor (u1%, u2%) :=
318 (if conversions_to_base_units = 'conversions_to_base_units
319 then compute_conversions_to_base_units (),
320 ev (simplify_units (u1% / u2%), conversions_to_base_units));
322 compute_conversions_to_base_units () :=
323 conversions_to_base_units : listify (compute_conversions_to_base_units0 (known_unit_conversions));
325 compute_conversions_to_base_units0 (conversion_eqns) :=
326 block ([derived_units, primitive_units, defined_so_far, undefined_so_far, definitions, definablep, now_definable],
328 derived_units : map (lhs, conversion_eqns),
329 primitive_units : setdifference (setify (listofvars (map (rhs, conversion_eqns))), derived_units),
330 defined_so_far : primitive_units,
331 undefined_so_far : conversion_eqns,
334 definablep : lambda ([e], every (lambda ([x], elementp (x, defined_so_far)), setify (listofvars (rhs (e))))),
335 now_definable : subset (undefined_so_far, definablep),
337 while length (now_definable) > 0
338 do block ([make_definition, new_definitions],
339 make_definition : lambda ([e], block ([L : listofvars (rhs (e))],
340 for x in definitions do if member (lhs (x), L) then e : subst (x, e)), e),
342 new_definitions : map (make_definition, now_definable),
343 definitions : union (definitions, new_definitions),
344 defined_so_far : union (defined_so_far, map (lhs, new_definitions)),
345 undefined_so_far : setdifference (undefined_so_far, now_definable),
346 now_definable : subset (undefined_so_far, definablep)),
348 if undefined_so_far # {} then print ("ezunits: hmm, these are still undefined:", map (lhs, undefined_so_far)),
351 apply_units_conversion (uu%, nd%) :=
353 /* Handle conversions of the form foo `` [u1, u2, u3].
354 * May want to ensure dimensions of u1, u2, u3 are all the same.
355 * Might change list to some other operator, e.g. noncommutative plus
356 * (which would have to be invented for the purpose).
358 then block ([q%, r%],
359 q% : convert_units [units (uu%), first (nd%)] (qty (uu%)),
360 r% : [(if rest (nd%) = [] then q% else floor (q%)) ` first (nd%)],
361 while rest (nd%) # []
363 (q% : convert_units [first (nd%), second (nd%)] (q% - floor (q%)),
365 r% : cons ((if rest (nd%) = [] then q% else floor (q%)) ` first (nd%), r%)),
368 else convert_units [units(uu%), nd%] (qty(uu%)) ` nd%;
370 /* kill the convert_units memoizing function (including all memoized values),
371 * then redefine it, and prep it with conversions for temperature units,
372 * which can't be computed by a simple multiplication.
373 * Hmm, I wonder if the temperature conversions should be declared in ezunits_functions.mac.
376 reset_convert_units () :=
377 (kill (convert_units),
379 convert_units [u1%, u2%] := block ([a%],
380 a% : compute_conversion_factor (u1%, u2%),
381 buildq ([a%], lambda ([x], a% * x))),
383 convert_units [degF, degC] : lambda ([x], 5/9*(x - 32)),
384 convert_units [degF, K] : lambda ([x], 27315/100 + 5/9*(x - 32)),
385 convert_units [degF, R] : lambda ([x], 45967/100 + x),
387 convert_units [degC, degF] : lambda ([x], 32 + 9/5*x),
388 convert_units [degC, R] : lambda ([x], 45967/100 + 32 + 9/5*x),
389 convert_units [degC, K] : lambda ([x], 27315/100 + x),
391 convert_units [K, degC] : lambda ([x], x - 27315/100),
392 convert_units [K, degF] : lambda ([x], 32 + 9/5*(x - 27315/100)),
394 convert_units [R, degC] : lambda ([x], 5/9*(x - 45967/100 - 32)),
395 convert_units [R, degF] : lambda ([x], x - 45967/100));
397 /* Dimensional analysis
400 declare_dimensions ([L%]) :=
402 then (declare_dimensions1 (L%[1], L%[2]), apply (declare_dimensions, rest (rest (L%))))
403 elseif length (L%) = 2
404 then (declare_dimensions1 (L%[1], L%[2]), 'done)
405 else error ("declare_dimensions: expected an even number of arguments.");
407 declare_dimensions1 (a%, b%) :=
409 then map (lambda ([a1%], declare_dimensions1 (a1%, b%)), a%)
411 then put (a%, b%, 'dimension)
412 else error ("declare_dimensions: second argument must be a symbol.");
414 remove_dimensions ([L%]) :=
415 (if not every (symbolp, L)
416 then error ("remove_dimensions: all arguments must be symbols."),
417 map (lambda ([a%], put (a%, false, 'dimension)), L%));
419 declare_fundamental_dimensions ([L]) :=
420 (if not every (symbolp, L)
421 then error ("declare_fundamental_dimensions: all arguments must be symbols."),
422 L : sublist (L, lambda ([a], not member (a, fundamental_dimensions))),
423 map (lambda ([a], assume (a > 0)), L),
424 fundamental_dimensions : append (fundamental_dimensions, L),
427 remove_fundamental_dimensions ([L]) :=
429 then apply (remove_fundamental_dimensions, fundamental_dimensions)
431 (fundamental_dimensions : sublist (fundamental_dimensions, lambda ([x], not member (x, L))),
432 map (lambda ([a], forget (a > 0)), L),
435 declare_fundamental_units ([L]) :=
436 (if not every (symbolp, L)
437 then error ("declare_fundamental_units: all arguments must be symbols."),
438 map (lambda ([x, y], put (x, y, 'dimension)), odds (L), evens (L)),
439 map (lambda ([x, y], put (y, x, 'unit)), odds (L), evens (L)));
441 remove_fundamental_units ([L]) :=
442 (if not every (symbolp, L)
443 then error ("remove_fundamental_units: all arguments must be symbols."),
444 map (lambda ([x], put (get (x, 'dimension), false, 'unit)), L),
445 map (lambda ([x], put (x, false, 'dimension)), L));
447 fundamental_dimensions : [];
449 L : [[m, length], [kg, mass], [s, time], [A, current], [K, temperature], [mol, quantity], [cd, luminous_intensity]],
450 apply (declare_fundamental_dimensions, map (second, L)),
451 apply (declare_fundamental_units, flatten (L)));
453 /* I GUESS RULES FOR DIMENSIONS COULD RECOGNIZE EQUATIONS, DERIVATIVES, AND INTEGRALS
457 (aa%, lambda ([e%], numberp (e%) or (atom (e%) and not symbolp (e%))),
458 bb%, lambda ([e%], symbolp (e%) and member (e%, fundamental_dimensions)),
459 cc%, lambda ([e%], symbolp (e%) and get (e%, 'units) # false),
460 dd%, lambda ([e%], symbolp (e%) and get (e%, 'constvalue) # false),
461 ee%, lambda ([e%], symbolp (e%) and get (e%, 'dimension) # false),
462 ee1%, lambda ([e%], symbolp (e%) and ?mget (e%, 'numer) # false),
463 ff%, lambda ([e%], symbolp (e%)),
464 gg%, lambda ([e%], not atom (e%) and op (e%) = "`"),
465 hh%, lambda ([e%], not atom (e%) and op (e%) = "^"),
466 ii%, lambda ([e%], not mapatom (e%) and member (op (e%), ["+", "-", "*", "/"])));
469 defrule (raa, 'dimensions (aa%), 1);
470 defrule (rbb, 'dimensions (bb%), bb%);
471 defrule (rcc, 'dimensions (cc%), dimensions (get (cc%, 'units)));
472 defrule (rdd, 'dimensions (dd%), dimensions (get (dd%, 'constvalue)));
473 defrule (ree, 'dimensions (ee%), get (ee%, 'dimension));
474 defrule (ree1, 'dimensions (ee1%), dimensions (?mget (ee1%, 'numer)));
479 ([ff%2 : ev (ff%, args (known_unit_conversions), infeval)],
480 if ff%2 # ff% then dimensions (ff%2) else 'dimensions (ff%)));
481 defrule (rgg, 'dimensions (gg%), (constvalue (first (gg%)), if unitp (%%) then dimensions (second (%%)) else 1) * dimensions (second (gg%)));
482 matchdeclare (aa%, all, bb%, constantp_not1);
483 defrule (rhh, 'dimensions (aa%^bb%), dimensions (aa%) ^ bb%);
484 defrule (rii, 'dimensions (ii%), map (dimensions, ii%));
485 /* ??? defrule (rjj, 'dimensions (sqrt (aa%)), sqrt (dimensions (aa%))); */
489 /* Binding *NOUNSFLAG* is pretty terrible,
490 * but it's the only way to disable ev(..., nouns);
491 * ev(..., nouns=false) has no effect!
493 block ([?\*nounsflag\*: false],
496 dimensions1 (expr) :=
497 if not atom (expr) and (setp (expr) or listp (expr) or matrixp (expr) or op (expr) = "=")
498 then map (dimensions, expr)
499 else apply (apply1, ['dimensions (expr), raa, rbb, rcc, rdd, ree, ree1, rff, rgg, rhh, rii]);
501 /* Adapted from dimensions_as_list in share/physics/dimension.mac.
502 * Thanks to Barton Willis.
504 /* FOLLOWING DEFINITION HANDLES DIMENSION NOUNS INCORRECTLY
505 * NEED TO REVISIT THIS
507 dimensions_as_list (e) :=
509 then map ('dimensions_as_list, e)
512 if polynomialp (e, fundamental_dimensions, lambda ([x], numberp (x)), lambda ([x], integerp (x)))
513 then makelist (hipow (e, d), d, fundamental_dimensions)
514 else 'dimensions_as_list (e));
516 fundamental_units ([L]) :=
520 (lambda ([x], (get (x, 'unit), if %% = false then 'unit (x) else %%)),
521 fundamental_dimensions)
524 then map ('fundamental_units, L [1])
526 d : dimensions_as_list (L [1]),
528 then apply ("*", map ("^", fundamental_units (), d))
529 else 'fundamental_units (L [1]));
531 /* Adapted from dimensionless in share/physics/dimension.mac.
532 * Thanks to Barton Willis.
537 then 'dimensionless (e)
541 (lambda ([s], xreduce ("*", map("^", e, first (transpose (s))))),
542 nullspace (transpose (funmake ('matrix, map ('dimensions_as_list, e))))));
544 /* Adapted from natural_unit in share/physics/dimension.mac.
545 * Thanks to Barton Willis.
548 natural_unit (d, e) :=
553 then return ('natural_unit (d, e)),
555 d : dimensions_as_list (d),
556 s : map ('dimensions_as_list, e),
557 vars : makelist (?gensym(), k, 1, length (e)),
560 ([linsolve_params : true,
562 globalsolve : false],
563 s : linsolve (s . vars - d, vars)),
565 s : xreduce ("*", map ("^", e, map ('rhs, s))),
566 s : subst (map ("=", %rnum_list, makelist (0, i, 1, length (%rnum_list))), s),
567 map (lambda ([x], s * x), cons (1, dimensionless (e))));
569 /* A couple of convenience functions.
573 (if conversions_to_base_units = 'conversions_to_base_units
574 then compute_conversions_to_base_units (),
575 listify (map (lhs, conversions_to_base_units)));
578 sort (append (fundamental_units (), derived_units ()));
580 display_known_unit_conversions () :=
581 for x in known_unit_conversions do display (x);
583 /* Rules to unit-ify 'diff and 'integrate expressions.
588 matchdeclare ([uu%, vv%, aa%, bb%], unitop_p);
589 matchdeclare (nn%, integerp);
591 tellsimp ('diff (uu%, vv%, nn%), 'diff (qty (uu%), qty (vv%), nn%) ` (units (uu%) / (units (vv%))^nn%));
593 tellsimp ('integrate (uu%, vv%), 'integrate (qty (uu%), qty (vv%)) ` (units (uu%) * units (vv%)));
596 ('integrate (uu%, vv%, aa%, bb%),
597 if units (aa%) = units (vv%) and units (bb%) = units (vv%)
598 then 'integrate (qty (uu%), qty (vv%), qty (aa%), qty (bb%)) ` (units (uu%) * units (vv%))
599 else 'integrate (qty (uu%), qty (vv%), qty (aa% `` units (vv%)), qty (bb% `` units (vv%))) ` (units (uu%) * units (vv%)));
603 /* Rules to handle arbitrary Maxima expressions dimensionally
604 * by replacing expressions a`b with a*gensymized(b).
605 * When a = 0, replace it with a gensym as well, to prevent b from getting lost.
608 matchdeclare (uu%, unitop_p);
609 defrule (rununitify, uu%, maybe_subst_qty (uu%) * subst_units (uu%));
611 maybe_subst_qty (u) :=
612 if qty (u) = 0 or qty (u) = 0.0 or qty (u) = 0b0
613 then (if qtys_gensyms [units (u)] = false
614 then (qtys_gensyms [units (u)] : gensym ("zero"),
615 assume (equal (qtys_gensyms [units (u)], 0))),
616 qtys_gensyms [units (u)])
619 subst_units (u) := block ([v : listofvars (units (u)), substs],
621 (if units_gensyms [v1] = false then units_gensyms [v1] : gensym (),
622 assume (units_gensyms [v1] > 0)),
623 substs : makelist (v1 = units_gensyms [v1], v1, v),
624 subst (substs, units (u)));
626 dimensionally (e%) ::=
627 buildq ([e%_op : op (e%), e%_args : args (e%), context_name],
629 ([qtys_gensyms, units_gensyms, unitless_args, reunitification_eqs, reunitified, rezeroified],
630 context_name : supcontext (),
631 context : context_name,
632 qtys_gensyms : make_array ('hashed),
633 units_gensyms : make_array ('hashed),
634 unitless_args : applyb1 (e%_args, rununitify),
635 reunitification_eqs : map (lambda ([e], lhs (e) = 1 ` rhs (e)), inverse_gensym_map (units_gensyms)),
636 reunitified : subst (reunitification_eqs, apply (e%_op, unitless_args)),
637 rezeroified : subst (makelist (x = 0, x, listarray (qtys_gensyms)), reunitified),
638 killcontext (context_name),
641 gensym_map (h) := map ("=", rest (arrayinfo (h), 2), listarray (h));
642 inverse_gensym_map (h) := map ("=", listarray (h), rest (arrayinfo (h), 2));
644 /* Nondimensional to pure number conversion for trig functions, exp, and log.
645 * Conversion is applied only for explicit "`" expressions;
646 * previous implementation in terms of dimensions(e) could be time-consuming.
650 declare_unit_conversion (degree = %pi/180);
651 matchdeclare (uu, lambda ([e], unitop_p(e) and dimensions(units(e)) = 1));
652 map (lambda ([f], apply (tellsimp, [apply (f, [uu]), apply (f, [uu `` 1])])), '[sin, cos, tan, csc, sec, cot, atan]);
654 tellsimp (%e^uu, %e^(uu `` 1));
655 tellsimp (log (uu), log (uu `` 1));
657 /* Convert atan2 arguments to pure numbers when arguments have same dimensions.
658 * Determining dimensions are the same is potentially time-consuming.
661 matchdeclare (aa, unitp, bb, lambda ([e], dimensions (e) = dimensions (aa)));
662 tellsimp (atan2 (aa, bb), atan2 (qty (aa), qty (bb `` units (aa))));
664 /* Subscript applied to "`" expression => exchange "`" with subscript
665 * Note that this applies only to literal "`" expressions.
668 matchdeclare ([aa, bb, cc], all);
669 tellsimpafter ((aa ` bb) [cc], aa [cc] ` bb);
671 /* "@" applied to "`" expression => exchange "`" with "@"
672 * Note that this applies only to literal "`" expressions.
675 matchdeclare ([aa, bb, cc], all);
676 /* substitute into "@" expression because "@" quotes its second argument */
677 tellsimpafter ((aa ` bb) @ cc, buildq ([aa, bb, cc], (aa @ cc) ` bb));
679 /* realpart/imagpart applied to "`" expression => exchange "`" with realpart/imagpart
680 * Note that this applies only to literal "`" expressions.
681 * Note further that this applies only to realpart/imagpart nouns.
684 matchdeclare ([aa, bb], all);
685 tellsimpafter ('realpart (aa ` bb), 'realpart (aa) ` bb);
686 tellsimpafter ('imagpart (aa ` bb), 'imagpart (aa) ` bb);