Adding New Classes¶
Code Directives¶
Galacticus is designed to be flexible and extensible, allowing you to add new classes and functionality without having to hack the code extensively. To achieve this it makes much use of embedded code directives which, for example, explain to the build system how a particular subroutine or function connects into the Galacticus code. Such code directives are enclosed in blocks delimited by !![ and !!], and take the form of short blocks of XML. For example, a typical code directive might look like:
!![
<accretionDisks name="accretionDisksADAF">
<description>An accretion disk class for ADAF (advection-dominated accretion flow) disks.</description>
</accretionDisks>
!!]
This directive appears just prior to the definition of a type which implements an advection-dominated accretion flow (ADAF) accretion disk. The accretionDisks tag explains to the Galacticus build system that this file contains an implementation of the black hole accretion disks class. The build system will then merge this implementation into the accretionDisks function class (see Section Function Classes), making it selectable at run time. A complete reference of the general-purpose preprocessor directives is given in Section Galacticus Preprocessor Directives; this chapter describes the directives used to define node components and to register task functions.
Identifying Components and Mass Types¶
Many functions can be applied to different components or groups of components and to different types of mass within a node. In general, these functions make use of a set of label defined in the Galactic_Structure_Options module. Components are identified by a componentType label which can take on the following values:
componentTypeAllAll components are matched;
componentTypeDiskOnly disk components are matched;
componentTypeSpheroidOnly spheroid components are matched.
componentTypeBlackHoleOnly black hole components are matched.
componentTypeHotHaloOnly hot halo components are matched.
componentTypeDarkHaloOnly dark matter halo components are matched.
Types of mass are identified by a massType which can take one of the following values:
massTypeAllAll mass is included;
massTypeDarkOnly dark matter is included;
massTypeBaryonicOnly baryonic mass is included;
massTypeGalacticOnly galactic mass is included.
massTypeGaseousOnly gaseous mass is included.
massTypeStellarOnly stellar mass is included.
massTypeBlackHoleOnly black hole mass is included.
Components¶
This section describes the internal structure of node components, and how a component is implemented.
Component Structure¶
Each node in the merger tree consists of an arbitrary number of “components”, each of which can actually be an array, allowing multiple components of a given class. Each component represents a specific class of object, which could be a dark matter halo, a galactic disk or a black hole etc. A component of each class may be of one or more different implementations of that component class. Component classes are extensions of the nodeComponent base class, while each implementation is an extension of its component class (or, sometimes, of another implementation of that same class). Each component implementation type consists of a set of data[1], representing the properties (mass, size etc.) of the component, along with the rates of change (and ODE solver tolerances) for any properties which are evolvable. Additionally, each component contains a large number of methods (functions) which can be used to access its properties, query its interfaces and which are used internally to perform ODE evolution, output etc. The nodeComponent base class and all classes derived from it are built automatically at compile time by the componentBuilder source-tree process hook (which drives the generator package python/Galacticus/Build/Components); the generated code is grafted into objects/nodes/_class.F90 during preprocessing, so take a look in work/build/objects/nodes/_class.p.F90 if you want to see it.
Extending Components¶
It is possible to create a component which extends an existing component (see the discussion of the extends element in Section Component Definition). This capability is intended to allow new properties to be added to a component without having to create a whole new copy of the component. It is not intended to allow changes in the way in which the component is evolved through the halo hierarchy. (With the exception that rules to describe how the newly added properties will evolve through the halo hierarchy can be added of course.)
A simple example of this extension capability can be found in the scaleShape dark matter profile component, which extends the scale dark matter profile component (see Component Classes). In this case, the scaleShape component adds a new property, shape, and specifies how it is to be initialized, output, and changed by node promotion events. It does not affect how the scale property, inherited from the scale dark matter profile component, is evolved.
Implementing a New Component¶
Implementing a new component involves writing some modules and functions which contain a definition of the component and, if necessary, handle initialization, creation, evolution, and responses to any events. Frequently, the easiest way to make a new component is to copy a previously existing one and modify it as needed. Details of the various functions that component modules must perform are given below. By convention, a component implementation lives in its own directory, objects/nodes/components/<component>/<implementation>/ (with <component> and <implementation> acting as placeholders for the component class and implementation names, e.g. disk/standard), and is split into three or four files, although some components might not need all of these files. These files are named as follows:
objects/nodes/components/<component>/<implementation>/_class.F90The primary file which describes the component and its properties, and which contains functions that manipulate the component as it evolves through a merger tree (ODE rates, behavior during mergers, etc.);
objects/nodes/components/<component>/<implementation>/bound_functions.IncContains functions which will be bound to the component object (i.e. the
nodeComponent<Class><Implementation>class), and so will be available as type bound procedures. Generally, these functions will include any which get or set values of properties in the component, those which return information about its internal state (such as amassDistributionClassobject describing the structure of the component), and any other functions which we may want to be overridden by extensions to the component.objects/nodes/components/<component>/<implementation>/data.F90Contains any data which may need to be shared between the above two files. This might contain parameters which control some property of the component that is the same for all instances (e.g. if spheroids are modeled as Sérsic profiles all with the same value of the Sérsic index, that value might be placed into this file).
objects/nodes/components/<component>/<implementation>/structure.F90Contains any functions which implement the structure (e.g. density, rotation curve) of the component and which cannot be placed in
objects/nodes/components/<component>/<implementation>/bound_functions.Incdue to dependencies on modules which in turn depend on theGalacticus_Nodesmodule.
In general, the _class.F90 file is the place for the component definition and functions which process the component during tree evolution (including output), while bound_functions.Inc is intended for functions which record or report the internal state of the component.
Component Definition¶
Component definition itself takes the form of an embedded XML document. The following example illustrates such a document:
!![
<component>
<class>disk</class>
<name>exponential</name>
<isDefault>yes</isDefault>
<properties>
<property>
<name>isInitialized</name>
<type>logical</type>
<rank>0</rank>
<attributes isSettable="true" isGettable="true" isEvolvable="false" />
</property>
<property>
<name>massStellar</name>
<type>real</type>
<rank>0</rank>
<attributes isSettable="true" isGettable="true" isEvolvable="true" isNonNegative="true" />
<output unitsInSI="massSolar" unitsDescription="Solar masses" unitsQuantity="solMass" comment="Mass of stars in the exponential disk."/>
</property>
<property>
<name>abundancesStellar</name>
<type>abundances</type>
<rank>0</rank>
<attributes isSettable="true" isGettable="true" isEvolvable="true" isNonNegative="true" />
<output unitsInSI="massSolar" unitsDescription="Solar masses" unitsQuantity="solMass" comment="Mass of metals in the stellar phase of the exponential disk."/>
</property>
<property>
<name>massGas</name>
<type>real</type>
<rank>0</rank>
<attributes isSettable="true" isGettable="true" isEvolvable="true" createIfNeeded="true" isNonNegative="true" />
<output unitsInSI="massSolar" unitsDescription="Solar masses" unitsQuantity="solMass" comment="Mass of gas in the exponential disk."/>
</property>
<property>
<name>coolingMass</name>
<attributes isSettable="false" isGettable="false" isEvolvable="true" isDeferred="rate" isVirtual="true" />
<type>real</type>
<rank>0</rank>
</property>
<property>
<name>halfMassRadius</name>
<attributes isSettable="false" isGettable="true" isEvolvable="false" isVirtual="true" />
<type>real</type>
<rank>0</rank>
<getFunction>Node_Component_Disk_Exponential_Half_Mass_Radius</getFunction>
</property>
<property>
<name>luminositiesStellar</name>
<type>real</type>
<rank>1</rank>
<attributes isSettable="true" isGettable="true" isEvolvable="true" isNonNegative="true" />
<classDefault modules="Stellar_Population_Properties_Luminosities" count="Stellar_Population_Luminosities_Count()">0.0d0</classDefault>
<output labels="':'//Stellar_Population_Luminosities_Name({i})" count="Stellar_Population_Luminosities_Count()" condition="Stellar_Population_Luminosities_Output({i},time)" modules="Stellar_Population_Properties_Luminosities" unitsInSI="luminosityZeroPointAB" unitsDescription="AB-magnitude zero point" unitsQuantity="4.465920e17 W/Hz" comment="Luminosity of disk stars."/>
</property>
</properties>
<bindings>
<binding method="attachPipes" function="Node_Component_Disk_Exponential_Attach_Pipes" type="void" />
</bindings>
<functions>objects/nodes/components/disk/exponential/custom_methods.inc</functions>
</component>
!!]
The elements of this document have the following meaning:
class[Required] Specifies the component class of which this is an implementation.
name[Required] Specifies the name of this specific implementation.
extends[Optional] If present, this element must contain
classandnameelements which specify the type of component which should be extended. The component then automatically inherits all properties and type-bound functions of the extended type.isDefault[Required] Specifies whether or not this should be the default implementation of this class. Note that only one implementation of each class can be declared to be the default. If no implementation of a given class is declared to be the default then the (automatically generated)
nullimplementation will be made the default.properties[Optional] Contains an array of
propertyelements which specify the properties of this implementation. Each memberpropertyhas the following structure:name[Required] The name of the property.
type[Required] The type (one of
real,integer,logical,history,abundances,chemicals, orkeplerOrbitat present) of the property.rank[Required] The rank of this property (currently
0for a scalar or1for a 1-D array).attributes[Required] Attributes of this property:
isSettableIf
truethen the value of this property can be set directly.isGettableIf
truethen the value of this property can be got directly.isEvolvableIf
truethis property evolves as part of the Galacticus ODE system.createIfNeededIf
truethen any attempt to get, set, or adjust the rate of this property will cause the component to be created if it does not already exist. This is useful if the component should be created in response to mass transfer from some other component for example.isDeferredContains a “
:” separated list which can containget,set, andrate. The methods present in this list will not have functions bound to them at compile time. Instead a function will be created which allows a function to be bound to these methods at run time. For example:call myComponent%massFunction (My_Component_Mass_Get_Function) call myComponent%massSetFunction (My_Component_Mass_Set_Function) call myComponent%massRateFunction(My_Component_Mass_Rate_Function)
Additionally, a method is created which returns true or false depending on whether the method has been attached to a function yet, e.g.
myComponent%massIsAttached () myComponent%massSetIsAttached () myComponent%massRateIsAttached()
output[Optional] If present, the property will be included in the Galacticus output file. The following attributes control the details of that output:
unitsInSIThe units of the output quantity in the SI system.
unitsDescriptionA human-readable description of the units of the output quantity.
unitsQuantityAn
astropy.units-parseable description of the units of the output quantity.commentA comment to be included with the HDF5 dataset for this property.
conditionA statement which must evaluate to
trueorfalseand which will be used to determine if the property will be output. The present output time for is available astime. In the case of an array property the construct “{i}” can be used to pass the index of the element for which the condition should be evaluated.modulesA comma-separated list of any modules required to perform the output (e.g. modules which contain functions or values that are used).
Additional attributes are required for array properties:
labelsThis can be an array, declared as “
[L_1,…,L_N]”, specifying the suffix to be added to the property name for each component of the array in the output, or a function which returns the suffix. In the case of a function the construct “{i}” can be used to pass the index of the element for which the suffix is required.countA statement which evaluates the the number of elements to be output (i.e. the length of the array).
isVirtual[Optional] If present and set to “
true”, this property is a virtual property. A virtual property has no data associated with it and must supply its own functions for getting, setting and adjusting its rate of change (if allowed by the property’s attributes). Virtual properties are used for quantities which are derived from actual properties of the component implementation (for example, a star formation rate could be a virtual property if it is derived from an actual gas mass property) or for adjusting the rates of several actual properties simultaneously.isNonNegative[Optional] If present and set to “
true”, this property should always be non-negative (i.e. zero or positive). This is typically the case for quantities such as masses for example. This attribute is informative only—it may or may not be taking into account by the class responsible for evolving the component properties. For example, themergerTreeNodeEvolverStandardclass will evolve properties marked as non-negative in such a way as to ensure they remain non-negative, but only if its parameter[enforceNonNegativity]=true.getFunction[Optional] Specifies the function to be used for getting the value of the property, overriding the default get function. The function must be included in the Galacticus_Nodes module by use of the
functionselement described below. Note that this function, by virtue of its privileged access to the internal structure of node components, can access the value of the data associated with the property using:myComponent%<property>Data%value
setFunction[Optional] The same as
getFunctionbut defines a function to set the value of the property.classDefault[Optional] Specifies the default value for this property if the component class has not been created (i.e. has no specific implementation yet). The content of this element gives the default value (which can be a scalar, an array, a function, etc.). Additional, optional attributes control the use of this element:
modulesSpecifies a comma-separated list of modules which are required to set the default values (e.g. modules which contain the value or function to be used).
countFor array properties whose size is not known at compile-time, it is possible to specify a function which will return the appropriate size of the array at run-time. The scalar default value given in the
classDefaultelement will then be replicated the appropriate number of times.
bindings[Optional] Contains an array of
bindingelements which specify functions to bind to this implementation. Each memberbindinghas the following structure:methodThe name of the bound method, such that the function can be accessed using
myComponent%<method>(...)
functionThe function to which the method should be bound. (This function must be included in the Galacticus_Nodes module by use of the
functionselement described below.typeThe type of function.
functions[Optional] Contains the name of a file which will be included into the Galacticus_Nodes module. This file can contain functions which will be bound to this implementation. By virtue of being included in the Galacticus_Nodes module these functions have privileged access to the internal structure of all node component objects.
Component Initialization¶
Initialization of a component module (if necessary, for example, to read parameters or allocate workspace) can occur at a number of different points in the execution of Galacticus. Providing initialization occurs in advance of any calculations then any point is acceptable. One possibility is simply to call an initialization function at the head of all functions defined in the component module. This initialization function should return immediately if it has already been called (to avoid duplicate initialization). Another option is to use a mergerTreeOperator to perform initialization just before merger trees are constructed (the initialization function must again return immediately if it has been previously called).
Alternatively, a component may register initialization (and uninitialization) functions using the following directives. Each is placed immediately before the subroutine that it registers, and names that subroutine in a function attribute (these directives, like all of the task directives described in this chapter, attach their function to a static event hook point—see Section Event Hooks). For example:
!![
<nodeComponentThreadInitializationTask function="Node_Component_Black_Hole_Standard_Thread_Initialize"/>
!!]
nodeComponentInitializationTaskThe function is called once when node components are first initialized. It receives a single argument,
parameters, of typetype(inputParameters), intent(inout), from which any required parameters can be read.nodeComponentThreadInitializationTaskThe function is called by each thread prior to merger tree evolution (with the same interface as above), and can therefore be used to perform any “per thread” initialization—for example, constructing threadprivate objects, or attaching functions to event hooks.
nodeComponentThreadUninitializationTaskThe function (which takes no arguments) is called by each thread once merger tree evolution is complete, and should undo any per-thread initialization (e.g. detaching functions from event hooks, and releasing objects).
Component Access, Creation and Destruction¶
When a node is created, it initially contains no components. A component must therefore create itself on the fly as needed. Typically, a component is first created when an attempt is made to set a property value, or to adjust the rate of change of a property value or in response to some event (e.g. a satellite component may be created in response to a node merging with a larger node). Requests for property values frequently do not require that the component exist, as a zero value can often be returned instead[2].
To access a component from a node, use:
myComponent => thisNode%<class>([instance=<N>,autoCreate=<create>])
where class is the component class required, the optional instance argument requests a specific instance of the component (relevant if the node contains more than one of a particular component, e.g. if it contains two supermassive black holes for example; if no instance is specified the first instance will be returned), and the autoCreate option specifies whether or not the component should be automatically created (assuming it does not already exist). autoCreate\(=\)true should be used to create components initially.
A component of a node can be destroyed using:
call thisNode%<class>Destroy()
Component Implementations¶
Component implementations optionally provide functions to get and set their properties (and to set the rate of change of evolvable properties) so that other components and functions within Galacticus to can interact with them in a way that is independent of the specific component implementation chosen. To permit this, Galacticus creates functions for each property to access it in all permitted ways. For example, the exponential implementation of the disk component class has a “massStellar” property defined by:
<method>
<name>massStellar</name>
<type>real</type>
<rank>0</rank>
<attributes isSettable="true" isGettable="true" isEvolvable="true" />
</method>
This causes Galacticus to define several functions bound to the nodeComponentDisk class:
massStellarIsSettableReturns
trueif this property is settable;massStellarIsGettableReturns
trueif this property is gettable;massStellarSetSets the value of this property to the supplied argument;
massStellarGetGets the value of this property;
massStellarRateCumulates its argument to the rate of change of this property;
massStellarScaleSets the absolute scale for this property used in ODE error control;
along with several others used internally for output, serialization etc.
Component Evolution¶
All component properties which have an isEvolvable attribute set to true are included in Galacticus’s ODE solver as the node is evolved forward in time. As described in Section Component Implementations, Galacticus will create two functions that permit the rate of change of a property adjusted and for the absolute scale used in ODE error control to be set.
When evolving ODEs the ODE solver aims to keep the error on property \(i\) below
where \(epsilon_\mathrm{abs}=\)[odeToleranceAbsolute], \(epsilon_\mathrm{rel}=\)[odeToleranceRelative], \(y_i\) is the value of property \(i\) and \(s_i\) is a scaling factor which controls the absolute tolerance for this property. By default, \(s_i=1\), but this can be changed for a component utilizing the scaleSetTask directive. This allows a function to be called in which the component sets suitable scale factors for each of its properties prior to any ODE evolution being carried out. This can be very useful, for example, in cases where two components are coupled. Consider a case where a disk is transferring material to a spheroid via a bar instability. If the disk is orders of magnitude more massive that the spheroid then the rate of mass transfer can be very high (i.e. \(\dot{y}/y\) for the spheroid will be large). With just a relative tolerance (i.e. the \(\epsilon_\mathrm{rel} |y_i|\) term) this would require very short timesteps for the spheroid. However, in such cases we don’t care about such tiny tolerances for the spheroid (since it will grow to be substantially more massive). Therefore, it may be appropriate to set \(s_i\) to be equal to the sum of the disk and spheroid properties for example. The scale set directive and associated subroutine should follow this template:
!![
<scaleSetTask function="Node_Component_Disk_Exponential_Scale_Set"/>
!!]
subroutine Node_Component_Disk_Exponential_Scale_Set(node)
implicit none
type (treeNode ), pointer, intent(inout) :: node
class(nodeComponentDisk), pointer :: disk
! Get the disk component.
disk => node%disk()
! Check if an exponential disk component exists.
select type (disk)
class is (nodeComponentDiskExponential)
...
call disk%massStellarScale(massScale)
...
end select
return
end subroutine Node_Component_Disk_Exponential_Scale_Set
Sensible choices for the \(s_i\) factors can significantly speed-up execution of Galacticus.
Other Component Task Directives¶
Several further task directives allow a component to hook into specific points of the evolution cycle. Each takes the same form as scaleSetTask above—the directive is placed immediately before the subroutine to be registered, and names it in a function attribute:
preEvolveTaskThe function is called for each node before differential evolution of that node begins. The interface is
(node)withnodeof typetype(treeNode), intent(inout), pointer. This is useful, for example, to ensure a component has been initialized before evolution starts.inactiveSetTaskThe function is called (when an ODE solver making use of inactive-property optimizations is in use) before evolution of each node, and should mark as inactive any properties of the component which are known to not affect the evolution of other (active) properties, by calling their
JacobianZeromethods. The interface is the same as forpreEvolveTask.stateStoreTaskandstateRetrieveTaskThe functions are called when the internal state is written to (or restored from) file to allow restarts, and should store (or restore) any state held by the component (typically making use of the
stateStore/stateRestoredirectives—see Section State Storing). The interface is(stateFile,gslStateFile,stateOperationID)wherestateFileis aninteger,gslStateFileis atype(c_ptr), andstateOperationIDis aninteger(c_size_t), allintent(in).
Evolution Interrupts¶
It is often necessary to interrupt the smooth ODE evolution of a node in Galacticus. This can happen if, for example, a galaxy mergers with another galaxy (in which case the merger must be processed prior to further evolution) or if a component must be created before evolution can continue. The rate adjust and rate compute subroutines allow for interrupts to be flagged via their interrupt and interruptProcedure arguments. If an interrupt is required then interrupt should be set to true, while interruptProcedure should be set to point to a procedure which will handle the interrupt. Then, providing no other interrupt occurred earlier, the evolution will be stopped and the interrupt procedure called before evolution is continued.
An interrupt procedure should have the form:
subroutine My_Interrupt_Procedure(thisNode)
implicit none
type(treeNode), pointer, intent(inout) :: thisNode
! Do whatever needs to be done to handle the interrupt.
return
end subroutine My_Interrupt_Procedure
Existing Classes¶
Function Classes¶
Functions implement basic calculations (e.g. computing the power spectrum). Additional implementations of a functionClass are added using the a directive with the name of that functionClass. The implementation should be placed in a file containing the directive. For example, for the mergerTreeTimestep functionClass the file must containing a directive of the form
!![
<mergerTreeTimestep name="mergerTreeTimestepMyImplementation">
<description>A short description of the implementation.</description>
</mergerTreeTimestep>
!!]
where MyImplementation is an appropriate name for the implementation. This file should be treated as a regular Fortran submodule, but without the initial submodule and final end submodule lines. That is, it may contain use statements and variable declarations prior to the contains line, and should contain all functions required by the implementation after that line. It is standard, but not required, that function names should be prefixed with the name of the implementation (e.g. “MyImplementation” in the above example). The file must define a type that extends the named functionClass class (or extends another type which is itself an extension of the named functionClass class), containing any data needed by the implementation along with type-bound functions required by the implementation.
Events¶
Events are triggered during merger tree evolution. Examples are when a node needs to be promoted to its parent node, or when a minor node merges with its parent. Code responds to such events by attaching functions to the corresponding event hooks (see Section Event Hooks)—for example, the nodePromotion event (triggered when a primary progenitor reaches the time of its parent halo), the haloFormation event (triggered when a halo is deemed to have formed, or reformed), the satelliteHostChange event (triggered when a satellite node moves to a new host), and the mergerTreeExtraOutput event (triggered for each node at each output time, allowing extra, non-standard output to be written). Attachment is done at run time via the event’s attach method (typically from a functionClass object’s autoHook method, or from a node component’s thread initialization task).
Note
In older versions of Galacticus these events were handled by dedicated directives (nodePromotionTask, haloFormationTask, satelliteHostChangeTask, mergerTreeExtraOutputTask, hdfPreCloseTask, and the mergerTreeOutputPropertyCount/mergerTreeOutputNames/mergerTreeOutputTask family). These directives no longer exist—use the event hooks described above instead (for output tasks, use a nodePropertyExtractorClass implementation, or attach to the mergerTreeExtraOutput or outputFileClose events).
Tasks¶
Tasks are any processing which must be performed at some specific point in the execution of Galacticus (e.g. before or after evolving a node, or when the output file is opened or closed). A function is registered as a task by a directive placed immediately before it, naming it in a function attribute. Each such directive corresponds to a static event hook point (an eventHookStatic directive of the same name—see Section Event Hooks) at which all registered functions are called. An optional after attribute (naming another registered function) may be used to constrain the order in which the registered functions are called.
The component-related task directives (nodeComponentInitializationTask, nodeComponentThreadInitializationTask, nodeComponentThreadUninitializationTask, scaleSetTask, preEvolveTask, inactiveSetTask, postStepTask, stateStoreTask, and stateRetrieveTask) are described in Section Implementing a New Component. In addition, the following global task directives are available:
outputFileOpenThe function is called immediately after the Galacticus output HDF5 file is opened (useful for writing one-time datasets, e.g. version or build information). The function takes no arguments.
outputFileCloseThe function is called immediately prior to closing the Galacticus output HDF5 file (typically to write accumulated data to that file). The function takes no arguments.
universePostEvolveTaskThe function is called once evolution of all merger trees is complete. The function takes no arguments.
Merger Tree Initialization Tasks¶
Additional tasks to be performed during merger tree initialization can be added using the mergerTreeInitializeTask directive. For example, the standard basic component uses this directive as follows:
!![
<mergerTreeInitializeTask function="Halo_Mass_Accretion_Rate"/>
!!]
Here, Halo_Mass_Accretion_Rate is the name of a subroutine which will be called to perform whatever initialization is required. The subroutine must have the following form:
subroutine Merger_Tree_Initialize_Task(node)
implicit none
type(treeNode), pointer, intent(inout) :: node
.
.
.
return
end subroutine Merger_Tree_Initialize_Task
where node is the node to be initialized. The subroutine will be called once for each node in the tree.
Post-step Tasks¶
Additional methods for post-step tasks (i.e. things that should be done after each ODE solver step when evolving a node differentially) can be added using the postStepTask directive. For example, the standard hot halo component adds a task as follows:
!![
<postStepTask function="Node_Component_Hot_Halo_Standard_Post_Step"/>
!!]
Here, Node_Component_Hot_Halo_Standard_Post_Step is the name of a subroutine which will be called to perform whatever tasks are required. The subroutine must have the following form:
subroutine Post_Step_Task(node,status)
implicit none
type (treeNode), intent(inout), pointer :: node
integer , intent(inout) :: status
.
.
.
return
end subroutine Post_Step_Task
where node is the node for which tasks should be performed. If any change is made to the state of the node then status should be set equal to GSL_Failure. Tasks typically involve cleaning up after differential evolution.
Radius Solver Tasks¶
Galactic radii solver functions (see galacticStructureSolver) need to be able to interact with the components of a tree node to
Determine whether the node is physically plausible (and hence whether radii should be solved for at all);
Determine which components want a radius to be solved for, and get and set the properties of those components.
The radiusSolverPlausibility and radiusSolverTask directives facilitate this. A component which has a radius to be solved for should include directives of the form:
!![
<radiusSolverPlausibility function="Component_Radius_Solver_Plausibility"/>
!!]
and
!![
<radiusSolverTask function="Component_Radius_Solver"/>
!!]
where Component_Radius_Solver_Plausibility is the name of a subroutine which will specify whether or not the component is physically plausible for radius solving (e.g. has non-negative mass) and should have the following form:
subroutine Component_Radius_Solver_Plausibility(node)
implicit none
type(treeNode), intent(inout) :: node
.
.
.
return
end subroutine Component_Radius_Solver_Plausibility
which should set node%isPhysicallyPlausible (and/or node%isSolvable) to false if the component is not physically plausible, but should otherwise leave them unchanged. Additionally, Component_Radius_Solver is the name of a subroutine which will supply the necessary information about the node, and which should have the following form:
subroutine Component_Radius_Solver(node,componentActive,component,specificAngularMomentumRequired,specificAngularMomentum, &
& Radius_Get,Radius_Set,Velocity_Get,Velocity_Set)
implicit none
type (treeNode ), intent(inout) :: node
logical , intent( out) :: componentActive
type (enumerationComponentTypeType), intent( out) :: component
logical , intent(in ) :: specificAngularMomentumRequired
double precision , intent( out) :: specificAngularMomentum
procedure (... ), intent( out), pointer :: Radius_Get,Velocity_Get
procedure (... ), intent( out), pointer :: Radius_Set,Velocity_Set
.
.
.
return
end subroutine Component_Radius_Solver
When called, the subroutine should set componentActive to indicate whether or not this node contains an active component of the type, and component to the corresponding component type (e.g. componentTypeDisk; see Section Identifying Components and Mass Types). If the component is active, it should also set specificAngularMomentum to reflect the specific angular momentum (in km s\(^{-1}\) Mpc) of the component (at whatever point in its profile the radius is required; this is required only if specificAngularMomentumRequired is true) and should point the four procedure pointers to routines which get and set the radius and circular velocity properties of the component (which should have the standard form for component get and set methods). It is acceptable for the set procedures to point to dummy routines.
The galactic structure radii solver routines will use this information to determine (and set) the radius and circular velocity of the component. An advantage of this approach is that different radii solver methods can all use this same system, ensuring that just a single interface is needed in each component.
Analytic Solvers¶
Galacticus typically solves the system of ODE which describe the evolution of a galaxy using a numerical solver. However, in some situations analytic solutions may be available. Galacticus allows for the use of analytic solvers in such cases. Currently available analytic solvers are described below.
Simple Disk Satellites¶
This solver, which can be activated by setting [diskVerySimpleUseAnalyticSolver]\(=\)true is applicable to satellite systems in the very simple disk component. In particular, the following conditions must be met for it to be valid:
The hot halo component must be of type
verySimpleorverySimpleDelayed;The satellite component must be of type
verySimple;The disk component must be of type
verySimple;Properties of all other components must not change for satellite galaxies (with the exception of the
timeproperty of the basic component), or else the component must benull;Disk star formation and outflow rates must scale as \(M_\mathrm{gas}/\tau\) where \(M_\mathrm{gas}\) is the instantaneous mass of gas in the galaxy disk, and \(\tau\) is a fixed timescale for any given satellite galaxy.
Under these conditions, the flow of mass between gas, stellar, and outflowed phases is analytically solvable.
Subsystems¶
This section describes some of the subsystems within Galacticus that support various physical entities or processes.
Kepler Orbits¶
The keplerOrbit object (provided by the Kepler_Orbits_Structure module) stores the parameters of a single Keplerian orbit. It internally handles computation of additional/alternate orbital parameters once an orbit has been fully defined. Currently, the orientation of orbits (i.e. the unit vector normal to the orbital plane and the argument of periapsis) is not tracked. As such, orbits are fully defined by three parameters (in addition to the masses of the orbiting bodies). The following limitations presently apply to the keplerOrbit object:
If an orbit is overdefined (i.e. if more than three parameters are set manually) no checking is performed to ensure that the parameters are consistent with a Keplerian orbit;
Not all interconversions between parameters are supported[3]. If a conversion cannot be performed, an error message will be given.
A keplerOrbit object can be reset by calling the reset() method, and its defined/undefined status can be tested with the isDefined() method or asserted with the assertIsDefined() method. The following orbital parameters are supported, each method returning the value of the parameter and a corresponding method suffixed with Set can be used to set the parameter: radius, velocityRadial, velocityTangential, energy, angularMomentum, eccentricity, semiMajorAxis, radiusPericenter, radiusApocenter. Additionally, the masses of the orbiting bodies are provided by the hostMass() and reducedMassSpecific()\(=M_\mathrm{host}/(M_\mathrm{host}+M_\mathrm{satellite})\) methods. Finally, the velocityScale() method returns \(\mathrm{G}M_\mathrm{host}/r\) where \(r\) is the radius of the orbit.
Chemicals¶
The chemicals subsystem provides both a interface to a database of known chemicals (allowing their physical properties to be queried) and a structure to store abundances/masses/etc. of the set of chemicals being tracked in Galacticus. The name “chemicals” is used to denote any chemical species that might be involved in reactions, including molecules, atoms, atomic and molecular ions and electrons.
Chemical Database¶
The file data/Chemical_Database.cml contains a database of chemicals that can currently be used by Galacticus. It uses a simplified version of the Chemical Markup Language to describe chemicals. An excerpt from the database is shown below:
<list>
<chemical>
<id>MolecularHydrogenAnion</id>
<formalCharge>-1</formalCharge>
<atomArray>
<atom>
<id>1</id>
<elementType>H</elementType>
</atom>
<atom>
<id>2</id>
<elementType>H</elementType>
</atom>
</atomArray>
<bondArray>
<bond>
<atomRefs2>1 2</atomRefs2>
<order>1</order>
</bond>
</bondArray>
</chemical>
.
.
.
</list>
The database contains a list of chemicals, each contained within a chemical element. The id element provides a label for the chemical (usually a descriptive name with no white space). The formalCharge element gives the charge of the chemical in units of the elementary charge. The chemical is then describe by a list of atoms and bonds inside atomArray and bondArray elements respectively. The atomArray can contain any number of atom elements, which should describe each atom in the chemical giving it a unique id number and an elementType, which is the short one or two letter label for the element (e.g. H, Ni, etc.). The bondArray should contain a bond entry for each atomic bond, which itself contains a atomRefs2 element giving the IDs of the two atoms participating in the bond and an order element which gives the order of the bond (e.g. “1” for a single bond).
Chemical Structure¶
Within Galacticus a chemical is represented using the chemicalStructure type which is provided by the Chemical_Structures module. A chemicalStructure object can be assigned a particular chemical by retrieving that chemical from the database using:
call myChemical%retrieve("chemicalID")
where chemicalID is the ID of the chemical in the database. Any chemical can be exported to a CML file using
call myChemical%export(fileName)
where fileName gives the name of the file to which to export.
Once assigned a chemical, basic properties such as mass and charge (in atomic units) can be accessed using myChemical%mass and myChemical%charge respectively. The mass is computed from the known atomic masses of the constituent atoms of the chemical.
Chemical Abundances¶
Within Galacticus a set of abundances (or masses, or densities…) for all chemicals being tracked, as specified by the [chemicalsToTrack] input parameter, is stored within a chemicalAbundancesStructure type, as provided by the Chemical_Abundances_Structure module. The structure provides interfaces for setting and retrieving the abundance of a given chemical species, to pack/unpack all chemicals to/from an array, to convert from mass-weighted to number-weighted quantities and to multiply and divide the chemicals abundances by a given amount. Additionally, the Chemical_Abundances_Structure module provides functions which provide a count of the number of chemicals tracked, to look up the index of a chemical array representation from its name, and to retrieve the name of a given chemical.
Coordinates¶
The coordinate class describes a position in three-dimensional space. Each extension of this class (currently, coordinateCartesian, coordinateCylindrical, and coordinateSpherical) supply methods to convert to and from Cartesian coordinates. The assignment operator (=) is overloaded such that coordinate objects of any class can be assigned to any other class and conversion to the appropriate coordinate system will happen automatically. A function accepting a coordinate object can therefore convert it to, for example, spherical coordinates simply using
class(coordinate ), intent(in) :: coordinates
type (coordinateSpherical) :: coordinatesSpherical
coordinatesSpherical=coordinates
and thereby allow a position to be passed to it in any coordinate system.