%mathpiper,def="I;CachedConstant;AssignCachedConstants;ClearCachedConstants"

/* def file definitions.
I
CachedConstant
AssignCachedConstants
ClearCachedConstants
*/

/* Definition of constants. */

/* TODO:
 * There is a problem with defining I this way: if I is used, but the
 * file "complex" has not been loaded, the interpreter can not deal
 * with "Complex".
 *
 * Note:tk:10/9/09: Perhaps use SetGlobalLazyVariable(I,Hold(Complex(0,1)));
 */

SetGlobalLazyVariable(I,Complex(0,1));

//////////////////////////////////////////////////
/// Cached constants support and definition of Pi
//////////////////////////////////////////////////

//TODO: here we wrap the entire file in LocalSymbols, this is inefficient in that it slows loading of this file. Needs optimization.
LocalSymbols(CacheOfConstantsN) [

/// declare a new cached constant C'atom and its associated function C'atom().
/// C'atom() will call C'func() at current precision to evaluate C'atom if it has not yet been cached at that precision. (note: any arguments to C'func() must be included)
Rulebase("CachedConstant", {C'cache, C'atom, C'func});
UnFence("CachedConstant", 3);	// not sure if this is useful
HoldArgument("CachedConstant", C'func);
HoldArgument("CachedConstant", C'cache);	// name of the cache
// check syntax: must be called on an atom and a function
Rule("CachedConstant", 3, 10, And(IsAtom(C'atom), IsFunction(C'func)))
[
 	Local(C'name,C'functionName);
	Bind(C'name, ToString(C'atom));	// this is for later conveniences
  Bind(C'functionName,ConcatStrings("Internal'",C'name));

	If(	// create the cache it if it does not already exist
		IsAtom(Eval(C'cache)),
		MacroBind(Eval(C'cache), {})
	);
//	Write({"debug step 0: ", C'cache, Eval(C'cache), C'atom, C'func, C'name});
	// check that the constant is not already defined
	If(
	  IsEqual(Builtin'Assoc(C'name, Eval(C'cache)), Empty),	// the constant is not already defined, so need to define "C'atom" and the corresponding function "C'atom"()
	  [	// e.g. C'atom evaluates to Pi, C'cache to a name e.g. CacheOfConstantsN, which is bound to a hash
		MacroUnbind(C'atom);
//		Write({"debug step 1: ", Cache'name, C'cache, Eval(C'cache)});
		// add the new constant to the cache
//		MacroBind(Cache'name, Insert(Eval(C'cache), 1, {C'name, 0, 0}));
		DestructiveInsert(Eval(C'cache), 1, {C'name, 0, 0});
//		Write({"debug step 2: ", Cache'name, C'cache, Eval(C'cache)});
		// define the new function "C'atom"()
		// note: this should not use N() because it may be called from inside N() itself

		MacroRulebase(C'functionName, {});
		`( Rule(@C'functionName, 0, 1024, True)
		[
			Local(new'prec, new'C, cached'C);
			Bind(new'prec, BuiltinPrecisionGet());
			// fetch the cache entry for this constant
			// note that this procedure will store the name of the cache here in this statement as Eval(C'cache)
			Bind(cached'C, Builtin'Assoc(@C'name, @C'cache));
			If( MathNth(cached'C, 2) != new'prec,
			  [	// need to recalculate at current precision
				If(IsEqual(InVerboseMode(),True), Echo("CachedConstant: Info: constant ", @C'name, " is being recalculated at precision ", new'prec));
				Bind(new'C, RoundTo(Eval(@C'func),new'prec));
				DestructiveReplace(cached'C, 2, new'prec);
				DestructiveReplace(cached'C, 3, new'C);
				new'C;
			  ],
			  // return cached value of C'atom
			  MathNth(cached'C, 3)
			);
		]);

		// calculate C'atom at current precision for the first time
//		Eval(ListToFunction({C'atom}));	// "C'name"();
		// we do not need this until the constant is used; it will just slow us down
	  ],
	  // the constant is defined
	  Echo("CachedConstant: Warning: constant ", C'atom, " already defined")
	);
];

Rule("CachedConstant", 3, 20, True)
	Echo("CachedConstant: Error: ", C'atom, " must be an atom and ", C'func, " must be a function.");

/// assign numerical values to all cached constants: using fixed cache "CacheOfConstantsN"
// this is called from N()
Function("AssignCachedConstantsN", {})
[
	Local(var,fname);
	ForEach(var, AssocIndices(CacheOfConstantsN))
	[
		MacroUnbind(ToAtom(var));
    Bind(fname,ConcatStrings("Internal'",var));
    Bind(var,ToAtom(var));
		// this way the routine Internal'Pi() will be actually called only when the variable 'Pi' is used, etcetera.
    `SetGlobalLazyVariable((@var), ListToFunction({ToAtom(fname)}));
	];
];
UnFence("AssignCachedConstantsN", 0);

/// clear values from all cached constants: using fixed cache "CacheOfConstantsN"
// this is called from N()
Function("ClearCachedConstantsN", {})
[
	Local(c'entry);
	ForEach(c'entry, CacheOfConstantsN)
		MacroUnbind(ToAtom(c'entry[1]));
];
UnFence("ClearCachedConstantsN", 0);

/// declare some constants now
CachedConstant(CacheOfConstantsN, Pi,
[// it seems necessary to precompute Pi to a few more digits
// so that Cos(0.5*Pi)=0 at precision 10
// FIXME: find a better solution
	Local(result,old'prec);
  Bind(old'prec,BuiltinPrecisionGet());
If(IsEqual(InVerboseMode(),True), Echo("Recalculating Pi at precision ",old'prec+5));
	BuiltinPrecisionSet(BuiltinPrecisionGet()+5);
	result := MathPi();
If(IsEqual(InVerboseMode(),True),Echo("Switching back to precision ",old'prec));
	BuiltinPrecisionSet(old'prec);
	result;
]
);
CachedConstant(CacheOfConstantsN, gamma, GammaConstNum());
CachedConstant(CacheOfConstantsN, GoldenRatio, N( (1+Sqrt(5))/2 ) );
CachedConstant(CacheOfConstantsN, Catalan, CatalanConstNum() );

]; // LocalSymbols(CacheOfConstantsN)

%/mathpiper





%mathpiper_docs,name="I",categories="User Functions;Constants (Mathematical);Numbers (Complex)"
*CMD I --- imaginary unit
*STD
*CALL
	I

*DESC

This symbol represents the imaginary unit, which equals the square
root of -1. It evaluates to {Complex(0,1)}.

*E.G.

In> I
Result: Complex(0,1);
In> I = Sqrt(-1)
Result: True;

*SEE Complex
%/mathpiper_docs



%mathpiper_docs,name="Pi",categories="User Functions;Constants (Mathematical)"
*CMD Pi --- mathematical constant $pi$

*STD
*CALL
	Pi

*DESC

Pi symbolically represents the exact value of $pi$. When the {N()} function is
used, {Pi} evaluates to a numerical value according to the current precision.
It is better to use {Pi} than {N(Pi)} except in numerical calculations, because exact
simplification will be possible.

This is a "cached constant" which is recalculated only when precision is increased.

*E.G.

In> Sin(3*Pi/2)
Result: -1;
In> Pi+1
Result: Pi+1;
In> N(Pi)
Result: 3.14159265358979323846;

*SEE Sin, Cos, N, CachedConstant
%/mathpiper_docs



%mathpiper_docs,name="GoldenRatio",categories="User Functions;Constants (Mathematical)"
*CMD GoldenRatio --- the Golden Ratio
*STD
*CALL
	GoldenRatio

*DESC

These functions compute the "golden ratio"
$$phi <=> 1.6180339887 <=> (1+Sqrt(5))/2 $$.

The ancient Greeks defined the "golden ratio" as follows:
If one divides a length 1 into two pieces $x$ and $1-x$, such that the ratio of 1 to $x$ is the same as the ratio of $x$ to $1-x$, then $1/x <=> 1.618$... is the "golden ratio".


The constant is available symbolically as {GoldenRatio} or numerically through {N(GoldenRatio)}.
This is a "cached constant" which is recalculated only when precision is increased.
The numerical value of the constant can also be obtained as {N(GoldenRatio)}.


*E.G.

In> x:=GoldenRatio - 1
Result: GoldenRatio-1;
In> N(x)
Result: 0.6180339887;
In> N(1/GoldenRatio)
Result: 0.6180339887;
In> V(N(GoldenRatio,20));

	CachedConstant: Info: constant GoldenRatio is
	being recalculated at precision 20 
Result: 1.6180339887498948482;


*SEE N, CachedConstant
%/mathpiper_docs



%mathpiper_docs,name="Catalan",categories="User Functions;Constants (Mathematical)"
*CMD Catalan --- Catalan's Constant
*STD
*CALL
	Catalan

*DESC

These functions compute Catalan's Constant $Catalan<=>0.9159655941$.

The constant is available symbolically as {Catalan} or numerically through {N(Catalan)} with {N(...)} the usual operator used to try to coerce an expression in to a numeric approximation of that expression.
This is a "cached constant" which is recalculated only when precision is increased.
The numerical value of the constant can also be obtained as {N(Catalan)}.
The low-level numerical computations are performed by the routine {CatalanConstNum}.


*E.G.

In> N(Catalan)
Result: 0.9159655941;
In> DirichletBeta(2)
Result: Catalan;
In> V(N(Catalan,20))

	CachedConstant: Info: constant Catalan is
	being recalculated at precision 20
Result: 0.91596559417721901505;


*SEE N, CachedConstant
%/mathpiper_docs



%mathpiper_docs,name="gamma",categories="User Functions;Constants (Mathematical)"
*CMD gamma --- Euler's constant $gamma$
*STD
*CALL
	gamma

*DESC

These functions compute Euler's constant $gamma<=>0.57722$...

The constant is available symbolically as {gamma} or numerically through using the usual function {N(...)} to get a numeric result, {N(gamma)}.
This is a "cached constant" which is recalculated only when precision is increased.
The numerical value of the constant can also be obtained as {N(gamma)}.
The low-level numerical computations are performed by the routine {GammaConstNum}.

Note that Euler's Gamma function $Gamma(x)$ is the capitalized {Gamma} in MathPiper.

*E.G.

In> gamma+Pi
Result: gamma+Pi;
In> N(gamma+Pi)
Result: 3.7188083184;
In> V(N(gamma,20))
	
	CachedConstant: Info: constant gamma is being
	  recalculated at precision 20 
	GammaConstNum: Info: used 56 iterations at
	  working precision 24 
Result: 0.57721566490153286061;

*SEE Gamma, N, CachedConstant
%/mathpiper_docs



%mathpiper_docs,name="CachedConstant",categories="User Functions;Constants (Mathematical)"
*CMD CachedConstant --- precompute multiple-precision constants
*STD
*CALL
	CachedConstant(cache, Cname, Cfunc)

*PARMS
{cache} -- atom, name of the cache

{Cname} -- atom, name of the constant

{Cfunc} -- expression that evaluates the constant

*DESC

This function is used to create precomputed multiple-precision values of
constants. Caching these values will save time if they are frequently used.

The call to {CachedConstant} defines a new function named {Cname()} that
returns the value of the constant at given precision. If the precision is
changed, the value will be recalculated as necessary, otherwise calling {Cname()} will take very little time.

The parameter {Cfunc} must be an expression that can be evaluated and returns
the value of the desired constant at the current precision. (Most arbitrary-precision mathematical functions do this by default.)

The associative list {cache} contains elements of the form {{Cname, prec, value}}, as illustrated in the example. If this list does not exist, it will be created.

This mechanism is currently used by {N()} to precompute the values of $Pi$ and $gamma$ (and the golden ratio through {GoldenRatio}, and {Catalan}).
The name of the cache for {N()} is {CacheOfConstantsN}.
The code in the function {N()} assigns unevaluated calls to {Internal'Pi()} and {Internal'gamma()} to the atoms {Pi} and {gamma} and declares them to be lazy global variables through {SetGlobalLazyVariable} (with equivalent functions assigned to other constants that are added to the list of cached constants).

The result is that the constants will be recalculated only when they are used in the expression under {N()}.
In other words, the code in {N()} does the equivalent of

	SetGlobalLazyVariable(mypi,Hold(Internal'Pi()));
	SetGlobalLazyVariable(mygamma,Hold(Internal'gamma()));

After this, evaluating an expression such as {1/2+gamma} will call the function {Internal'gamma()} but not the function {Internal'Pi()}.

*E.G. notest

In> CachedConstant( my'cache, Ln2, Internal'LnNum(2) )
Result: True;
In> Internal'Ln2()
Result: 0.6931471806;
In> V(N(Internal'Ln2(),20))
	CachedConstant: Info: constant Ln2 is being
	  recalculated at precision 20 
Result: 0.69314718055994530942;
In> my'cache
Result: {{"Ln2",20,0.69314718055994530942}};


*SEE N, BuiltinPrecisionSet, Pi, GoldenRatio, Catalan, gamma
%/mathpiper_docs
