Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Automate listing of defined in-code constants in the documentation #776

Merged
merged 8 commits into from
Feb 1, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 18 additions & 3 deletions doc/Coding.tex
Original file line number Diff line number Diff line change
Expand Up @@ -570,7 +570,7 @@ \subsection{Finding Roots of Equations}\index{root finding}\index{numerical algo
\end{lstlisting}
The above example begins by importing the {\normalfont \ttfamily Root\_Finder} module and then creating a {\normalfont \ttfamily rootFinder} object called {\normalfont \ttfamily finder}. This is made OpenMP {\normalfont \ttfamily threadprivate} so that it may be used simultaneously by all threads. The first step is to initialize {\normalfont \ttfamily finder}---the {\normalfont \ttfamily isInitialized} method tells us if this has already happened. The most important step is to specify the function that will evaluate $f(x)$. This is done via the {\normalfont \ttfamily rootFunction} method---once done, the {\normalfont \ttfamily rootFinder} object is marked as initialized (and the {\normalfont \ttfamily isInitialized} method will return {\normalfont \ttfamily true}). All other initialization steps are optional. In this example, we use the {\normalfont \ttfamily type} method to specify that the {\normalfont \ttfamily Brent} algorithm should be used for root finding. Any valid \href{http://www.gnu.org/software/gsl/manual/html_node/Root-Bracketing-Algorithms.html}{GSL-supported root finding algorithm} can be used. We then use the {\normalfont \ttfamily tolerance} method to specify both the absolute and relative tolerances in the $x$ variable that must be attained to declare the root to be found. Both arguments are optional---default values of $10^{-10}$ will be used if either tolerance is not specified.

The final step of initialization is to call the {\normalfont \ttfamily rangeExpand} method. This specifies how the initial guessed value or range for $x$ should be expanded to bracket the root. If you plan to always specify an initial range, and know that it will always bracket the root, you do not need to specify how the range should be expanded. In this case we've specified that range expansion is multiplicative---that is, the lower and upper values of $x$ defining the range will be multiplied by fixed factors until the root is bracketed---via the {\normalfont \ttfamily rangeExpandType=rangeExpandMultiplicative} option. Alternatively, additive expansion is possible using {\normalfont \ttfamily rangeExpandType=rangeExpandAdditive}. The factors by which to multiply the lower and upper bounds of the range (or the factor to add in the case of additive expansion) are specified by the {\normalfont \ttfamily rangeExpandDownward} and {\normalfont \ttfamily rangeExpandUpward} options. Iit is possible to specify absolute lower/upper limits to the range via the {\normalfont \ttfamily rangeDownwardLimit} and {\normalfont \ttfamily rangeUpwardLimit} options. The range will not be expanded beyond these limits---if the root cannot be bracketed without exceeding these limits an error condition will occur. Finally, it is possible to indicate the expected sign of $f(x)$ at the lower and/or upper limits via the {\normalfont \ttfamily rangeExpandDownwardSignExpect} and {\normalfont \ttfamily rangeExpandUpwardSignExpect} options. Valid settings are {\normalfont \ttfamily rangeExpandSignExpectNegative}, {\normalfont \ttfamily rangeExpandSignExpectPositive}, and {\normalfont \ttfamily rangeExpandSignExpectNone} (the default---implying that there is no expectation for the sign). If the sign of $f(x)$ is specified, then range expansion will stop once the expected sign is found. This can often improve efficiency, by allowing the range expander to expand the range in only one direction, resulting in a narrower range in which to search for the root.
The final step of initialization is to call the {\normalfont \ttfamily rangeExpand} method. This specifies how the initial guessed value or range for $x$ should be expanded to bracket the root. If you plan to always specify an initial range, and know that it will always bracket the root, you do not need to specify how the range should be expanded. In this case we've specified that range expansion is multiplicative---that is, the lower and upper values of $x$ defining the range will be multiplied by fixed factors until the root is bracketed---via the {\normalfont \ttfamily rangeExpandType=rangeExpandMultiplicative} option. Alternatively, additive expansion is possible using {\normalfont \ttfamily rangeExpandType=rangeExpandAdditive}. The factors by which to multiply the lower and upper bounds of the range (or the factor to add in the case of additive expansion) are specified by the {\normalfont \ttfamily rangeExpandDownward} and {\normalfont \ttfamily rangeExpandUpward} options. It is possible to specify absolute lower/upper limits to the range via the {\normalfont \ttfamily rangeDownwardLimit} and {\normalfont \ttfamily rangeUpwardLimit} options. The range will not be expanded beyond these limits---if the root cannot be bracketed without exceeding these limits an error condition will occur. Finally, it is possible to indicate the expected sign of $f(x)$ at the lower and/or upper limits via the {\normalfont \ttfamily rangeExpandDownwardSignExpect} and {\normalfont \ttfamily rangeExpandUpwardSignExpect} options. Valid settings are {\normalfont \ttfamily rangeExpandSignExpectNegative}, {\normalfont \ttfamily rangeExpandSignExpectPositive}, and {\normalfont \ttfamily rangeExpandSignExpectNone} (the default---implying that there is no expectation for the sign). If the sign of $f(x)$ is specified, then range expansion will stop once the expected sign is found. This can often improve efficiency, by allowing the range expander to expand the range in only one direction, resulting in a narrower range in which to search for the root.

Finally, we use the {\normalfont \ttfamily find} method to return the value of the root. The first argument to {\normalfont \ttfamily find} is the name of the function that evaluates $f(x)$. Additionally, we must supply either {\normalfont \ttfamily rootGuess} (a scalar value guess to use as the initial value for both the lower and upper values of the range---note that range expansion must be allowed in this case), or {\normalfont \ttfamily rootRange} (a two-element array to use as the initial lower and upper values of the range bracketing the root).

Expand All @@ -580,7 +580,7 @@ \section{Computation Dependencies and Data Files}\label{sec:codeUniqueLabels}\in

In many situations, some module in \glc\ might want to perform a calculation and then store the results to a file so that they can be reused later. A good example is the \refPhysics{transferFunctionCAMB} transfer function model, which computes a transfer function using {\normalfont \scshape CAMB} and stores this function in a file so that it can be re-read next time, avoiding the need to recompute the transfer function. A problem arises in such cases as the calculation may depend on the values of parameters (in our example, the transfer function will depend on cosmological parameters for example). We would like to record which parameter values this calculation refers to, perhaps encoding these into the file name, so that we can reuse these data in a future run only if the parameter values are unchanged. Given the modular nature of \glc\ it is impossible to know in advance which parameters will be relevant (e.g. does the cosmological parameter implementation have a parameter that describes a time varying equation of state for dark energy?).

To addres this problem, \glc\ provides a mechanism to generate a unique descriptor for a given object. This descriptor encodes the parameter used to construct the object, and recursively includes the parameters used to construct any other object which is composited. A long-form (human readable) descriptor is returned by the {\normalfont \ttfamily descriptor} method associated with all {\normalfont \ttfamily functionClass} objects. Additionally, the {\normalfont \ttfamily hashedDescriptor} method will return an MD5 hash of the descriptor, which will be unique (up to collisions) and can be used to idetify the object both internally and, for example, when used as a suffix to file names. If the optional {\normalfont \ttfamily includeSourceDigest} argument is set to true in the {\normalfont \ttfamily hashedDescriptor} method then the hashed descriptor will include a hash of the source code of the object (and all composited objects) such that the descriptor will change should the source code be changed.
To address this problem, \glc\ provides a mechanism to generate a unique descriptor for a given object. This descriptor encodes the parameter used to construct the object, and recursively includes the parameters used to construct any other object which is composited. A long-form (human readable) descriptor is returned by the {\normalfont \ttfamily descriptor} method associated with all {\normalfont \ttfamily functionClass} objects. Additionally, the {\normalfont \ttfamily hashedDescriptor} method will return an MD5 hash of the descriptor, which will be unique (up to collisions) and can be used to identify the object both internally and, for example, when used as a suffix to file names. If the optional {\normalfont \ttfamily includeSourceDigest} argument is set to true in the {\normalfont \ttfamily hashedDescriptor} method then the hashed descriptor will include a hash of the source code of the object (and all composited objects) such that the descriptor will change should the source code be changed.

\section{Optimization}\label{sec:Optimization}\index{optimization}

Expand Down Expand Up @@ -626,6 +626,21 @@ \section{Enumerations}

\input{autoEnumerationDefinitions}

\section{Defined Constants}

\glc\ defines numerous constants, including mathematical constants (e.g. $\pi$), physical constants (e.g. the speed of light), unit conversions (e.g. Angstroms to meters), and prefixes (e.g. ``kilo'', ``mega'', etc.). These should be used whenever a constant is needed in the code---it is bad practice to use the numerical value of a constant directly in the code\footnote{Both because it is prone to mistakes (the more times a numerical value is used directly, the more chances there are for typos and other errors), and because using a named constant makes it much easier to understand \emph{what} the code is doing and \emph{why}.}.

All defined constants are described below, along with references to their source, and the name of the module in which the constant is defined. To import a constant into a function, you would add a {\normalfont \ttfamily use} statement. For example, for the constant $\pi$, you would use:
\begin{verbatim}
use :: Numerical_Constants_Math, only : Pi
\end{verbatim}
after which you can use this constant, e.g.:
\begin{verbatim}
volumeSphere=4.0d0*Pi/3.0d0*radiusSphere**3
\end{verbatim}

\input{constants}

\section{Classes}

The return type of each method, and the interfaces (i.e. the types and names of its arguments) are specified for each method of each object. A ``{\normalfont \ttfamily void}'' return type indicates a subroutine.
Expand Down Expand Up @@ -696,5 +711,5 @@ \subsection{Methods available to all {\normalfont \ttfamily functionClass}es}\la
\end{itemize}

\end{description}

\input{dataMethods}
32 changes: 32 additions & 0 deletions doc/Galacticus.bib
Original file line number Diff line number Diff line change
Expand Up @@ -3665,6 +3665,19 @@ @article{inoue_updated_2014
adsnote = {Provided by the SAO/NASA Astrophysics Data System}
}

@book{jackson_classical_1999,
address = {New York},
edition = {3. ed},
title = {Classical electrodynamics},
isbn = {978-0-471-30932-1},
language = {eng},
publisher = {Wiley},
author = {Jackson, John David},
year = {1999},
note = {OCLC: 925677836},
keywords = {Electrodynamics, Électrodynamique, Elektrodynamik},
}

@article{jassal_wmap_2005,
title = {{WMAP} constraints on low redshift evolution of dark energy},
volume = {356},
Expand Down Expand Up @@ -5755,6 +5768,25 @@ @article{obreschkow_simulation_2009
pages = {1467--1484}
}

@article{oke_secondary_1983,
title = {Secondary standard stars for absolute spectrophotometry.},
volume = {266},
issn = {0004-637X},
url = {https://ui.adsabs.harvard.edu/abs/1983ApJ...266..713O},
doi = {10.1086/160817},
abstract = {Based on an adopted absolute spectral energy distribution for the primary standard star Alpha Lyrae, absolute fluxes are given for the four very metal-deficient F type subdwarfs HD 19445, HD 84937, BD + 26.2606 deg, and BD + 17.4703 deg. Somewhat inferior data are also given for HD 140283. The data are given for 40-A bands and cover the wavelength range from 3080 A to 12,000 A. The four stars, all near magnitude 9 and distributed around the sky, are intended as secondary standards for absolute spectrophotometry.},
urldate = {2025-01-29},
journal = {The Astrophysical Journal},
author = {Oke, J. B. and Gunn, J. E.},
month = mar,
year = {1983},
note = {Publisher: IOP
ADS Bibcode: 1983ApJ...266..713O},
keywords = {Astronomy, Calibrating, Line Spectra, Reference Stars, Spectral Energy Distribution, Stellar Spectrophotometry, Subdwarf Stars},
pages = {713--717},
file = {Full Text PDF:/home/abensonca/.mozilla/firefox/f54gqgdx.default/zotero/storage/FBTQED33/Oke and Gunn - 1983 - Secondary standard stars for absolute spectrophoto.pdf:application/pdf},
}

@article{ondaro-mallea_non-universality_2022,
title = {Non-universality of the mass function: dependence on the growth rate and power spectrum shape},
volume = {509},
Expand Down
2 changes: 1 addition & 1 deletion perl/Galacticus/Build/SourceTree.pm
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ use Galacticus::Build::SourceTree::Process::DebugHDF5;
use Galacticus::Build::SourceTree::Process::DebugMPI;
use Galacticus::Build::SourceTree::Process::ProfileOpenMP;
use Galacticus::Build::SourceTree::Process::ThreadSafeIO;
use Galacticus::Build::SourceTree::Process::GSLConstants;
use Galacticus::Build::SourceTree::Process::Constants;
use Galacticus::Build::SourceTree::Process::HDF5FCInterop;
use Galacticus::Build::SourceTree::Process::Constructors;
use Galacticus::Build::SourceTree::Process::ConditionalCall;
Expand Down
145 changes: 145 additions & 0 deletions perl/Galacticus/Build/SourceTree/Process/Constants.pm
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
# Contains a Perl module which implements physical/numerical constants.

package Galacticus::Build::SourceTree::Process::Constants;
use strict;
use warnings;
use utf8;
use Cwd;
use lib $ENV{'GALACTICUS_EXEC_PATH'}."/perl";
use File::Temp qw/ tempfile /;
use XML::Simple;

# Insert hooks for our functions.
$Galacticus::Build::SourceTree::Hooks::processHooks{'constant'} = \&Process_Constant;

sub Process_Constant {
# Get the tree.
my $tree = shift();
# Initialize structure to store lists of constants.
my $constants;
my @constantsOrphaned;
my $fileName;
# Walk the tree.
my $node = $tree;
my $depth = 0;
while ( $node ) {
if ( $node->{'type'} eq "file" ) {
$fileName = $node->{'name'};
} elsif ( $node->{'type'} eq "module" ) {
my $moduleName = $node->{'name'};
if ( @constantsOrphaned ) {
foreach my $constant ( @constantsOrphaned ) {
$constant->{'module'} = $moduleName;
}
push(@{$constants->{'constant'}},@constantsOrphaned);
undef(@constantsOrphaned);
}
} elsif ( $node->{'type'} eq "constant" && ! $node->{'directive'}->{'processed'} ) {
my $code;
my $type = exists($node->{'directive'}->{'type'}) ? $node->{'directive'}->{'type'} : "double precision";
if ( exists($node->{'directive'}->{'gslSymbol'}) ) {
# Constant value is to be extracted from GSL.
# Generate, compile, and execute a minimal C code which simply outputs the relevant GSL constant.
if ( $type eq "enum" ) {
# An enum type.
my @members = split(/\s*,\s*/,$node->{'directive'}->{'members'});
(undef, my $tmpSourceFileName) = tempfile('tempXXXXX', SUFFIX => '.c', OPEN => 0, DIR => $ENV{'BUILDPATH'});
(undef, my $tmpExecFileName ) = tempfile('tempXXXXX', SUFFIX => '.x', OPEN => 0, DIR => $ENV{'BUILDPATH'});
open(my $tmpSource,">".$tmpSourceFileName);
print $tmpSource "\#include <stdio.h>\n";
print $tmpSource "\#include <float.h>\n";
print $tmpSource "\#include <gsl/".$node->{'directive'}->{'gslHeader'}.".h>\n";
print $tmpSource "int main () {\n";
print $tmpSource " printf(\"".join(" ",("%i") x scalar(@members))."\", ".$node->{'directive'}->{'members'}.");\n";
print $tmpSource "}\n";
close($tmpSource);
system($ENV{'CCOMPILER'}." -o ".$tmpExecFileName." ".$tmpSourceFileName." ".$ENV{'CFLAGS'});
die("Galacticus::Build::SourceTree::Process::GSLConstants: failed to compile")
unless ( $? == 0 );
open(my $gslPipe,$tmpExecFileName."|");
my $gslEnum = <$gslPipe>;
close($gslPipe);
unlink($tmpSourceFileName,$tmpExecFileName);
my @values = split(" ",$gslEnum);
my @definitions;
for(my $i=0;$i<scalar(@members);++$i) {
push(@definitions,$members[$i]."=".$values[$i]);
}
$code = "enum, bind(c)\n";
$code .= " enumerator :: ".join(", ",@definitions)."\n";
$code .= "end enum\n";
} else {
# A non-enum type.
# For double precision constants we use a macro to define the precision with which the constant should be output to
# preserve full precision (see https://stackoverflow.com/a/19897395).
(undef, my $tmpSourceFileName) = tempfile('tempXXXXX', SUFFIX => '.c', OPEN => 0, DIR => $ENV{'BUILDPATH'});
(undef, my $tmpExecFileName ) = tempfile('tempXXXXX', SUFFIX => '.x', OPEN => 0, DIR => $ENV{'BUILDPATH'});
open(my $tmpSource,">".$tmpSourceFileName);
print $tmpSource "\#include <stdio.h>\n";
print $tmpSource "\#include <float.h>\n";
print $tmpSource "\#include <gsl/".$node->{'directive'}->{'gslHeader'}.".h>\n";
if ( $type eq "double precision" ) {
print $tmpSource "\#ifdef LDBL_DECIMAL_DIG\n";
print $tmpSource " \#define OP_LDBL_Digs (LDBL_DECIMAL_DIG)\n";
print $tmpSource "\#else \n";
print $tmpSource " \#ifdef DECIMAL_DIG\n";
print $tmpSource " \#define OP_LDBL_Digs (DECIMAL_DIG)\n";
print $tmpSource " \#else \n";
print $tmpSource " \#define OP_LDBL_Digs (LDBL_DIG + 3)\n";
print $tmpSource " \#endif\n";
print $tmpSource "\#endif\n";
print $tmpSource "int main () {\n";
print $tmpSource " printf(\"%.*e\", OP_LDBL_Digs - 1, ".$node->{'directive'}->{'gslSymbol'}.");\n";
print $tmpSource "}\n";
} elsif ( $type eq "integer" ) {
print $tmpSource "int main () {\n";
print $tmpSource " printf(\"%i\", ".$node->{'directive'}->{'gslSymbol'}.");\n";
print $tmpSource "}\n";
}
close($tmpSource);
system($ENV{'CCOMPILER'}." -o ".$tmpExecFileName." ".$tmpSourceFileName." ".$ENV{'CFLAGS'});
die("Galacticus::Build::SourceTree::Process::GSLConstants: failed to compile")
unless ( $? == 0 );
open(my $gslPipe,$tmpExecFileName."|");
my $gslConstant = <$gslPipe>;
close($gslPipe);
unlink($tmpSourceFileName,$tmpExecFileName);
$gslConstant =~ s/e/d/;
$node->{'directive'}->{'value'} = $gslConstant;
$code = $type.", parameter, public :: ".$node->{'directive'}->{'variable'}."=".$gslConstant."\n";
}
} else {
# Constant value is defined internally.
$code = $type.", parameter, public :: ".$node->{'directive'}->{'variable'}."=".$node->{'directive'}->{'value'}."\n";
}
# Insert a new code node to define the constant.
my $newNode =
{
type => "code",
content => $code,
firstChild => undef()
};
&Galacticus::Build::SourceTree::InsertAfterNode($node,[$newNode]);
# Accumulate to a list of constants from this module.
push(@constantsOrphaned,$node->{'directive'});
# Mark the directive as processed.
$node->{'directive'}->{'processed'} = 1;
}
# Walk to the next node in the tree.
$node = &Galacticus::Build::SourceTree::Walk_Tree($node,\$depth);
}
# Add file name attribute.
foreach my $constant ( @{$constants->{'constant'}} ) {
$constant->{'fileName'} = $fileName;
}
# If building documentation, output accumulated constants to file.
if ( defined($constants) && exists($ENV{'GALACTICUS_BUILD_DOCS'}) && $ENV{'GALACTICUS_BUILD_DOCS'} eq "yes" ) {
my $xml = new XML::Simple();
(my $constantsName = $fileName) =~ s/\.F90$/.constants.xml/;
open(my $constantsFile,">",$ENV{'BUILDPATH'}."/".$constantsName);
print $constantsFile $xml->XMLout($constants,RootName => "constants");
close($constantsFile);
}
}

1;
Loading