3 ***************************************************************
4 * Unit Conversion and Definition Package *
7 * This package gives one the ability to convert *
8 * between units, define globally all units of a *
9 * given property (mass, for example) in terms of *
10 * a base unit, and various other abilities. *
16 * ------------------------------------------ *
17 * Programmed by Cliff Yapp with invaluable help from *
18 * Barton Willis, Robert Dodier and the Maxima list *
19 ***************************************************************
24 The variables current_let_rule_package and letrat are set on a
25 per function level in order to cause the minimum of disruption
26 to the default system behavior.
30 0.50 - convert now works on derived units, added usersetunits
31 option to allow user to change default behavior. New
32 command uforget will respect usersetunits. Other misc
33 changes. Still in flux, but it's coming together.
34 0.40 - Display of units has been fixed, with optional settings
35 to allow grouping over the plus operator as well. A
36 variety of improvements, not all of them fully functional
38 0.11 - As of this version, setunits and convert should be
39 fully operational, although definitions are still
41 0.10 - total rewrite using more advanced rule based
42 techniques for handling units has begun.
43 0.03 - fixed unitfactor so it would work on standard unit
44 names even if setunits had already defined them. Added
45 command findnonpivotunit which will return if defined
46 the non pivot unit corresponding to a given conversion
47 factor times a given pivot unit.
48 0.02 - added ability to control how many metric prefixes are
49 used to generate the meta list, and a killunits function
50 to undo what setunits does.
51 0.01 - first development version - metalist generateable,
52 unitfactor and setunits operational. Only basic Mass
53 and Time arrays present. Metric prefixes entered.
54 ============================================================================*/
56 ttyoff: nolabels, true$
61 metricprefull : [[yotta,10^24,4],[zetta,10^21,4],[exa,10^18,4],[peta,10^15,3],
62 [tera,10^12,3],[giga,10^9,2],[mega,10^6,2],[kilo,10^3,1],
63 [hecto,10^2,2],[deka,10^1,2],[deci,10^-1,2],[centi,10^-2,1],
64 [milli,10^-3,1],[micro,10^-6,2],[nano,10^-9,2],[pico,10^-12,3],
65 [femto,10^-15,3],[atto,10^-18,4],[zepto,10^-21,4],
67 metricpre : [[Y,10^24,4],[Z,10^21,4],[E,10^18,4],[P,10^15,3],[T,10^12,3],
68 [G,10^9,2],[M,10^6,2],[k,10^3,1],[h,10^2,2],[da,10^1,2],
69 [d,10^-1,2],[c,10^-2,1],[%%m,10^-3,1],[%mu,10^-6,2],[n,10^-9,2],
70 [p,10^-12,3],[f,10^-15,3],[a,10^-18,4],[z,10^-21,4],
73 /*============================ Unit Information ============================*/
75 /* Seven Mutually Independent Base Dimensions
77 The globalbaseunitlisting maintains a list of all dimensions considered to be
78 Base Dimensions. This means all other units result from combinations of
79 these units. The list associated with each dimension is named %dimensionlisting
80 and is structured as follows:
82 - The first unit in each list is called the pivot unit. All other units
83 for this dimension, and all derived units containing this dimension, will be
84 defined in terms of this unit. The pivot unit is also the standard SI
85 unit in the case of Base Dimensions
87 - Each entry in the top level list is itself a list, containing as its
88 first entry a list of the names to be associated with this unit. The
89 second position contains the conversion factor between the unit in
90 question and the pivot unit. In the case of a pivot unit, this value
91 is 1. The third position is a flag which tells Maxima whether this
92 unit is metric or not. Metric units can be automatically generated
93 using the base name and the metric prefix lists, which contain both the
94 prefix and the power of ten associated with it. These definitions will
95 make the following assumptions:
96 - The first unit name is the abbreviation of the unit's full name,
97 and will append the metric abbreviation prefix. In the case of a
98 % at the beginning of the name, this is removed before appending
100 - All other unit names in the list are assumed to be full names, and
101 will have the full metric prefix appended to them.
105 globalbaseunitlisting : [%length,%mass,%time,%current,%temperature,
106 %amount_of_substance,%luminous_intensity];
109 %lengthlisting : [[[m,meter,meters],1,m],[[%in,inch,inches],0.0254,e]];
112 %masslisting : [[[kg,kilogram,kilograms],1,0],[[g,gram,grams],1/1000,m],
113 [[gr,grain,grains],6.479891*10^-5,e],[[slug,slugs],1.459390,e]];
116 %timelisting : [[[s,second,seconds],1,m],[[%min,minute,minutes],60,e]];
119 %currentlisting : [[[A,ampere,amperes],1,m]];
121 /*Thermodynamic Temperature*/
122 %temperaturelisting : [[[K,kelvin,kelvins],1,m]];
124 /*Amount of Substance*/
125 %amount_of_substancelisting : [[[mol,mole,moles],1,m]];
127 /*Luminous Intensity*/
128 %luminous_intensitylisting: [[[cd,candela],1,m],[[lm,lumen],1,m]];
133 The structure of these lists is slightly different from the Base
134 Dimensions. The first entry in each list describes the Derived Dimension
135 in terms of the relevant Base Dimensions, using the pivot unit of the
136 Base Dimensions. It is a list of two entries, the definition in abbreviated
137 units and fullnames. After that, the structure is the same as Base Dimension
138 lists. globalderivedunitlisting lists all Derived Dimensions known to
139 Maxima. A second global listing, called globalderivedunitlisting_use, is
140 used to define the search order for substitution in unit processing.*/
142 globalderivedunitlisting:[%volume,%frequency,%force,%pressure,
143 %energy,%power,%electric_charge,%electric_potential_difference,
144 %capacitance,%resistance,%conductance,%magnetic_flux,
145 %magnetic_flux_density,%inductance,%illuminance,%absorbed_dose,
146 %catalytic_activity];
149 %volumelisting : [m^3,[[L,liter,liters],0.001,m]];
152 /* Derived Units with Special SI Names and Symbols
154 These are similar in structure to the Derived Unit lists above, but
155 because they have a unique name associated with them in SI the policy
156 is to have the first entry after the base dimension definition be the
157 SI unit. Overall structure is the same. These are listed in the same
158 globalderivedunitlisting as the previous Derived Units.
160 Note - depending on circumstances, it is sometimes of interest to
161 have energy reported as work or heat in dimensional analysis.
162 Need to work on this.*/
165 %frequencylisting : [1/s,[[Hz,hertz],1,m]];
168 %forcelisting : [kg*m/s^2,[[N,%Newton,Newtons],1,m],
169 [[dyn,dyne,dynes],1*10^-5,d]];
172 %pressurelisting : [kg/(m*s^2),[[Pa,pascal,pascals],1,m],[[torr],133.32239,m]];
174 /*Energy, Work, Quantity of Heat */
175 %energylisting : [kg*m^2/s^2,[[J,joule,joules],1,m],
176 [[eV,electron_volt,electron_volts],1.6021765*10^-19,m]];
179 %powerlisting : [kg*m^2/s^3,[[W,watt,watts],1,m]];
181 /*Electric Charge, Quantity of Electricity*/
182 %electric_chargelisting : [s*A,[[C,coulomb,coulombs],1,m]];
184 /*Electric Potential Difference, Electromotive Force */
185 %electric_potential_differencelisting : [kg*m^2/(s^3*A),[[V,volt,volts],1,m]];
188 %capacitancelisting : [s^4*A^2/(m^2*kg),[[F,farad,farads],1,m]];
190 /*Electric Resistance*/
191 %resistancelisting : [m^2*kg/(s^3*A^2),[[Ohm,ohm,ohms],1,m]];
193 /*Electric Conductance*/
194 %conductancelisting : [s^3*A^2/(m^2*kg),[[S,siemens],1,m]];
197 %magnetic_fluxlisting : [m^2*kg/(s^2*A),[[Wb,weber,webers],1,m]];
199 /*Magnetic Flux Density*/
200 %magnetic_flux_densitylisting : [kg/(s^2*A),[[T,tesla],1,m]];
203 %inductancelisting : [m^2*kg/(s^2*A^2),[[H,henry,henries],1,m]];
206 %illuminancelisting : [cd/m^2,[[lx,lux],1,m]];
208 /*Absorbed Dose, Specific Energy (imparted), Kerma */
209 %absorbed_doselisting : [m^2/s^2,[[Gy,gray,grays],1,m]];
211 /*Catalytic Activity */
212 %catalytic_activitylisting : [mol/s,[[kat,katal,katals],1,m]];
215 /*===================== Package Functions ====================*/
217 /*The default behavior of let doesn't work for the purposes of this
218 package, so we define unitlet here using Jeff Golden's trick of
219 having apply evaluate the inputs. (Thanks to Barton Willis
220 for an elegant solution.) A source level fix to let is also
221 possible but causes a default behavior change in let:
224 < (setq replacement (cdr l))
225 > (setq replacement (list (meval(cadr l))))
227 if it is ever applied we can convert back to the default let, but
228 it shouldn't matter one way or the other*/
230 unitlet(expression,ruleset) := apply('let,[expression,ruleset]);
232 /* A similar device is needed for a looping application of letsimp*/
233 unitletsimp(expression,ruleset) := apply('letsimp,[expression,ruleset]);
235 /* Something similar is also needed to kill rulesets */
236 killruleset(ruleset) := apply('kill,[ruleset]);
238 /* Tool to check for duplicates in a list - returns false if found,
241 checkforuniqueness(list1) := block([i,j,return],
243 for i : 1 thru length(list1) do (
244 for j : i+1 thru length(list1) do (
245 if ?equal(list1[i],list1[j]) then (
250 /* This function is used to remove up to two preceding % from
251 a name when doing the concat operation (thanks to Dr. Willis)*/
252 unitconcat(a,b) := concat(a,(?stripdollar(?stripdollar(b))));
254 /* Add all desired metric dimensions into %dimensionlisting_use lists.
255 %dimensionlisting_use lists are the one actually used in functions. This allows
256 the program to regenerate a new metric list from "original" package lists
257 in order to avoid multiple definitions of metric prefix dimensions. An
258 exception is made for kg, kilogram, and kilograms because they are defined
259 already as the pivot unit for mass. This is a consequence of the nature of
262 metricexpandall(%limit) := block([i,j,k,l,unitlistname,
263 unitlistusename,tempentry],
264 modedeclare([i,j,k,l], fixnum),
265 for i : 1 thru length(globalbaseunitlisting) do (
266 unitlistname : concat(globalbaseunitlisting[i],listing),
267 unitlistusename : concat(globalbaseunitlisting[i],listing_use),
268 unitlistusename :: ev(unitlistname),
269 for j : 1 thru length(ev(unitlistname)) do (
270 if (?equal(unitlistname[j][3],m)) then (
271 for k : 1 thru length(metricpre) do (
272 tempentry : [[0],0,expanded],
273 if is(metricpre[k][3] <= %limit) then (
274 if (not(?equal(metricpre[k][1],'k) and
275 ?equal(unitlistname[j][1][1],g))) then (
276 tempentry[1][1] : unitconcat(metricpre[k][1],unitlistname[j][1][1]),
277 tempentry[2] : metricpre[k][2]*unitlistname[j][2])),
278 if is(metricprefull[k][3] <= %limit) then (
279 for l : 2 thru length(unitlistname[j][1]) do(
280 if (not(?equal(metricprefull[k][1],kilo) and
281 (?equal(unitlistname[j][1][l],gram) or
282 ?equal(unitlistname[j][1][l],grams)))) then (
283 tempentry[1] : append(tempentry[1],[unitconcat(metricprefull[k][1],
284 unitlistname[j][1][l])])
286 if not(?equal(tempentry[1][1],0)) then (
287 unitlistusename :: append(ev(unitlistusename),[tempentry])))))),
289 for i : 1 thru length(globalderivedunitlisting) do (
290 unitlistname : concat(globalderivedunitlisting[i],listing),
291 unitlistusename : concat(globalderivedunitlisting[i],listing_use),
292 unitlistusename :: ev(unitlistname),
293 for j : 2 thru length(ev(unitlistname)) do (
294 if (?equal(unitlistname[j][3],m)) then (
295 for k : 1 thru length(metricpre) do (
296 tempentry : [[0],0,expanded],
297 if is(metricpre[k][3] <= %limit) then (
298 tempentry[1][1] : unitconcat(metricpre[k][1],unitlistname[j][1][1]),
299 tempentry[2] : metricpre[k][2]*unitlistname[j][2]),
300 if is(metricprefull[k][3] <= %limit) then (
301 for l : 2 thru length(unitlistname[j][1]) do(
302 tempentry[1] : append(tempentry[1],[unitconcat(metricprefull[k][1],
303 unitlistname[j][1][l])])),
304 unitlistusename :: append(ev(unitlistusename),[tempentry]))))))
307 /* This function creates a list of all defined units */
308 makeallunitslist() := block([i,j,k,b,allunitslisttemp],
309 allunitslisttemp : [],
310 modedeclare([i,j,k], fixnum),
311 for i : 1 thru length(globalbaseunitlisting) do (
312 unitlistname : concat(globalbaseunitlisting[i],listing_use),
313 for j : 1 thru length(ev(unitlistname)) do (
314 for k : 1 thru length(unitlistname[j][1]) do (
315 allunitslisttemp : append([unitlistname[j][1][k]],allunitslisttemp)
318 /* j needs to start at the second entry for Derived Dimension Arrays */
319 for i : 1 thru length(globalderivedunitlisting_use) do (
320 unitlistname : concat(globalderivedunitlisting_use[i],listing_use),
321 for j : 2 thru length(ev(unitlistname)) do (
322 for k : 1 thru length(unitlistname[j][1]) do (
323 allunitslisttemp : append([unitlistname[j][1][k]],allunitslisttemp)
325 reverse(sort(allunitslisttemp))
329 /* This function creates a ruleset converting all dimensions to one */
330 makedimentoonerules() := block([i,j,k,b,allunitslisttemp],
331 allunitslisttemp : [],
332 modedeclare([i,j,k], fixnum),
333 for i : 1 thru length(globalbaseunitlisting) do (
334 unitlet([globalbaseunitlisting[i],1],dimentoone)
337 /* j needs to start at the second entry for Derived Dimension Arrays */
338 for i : 1 thru length(globalderivedunitlisting_use) do (
339 unitlet([globalderivedunitlisting[i],1],dimentoone)
342 /* This command is used to define the display order of units, in order to
343 ensure that units appear at the end of an expression when possible. Uses
344 the array created by makeallunitslist */
345 unitgreat(list1) := apply('ordergreat, list1);
348 /* Create unitsimplify ruleset
350 This ruleset is used to simplify unit processing. When a unit is processed
351 a simplification of that unit using todimension will return the
352 dimension it represents. This enables subsequent operations on the unit to
353 know what list to look at for conversions and at the same time simplifies
354 the programming of dimensional analysis functionality. The addunit command
355 will automatically add on information to this list in order to maintain the
356 validity of the operation. The metricexpandall function must be called
357 before this command is run.*/
359 maketodimensionruleset() := block([i,j,k,b,unitlistname],
360 modedeclare([i,j,k], fixnum),
361 for i : 1 thru length(globalbaseunitlisting) do (
362 unitlistname : concat(globalbaseunitlisting[i],listing_use),
363 for j : 1 thru length(ev(unitlistname)) do (
364 for k : 1 thru length(unitlistname[j][1]) do (
365 unitlet([unitlistname[j][1][k],globalbaseunitlisting[i]],
369 /* j needs to start at the second entry for Derived Dimension Arrays */
370 for i : 1 thru length(globalderivedunitlisting_use) do (
371 unitlistname : concat(globalderivedunitlisting_use[i],listing_use),
372 for j : 2 thru length(ev(unitlistname)) do (
373 for k : 1 thru length(unitlistname[j][1]) do (
374 unitlet([unitlistname[j][1][k],globalderivedunitlisting_use[i]],
379 /* Determines if an input is a Unit. Note that units must be
380 atoms for this test to work, so this package will adopt a
381 policy of unit names being atoms only. */
383 isunit(candidate) := block([letrat:true,result],
384 if not(?atom(candidate)) then (
385 /*Error message if argument to isunit isn't an atom*/
386 (error ("Argument to isunit was not an atom. Maxima requires units to be atoms. Erroneous input: ",candidate))
388 /* This test simply checks if the candidate will return
389 a dimension under the previously defined rules. If it
390 does, then it is a unit. If it does not, then it will
391 not be impacted by those simplification rules and the
392 condition of equality will be satisfied. In such a
393 case the result is that candidate is not a unit. */
394 if ?equal(candidate,letsimp(candidate,todimension)) then (
395 /*Error if input isn't a unit - mentions the addunit command.*/
396 (error ("Input is not a unit. To define your own unit, use the addunit command."),candidate))
400 /* Determines if a unit is a Base Dimension */
401 isbase(unit) := block([letrat:true,result,dimension],
402 /*Uses the simplify to dimension ruleset and checks if the dimension
403 is present in the base list.*/
404 if not(lfreeof(globalbaseunitlisting,
405 letsimp(unit,todimension))) then (
408 if isunit(unit) then (result : false)),
411 /* Returns the definition of a unit in terms of the seven base dimensions.*/
412 base(unit):= block([letrat:true,result,i,j,listname],
413 modedeclare([i,j], fixnum),
414 if not(isbase(unit)) then (
415 listname:concat(letsimp(unit,todimension),listing_use),
416 for i:1 thru length(ev(listname)) do (
417 for j:1 thru length(listname[i][1]) do (
418 if (?equal(unit,listname[i][1][j])) then (
419 result : listname[1]*listname[i][2] )
425 /* Returns the mks base units for a dimension.*/
426 dimentomks(dimension):= block([letrat:true,result,listname],
427 listname:concat(dimension,listing_use),
428 if not(lfreeof(globalbaseunitlisting,dimension)) then (
429 result : listname[1][1])
431 if not(lfreeof(globalderivedunitlisting,dimension)) then (
432 result : listname[1])
434 error ("dimension not found"))),
437 /* This utility makes a list of dimensions from a list of units. It is
438 used to validate input for setunits, and also in the derived dimension
440 makedimenlist(unitlist) := block([current_let_rule_package : todimension,
442 return1 : map('letsimp,unitlist));
444 /* This defines rules for converting all base units to MKS. This is only
445 updated when a new base unit is added - otherwise these rules are constant. */
446 makebasetoMKSrules():= block([letrat:true,i,j,a,b,fakerule],
447 killruleset(basetoMKSrules),
448 unitlet([fakerule,fakerule],basetoMKSrules),
449 for a : 1 thru length(globalbaseunitlisting) do (
450 unitlistname : concat(globalbaseunitlisting[a],listing_use),
451 for i : 2 thru length(ev(unitlistname)) do (
452 for j : 1 thru length(unitlistname[i][1]) do (
453 unitlet([unitlistname[i][1][j], unitlistname[1][1][1]*unitlistname[i][2]],basetoMKSrules)
457 /* This routine handles the creation of rulesets used for converting from
458 MKS base units to user selected base units. */
459 makeMKStobaserules(currentunitlist,unitlistname,baseunitrules):=
460 block([letrat:true,i,j,a,b,fakerule],
461 killruleset(baseunitrules),
462 unitlet([fakerule,fakerule],baseunitrules),
463 for i : 1 thru length(ev(unitlistname)) do (
464 if lfreeof(unitlistname[i][1],currentunitlist[1]) then (
465 for j : 1 thru length(unitlistname[i][1]) do (
466 if ?equal(abbrevsimp,1) then (
467 unitlet([unitlistname[i][1][j],
468 unitlistname[i][2]/currentunitlist[2]*
469 first(currentunitlist[1])],baseunitrules)
471 if ?equal(abbrevsimp,2) then (
472 unitlet([unitlistname[i][1][j],
473 unitlistname[i][2]/currentunitlist[2]*
474 last(currentunitlist[1])],baseunitrules)
476 a : length(unitlistname[i][1]),
477 b : length(currentlistname[1]),
479 if (?equal(a,b) or ?equal(j,1)) then (
480 unitlet([unitlistname[i][1][j],
481 unitlistname[i][2]/currentunitlist[2]*
482 currentunitlist[1][j]],baseunitrules)
484 unitlet([unitlistname[i][1][j],
485 unitlistname[i][2]/currentunitlist[2]*
486 last(currentunitlist[1])],baseunitrules)
492 /* This defines rules for converting all derived units to MKS. This is only
493 updated when a new derived unit is added - otherwise these rules are constant. */
494 makederivedtoMKSrules():= block([letrat:true,i,j,a,b,fakerule],
495 killruleset(derivedtoMKSrules),
496 unitlet([fakerule,fakerule],derivedtoMKSrules),
497 for a : 1 thru length(globalderivedunitlisting_use) do (
498 unitlistname : concat(globalderivedunitlisting_use[a],listing_use),
499 for i : 2 thru length(ev(unitlistname)) do (
500 for j : 1 thru length(unitlistname[i][1]) do (
501 unitlet([unitlistname[i][1][j], base(unitlistname[i][1][j])],derivedtoMKSrules)
506 /* This routine takes a list of definitions for a derived unit, and makes a
507 ruleset for converting from MKS to the derived dimension.*/
508 makeMKStoderivedrules(currentunit,unitlistname,derivedunitrules):=
509 block([letrat:true,i,j,fakerule],
510 killruleset(derivedunitrules),
511 unitlet([fakerule,fakerule],derivedunitrules),
512 if lfreeof(dontuselist,letsimp(currentunit,todimension)) then (
513 for i : 2 thru length(ev(unitlistname)) do (
514 if not(lfreeof(unitlistname[i][1],currentunit)) then (
515 unitlet([unitlistname[1],unitlistname[i][1][1]/unitlistname[i][2]],derivedunitrules)
518 /* Sets rad, deg, Rad, Deg, RAD, and DEG to the appropriate values to do
519 what the user expects. Maxima normally calculates in radians, hence rad is 1.*/
528 /* This command is used to do final global simplification of units */
529 processunits(expression) := block([letrat:true,unitrules,rattemp,result1],
531 if ?equal(unitverbose,off) then (ratprint:false),
532 if ?equal(unitverbose,on) then (ratprint:true),
533 unitformatresults:true,
534 if ?equal(currentdoeval,true) then (
535 result1 : letsimp(expression,derivedtoMKSrules),
536 result1 : letsimp(result1,basetoMKSrules),
537 for i : 1 thru length(globalderivedunitlisting_use) do (
538 unitrules : concat(globalderivedunitlisting_use[i],'rules),
539 result1 : unitletsimp(result1,unitrules)
541 result1 : letsimp(result1,%lengthrules),
542 result1 : letsimp(result1,%massrules),
543 result1 : letsimp(result1,%timerules),
544 result1 : letsimp(result1,%currentrules),
545 result1 : letsimp(result1,%temperaturerules),
546 result1 : letsimp(result1,%amount_of_substancerules),
547 result1 : letsimp(result1,%luminous_intensityrules)
549 result1 : expression),
550 currentdoeval : true,
554 /* This function is used to reset variables for the next calculation */
555 resetunitvars(args) := block(unitformatresults:false,args);
557 /*====================== User Functions ======================*/
559 /* Returns the Maxima abbreviation used for fullunitname */
560 showabbr(fullunitname):= block([letrat:true,i,j,unittype,unitlistname,
561 currentunitlist,flag,return1],
562 isunit(fullunitname),
563 unittype : letsimp(fullunitname,todimension),
564 unitlistname : concat(unittype,listing_use),
566 if not(isbase(fullunitname)) then (i : i+1),
567 while ?equal(flag,0) do(
569 if not(lfreeof(unitlistname[i][1],fullunitname)) then (
570 return1 : unitlistname[i][1][1],
572 if ?equal(i, length(ev(unitlistname))) then (flag : 1)
574 currentdoeval : false, /* This tells processunits not to simplify
575 for this operation only */
579 /* Returns all the full names of the unit abbr */
580 showfullname(abbr):= block([letrat:true,i,j,unittype,unitlistname,
581 currentunitlist,flag,return1],
583 unittype : letsimp(abbr,todimension),
584 unitlistname : concat(unittype,listing_use),
586 if not(isbase(abbr)) then (i : i+1),
587 while ?equal(flag,0) do(
589 if not(lfreeof(unitlistname[i][1],abbr)) then (
590 return1 : rest(unitlistname[i][1],1),
592 if ?equal(i, length(ev(unitlistname))) then (flag : 1)
594 currentdoeval : false, /* This tells processunits not to simplify
595 for this operation only */
598 /* Command to set user selected units as defaults. Checks to ensure the
599 selected units don't contain two units of the same dimension. */
600 setunits(units):= block([letrat:true,unittype,unitlistname,
601 unitrules,currentunitlist, flag, i, j, derivedunits],
602 if listp(units) then (
603 if (checkforuniqueness(makedimenlist(units))) then (
604 for i : 1 thru length(units) do (
605 unittype : letsimp(units[i],todimension),
606 unitlistname : concat(unittype,listing_use),
607 unitrules : concat(unittype,'rules),
608 if (not(isbase(units[i])) and (isunit(units[i]))) then (
609 makeMKStoderivedrules(units[i],unitlistname,unitrules)
612 while ?equal(flag,0) do (
614 if not(lfreeof(unitlistname[j][1],units)) then (
615 currentunitlist : unitlistname[j],
617 if ?equal(j, length(ev(unitlistname))) then (flag : 1)
619 makeMKStobaserules(currentunitlist,unitlistname,unitrules)
622 error ("Error - two or more of these units describe the same dimension."))
627 /* These two functions control the dontuselist array. usedimension will remove
628 a dimension from the do not use list*/
629 usedimension(input1):= block([letrat:true,unittype,unitlistname,
630 unitrules,currentunitlist, flag, i, j, derivedunits],
631 if ?equal(input1,letsimp(input1,todimension)) then (
632 dontuselist : delete(input1,dontuselist))
634 temp : letsimp(input1,todimension),
635 dontuselist : delete(temp,dontuselist)
637 print("Done. To have Maxima simplify to this dimension, use setunits([unit])"),
638 print("to select a unit."),
640 dontusedimension(input1):= block([letrat:true,unittype,unitlistname,
641 unitrules,currentunitlist, temp, i, j, derivedunits],
642 if ?equal(input1,letsimp(input1,todimension)) then (
643 unitrules : concat(input1,'rules),
644 killruleset(unitrules),
645 dontuselist : append([input1],dontuselist)
647 temp : letsimp(input1,todimension),
648 unitrules : concat(temp,'rules),
649 killruleset(unitrules),
650 dontuselist : append([temp],dontuselist)
653 /* This function will reset a particular dimension to its default state, but
654 not place it on the dontuse list */
655 uforget(input1) := block([letrat:true,unittype,unitlistname,
656 unitrules,currentunitlist, temp, i, j, derivedunits],
657 if listp(input1) then (map('uforget,input1)) else (
658 if ?equal(input1,letsimp(input1,todimension)) then (
659 unitrules : concat(input1,'rules),
660 killruleset(unitrules),
661 if listp(usersetunits) then (
662 for i : 1 thru length(usersetunits) do (
663 if ?equal(input1,letsimp(usersetunits[i],todimension)) then (
664 setunits([usersetunits[i]]))))
666 temp : letsimp(input1,todimension),
667 unitrules : concat(temp,'rules),
668 killruleset(unitrules),
669 if listp(usersetunits) then (
670 for i : 1 thru length(usersetunits) do (
671 if ?equal(temp,letsimp(usersetunits[i],todimension)) then (
672 setunits([usersetunits[i]]))))
675 /* This function returns the dimension described by a unit */
676 dimension(input1) := letsimp(input1,todimension);
678 /* This function allows a user to set a common prefix for all unit names,
680 setunitprefix(prefix) := block([i,j,k,b],
681 print("Renaming units..."),
682 modedeclare([i,j,k], fixnum),
683 for i : 1 thru length(globalbaseunitlisting) do (
684 unitlistname : concat(globalbaseunitlisting[i],listing_use),
685 for j : 1 thru length(ev(unitlistname)) do (
686 for k : 1 thru length(unitlistname[j][1]) do (
687 unitlistname[j][1][k] : unitconcat(prefix,unitlistname[j][1][k])
690 /* j needs to start at the second entry for Derived Dimension Arrays */
691 for i : 1 thru length(globalderivedunitlisting_use) do (
692 unitlistname : concat(globalderivedunitlisting_use[i],listing_use),
693 for j : 2 thru length(ev(unitlistname)) do (
694 for k : 1 thru length(unitlistname[j][1]) do (
695 unitlistname[j][1][k] : unitconcat(prefix,unitlistname[j][1][k])
697 print("Rebuilding unit conversion rules..."),
698 maketodimensionruleset(),
699 makebasetoMKSrules(),
700 makederivedtoMKSrules(),
701 makedimentoonerules(),
702 allunitslist : makeallunitslist(),
703 currentunitprefix : prefix,
707 /* Undoes setunitprefix, or more properly resets things to their
709 clearunitprefix():= block([i,j,k,b],
710 print("Renaming units..."),
711 metricexpandall(%unitexpand),
712 print("Rebuilding unit conversion rules..."),
713 maketodimensionruleset(),
714 makebasetoMKSrules(),
715 makederivedtoMKSrules(),
716 makedimentoonerules(),
717 allunitslist : makeallunitslist(),
718 currentunitprefix : "",
722 /* Not yet implemented
724 /* Prints out information about a unit */
725 unitinfo(unit):= block([letrat:true,result,dimension],
727 /* Allows the user to create their own units */
728 addunits([[unitname(s)],definition,0]):=
731 /* Converts an expression into one using the specified units. Or the
732 user can specify MKS instead of a list of units, to have all units
733 rendered in terms of the seven base dimensions and their associated MKS
735 convert(expression,desiredunits):= block([letrat:true,convertrules,result1,
736 currentunitlist,flag,i,j],
737 if (?equal(desiredunits,MKS) or ?equal(desiredunits,mks)) then (
738 result1 : letsimp(expression,derivedtoMKSrules),
739 result1 : letsimp(result1,basetoMKSrules)
741 result1 : letsimp(expression,derivedtoMKSrules),
742 result1 : letsimp(result1,basetoMKSrules),
743 if listp(desiredunits) then (
744 if (checkforuniqueness(makedimenlist(desiredunits))) then (
745 for i : 1 thru length(desiredunits) do (
746 unittype : letsimp(desiredunits[i],todimension),
747 unitlistname : concat(unittype,listing_use),
748 if (not(isbase(desiredunits[i])) and (isunit(desiredunits[i]))) then (
749 makeMKStoderivedrules(desiredunits[i],unitlistname,convertrules)
751 if isunit(desiredunits[i]) then (
753 while ?equal(flag,0) do (
755 if not(lfreeof(unitlistname[j][1],desiredunits)) then (
756 currentunitlist : unitlistname[j],
758 if ?equal(j, length(ev(unitlistname))) then (flag : 1)
760 makeMKStobaserules(currentunitlist,unitlistname,convertrules)
762 error("Non unit argument to convert"))
764 result1 : letsimp(result1,convertrules))
766 error ("Error - two or more of these units describe the same dimension."))
768 result1 : convert(expression,[desiredunits]))),
769 currentdoeval : false, /* This tells processunits not to simplify
770 for this operation only */
773 /* Associates a variable and a dimension/dimension, e.g. t and %time */
774 assigndimension(exp,dimension) := block([],
775 if (not(lfreeof(globalbaseunitlisting,dimension)) or
776 not(lfreeof(globalderivedunitlisting))) then (
777 variabledimensionslist : append([[exp,dimension]],variabledimensionslist),
778 apply('qput,[exp,dimension,%dimension])
780 (error ("Invalid dimension. Valid entries are:")),
781 print(globalbaseunitlisting),
782 print(globalderivedunitlisting)));
784 /* Basic dimensional function. This doesn't have the power of dimension.mac but
785 it will work for basic cases */
786 dimension(exp) := block([letrat:true,tmp1,tmp2,a1,a2,result1,i,j],
787 killruleset(dimenrules),
788 unitlet([fakerule,fakerule],dimenrules),
789 unitlet([fakerule,fakerule],dimentoone),
790 for i:1 thru length(variabledimensionslist) do (
791 unitlet([variabledimensionslist[i][1],variabledimensionslist[i][2]],dimenrules)),
792 exp : processunits(exp),
793 tmp1 : letsimp(letsimp(exp,dimenrules),todimension),
794 tmp2 : letsimp(letsimp(letsimp(exp,todimension),dimenrules),dimentoone),
798 /* These functions turn on and off grouping common units over addition */
799 enablegroupbyadd() := block(post_eval_functions : [processunits,?groupadd]);
800 disablegroupbyadd() := block(post_eval_functions : [processunits]);
802 /*====================== Initialization =======================*/
804 /* Startup Message */
805 print("*******************************************************************");
806 print("* Units version 0.50 *");
807 print("* Definitions based on the NIST Reference on *");
808 print("* Constants, Units, and Uncertainty *");
809 print("* Conversion factors from various sources including *");
810 print("* NIST and the GNU units package *");
811 print("*******************************************************************");
813 print("Redefining necessary functions...");
815 /* This file contains the lisp level definitions required to properly
817 load("contrib/unit/unit-functions.lisp")$
819 print("Initializing unit arrays...");
821 /* This variable controls how many of the metric prefixes are added
822 on to the default lists*/
825 /* This is a list which contains dimensions the user doesn't want
826 simplified by the derived units simplifier. Default is empty. */
829 /* By default, we want to simplify addition of terms with units. Currently
830 this is disabled due to bugs */
833 /* Places all relevant metric definitions into the %dimensionlist lists.*/
834 metricexpandall(%unitexpand);
836 /* This list dictates in what order Maxima will look for derived units.
837 Initially an attempt was made to automate this process, but it proved
838 rather difficult to automate. (For example, given an expression:
844 this can be simplified to:
846 ---- or N m or kg*L*Hz
849 Normal practice would of course be to adopt the N m answer, but
850 this convention cannot be readily expressed in an algorithmic fashion
851 for all cases dealt with in this package. Hence, for the time being a
852 manual definition of the simplification order has been adopted.*/
854 globalderivedunitlisting_use : [%capacitance,%resistance,%conductance,
855 %electric_potential_difference,%magnetic_flux,
856 %magnetic_flux_density,%inductance,%electric_charge,%power,
857 %energy,%force,%pressure,%volume,%frequency,%illuminance,%absorbed_dose,
858 %catalytic_activity];
860 /* Variable to control how simplification behaves with respect to
861 fullnames and abbreviations. The default, 0, simplifies abbrev. to
862 abbrev. and fullnames to fullnames, also preserving plurals when
863 possible. There are two other possible settings:
864 1 : fullname -> abbreviation (singular and plural fullnames -> abbrev.)
865 2 : abbreviation -> fullname (will use plural form) */
868 /* Create default rulesets*/
869 maketodimensionruleset();
870 makebasetoMKSrules();
871 makederivedtoMKSrules();
872 makedimentoonerules();
874 /* Create a list of all units */
875 allunitslist : makeallunitslist();
878 /* Basic dimension functionality - initialize list of dimensions in use */
879 variabledimensionslist : [];
881 /* This variable is used to ensure Maxima only formats units at the end
883 unitformatresults : false;
885 /* This option controls the reporting of numerical warning messages in rat
886 substitutions during unit processing. Default is off since this output is
887 rather verbose and normally not needed. */
890 /* This routine will create a default user environment according to pre-set
891 user requests. uforget will also revert to this state. */
892 if listp(usersetunits) then (
893 print("User defaults found..."),
894 setunits(usersetunits),
895 print("User defaults initialized.")
898 /* Use pre eval to reset unitformatresults to false after a calculation - this
899 is necessary because if it is reset at the end of the earlier calculation the
900 unit formatting will be undone in the display process */
901 pre_eval_functions : [resetunitvars];
903 /* Activate processunits as a post_eval_function in order to enable
904 automatic simplification of all unit output, and groupadd to enable
905 sorting by common unit. */
906 post_eval_functions : [processunits,?groupadd];
908 /* This will ensure processunits starts out in working mode. */
909 currentdoeval : true;
911 ttyoff: nolabels, false$