diff --git a/aux/words.dict b/aux/words.dict index 7b478a1f43..92536c55fb 100644 --- a/aux/words.dict +++ b/aux/words.dict @@ -319,6 +319,7 @@ christoph chunked chunking circum +circumgalactic circumnuclear cladistic collisional @@ -669,11 +670,13 @@ sanes scalarize scalarizes scalelengths +scaler scalings sendmail sersic sexagesimal shakura +sidm sightline sigmoid sinusoid diff --git a/doc/Galacticus.bib b/doc/Galacticus.bib index 793ad7a529..c43deb311f 100644 --- a/doc/Galacticus.bib +++ b/doc/Galacticus.bib @@ -3715,6 +3715,24 @@ @article{jiang_generating_2014 pages = {193--207} } +@article{jiang_semi-analytic_2023, + title = {A semi-analytic study of self-interacting dark-matter haloes with baryons}, + volume = {521}, + issn = {0035-8711}, + url = {https://ui.adsabs.harvard.edu/abs/2023MNRAS.521.4630J}, + doi = {10.1093/mnras/stad705}, + abstract = {We combine the isothermal Jeans model and the model of adiabatic halo contraction into a semi-analytic procedure for computing the density profile of self-interacting dark-matter (SIDM) haloes with the gravitational influence from the inhabitant galaxies. The model agrees well with cosmological SIDM simulations over the entire core-forming stage up to the onset of gravothermal core-collapse. Using this model, we show that the halo response to baryons is more diverse in SIDM than in CDM and depends sensitively on galaxy size, a desirable feature in the context of the structural diversity of bright dwarfs. The fast speed of the method facilitates analyses that would be challenging for numerical simulations - notably, we quantify the SIDM halo response as functions of the baryonic properties, on a fine mesh grid spanned by the baryon-to-total-mass ratio, Mb/Mvir, and galaxy compactness, r1/2/Rvir; we show with high statistical precision that for typical Milky-Way-like systems, the SIDM profiles are similar to their CDM counterparts; and we delineate the regime of core-collapse in the Mb/Mvir - r1/2/Rvir space, for a given cross section and concentration. Finally, we compare the isothermal Jeans model with the more sophisticated gravothermal fluid model, and show that the former yields faster core formation and agrees better with cosmological simulations. We attribute the difference to whether the target CDM halo is used as a boundary condition or as the initial condition for the gravothermal evolution, and thus comment on possible improvements of the fluid model. We have made our model publicly available at https://github.com/JiangFangzhou/SIDM.}, + urldate = {2023-08-02}, + journal = {Monthly Notices of the Royal Astronomical Society}, + author = {Jiang, Fangzhou and Benson, Andrew and Hopkins, Philip F. and Slone, Oren and Lisanti, Mariangela and Kaplinghat, Manoj and Peter, Annika H. G. and Zeng, Zhichao Carton and Du, Xiaolong and Yang, Shengqi and Shen, Xuejian}, + month = may, + year = {2023}, + note = {ADS Bibcode: 2023MNRAS.521.4630J}, + keywords = {Astrophysics - Cosmology and Nongalactic Astrophysics, Astrophysics - High Energy Astrophysical Phenomena, cosmology: dark matter, galaxies: dwarf, galaxies: evolution, galaxies: haloes, galaxies: structure}, + pages = {4630--4644}, + file = {Full Text PDF:/home/abensonca/.mozilla/firefox/f54gqgdx.default/zotero/storage/PJTZC47T/Jiang et al. - 2023 - A semi-analytic study of self-interacting dark-mat.pdf:application/pdf}, +} + @article{johnson_random_2021, title = {A {Random} {Walk} {Model} for {Dark} {Matter} {Halo} {Concentrations}}, volume = {908}, diff --git a/parameters/reference/evolutionGalaxyFormation.xml b/parameters/reference/evolutionGalaxyFormation.xml index a573b61919..59d5b95ba7 100644 --- a/parameters/reference/evolutionGalaxyFormation.xml +++ b/parameters/reference/evolutionGalaxyFormation.xml @@ -117,9 +117,7 @@ - - - + diff --git a/parameters/reference/evolutionGalaxyFormationNBody.xml b/parameters/reference/evolutionGalaxyFormationNBody.xml index e1a5e118c5..7b5c36c1e4 100644 --- a/parameters/reference/evolutionGalaxyFormationNBody.xml +++ b/parameters/reference/evolutionGalaxyFormationNBody.xml @@ -107,9 +107,7 @@ - - - + diff --git a/parameters/treeAugmentMonteCarlo.xml b/parameters/treeAugmentMonteCarlo.xml index fc132bc45a..2cb35b46b9 100644 --- a/parameters/treeAugmentMonteCarlo.xml +++ b/parameters/treeAugmentMonteCarlo.xml @@ -13,7 +13,7 @@ - + @@ -204,6 +204,8 @@ + + diff --git a/parameters/treeAugmentNBody.xml b/parameters/treeAugmentNBody.xml index eb1ef70b82..597f1967e8 100644 --- a/parameters/treeAugmentNBody.xml +++ b/parameters/treeAugmentNBody.xml @@ -11,7 +11,7 @@ - + @@ -166,9 +166,8 @@ - - - + + diff --git a/perl/Galacticus/Build/Components/Classes/Deferred.pm b/perl/Galacticus/Build/Components/Classes/Deferred.pm index 06927f2535..2a174fa813 100644 --- a/perl/Galacticus/Build/Components/Classes/Deferred.pm +++ b/perl/Galacticus/Build/Components/Classes/Deferred.pm @@ -84,7 +84,7 @@ sub Class_Deferred_Binding_Attachers { my $function = { type => "void", - name => $code::classFunctionName."DeferredFunctionSet", + name => $code::classFunctionName."DfrrdFnctnSet", description => "Set the function to be used for the {\\normalfont \\ttfamily ".$binding->{'method'}."} method of the {\\normalfont \\ttfamily ".$class->{'name'}."} component class.", variables => [ @@ -158,6 +158,8 @@ sub Class_Deferred_Binding_Wrappers { foreach $code::binding ( grep {$_->{'isDeferred'} && $_->{'bindsTo'} eq 'componentClass'} @{$member->{'bindings'}->{'binding'}} ) { # Create the name of the function. $code::classFunctionName = $class->{'name'}.ucfirst($code::binding->{'method'}); + my $specificType = $code::binding->{'interface'}->{'type'} ne "void" && ! exists($intrinsicTypes{$code::binding->{'interface'}->{'type'}}); + my $isPointer = $specificType && $code::binding->{'interface'}->{'type'} =~ m/,\s*pointer/; # Create the function if necesasary. next if ( exists($classFunctions->{$code::classFunctionName}->{'wrapper'}) ); @@ -201,8 +203,9 @@ CODE call {$classFunctionName}Deferred({join(",",@arguments)}) CODE } else { + $code::assigner = $isPointer ? " => " : "="; $function->{'content'} .= fill_in_string(<<'CODE', PACKAGE => 'code'); - {$classFunctionName}={$classFunctionName}Deferred({join(",",@arguments)}) + {$classFunctionName}{$assigner}{$classFunctionName}Deferred({join(",",@arguments)}) CODE } $function->{'content'} .= fill_in_string(<<'CODE', PACKAGE => 'code'); diff --git a/perl/Galacticus/Build/Components/Components.pm b/perl/Galacticus/Build/Components/Components.pm index 3e5b73c84e..420aeb23f9 100644 --- a/perl/Galacticus/Build/Components/Components.pm +++ b/perl/Galacticus/Build/Components/Components.pm @@ -158,39 +158,19 @@ sub Build_Node_Component_Class { }, { type => "procedure" , - name => "enclosedMass" , - function => "Node_Component_Enclosed_Mass_Null" , - description => "Compute the mass enclosed within a radius." , - mappable => "summation" , - returnType => "\\doublezero" , - arguments => "\\doublezero\\ radius\\argin, \\enumComponentType\\ [componentType]\\argin, \\enumMassType\\ [massType]\\argin, \\enumWeightBy\\ [weightBy]\\argin, \\intzero\\ [weightIndex]\\argin" - }, - { - type => "procedure" , - name => "acceleration" , - function => "Node_Component_Acceleration_Null" , - description => "Compute the gravitational acceleration at a point." , - mappable => "summation" , - returnType => "\\doubleone" , - arguments => "\\doubleone\\ position\\argin, \\enumComponentType\\ [componentType]\\argin, \\enumMassType\\ [massType]\\argin" + name => "massDistribution" , + function => "Node_Component_Mass_Distribution_Null" , + description => "Return the mass distribution for this component." , + returnType => "\\textcolor{red}{\\textless class(massDistribution)\\textgreater}" , + arguments => "\\textcolor{red}{\\textless type(enumerationComponentTypeType)\\textgreater} [componentType]\\argin, \\textcolor{red}{\\textless type(enumeratioMassTypeType)\\textgreater} [massType]\\argin, \\textcolor{red}{\\textless type(enumeratioWeightByType)\\textgreater} [weightBy]\\argin, \\intzero\\ [weightIndex]\\argin" }, { type => "procedure" , - name => "chandrasekharIntegral" , - function => "Node_Component_Chandrasekhar_Integral_Null" , - description => "Compute the Chandrasekhar integral for a given position and velocity." , - mappable => "summation" , - returnType => "\\doubleone" , - arguments => "\\doubleone\\ position\\argin, \\doubleone\\ velocity\\argin, \\doublezero\\ radiusHalfMass\\argin, \\enumComponentType\\ [componentType]\\argin, \\enumMassType\\ [massType]\\argin" - }, - { - type => "procedure" , - name => "tidalTensor" , - function => "Node_Component_Tidal_Tensor_Null" , - description => "Compute the gravitational tidal tensor at a point." , - mappable => "summation" , - returnType => "\\textcolor{red}{\\textless type(tensorRank2Dimension3Symmetric)\\textgreater}" , - arguments => "\\doubleone\\ position\\argin, \\enumComponentType\\ [componentType]\\argin, \\enumMassType\\ [massType]\\argin" + name => "massBaryonic" , + function => "Node_Component_Mass_Baryonic_Null" , + description => "Return the total baryonic mass for this component." , + returnType => "\\doublezero" , + arguments => "" }, { type => "procedure" , @@ -218,33 +198,6 @@ sub Build_Node_Component_Class { mappable => "summation" , returnType => "\\doublezero" , arguments => "\\textcolor{red}{\\textless double(3)\\textgreater} positionCylindrical\\argin, \\enumComponentType\\ [componentType]\\argin, \\enumMassType\\ [massType]\\argin, \\enumWeightBy\\ [weightBy]\\argin, \\intzero\\ [weightIndex]\\argin" - }, - { - type => "procedure" , - name => "potential" , - function => "Node_Component_Potential_Null" , - description => "Compute the gravitational potential." , - mappable => "summation" , - returnType => "\\doublezero" , - arguments => "\\doublezero\\ radius\\argin, \\enumComponentType\\ [componentType]\\argin, \\enumMassType\\ [massType]\\argin" - }, - { - type => "procedure" , - name => "rotationCurve" , - function => "Node_Component_Rotation_Curve_Null" , - description => "Compute the rotation curve." , - mappable => "summation" , - returnType => "\\doublezero" , - arguments => "\\doublezero\\ radius\\argin, \\enumComponentType\\ [componentType]\\argin, \\enumMassType\\ [massType]\\argin" - }, - { - type => "procedure" , - name => "rotationCurveGradient" , - function => "Node_Component_Rotation_Curve_Gradient_Null" , - description => "Compute the rotation curve gradient." , - mappable => "summation" , - returnType => "\\doublezero" , - arguments => "\\doublezero\\ radius\\argin, \\enumComponentType\\ [componentType]\\argin, \\enumMassType\\ [massType]\\argin" } ); # Add meta-property methods. diff --git a/perl/Galacticus/Build/Components/Implementations/Deferred.pm b/perl/Galacticus/Build/Components/Implementations/Deferred.pm index 2c444500fe..61efdf2b73 100644 --- a/perl/Galacticus/Build/Components/Implementations/Deferred.pm +++ b/perl/Galacticus/Build/Components/Implementations/Deferred.pm @@ -38,13 +38,18 @@ sub Implementation_Deferred_Binding_Pointers { # Skip non-deferred bindings. next unless ( $binding->{'isDeferred'} ); + # Determine the name of the procedure template. + my $baseMember = $member; + while ( exists($baseMember->{'extends'}) && grep {$_->{'method'} eq $binding->{'method'}} @{$baseMember->{'extends'}->{'implementation'}->{'bindings'}->{'binding'}} ) { + $baseMember = $baseMember->{'extends'}; + } # Create a pointer at the implementation level. my $componentFunctionName = $class->{'name'}.ucfirst($member->{'name'}).ucfirst($binding->{'method'}); push( @{$build->{'variables'}}, { intrinsic => "procedure", - type => $class->{'name'}.ucfirst($member->{'name'}).ucfirst($binding->{'method'}), + type => $class->{'name'}.ucfirst($baseMember->{'name'}).ucfirst($binding->{'method'}), attributes => [ "pointer" ], variables => [ $componentFunctionName."Deferred" ] }, @@ -65,17 +70,22 @@ sub Implementation_Deferred_Binding_Attachers { foreach my $binding ( grep {$_->{'isDeferred'}} @{$member->{'bindings'}->{'binding'}} ) { # Create the name of the function. $code::memberFunctionName = $class->{'name'}.ucfirst($member->{'name'}).ucfirst($binding->{'method'}); + # Determine the name of the procedure template. + my $baseMember = $member; + while ( exists($baseMember->{'extends'}) && grep {$_->{'method'} eq $binding->{'method'}} @{$baseMember->{'extends'}->{'implementation'}->{'bindings'}->{'binding'}} ) { + $baseMember = $baseMember->{'extends'}; + } # Create the function. my $function = { type => "void", - name => $code::memberFunctionName."DeferredFunctionSet", + name => $code::memberFunctionName."DfrrdFnctnSet", description => "Set the function to be used for the {\\normalfont \\ttfamily ".$binding->{'method'}."} method of the {\\normalfont \\ttfamily ".$member->{'name'}."} implementation of the {\\normalfont \\ttfamily ".$class->{'name'}."} component class.", variables => [ { intrinsic => "procedure", - type => $class->{'name'}.ucfirst($member->{'name'}).ucfirst($binding->{'method'}), + type => $class->{'name'}.ucfirst($baseMember->{'name'}).ucfirst($binding->{'method'}), isArgument => 1, variables => [ "deferredFunction" ] } @@ -138,12 +148,16 @@ sub Implementation_Deferred_Binding_Wrappers { # Iterate over deferred bindings which bind at the class level. foreach $code::binding ( grep {$_->{'isDeferred'}} @{$code::member->{'bindings'}->{'binding'}} ) { # Create the name of the function. - $code::memberFunctionName = $code::class->{'name'}.ucfirst($code::member->{'name'}).ucfirst($code::binding->{'method'}); - $code::returnName = $code::memberFunctionName; + $code::memberFunctionName = $code::class->{'name'}.ucfirst($code::member->{'name'}).ucfirst($code::binding->{'method'}); + $code::returnName = $code::memberFunctionName; + my $specificType = $code::binding->{'interface'}->{'type'} ne "void" && ! exists($intrinsicTypes{$code::binding->{'interface'}->{'type'}}); + my $isPointer = $specificType && $code::binding->{'interface'}->{'type'} =~ m/,\s*pointer/; + $code::returnName .="_" + if ( $specificType ); # Create the function. my $function = { - type => $code::binding->{'interface'}->{'type'} eq "void" ? "void" : $intrinsicTypes{$code::binding->{'interface'}->{'type'}}, + type => $code::binding->{'interface'}->{'type'} eq "void" ? "void" : ($specificType ? $code::binding->{'interface'}->{'type'}." => ".$code::returnName : $intrinsicTypes{$code::binding->{'interface'}->{'type'}}), name => $code::memberFunctionName, description => "Call the deferred function for the {\\normalfont \\ttfamily ".$code::binding->{'method'}."} method of the {\\normalfont \\ttfamily ".$code::class->{'name'}."} component class if it has been set.", modules => @@ -151,6 +165,8 @@ sub Implementation_Deferred_Binding_Wrappers { "Error" ] }; + push(@{$function->{'modules'}},&List::ExtraUtils::as_array($code::binding->{'interface'}->{'module'})) + if ( exists($code::binding->{'interface'}->{'module'}) ); # Handle any rank/shape. if ( exists($code::binding->{'interface'}->{'rank'}) && $code::binding->{'interface'}->{'rank'} > 0 ) { die('can not specify both "rank" and "shape"') @@ -193,8 +209,9 @@ CODE call {$memberFunctionName}Deferred({join(",",@arguments)}) CODE } else { + $code::assigner = $isPointer ? " => " : "="; $function->{'content'} .= fill_in_string(<<'CODE', PACKAGE => 'code'); - {$returnName}={$memberFunctionName}Deferred({join(",",@arguments)}) + {$returnName}{$assigner}{$memberFunctionName}Deferred({join(",",@arguments)}) CODE } $function->{'content'} .= fill_in_string(<<'CODE', PACKAGE => 'code'); @@ -212,8 +229,9 @@ CODE call self%{$parentType}%{$binding->{'method'}}({join(",",grep {$_ ne "self"} @arguments)}) CODE } else { + $code::assigner = $isPointer ? " => " : "="; $function->{'content'} .= fill_in_string(<<'CODE', PACKAGE => 'code'); - {$returnName}=self%{$parentType}%{$binding->{'method'}}({join(",",grep {$_ ne "self"} @arguments)}) + {$returnName}{$assigner}self%{$parentType}%{$binding->{'method'}}({join(",",grep {$_ ne "self"} @arguments)}) CODE } } else { diff --git a/perl/Galacticus/Build/Components/TreeNodes/Utils.pm b/perl/Galacticus/Build/Components/TreeNodes/Utils.pm index af22331227..920029b27d 100644 --- a/perl/Galacticus/Build/Components/TreeNodes/Utils.pm +++ b/perl/Galacticus/Build/Components/TreeNodes/Utils.pm @@ -19,8 +19,10 @@ use Galacticus::Build::Components::DataTypes; { functions => [ - \&Tree_Node_Copy, - \&Tree_Node_Move + \&Tree_Node_Copy , + \&Tree_Node_Move , + \&Tree_Node_Mass_Distribution, + \&Tree_Node_Mass_Baryonic ] } ); @@ -193,4 +195,383 @@ CODE ); } +sub Tree_Node_Mass_Distribution { + # Generate a function to construct and return the mass distribution associated with a node. + my $build = shift(); + my $function = + { + type => "class(massDistributionClass), pointer => massDistribution_", + name => "treeNodeMassDistribution", + description => "Construct and return the mass distribution associated with {\\normalfont \\ttfamily self}.", + modules => + [ + "Mass_Distributions , only : massDistributionClass , massDistributionComposite, massDistributionList , massDistributionZero, kinematicsDistributionClass, kinematicsDistributionIsothermal" , + "Galactic_Structure_Options, only : enumerationComponentTypeType, enumerationMassTypeType , enumerationWeightByType, componentTypeAll , componentTypeDarkMatterOnly, massTypeAll , massTypeDark, weightByMass" + ], + variables => + [ + { + intrinsic => "class", + type => "treeNode", + attributes => [ "intent(inout)" ], + variables => [ "self" ] + }, + { + intrinsic => "type", + type => "enumerationComponentTypeType", + attributes => [ "intent(in )", "optional" ], + variables => [ "componentType" ] + }, + { + intrinsic => "type", + type => "enumerationMassTypeType", + attributes => [ "intent(in )", "optional" ], + variables => [ "massType" ] + }, + { + intrinsic => "type", + type => "enumerationWeightByType", + attributes => [ "intent(in )", "optional" ], + variables => [ "weightBy" ] + }, + { + intrinsic => "integer", + attributes => [ "intent(in )", "optional" ], + variables => [ "weightIndex" ] + }, + { + intrinsic => "type", + type => "enumerationComponentTypeType", + variables => [ "componentType_", "componentType__" ] + }, + { + intrinsic => "type", + type => "enumerationMassTypeType", + variables => [ "massType_", "massType__" ] + }, + { + intrinsic => "type", + type => "enumerationWeightByType", + variables => [ "weightBy_" ] + }, + { + intrinsic => "integer", + variables => [ "weightIndex_" ] + }, + { + intrinsic => "class", + type => "massDistributionClass", + attributes => [ "pointer" ], + variables => [ "massDistributionComponent" ] + }, + { + intrinsic => "integer", + variables => [ "i", "iMassDistribution", "iMassDistributionAll", "iMassDistributionConstruct", "iEmpty" ] + }, + { + intrinsic => "logical", + variables => [ "construct", "isDarkMatterOnly" ] + }, + { + intrinsic => "integer", + type => "kind_int8", + variables => [ "uniqueID", "uniqueIDParent" ] + }, + { + intrinsic => "type", + type => "massDistributionList", + attributes => [ "pointer" ], + variables => [ "massDistributionList_", "next_" ] + }, + { + intrinsic => "class", + type => "kinematicsDistributionClass", + attributes => [ "pointer" ], + variables => [ "kinematicsDistribution_" ] + } + ] + }; + $function->{'content'} .= fill_in_string(<<'CODE', PACKAGE => 'code'); +! Set defaults. +if (present(componentType)) then + componentType_=componentType +else + componentType_=componentTypeAll +end if +if (present(massType)) then + massType_=massType +else + massType_=massTypeAll +end if +if (present(weightBy)) then + weightBy_=weightBy +else + weightBy_=weightByMass +end if +if (present(weightIndex)) then + weightIndex_=weightIndex +else + weightIndex_=-1 +end if +! Search for a match to our ID. +iMassDistribution =0 +iMassDistributionAll=0 +iEmpty =0 +uniqueID =self %uniqueID() +if (associated(self%parent)) then + uniqueIDParent =self%parent%uniqueID() +else + uniqueIDParent =-1_kind_int8 +end if +do i=1,massDistributionsCount + if ( & + & massDistributions__(i)%uniqueID == uniqueID & + & .and. & + & massDistributions__(i)%componentType == componentType_ & + & .and. & + & massDistributions__(i)%massType == massType_ & + & .and. & + & massDistributions__(i)%weightBy == weightBy_ & + & .and. & + & massDistributions__(i)%weightIndex == weightIndex_ & + & ) then + iMassDistribution=i + exit + else if (massDistributions__(i)%uniqueID < 0_kind_int8 .and. iEmpty == 0) then + iEmpty =i + end if + if ( & + & massDistributions__(i)%uniqueID == uniqueID & + & .and. & + & massDistributions__(i)%componentType == componentTypeAll & + & .and. & + & massDistributions__(i)%massType == massTypeAll & + & .and. & + & massDistributions__(i)%weightBy == weightBy_ & + & .and. & + & massDistributions__(i)%weightIndex == weightIndex_ & + & ) then + iMassDistributionAll=i + end if +end do +! If we found no match, we need to create the distribution. +construct=.false. +if (iMassDistribution == 0) then + isDarkMatterOnly=.false. + if (componentType_ == componentTypeDarkMatterOnly) then + isDarkMatterOnly=.true. + construct =.true. + componentType__ =componentTypeDarkMatterOnly + massType__ =massType_ + else if (iMassDistributionAll == 0 ) then + construct =.true. + componentType__ =componentTypeAll + massType__ =massTypeAll + end if + ! If no existing all/all mass distribution matched..... + if (construct) then + if (iEmpty /= 0) then + ! If we have an empty slot, use that. + iMassDistributionConstruct=iEmpty + else + ! Simply use the next slot, unless it is occupied by a parent node massDistribution (unless we have no choice because we have run out of slots). + do i=1,massDistributionsCount+1 + massDistributionsLast=mod(massDistributionsLast,massDistributionsCount)+1 + if (massDistributions__(massDistributionsLast)%uniqueID == uniqueIDParent) cycle + exit + end do + iMassDistributionConstruct= massDistributionsLast + !![ + + !!] + massDistributions__(massDistributionsLast)%uniqueID=-huge(kind_int8) + end if + if (isDarkMatterOnly) then + iMassDistribution =iMassDistributionConstruct + else + iMassDistributionAll=iMassDistributionConstruct + end if + if (.not.associated(massDistributions__(iMassDistributionConstruct)%massDistribution_)) then + massDistributionList_ => null() + next_ => null() +CODE + # Iterate over all component classes + foreach $code::class ( &List::ExtraUtils::hashList($build->{'componentClasses'}) ) { + next + unless ( grep {$code::class->{'name'} eq $_} @{$build->{'componentClassListActive'}} ); + $function->{'content'} .= fill_in_string(<<'CODE', PACKAGE => 'code'); + if (allocated(self%component{ucfirst($class->{'name'})})) then + do i=1,size(self%component{ucfirst($class->{'name'})}) + massDistributionComponent => self%component{ucfirst($class->{'name'})}(i)%massDistribution(componentType__,massType__,weightBy_,weightIndex_) + if (associated(massDistributionComponent)) then + if (associated(massDistributionList_)) then + allocate(next_%next) + next_ => next_%next + else + allocate(massDistributionList_) + next_ => massDistributionList_ + end if + next_%massDistribution_ => massDistributionComponent + next_%next => null() + end if + end do + end if +CODE + } + $function->{'content'} .= fill_in_string(<<'CODE', PACKAGE => 'code'); + allocate(massDistributionComposite :: massDistributions__(iMassDistributionConstruct)%massDistribution_) + select type (massDistribution__ => massDistributions__(iMassDistributionConstruct)%massDistribution_) + type is (massDistributionComposite) + !![ + + !!] + end select + massDistributions__(iMassDistributionConstruct)%uniqueID =self%uniqueID () + massDistributions__(iMassDistributionConstruct)%componentType= componentType__ + massDistributions__(iMassDistributionConstruct)%massType = massType__ + massDistributions__(iMassDistributionConstruct)%weightBy = weightBy_ + massDistributions__(iMassDistributionConstruct)%weightIndex = weightIndex_ + next_ => massDistributionList_ + do while (associated(next_)) + !![ + + !!] + next_ => next_%next + end do + nullify(massDistributionList_ ) + end if + end if + if (isDarkMatterOnly) then + ! We already have the relevant mass distribution constructed in the iMassDistribution slot - nothing more to do here. + else if (componentType_ == componentTypeAll .and. massType_ == massTypeAll) then + ! The all/all mass distribution was required - we have just created it, so return it. + iMassDistribution=iMassDistributionAll + else + ! Some other mass distribution was required - get it as a subset of the all/all mass distribution. + iEmpty=0 + do i=1,massDistributionsCount + if (massDistributions__(i)%uniqueID < 0_kind_int8 .and. iEmpty == 0) iEmpty=i + end do + if (iEmpty /= 0) then + ! If we have an empty slot, use that. + iMassDistribution=iEmpty + else + ! Simply use the next slot, unless it is occupied by a parent node massDistribution (unless we have no choice because we have run out of slots). + do i=1,massDistributionsCount+1 + massDistributionsLast=mod(massDistributionsLast,massDistributionsCount)+1 + massDistributionsLast=mod(massDistributionsLast,massDistributionsCount)+1 + ! But never replace the all/all distribution. + if ( & + & massDistributions__(massDistributionsLast)%uniqueID == uniqueID & + & .and. & + & massDistributions__(massDistributionsLast)%componentType == componentTypeAll & + & .and. & + & massDistributions__(massDistributionsLast)%massType == massTypeAll & + & .and. & + & massDistributions__(massDistributionsLast)%weightBy == weightBy_ & + & .and. & + & massDistributions__(massDistributionsLast)%weightIndex == weightIndex_ & + & ) cycle + if ( & + & massDistributions__(massDistributionsLast)%uniqueID == uniqueIDParent & + & ) cycle + exit + end do + iMassDistribution=massDistributionsLast + !![ + + !!] + massDistributions__(massDistributionsLast)%uniqueID=-huge(kind_int8) + end if + massDistributions__(iMassDistribution)%massDistribution_ => massDistributions__(iMassDistributionAll)%massDistribution_%subset (componentType_,massType_) + massDistributions__(iMassDistribution)%uniqueID = self %uniqueID ( ) + massDistributions__(iMassDistribution)%componentType = componentType_ + massDistributions__(iMassDistribution)%massType = massType_ + massDistributions__(iMassDistribution)%weightBy = weightBy_ + massDistributions__(iMassDistribution)%weightIndex = weightIndex_ + if (.not.associated(massDistributions__(iMassDistribution)%massDistribution_)) then + allocate(massDistributionZero :: massDistributions__(iMassDistribution)%massDistribution_) + select type (massDistributions___ => massDistributions__(iMassDistribution)%massDistribution_) + type is (massDistributionZero) + !![ + + !!] + allocate(kinematicsDistributionIsothermal :: kinematicsDistribution_) + select type (kinematicsDistribution_) + type is (kinematicsDistributionIsothermal) + !![ + + !!] + end select + call massDistributions___%setKinematicsDistribution(kinematicsDistribution_) + !![ + + !!] + end select + end if + end if +end if +!![ + +!!] +CODE + # Insert a type-binding for this function into the treeNode type. + push( + @{$build->{'types'}->{'treeNode'}->{'boundFunctions'}}, + { + type => "procedure", + descriptor => $function, + name => "massDistribution" + } + ); +} + +sub Tree_Node_Mass_Baryonic { + # Generate a function to return the total baryonic mass associated with a node. + my $build = shift(); + my $function = + { + type => "double precision", + name => "treeNodeMassBaryonic", + description => "Return the total baryonic mass associated with {\\normalfont \\ttfamily self}.", + variables => + [ + { + intrinsic => "class", + type => "treeNode", + attributes => [ "intent(inout)" ], + variables => [ "self" ] + }, + { + intrinsic => "integer", + variables => [ "i" ] + } + ] + }; + $function->{'content'} .= fill_in_string(<<'CODE', PACKAGE => 'code'); +treeNodeMassBaryonic=0.0d0 +CODE + # Iterate over all component classes + foreach $code::class ( &List::ExtraUtils::hashList($build->{'componentClasses'}) ) { + next + unless ( grep {$code::class->{'name'} eq $_} @{$build->{'componentClassListActive'}} ); + $function->{'content'} .= fill_in_string(<<'CODE', PACKAGE => 'code'); + if (allocated(self%component{ucfirst($class->{'name'})})) then + do i=1,size(self%component{ucfirst($class->{'name'})}) + treeNodeMassbaryonic=treeNodeMassBaryonic+self%component{ucfirst($class->{'name'})}(i)%massBaryonic() + end do + end if +CODE + } + # Insert a type-binding for this function into the treeNode type. + push( + @{$build->{'types'}->{'treeNode'}->{'boundFunctions'}}, + { + type => "procedure", + descriptor => $function, + name => "massBaryonic" + } + ); +} + 1; diff --git a/perl/Galacticus/Build/SourceTree/Process/Constructors.pm b/perl/Galacticus/Build/SourceTree/Process/Constructors.pm index 5ef5c738af..63237ed4f3 100755 --- a/perl/Galacticus/Build/SourceTree/Process/Constructors.pm +++ b/perl/Galacticus/Build/SourceTree/Process/Constructors.pm @@ -29,6 +29,15 @@ sub Process_Constructors { my $depth = 0; while ( $node ) { if ( $node->{'type'} eq "constructorAssign" && ! $node->{'directive'}->{'processed'} ) { + # All variables named in the directive are assigned to + # `self`. Optional arguments and allocation of allocatables + # are automatically handled. By default standard assignment + # is used. If a variable name is prefixed with `*` then + # pointer assignment is used, and any `functionClass` + # objects have their reference count incremented. If + # prefixed with `*/` then pointer assignment is used but no + # reference count increment is performed. + # # Assert that our parent is a function. unless ( $node->{'parent'}->{'type'} eq "function" || $node->{'parent'}->{'type'} eq "moduleProcedure" ) { my $nodeRoot = $node; @@ -54,9 +63,10 @@ sub Process_Constructors { my $assignmentSource = " ! Auto-generated constructor assignment\n"; (my $variables = $node->{'directive'}->{'variables'}) =~ s/^\s*(.*?)\s*$/$1/; foreach ( grep {$_ ne ""} split(/\s*,\s*/,$variables) ) { - my $matches = $_ =~ m/^(\*??)([a-zA-Z0-9_]+)/; + my $matches = $_ =~ m/^(\*??)(\/??)([a-zA-Z0-9_]+)/; my $isPointer = $1 eq "*"; - my $argumentName = $2; + my $doCount = $2 ne "/"; + my $argumentName = $3; my $assigner = $isPointer ? " => " : "="; my $hasDefault = $_ =~ m/=\s*(.+)/; my $default = $hasDefault ? $1 : undef(); @@ -65,7 +75,7 @@ sub Process_Constructors { # Get the variable declaration. my $declaration; if ( $node->{'parent'}->{'type'} eq "moduleProcedure" ) { - + # Nothing to do. } else { $declaration = &Galacticus::Build::SourceTree::Parse::Declarations::GetDeclaration($node->{'parent'},$argumentName); } @@ -90,6 +100,8 @@ sub Process_Constructors { ( $declaration->{'intrinsic'} eq "type" || $declaration->{'intrinsic'} eq "class" ) && $isPointer + && + $doCount ) { my $type = lc($declaration->{'type'}); $type =~ s/\s//g; diff --git a/perl/Galacticus/Build/SourceTree/Process/EventHooks.pm b/perl/Galacticus/Build/SourceTree/Process/EventHooks.pm index 944973eb77..63c009984d 100644 --- a/perl/Galacticus/Build/SourceTree/Process/EventHooks.pm +++ b/perl/Galacticus/Build/SourceTree/Process/EventHooks.pm @@ -496,7 +496,7 @@ CODE my $waitTimeWriter = fill_in_string(<<'CODE', PACKAGE => 'code'); subroutine eventsHooksWaitTimes() #ifdef OMPPROFILE - use :: Galacticus_HDF5 , only : galacticusOutputFile + use :: Output_HDF5 , only : outputFile use :: IO_HDF5 , only : hdf5Object use :: HDF5_Access , only : hdf5Access use :: ISO_Varying_String, only : varying_string , var_str @@ -518,7 +518,7 @@ CODE $waitTimeWriter .= fill_in_string(<<'CODE', PACKAGE => 'code'); ! Open output group. !$ call hdf5Access%set() - metaDataGroup=galacticusOutputFile%openGroup('metaData','Galacticus meta data.' ) + metaDataGroup=outputFile%openGroup('metaData','Galacticus meta data.' ) waitTimeGroup=metaDataGroup %openGroup('openMP' ,'Meta-data on OpenMP performance.') ! Write wait time data. call waitTimeGroup%writeDataset(eventHookNames ,"eventHookNames" ,"Names of event hooks" ) diff --git a/perl/Galacticus/Build/SourceTree/Process/FunctionClass.pm b/perl/Galacticus/Build/SourceTree/Process/FunctionClass.pm index 6c989b076c..3f5f9f5072 100644 --- a/perl/Galacticus/Build/SourceTree/Process/FunctionClass.pm +++ b/perl/Galacticus/Build/SourceTree/Process/FunctionClass.pm @@ -1181,6 +1181,9 @@ CODE # Add a class guard for resets. $deepCopy->{'resetCode' } .= "type is (".$nonAbstractClass->{'name'}.")\n"; $deepCopy->{'finalizeCode'} .= "type is (".$nonAbstractClass->{'name'}.")\n"; + # Initialize a list of explicity-deep-copied variables that have been found. + my $foundDeepCopyNames; + @{$foundDeepCopyNames} = (); while ( $class ) { my $node = $class->{'tree'}->{'firstChild'}; $node = $node->{'sibling'} @@ -1201,7 +1204,7 @@ CODE my @ignore = exists($class->{'deepCopy'}->{'ignore'}) ? split(/\s*,\s*/,$class->{'deepCopy'}->{'ignore'}->{'variables'}) : (); $node = $node->{'firstChild'}; while ( $node ) { - &deepCopyDeclarations($class,$nonAbstractClass,$node,$node->{'declarations'},\@ignore,$lineNumber,$deepCopy) + &deepCopyDeclarations($class,$nonAbstractClass,$node,$node->{'declarations'},\@ignore,$lineNumber,$deepCopy,$foundDeepCopyNames) if ( $node->{'type'} eq "declaration" ); $node = $node->{'sibling'}; } @@ -1221,7 +1224,7 @@ CODE unless ( defined($declarationSource) ); my $declaration = &Fortran::Utils::Unformat_Variables($declarationSource); my @ignore = (); - &deepCopyDeclarations($class,$nonAbstractClass,$node,$declaration,\@ignore,$lineNumber,$deepCopy); + &deepCopyDeclarations($class,$nonAbstractClass,$node,$declaration,\@ignore,$lineNumber,$deepCopy,$foundDeepCopyNames); } # Add any objects declared in the functionClassType class. if ( defined($functionClassType) ) { @@ -1229,7 +1232,7 @@ CODE my @ignore = (); my $node = $functionClassType->{'node'}->{'firstChild'}; while ( $node ) { - &deepCopyDeclarations($class,$nonAbstractClass,$node,$node->{'declarations'},\@ignore,$lineNumber,$deepCopy) + &deepCopyDeclarations($class,$nonAbstractClass,$node,$node->{'declarations'},\@ignore,$lineNumber,$deepCopy,$foundDeepCopyNames) if ( $node->{'type'} eq "declaration" ); $node = $node->{'sibling'}; } @@ -1246,6 +1249,19 @@ CODE $deepCopy->{'code'} .= "end select\n"; # Specify required modules. $deepCopy->{'modules'}->{'Error'} = 1; + # Check that all explicit variables were found. + { + my $class = $nonAbstractClass; + while ( $class ) { + if ( exists($class->{'deepCopy'}->{'functionClass'}) ) { + foreach my $variable ( split(/\s*,\s*/,$class->{'deepCopy'}->{'functionClass'}->{'variables'}) ) { + die("Error: unable to find variable '".$variable."' marked for deep copy in class '".$class->{'name'}."'") + unless ( grep {$_ eq lc($variable)} @{$foundDeepCopyNames} ); + } + } + $class = ($class->{'extends'} eq $directive->{'name'}) ? undef() : $classes{$class->{'extends'}}; + } + } } $deepCopy->{'code' } .= "end select\n"; $deepCopy->{'resetCode' } .= "end select\n"; @@ -1364,6 +1380,8 @@ CODE my $extensionOf; # Generate code to output all variables from this class (and any parent class). @{$stateStore->{'staticVariables'}} = (); + my $explicitNamesFound; + @{$explicitNamesFound} = (); my $class = $nonAbstractClass; while ( $class ) { my $node = $class->{'tree'}->{'firstChild'}; @@ -1380,7 +1398,7 @@ CODE # Search the node for declarations. $node = $node->{'firstChild'}; while ( $node ) { - &stateStoreVariables($stateStores,$stateStore,$class,$node->{'declarations'}) + &stateStoreVariables($stateStores,$stateStore,$class,$node->{'declarations'},$explicitNamesFound) if ( $node->{'type'} eq "declaration" ); $node = $node->{'type'} eq "contains" ? $node->{'firstChild'} : $node->{'sibling'}; } @@ -1419,17 +1437,31 @@ CODE my $declaration = &Fortran::Utils::Unformat_Variables($declarationSource); die("Galacticus::Build::SourceTree::Process::FunctionClass::Process_FunctionClass(): unable to parse variable declaration") unless ( defined($declaration) ); - &stateStoreVariables($stateStores,$stateStore,undef(),$declaration); + &stateStoreVariables($stateStores,$stateStore,undef(),$declaration,$explicitNamesFound); } # Add any variables declared in the functionClassType class. if ( defined($functionClassType) ) { my $node = $functionClassType->{'node'}->{'firstChild'}; while ( $node ) { - &stateStoreVariables($stateStores,$stateStore,undef(),$node->{'declarations'}) + &stateStoreVariables($stateStores,$stateStore,undef(),$node->{'declarations'},$explicitNamesFound) if ( $node->{'type'} eq "declaration" ); $node = $node->{'type'} eq "contains" ? $node->{'firstChild'} : $node->{'sibling'}; } } + # Check that all explicit variables were found. + { + my $class = $nonAbstractClass; + while ( $class ) { + if ( exists($class->{'stateStorable'}->{'functionClass'}) && exists($class->{'stateStorable'}->{'functionClass'}->{'variables'}) ) { + foreach my $variable ( split(/\s*,\s*/,$class->{'stateStorable'}->{'functionClass'}->{'variables'}) ) { + die("Error: unable to find variable '".$variable."' marked as state storable in class '".$class->{'name'}."'") + unless ( grep {$_ eq lc($variable)} @{$explicitNamesFound} ); + } + } + # Move to the parent class. + $class = ($class->{'extends'} eq $directive->{'name'}) ? undef() : $classes{$class->{'extends'}}; + } + } # Add code to method. $stateStores->{'stateFileUsed'} = 1 if ( scalar(@{$stateStore->{'staticVariables'}}) > 0 ); @@ -2447,7 +2479,8 @@ CODE } } } else { - $modulePostContains->{'content'} .= " use Error\n"; + $modulePostContains->{'content'} .= " use Error , only : Error_Report\n"; + $modulePostContains->{'content'} .= " use ISO_Varying_String, only : char\n"; } $modulePostContains->{'content'} .= " implicit none\n"; $modulePostContains->{'content'} .= $argumentCode; @@ -2458,7 +2491,7 @@ CODE $code =~ s/\n/\n /g; $modulePostContains->{'content'} .= $code."\n"; } else { - $modulePostContains->{'content'} .= " call Error_Report('this is a null method - initialize the ".$directive->{'name'}." object before use'//".&Galacticus::Build::SourceTree::Process::SourceIntrospection::Location($node,$node->{'line'}).")\n"; + $modulePostContains->{'content'} .= " call Error_Report('this is a null method - initialize the ".$directive->{'name'}." object before use and/or check that the \"'//char(self%objectType())//'\" class implements this method'//".&Galacticus::Build::SourceTree::Process::SourceIntrospection::Location($node,$node->{'line'}).")\n"; if ( $category eq "function" ) { # Avoid warnings about unset function values. $modulePostContains->{'content'} .= " ".$directive->{'name'}.ucfirst($methodName).$extension."="; @@ -3083,13 +3116,14 @@ sub potentialDescriptorParameters { sub deepCopyDeclarations { # Process variable declarations from a node for deep copy. - my $class = shift() ; - my $nonAbstractClass = shift() ; - my $node = shift() ; - my $declarations = shift() ; - my @ignore = @{shift()}; - my $lineNumber = shift() ; - my $deepCopy = shift() ; + my $class = shift() ; + my $nonAbstractClass = shift() ; + my $node = shift() ; + my $declarations = shift() ; + my @ignore = @{shift()}; + my $lineNumber = shift() ; + my $deepCopy = shift() ; + my $foundDeepCopyNames = shift() ; our $stateStorables; our $debugging; our $deepCopyActions; @@ -3188,6 +3222,7 @@ sub deepCopyDeclarations { foreach my $object ( @{$declaration->{'variables'}} ) { (my $name = $object) =~ s/^([a-zA-Z0-9_]+).*/$1/; # Strip away anything (e.g. assignment operators) after the variable name. if ( grep {lc($_) eq lc($name)} split(/\s*,\s*/,$class->{'deepCopy'}->{'functionClass'}->{'variables'}) ) { + push(@{$foundDeepCopyNames},$name); if ( grep {$_ eq "pointer"} @{$declaration->{'attributes'}} ) { $deepCopy->{'assignments' } .= "nullify(destination%".$name.")\n"; $deepCopy->{'assignments' } .= "if (associated(self%".$name.")) then\n"; @@ -3333,16 +3368,16 @@ sub deepCopyDeclarations { } } } - } sub stateStoreVariables { # Generate code to store/restore variables in functionClass objects. - my $stateStores = shift(); - my $stateStore = shift(); - my $class = shift(); - my $declarations = shift(); - our $stateStorables ; + my $stateStores = shift(); + my $stateStore = shift(); + my $class = shift(); + my $declarations = shift(); + my $explicitNamesFound = shift(); + our $stateStorables ; foreach my $declaration ( &List::ExtraUtils::as_array($declarations) ) { # Identify variable type. if ( $declaration->{'intrinsic'} eq "procedure" || $declaration->{'intrinsic'} eq "final" ) { @@ -3459,8 +3494,11 @@ sub stateStoreVariables { (my $variableName = $_) =~ s/\s*=.*$//; next if ( grep {lc($_) eq lc($variableName)} @{$stateStore->{'excludes'}} ); + my $isExplicit = grep {lc($_) eq lc($variableName)} @explicits; next - unless ( (! $isPointer) || grep {lc($_) eq lc($variableName)} @explicits ); + unless ( (! $isPointer) || $isExplicit ); + push(@{$explicitNamesFound},lc($variableName)) + if ( $isExplicit ); my $rank = 0; if ( grep {$_ =~ m/^dimension\s*\(/} @{$declaration->{'attributes'}} ) { my $dimensionDeclarator = join(",",map {/^dimension\s*\(([a-zA-Z0-9_,:\s]+)\)/} @{$declaration->{'attributes'}}); diff --git a/perl/Galacticus/Build/SourceTree/Process/NonProcessed.pm b/perl/Galacticus/Build/SourceTree/Process/NonProcessed.pm index 7ec9225542..0c582c7956 100644 --- a/perl/Galacticus/Build/SourceTree/Process/NonProcessed.pm +++ b/perl/Galacticus/Build/SourceTree/Process/NonProcessed.pm @@ -14,7 +14,7 @@ sub Process_NonProcessed { # Get the tree. my $tree = shift(); # Non-processed directives that we simply mark as processed to avoid warnings. - my @nonProcessedDirectives = ( "methods", "workaround", "include", "functionGlobal", "component", "radiusSolverPlausibility", "interTreePositionInsert", "expiry", "scoping", "functionClassType" ); + my @nonProcessedDirectives = ( "methods", "workaround", "include", "functionGlobal", "component", "radiusSolverPlausibility", "interTreePositionInsert", "expiry", "scoping" ); # Walk the tree, looking for our directive. my $node = $tree; my $depth = 0; diff --git a/perl/Galacticus/Build/SourceTree/Process/ObjectBuilder.pm b/perl/Galacticus/Build/SourceTree/Process/ObjectBuilder.pm index 6961892b77..32ea8843f8 100755 --- a/perl/Galacticus/Build/SourceTree/Process/ObjectBuilder.pm +++ b/perl/Galacticus/Build/SourceTree/Process/ObjectBuilder.pm @@ -439,7 +439,8 @@ sub Process_ObjectBuilder { $destructorCode .= " else\n"; $destructorCode .= " ! Nullify the pointer.\n"; $destructorCode .= $debugMessage; - $destructorCode .= " nullify(".$node->{'directive'}->{'name'}.")\n"; + $destructorCode .= " nullify(".$node->{'directive'}->{'name'}.")\n" + unless ( exists($node->{'directive'}->{'nullify'}) && $node->{'directive'}->{'nullify'} eq "no" ); $destructorCode .= " end if\n"; $destructorCode .= "end if\n"; # Build a code node. diff --git a/perl/Galacticus/Launch/Slurm.pm b/perl/Galacticus/Launch/Slurm.pm index cfe4d1a707..4dce9dd0e2 100644 --- a/perl/Galacticus/Launch/Slurm.pm +++ b/perl/Galacticus/Launch/Slurm.pm @@ -81,7 +81,7 @@ sub Launch { print $slurmFile "#!/bin/bash\n"; print $slurmFile "#SBATCH -J Galacticus_".$job->{'label'}."\n"; my $currentDirectory = cwd(); - print $slurmFile "#SBATCH --workdir=".$currentDirectory."\n"; + print $slurmFile "#SBATCH --chdir=".$currentDirectory."\n"; if ( exists($launchScript->{'config'}->{'contact'}->{'email'}) ) { if ( $launchScript->{'config'}->{'contact'}->{'email'} =~ m/\@/ && exists($launchScript->{'emailReport'}) && $launchScript->{'emailReport'} eq "yes" ) { print $slurmFile "#SBATCH --mail-user=".$launchScript->{'config'}->{'contact'}->{'email'}."\n"; @@ -112,6 +112,11 @@ sub Launch { print $slurmFile "export ".$environment."\n"; } } + if ( exists($launchScript->{'slurm'}->{'module'}) ) { + foreach my $module ( @{$launchScript->{'slurm'}->{'module'}} ) { + print $slurmFile "module load ".$module."\n"; + } + } my $coreDump = "no"; $coreDump = $launchScript->{'slurm'}->{'coreDump'} if ( exists($launchScript->{'slurm'}->{'coreDump'}) ); @@ -341,8 +346,10 @@ sub SubmitJobs { print $scriptFile "if [ ! -z \${SLURM_SUBMIT_DIR+x} ]; then\n"; print $scriptFile " cd \$SLURM_SUBMIT_DIR\n"; print $scriptFile "fi\n"; - print $scriptFile "export ".$_."\n" + print $scriptFile "export " .$_."\n" foreach ( &List::ExtraUtils::as_array($slurmConfig->{'environment'}) ); + print $scriptFile "module load ".$_."\n" + foreach ( &List::ExtraUtils::as_array($slurmConfig->{'module' }) ); print $scriptFile "ulimit -t unlimited\n"; print $scriptFile "ulimit -c unlimited\n"; print $scriptFile "export OMP_NUM_THREADS=".$ompThreads."\n"; diff --git a/schema/componentSchema.xsd b/schema/componentSchema.xsd index efdb815d1a..6fbcc2a19f 100644 --- a/schema/componentSchema.xsd +++ b/schema/componentSchema.xsd @@ -149,6 +149,7 @@ + diff --git a/schema/objectDestructor.xsd b/schema/objectDestructor.xsd new file mode 100644 index 0000000000..7e1be04b98 --- /dev/null +++ b/schema/objectDestructor.xsd @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/schema/referenceAcquire.xsd b/schema/referenceAcquire.xsd new file mode 100644 index 0000000000..683e1b27ae --- /dev/null +++ b/schema/referenceAcquire.xsd @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/schema/referenceConstruct.xsd b/schema/referenceConstruct.xsd new file mode 100644 index 0000000000..89145f3fa6 --- /dev/null +++ b/schema/referenceConstruct.xsd @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/scripts/analysis/analysesPlot.pl b/scripts/analysis/analysesPlot.pl index a823b14b96..3e3c9e4170 100755 --- a/scripts/analysis/analysesPlot.pl +++ b/scripts/analysis/analysesPlot.pl @@ -184,8 +184,10 @@ sub function1DPlot { if ( $attributes->{'yAxisIsLog'} ) { my $negativeY = which($yLower <= 1.0e-10*$data->{'yDataset' }); my $negativeYTarget = which($yLowerTarget <= 1.0e-10*$data->{'yDatasetTarget'}); - $yLower ->($negativeY ) .= $data->{'yDataset' }->($negativeY ); - $yLowerTarget->($negativeYTarget) .= $data->{'yDatasetTarget'}->($negativeYTarget); + $yLower ->($negativeY ) .= $data->{'yDataset' }->($negativeY ) + if ( nelem($negativeY ) > 0 ); + $yLowerTarget->($negativeYTarget) .= $data->{'yDatasetTarget'}->($negativeYTarget) + if ( nelem($negativeYTarget) > 0 ); } my $yUpperBoth = $yUpper->($nonZero)->append($yUpperTarget->($nonZeroTarget)); my $yLowerBoth = $yLower->($nonZero)->append($yLowerTarget->($nonZeroTarget)); diff --git a/scripts/analysis/analysesPlot.py b/scripts/analysis/analysesPlot.py index 498add7207..0744396b67 100755 --- a/scripts/analysis/analysesPlot.py +++ b/scripts/analysis/analysesPlot.py @@ -248,6 +248,9 @@ def restricted_float(x): yLower = y-errors yUpper = y+errors haveErrorBars = True + else: + yLower = y + yUpper = y nonZero = np.nonzero(datasets['yDatasetTarget']['data']) if haveErrorBars: axes.errorbar(datasets['xDataset']['data'][nonZero],datasets['yDatasetTarget']['data'][nonZero],yerr=errors[nonZero],fmt='none',ecolor='#4c3af2',zorder=phase*10) diff --git a/scripts/aux/migrations.xml b/scripts/aux/migrations.xml index 34ee14a44e..447aee9185 100644 --- a/scripts/aux/migrations.xml +++ b/scripts/aux/migrations.xml @@ -208,10 +208,23 @@ - + - + + + + + + + + + + + + + + diff --git a/scripts/build/libraryInterfaces.pl b/scripts/build/libraryInterfaces.pl index 6a2b78b9ef..5bc4272a2e 100755 --- a/scripts/build/libraryInterfaces.pl +++ b/scripts/build/libraryInterfaces.pl @@ -65,6 +65,40 @@ $functionClass->{'methods'} = $node->{'directive'}->{'method'}; } $functionClass->{'moduleUses'} = \@moduleUses; + # Find the parent-child class dependencies. + my $extensions; + my $moduleUsesImplementations; + foreach my $fileNameImplementation ( &List::ExtraUtils::as_array($directiveLocations->{$functionClass->{'name'}}->{'file'}) ) { + my $treeImplementation = &Galacticus::Build::SourceTree::ParseFile($fileNameImplementation); + my $depthImplementation = 0; + my $nodeImplementation = $treeImplementation; + my $nameImplementation; + my @moduleUsesImplementation; + while ( $nodeImplementation ) { + if ( $nodeImplementation->{'type'} eq $functionClass->{'name'} ) { + # Find the name of the implementation. + $nameImplementation = $nodeImplementation->{'directive'}->{'name'}; + } elsif ( $nodeImplementation->{'type'} eq "type" && $nodeImplementation->{'name'} eq $nameImplementation ) { + # Find the extended class. + if ( $nodeImplementation->{'opener'} =~ m/,\s*extends\s*\(\s*([a-zA-Z0-9_]+)\s*\)/ ) { + $extensions->{$nodeImplementation->{'name'}} = $1; + } + } elsif ( $nodeImplementation->{'type'} eq "moduleUse" ) { + # Module use statements, extract for later reference. + push(@moduleUsesImplementation,$nodeImplementation->{'moduleUse'}); + } + + $nodeImplementation = &Galacticus::Build::SourceTree::Walk_Tree($nodeImplementation,\$depthImplementation); + } + @{$moduleUsesImplementations->{$nameImplementation}} = @moduleUsesImplementation + unless ( + exists($functionClass->{$nameImplementation} ) + && + exists($functionClass->{$nameImplementation}->{'exclude'} ) + && + $functionClass->{$nameImplementation}->{'exclude'} eq "yes" + ); + } # Find all implementations of this class. my $classID = 0; foreach my $fileNameImplementation ( &List::ExtraUtils::as_array($directiveLocations->{$functionClass->{'name'}}->{'file'}) ) { @@ -139,18 +173,28 @@ @{$functionClass->{'implementations'}}, $implementation ) - unless ( $abstractImplementation ); + unless ( + $abstractImplementation + || + ( + exists($functionClass->{$nameImplementation} ) + && + exists($functionClass->{$nameImplementation}->{'exclude'} ) + && + $functionClass->{$nameImplementation}->{'exclude'} eq "yes" + ) + ); } # Add Python parent class. - &interfacesPythonClasses( $python,$functionClass ); + &interfacesPythonClasses( $python,$functionClass ); # Add pointer get functions. - &interfacesPointerGet ($code ,$functionClass ); + &interfacesPointerGet ($code ,$functionClass ); # Add constructors. - &interfacesConstructors ($code,$python,$functionClass,$libraryFunctionClasses); + &interfacesConstructors ($code,$python,$functionClass,$libraryFunctionClasses,$extensions,$moduleUsesImplementations); # Add interfaces to all methods. - &interfacesMethods ($code,$python,$functionClass ); + &interfacesMethods ($code,$python,$functionClass ,$extensions,$moduleUsesImplementations); # Add a destructor. - &interfacesDestructor ($code,$python,$functionClass ); + &interfacesDestructor ($code,$python,$functionClass ); } } @@ -272,17 +316,19 @@ sub interfacesPointerGet { sub interfacesConstructors { # Build interfaces to constructors. - my $code = shift(); - my $python = shift(); - $ext::functionClass = shift(); - my $libraryFunctionClasses = shift(); + my $code = shift(); + my $python = shift(); + $ext::functionClass = shift(); + my $libraryFunctionClasses = shift(); + my $extensions = shift(); + my $moduleUsesImplementations = shift(); foreach $ext::implementation ( @{$ext::functionClass->{'implementations'}} ) { # Extract the list of arguments and process to determine how interfaces should be built. my @argumentList = @{$ext::implementation->{'arguments'}}; - @argumentList = &assignCTypes (\@argumentList ); - @argumentList = &assignCAttributes (\@argumentList ); - @argumentList = &buildPythonReassignments (\@argumentList ); - @argumentList = &buildFortranReassignments(\@argumentList,$ext::functionClass,$ext::implementation); + @argumentList = &assignCTypes (\@argumentList ); + @argumentList = &assignCAttributes (\@argumentList ); + @argumentList = &buildPythonReassignments (\@argumentList ); + @argumentList = &buildFortranReassignments(\@argumentList,$ext::functionClass,$ext::implementation,$extensions,$moduleUsesImplementations); # Construct pre- and post-arguments content for the call from Fortran to Galacticus. my $preArguments .= fill_in_string(<<'CODE', PACKAGE => 'ext'); !![ @@ -408,6 +454,8 @@ sub interfacesMethods { my $code = shift(); my $python = shift(); $ext::functionClass = shift(); + my $extensions = shift(); + my $moduleUsesImplementations = shift(); foreach $ext::method ( &List::ExtraUtils::hashList($ext::functionClass->{'methods'},keyAs => 'name') ) { # Construct function declaration. my %isoCBindingSymbols; @@ -429,7 +477,9 @@ sub interfacesMethods { } elsif ( $ext::method->{'type'} eq "void" ) { $ext::procedure = "subroutine"; } else { - die("unsupported type '".$ext::method->{'type'}."'"); + print "unsupported type '".$ext::method->{'type'}."'\n"; + delete($ext::functionClass->{'methods'}->{$ext::method}); + next; } $ext::functionDeclaration = $ext::method->{'type'} eq "void" ? "" : $functionCType." :: ".$ext::functionClass->{'name'}.ucfirst($ext::method->{'name'})."L\n"; # Create a list of arguments. @@ -464,10 +514,10 @@ sub interfacesMethods { } } # Process argument list. - @argumentList = &assignCTypes (\@argumentList ); - @argumentList = &assignCAttributes (\@argumentList ); - @argumentList = &buildPythonReassignments (\@argumentList ); - @argumentList = &buildFortranReassignments(\@argumentList,$ext::functionClass,undef()); + @argumentList = &assignCTypes (\@argumentList ); + @argumentList = &assignCAttributes (\@argumentList ); + @argumentList = &buildPythonReassignments (\@argumentList ); + @argumentList = &buildFortranReassignments(\@argumentList,$ext::functionClass,undef(),$extensions,$moduleUsesImplementations); # Generate the pre- and post-arguments content of the function call. my $preArguments = ($ext::method->{'type'} eq "void" ? "call " : $ext::functionClass->{'name'}.ucfirst($ext::method->{'name'})."L=").$ext::resultConversionOpen."self_%".$ext::method->{'name'}."( &\n"; my $postArguments = "& )".$ext::resultConversionClose."\n"; @@ -749,9 +799,11 @@ sub buildPythonReassignments { sub buildFortranReassignments { # Geneate reassignments of Fortran arguments to allow passing between languages. - my @argumentList = @{shift()}; - my $functionClass = shift() ; - my $implementation = shift() ; + my @argumentList = @{shift()}; + my $functionClass = shift() ; + my $implementation = shift() ; + my $extensions = shift() ; + my $moduleUsesImplementations = shift() ; my @argumentListNew; while ( @argumentList ) { # Get the next argument from the list. @@ -780,12 +832,17 @@ sub buildFortranReassignments { # Search for any module import of this enumeration type. my $importModule; if ( defined($implementation) ) { - foreach my $useBlock ( @{$implementation->{'moduleUses'}} ) { - foreach my $module ( keys(%{$useBlock}) ) { - if ( grep {$_ eq $argument->{'type'}} keys(%{$useBlock->{$module}->{'only'}}) ) { - $importModule = $module; + + my $className = $implementation->{'name'}; + while ( defined($className) && ! defined($importModule) ) { + foreach my $useBlock ( @{$moduleUsesImplementations->{$className}} ) { + foreach my $module ( keys(%{$useBlock}) ) { + if ( grep {$_ eq $argument->{'type'}} keys(%{$useBlock->{$module}->{'only'}}) ) { + $importModule = $module; + } } } + $className = exists($extensions->{$className}) ? $extensions->{$className} : undef(); } } unless ( defined($importModule) ) { diff --git a/scripts/doc/Code_Analyzer.pl b/scripts/doc/Code_Analyzer.pl index 999ec91a0d..a2aea4a378 100755 --- a/scripts/doc/Code_Analyzer.pl +++ b/scripts/doc/Code_Analyzer.pl @@ -70,10 +70,10 @@ module => { unitName => 1, regEx => "^\\s*module\\s+(?!procedure\\s)([a-z0-9_]+)\$" }, # Find program openings. program => { unitName => 1, regEx => "^\\s*program\\s+([a-z0-9_]+)" }, - # Find subroutine openings, allowing for pure, elemental and recursive subroutines. - subroutine => { unitName => 2, regEx => "^\\s*(pure\\s+|elemental\\s+|recursive\\s+|module\\s+)*\\s*subroutine\\s+([a-z0-9_]+)"}, - # Find function openings, allowing for pure, elemental and recursive functions, and different function types. - function => { unitName => 6, regEx => "^\\s*(pure\\s+|elemental\\s+|recursive\\s+)*\\s*(real\\s*|integer\\s*|double\\s+precision\\s*|double\\s+complex\\s*|character\\s*|logical\\s*|module\\s*)*\\s*(\\(((kind|len)=)??[\\w\\d]*\\))*\\s*function\\s+([a-z0-9_]+)"}, + # Find subroutine openings, allowing for impure, pure, elemental and recursive subroutines. + subroutine => { unitName => 2, regEx => "^\\s*(impure\\s+|pure\\s+|elemental\\s+|recursive\\s+|module\\s+)*\\s*subroutine\\s+([a-z0-9_]+)"}, + # Find function openings, allowing for impure, pure, elemental and recursive functions, and different function types. + function => { unitName => 6, regEx => "^\\s*(impure\\s+|pure\\s+|elemental\\s+|recursive\\s+)*\\s*(real\\s*|integer\\s*|double\\s+precision\\s*|double\\s+complex\\s*|character\\s*|logical\\s*|module\\s*)*\\s*(\\(((kind|len)=)??[\\w\\d]*\\))*\\s*function\\s+([a-z0-9_]+)"}, # Find interfaces. interface => { unitName => 2, regEx => "^\\s*(abstract\\s+)??interface\\s+([a-z0-9_\\(\\)\\/\\+\\-\\*\\.=]*)"}, # Find types. diff --git a/source/Galacticus.F90 b/source/Galacticus.F90 index 5415df332b..4a58cae968 100644 --- a/source/Galacticus.F90 +++ b/source/Galacticus.F90 @@ -65,6 +65,8 @@ program Galacticus #endif ! Register error handlers. call Error_Handler_Register() + ! Show the Galacticus banner. + call Display_Banner_Show() ! Check that we have at least one command line argument. if (Command_Argument_Count() < 1) call usageError() ! Get the name of the parameter file from the first command line argument. @@ -97,8 +99,6 @@ program Galacticus call Error_Wait_Set_From_Parameters (parameters) ! Set resource limits. call System_Limits_Set (parameters) - ! Show the Galacticus banner. - call Display_Banner_Show() ! Validate parameter file. call parameters%checkParameters() ! Perform task. diff --git a/source/accretion.Bondi_Hoyle_Lyttleton.F90 b/source/accretion.Bondi_Hoyle_Lyttleton.F90 index 083645cad6..c550564b6e 100644 --- a/source/accretion.Bondi_Hoyle_Lyttleton.F90 +++ b/source/accretion.Bondi_Hoyle_Lyttleton.F90 @@ -58,7 +58,7 @@ double precision function Bondi_Hoyle_Lyttleton_Accretion_Rate(mass,density,velo return end function Bondi_Hoyle_Lyttleton_Accretion_Rate - double precision function Bondi_Hoyle_Lyttleton_Accretion_Radius(mass,temperature) + double precision function Bondi_Hoyle_Lyttleton_Accretion_Radius(mass,temperature) result(radiusAccretion) !!{ Computes the Bondi-Hoyle-Lyttleton accretion radius (in Mpc; \citealt{edgar_review_2004}). !!} @@ -68,11 +68,12 @@ double precision function Bondi_Hoyle_Lyttleton_Accretion_Radius(mass,temperatur double precision, intent(in ) :: mass , temperature double precision :: soundSpeed - ! Compute the sound speed. - soundSpeed=Ideal_Gas_Sound_Speed(temperature) - - ! Compute the accretion radius - Bondi_Hoyle_Lyttleton_Accretion_Radius=gravitationalConstantGalacticus*mass/soundSpeed**2 + if (temperature > 0.0d0) then + soundSpeed =Ideal_Gas_Sound_Speed(temperature) + radiusAccretion=gravitationalConstantGalacticus*mass/soundSpeed**2 + else + radiusAccretion=huge(0.0d0) + end if return end function Bondi_Hoyle_Lyttleton_Accretion_Radius diff --git a/source/accretion.halo.Bertschinger.F90 b/source/accretion.halo.Bertschinger.F90 index e3bdf1b4e9..db455a708f 100644 --- a/source/accretion.halo.Bertschinger.F90 +++ b/source/accretion.halo.Bertschinger.F90 @@ -104,14 +104,20 @@ subroutine bertschingerDestructor(self) return end subroutine bertschingerDestructor - double precision function bertschingerVelocityScale(self,node) + double precision function bertschingerVelocityScale(self,node) result(velocityScale) !!{ Returns the velocity scale to use for {\normalfont \ttfamily node}. Use the maximum circular velocity. !!} + use :: Mass_Distributions, only : massDistributionClass implicit none class(accretionHaloBertschinger), intent(inout) :: self type (treeNode ), intent(inout) :: node - - bertschingerVelocityScale=self%darkMatterProfileDMO_%circularVelocityMaximum(node) + class(massDistributionClass ), pointer :: massDistribution_ + + massDistribution_ => self %darkMatterProfileDMO_ %get(node) + velocityScale = massDistribution_%velocityRotationCurveMaximum ( ) + !![ + + !!] return end function bertschingerVelocityScale diff --git a/source/accretion.halo.Naoz_Barkana_2007.F90 b/source/accretion.halo.Naoz_Barkana_2007.F90 index 0fa66738f2..3ec3540a32 100644 --- a/source/accretion.halo.Naoz_Barkana_2007.F90 +++ b/source/accretion.halo.Naoz_Barkana_2007.F90 @@ -25,7 +25,6 @@ !!} use :: Intergalactic_Medium_Filtering_Masses, only : intergalacticMediumFilteringMass, intergalacticMediumFilteringMassClass - use :: Dark_Matter_Profiles_DMO , only : darkMatterProfileDMOClass use :: Virial_Density_Contrast , only : virialDensityContrastClass !![ @@ -74,7 +73,6 @@ & rateCorrectionComputed integer (kind=kind_int8 ) :: lastUniqueID class (intergalacticMediumFilteringMassClass), pointer :: intergalacticMediumFilteringMass_ => null() - class (darkMatterProfileDMOClass ), pointer :: darkMatterProfileDMO_ => null() class (virialDensityContrastClass ), pointer :: virialDensityContrast_ => null() contains !![ @@ -144,7 +142,6 @@ function naozBarkana2007ConstructorParameters(parameters) result(self) self%massMinimum - !!] @@ -152,7 +149,7 @@ function naozBarkana2007ConstructorParameters(parameters) result(self) return end function naozBarkana2007ConstructorParameters - function naozBarkana2007ConstructorInternal(timeReionization,velocitySuppressionReionization,accretionNegativeAllowed,accretionNewGrowthOnly,rateAdjust,massMinimum,cosmologyParameters_,cosmologyFunctions_,darkMatterHaloScale_,accretionHaloTotal_,chemicalState_,intergalacticMediumState_,intergalacticMediumFilteringMass_,darkMatterProfileDMO_,virialDensityContrast_) result(self) + function naozBarkana2007ConstructorInternal(timeReionization,velocitySuppressionReionization,accretionNegativeAllowed,accretionNewGrowthOnly,rateAdjust,massMinimum,cosmologyParameters_,cosmologyFunctions_,darkMatterHaloScale_,accretionHaloTotal_,chemicalState_,intergalacticMediumState_,intergalacticMediumFilteringMass_,virialDensityContrast_) result(self) !!{ Internal constructor for the {\normalfont \ttfamily naozBarkana2007} halo accretion class. !!} @@ -170,10 +167,9 @@ function naozBarkana2007ConstructorInternal(timeReionization,velocitySuppression class (chemicalStateClass ), intent(in ), target :: chemicalState_ class (intergalacticMediumStateClass ), intent(in ), target :: intergalacticMediumState_ class (intergalacticMediumFilteringMassClass), intent(in ), target :: intergalacticMediumFilteringMass_ - class (darkMatterProfileDMOClass ), intent(in ), target :: darkMatterProfileDMO_ class (virialDensityContrastClass ), intent(in ), target :: virialDensityContrast_ !![ - + !!] self%accretionHaloSimple=accretionHaloSimple(timeReionization,velocitySuppressionReionization,accretionNegativeAllowed,accretionNewGrowthOnly,cosmologyParameters_,cosmologyFunctions_,darkMatterHaloScale_,accretionHaloTotal_,chemicalState_,intergalacticMediumState_) @@ -218,7 +214,6 @@ subroutine naozBarkana2007Destructor(self) if (calculationResetEvent%isAttached(self,naozBarkana2007CalculationReset)) call calculationResetEvent%detach(self,naozBarkana2007CalculationReset) !![ - !!] return @@ -298,7 +293,6 @@ double precision function naozBarkana2007FilteredFraction(self,node) & densityContrastVirial , & & cosmologyParameters_ =self %cosmologyParameters_ , & & cosmologyFunctions_ =self %cosmologyFunctions_ , & - & darkMatterProfileDMO_ =self %darkMatterProfileDMO_ , & & virialDensityContrast_=self %virialDensityContrast_ & & ) self%filteredFractionStored = self%filteredFractionCompute(massHalo,massFiltering) @@ -336,7 +330,6 @@ double precision function naozBarkana2007FilteredFractionRate(self,node) & densityContrastVirial , & & cosmologyParameters_ =self %cosmologyParameters_ , & & cosmologyFunctions_ =self %cosmologyFunctions_ , & - & darkMatterProfileDMO_ =self %darkMatterProfileDMO_ , & & virialDensityContrast_=self %virialDensityContrast_ & & ) if (.not.self%filteredFractionComputed) then diff --git a/source/benchmarks.stellar_luminosities.F90 b/source/benchmarks.stellar_luminosities.F90 index 7cec022b9f..459d7e35e8 100644 --- a/source/benchmarks.stellar_luminosities.F90 +++ b/source/benchmarks.stellar_luminosities.F90 @@ -25,11 +25,12 @@ program Benchmark_Stellar_Populations_Luminosities !!{ Benchmarking of stellar population luminosity calculations. !!} - use, intrinsic :: ISO_Fortran_Env, only : output_unit + use, intrinsic :: ISO_Fortran_Env , only : output_unit use :: Abundances_Structure , only : abundances , metallicityTypeLinearByMassSolar use :: Cosmology_Functions , only : cosmologyFunctionsMatterLambda use :: Cosmology_Parameters , only : cosmologyParametersSimple use :: Display , only : displayVerbositySet , verbosityLevelWorking + use :: Events_Hooks , only : eventsHooksInitialize use :: Input_Paths , only : inputPath , pathTypeDataDynamic , pathTypeDataStatic use :: ISO_Varying_String , only : char , operator(//) , var_str use :: Input_Parameters , only : inputParameters @@ -79,6 +80,7 @@ program Benchmark_Stellar_Populations_Luminosities parameters=inputParameters() call displayVerbositySet(verbosityLevelWorking) + call eventsHooksInitialize() ! Construct cosmology and stellar populations. cosmologyParameters_ =cosmologyParametersSimple ( & & OmegaMatter = 0.3d0 , & @@ -186,8 +188,8 @@ program Benchmark_Stellar_Populations_Luminosities & Filter_Get_Index(var_str("VIRCAM_NB980" )),Filter_Get_Index(var_str("VIRCAM_NB990" )),Filter_Get_Index(var_str("VIRCAM_Y" )),Filter_Get_Index(var_str("VIRCAM_Z" )), & & Filter_Get_Index(var_str("WFC3IR_f105w" )),Filter_Get_Index(var_str("WFC3IR_f125w" )),Filter_Get_Index(var_str("WFC3IR_f160w" )),Filter_Get_Index(var_str("WFCAM_H" )), & & Filter_Get_Index(var_str("WFCAM_J" )),Filter_Get_Index(var_str("WFCAM_K" )),Filter_Get_Index(var_str("WFCAM_Y" )),Filter_Get_Index(var_str("WFCAM_Z" )), & - & Filter_Get_Index(var_str("WFIRST_BAO-Grism" )),Filter_Get_Index(var_str("WFIRST_F184" )),Filter_Get_Index(var_str("WFIRST_H158" )),Filter_Get_Index(var_str("WFIRST_J129" )), & - & Filter_Get_Index(var_str("WFIRST_SNPrism" )),Filter_Get_Index(var_str("WFIRST_W149" )),Filter_Get_Index(var_str("WFIRST_Y106" )),Filter_Get_Index(var_str("WFIRST_Z087" )), & + & Filter_Get_Index(var_str("Roman_F062" )),Filter_Get_Index(var_str("Roman_F087" )),Filter_Get_Index(var_str("Roman_F106" )),Filter_Get_Index(var_str("Roman_F129" )), & + & Filter_Get_Index(var_str("Roman_F146" )),Filter_Get_Index(var_str("Roman_F158" )),Filter_Get_Index(var_str("Roman_F184" )),Filter_Get_Index(var_str("Roman_F213" )), & & Filter_Get_Index(var_str("WIRCAM_K" )),Filter_Get_Index(var_str("bJ" )),Filter_Get_Index(var_str("xRayFull" )),Filter_Get_Index(var_str("xRayHard" )), & & Filter_Get_Index(var_str("xRaySoft" )) & & ] diff --git a/source/black_holes.accretion_rates.standard.F90 b/source/black_holes.accretion_rates.standard.F90 index 214d2f54d5..82f0aaf9a0 100644 --- a/source/black_holes.accretion_rates.standard.F90 +++ b/source/black_holes.accretion_rates.standard.F90 @@ -22,7 +22,6 @@ !!} use :: Black_Hole_Binary_Separations, only : blackHoleBinarySeparationGrowthRateClass - use :: Galactic_Structure , only : galacticStructureClass use :: Accretion_Disks , only : accretionDisksClass use :: Hot_Halo_Temperature_Profiles, only : hotHaloTemperatureProfileClass use :: Cooling_Radii , only : coolingRadiusClass @@ -40,7 +39,6 @@ The standard black hole accretion rate calculation. !!} private - class (galacticStructureClass ), pointer :: galacticStructure_ => null() class (blackHoleBinarySeparationGrowthRateClass), pointer :: blackHoleBinarySeparationGrowthRate_ => null() class (accretionDisksClass ), pointer :: accretionDisks_ => null() class (hotHaloTemperatureProfileClass ), pointer :: hotHaloTemperatureProfile_ => null() @@ -83,7 +81,6 @@ function standardConstructorParameters(parameters) result(self) implicit none type (blackHoleAccretionRateStandard ) :: self type (inputParameters ), intent(inout) :: parameters - class (galacticStructureClass ), pointer :: galacticStructure_ class (blackHoleBinarySeparationGrowthRateClass), pointer :: blackHoleBinarySeparationGrowthRate_ class (accretionDisksClass ), pointer :: accretionDisks_ class (hotHaloTemperatureProfileClass ), pointer :: hotHaloTemperatureProfile_ @@ -119,17 +116,15 @@ function standardConstructorParameters(parameters) result(self) parameters - !!] - self=blackHoleAccretionRateStandard(bondiHoyleAccretionEnhancementHotHalo,bondiHoyleAccretionEnhancementSpheroid,bondiHoyleAccretionTemperatureSpheroid,bondiHoyleAccretionHotModeOnly,galacticStructure_,blackHoleBinarySeparationGrowthRate_,hotHaloTemperatureProfile_,accretionDisks_,coolingRadius_,darkMatterHaloScale_) + self=blackHoleAccretionRateStandard(bondiHoyleAccretionEnhancementHotHalo,bondiHoyleAccretionEnhancementSpheroid,bondiHoyleAccretionTemperatureSpheroid,bondiHoyleAccretionHotModeOnly,blackHoleBinarySeparationGrowthRate_,hotHaloTemperatureProfile_,accretionDisks_,coolingRadius_,darkMatterHaloScale_) !![ - @@ -138,14 +133,13 @@ function standardConstructorParameters(parameters) result(self) return end function standardConstructorParameters - function standardConstructorInternal(bondiHoyleAccretionEnhancementHotHalo,bondiHoyleAccretionEnhancementSpheroid,bondiHoyleAccretionTemperatureSpheroid,bondiHoyleAccretionHotModeOnly,galacticStructure_,blackHoleBinarySeparationGrowthRate_,hotHaloTemperatureProfile_,accretionDisks_,coolingRadius_,darkMatterHaloScale_) result(self) + function standardConstructorInternal(bondiHoyleAccretionEnhancementHotHalo,bondiHoyleAccretionEnhancementSpheroid,bondiHoyleAccretionTemperatureSpheroid,bondiHoyleAccretionHotModeOnly,blackHoleBinarySeparationGrowthRate_,hotHaloTemperatureProfile_,accretionDisks_,coolingRadius_,darkMatterHaloScale_) result(self) !!{ Internal constructor for the {\normalfont \ttfamily standard} node operator class. !!} use :: Galacticus_Nodes, only : defaultHotHaloComponent implicit none type (blackHoleAccretionRateStandard ) :: self - class (galacticStructureClass ), target, intent(in ) :: galacticStructure_ class (blackHoleBinarySeparationGrowthRateClass), target, intent(in ) :: blackHoleBinarySeparationGrowthRate_ class (accretionDisksClass ), target, intent(in ) :: accretionDisks_ class (hotHaloTemperatureProfileClass ), target, intent(in ) :: hotHaloTemperatureProfile_ @@ -155,7 +149,7 @@ function standardConstructorInternal(bondiHoyleAccretionEnhancementHotHalo,bondi & bondiHoyleAccretionTemperatureSpheroid logical , intent(in ) :: bondiHoyleAccretionHotModeOnly !![ - + !!] ! Check if cold mode is explicitly tracked. @@ -172,7 +166,6 @@ subroutine standardDestructor(self) !![ - @@ -193,16 +186,21 @@ subroutine standardRateAccretion(self,blackHole,rateMassAccretionSpheroid,rateMa use :: Ideal_Gases_Thermodynamics , only : Ideal_Gas_Jeans_Length , Ideal_Gas_Sound_Speed use :: Numerical_Constants_Astronomical, only : Mpc_per_km_per_s_To_Gyr , gigaYear , megaParsec use :: Numerical_Constants_Prefixes , only : kilo + use :: Mass_Distributions , only : massDistributionClass , kinematicsDistributionClass + use :: Coordinates , only : coordinateSpherical , assignment(=) implicit none class (blackHoleAccretionRateStandard), intent(inout) :: self class (nodeComponentBlackHole ), intent(inout) :: blackHole - double precision , intent( out) :: rateMassAccretionSpheroid ,rateMassAccretionHotHalo + double precision , intent( out) :: rateMassAccretionSpheroid , rateMassAccretionHotHalo type (treeNode ), pointer :: node class (nodeComponentSpheroid ), pointer :: spheroid class (nodeComponentHotHalo ), pointer :: hotHalo + class (massDistributionClass ), pointer :: massDistributionSpheroid_ , massDistributionHotHalo_, & + & massDistributionColdHalo_ + class (kinematicsDistributionClass ), pointer :: kinematicsDistribution_ + type (coordinateSpherical ) :: coordinates ! Lowest gas density to consider when computing accretion rates onto black hole (in units of M☉/Mpc³). double precision , parameter :: densityGasMinimum =1.0d0 - double precision , dimension(3) :: position double precision :: radiusAccretion , rateAccretionMaximum , & & massBlackHole , densityGas , & & temperatureHotHalo , fractionHotMode , & @@ -227,9 +225,10 @@ subroutine standardRateAccretion(self,blackHole,rateMassAccretionSpheroid,rateMa & ,blackHole%radialPosition() & & ) ! Set the position. - position=[radiusAccretion,0.0d0,0.0d0] + coordinates = [radiusAccretion,0.0d0,0.0d0] ! Get density of gas at the galactic center. - densityGas=self%galacticStructure_%density(node,position,coordinateSystem=coordinateSystemCylindrical,componentType=componentTypeSpheroid,massType =massTypeGaseous) + massDistributionSpheroid_ => node %massDistribution(componentTypeSpheroid,massTypeGaseous) + densityGas = massDistributionSpheroid_%density (coordinates ) ! Check if we have a non-negligible gas density. if (densityGas > densityGasMinimum) then ! Get the spheroid component. @@ -242,9 +241,9 @@ subroutine standardRateAccretion(self,blackHole,rateMassAccretionSpheroid,rateMa ! radius, as the gas should be smoothly distributed on scales below the Jeans length. if (lengthJeans > radiusAccretion) then ! Set the position. - position=[lengthJeans,0.0d0,0.0d0] + coordinates=[lengthJeans,0.0d0,0.0d0] ! Get density of gas at the galactic center. - densityGas=self%galacticStructure_%density(node,position,coordinateSystem=coordinateSystemCylindrical,componentType=componentTypeSpheroid,massType=massTypeGaseous) + densityGas =massDistributionSpheroid_%density(coordinates) end if ! Compute the accretion rate. rateMassAccretionSpheroid=max( & @@ -264,12 +263,15 @@ subroutine standardRateAccretion(self,blackHole,rateMassAccretionSpheroid,rateMa ! Get the hot halo component. hotHalo => node%hotHalo() ! Get halo gas temperature. - temperatureHotHalo=self%hotHaloTemperatureProfile_%temperature(node,radius=0.0d0) + massDistributionHotHalo_ => node %massDistribution (componentType=componentTypeHotHalo,massType=massTypeGaseous) + kinematicsDistribution_ => massDistributionHotHalo_%kinematicsDistribution( ) + coordinates = [0.0d0,0.0d0,0.0d0] + temperatureHotHalo = kinematicsDistribution_%temperature(coordinates) ! Get the accretion radius. radiusAccretion=Bondi_Hoyle_Lyttleton_Accretion_Radius(massBlackHole,temperatureHotHalo) radiusAccretion=min(radiusAccretion,hotHalo%outerRadius()) ! Set the position. - position=[radiusAccretion,0.0d0,0.0d0] + coordinates=[radiusAccretion,0.0d0,0.0d0] ! Find the fraction of gas in the halo which is in the hot mode. Set this to unity if hot/cold mode is not to be ! considered. select case (self%bondiHoyleAccretionHotModeOnly) @@ -289,24 +291,17 @@ subroutine standardRateAccretion(self,blackHole,rateMassAccretionSpheroid,rateMa end if end select ! Get density of gas at the galactic center - scaled by the fraction in the hot accretion mode. - densityGas =+fractionHotMode & - & *self%galacticStructure_%density( & - & node , & - & position , & - & coordinateSystem=coordinateSystemCylindrical, & - & componentType =componentTypeHotHalo , & - & massType =massTypeGaseous & - & ) - if (self%coldModeTracked.and.fractionColdMode > 0.0d0) & - & densityGas=+densityGas & - & +fractionColdMode & - & *self%galacticStructure_%density( & - & node , & - & position , & - & coordinateSystem=coordinateSystemCylindrical, & - & componentType =componentTypeColdHalo , & - & massType =massTypeGaseous & - & ) + densityGas = +fractionHotMode & + & *massDistributionHotHalo_%density (coordinates ) + if (self%coldModeTracked.and.fractionColdMode > 0.0d0) then + massDistributionColdHalo_ => node %massDistribution(componentTypeColdHalo,massTypeGaseous) + densityGas = +densityGas & + & +fractionColdMode & + & *massDistributionColdHalo_%density (coordinates ) + !![ + + !!] + end if ! Check if we have a non-zero gas density. if (densityGas > densityGasMinimum) then ! Compute the accretion rate. @@ -339,6 +334,11 @@ subroutine standardRateAccretion(self,blackHole,rateMassAccretionSpheroid,rateMa ! No gas density, so zero accretion rate. rateMassAccretionHotHalo=0.0d0 end if + !![ + + + + !!] else rateMassAccretionSpheroid=0.0d0 rateMassAccretionHotHalo =0.0d0 diff --git a/source/black_holes.binaries.initial_separation.tidal_radius.F90 b/source/black_holes.binaries.initial_separation.tidal_radius.F90 index 077aa95355..1d3629b541 100644 --- a/source/black_holes.binaries.initial_separation.tidal_radius.F90 +++ b/source/black_holes.binaries.initial_separation.tidal_radius.F90 @@ -23,7 +23,7 @@ Implements a class for black hole binary initial separation based on tidal disruption of the satellite galaxy. !!} - use :: Galactic_Structure, only : galacticStructureClass + use :: Mass_Distributions, only : massDistributionClass use :: Root_Finder , only : rangeExpandMultiplicative, rangeExpandSignExpectNegative, rangeExpandSignExpectPositive, rootFinder !![ @@ -46,10 +46,8 @@ A black hole binary initial separation class in which the radius is based on tidal disruption of the satellite galaxy. !!} private - class(galacticStructureClass), pointer :: galacticStructure_ => null() - type (rootFinder ) :: finder + type (rootFinder) :: finder contains - final :: tidalRadiusDestructor procedure :: separationInitial => tidalRadiusSeparationInitial end type blackHoleBinaryInitialSeparationTidalRadius @@ -62,10 +60,9 @@ end interface blackHoleBinaryInitialSeparationTidalRadius ! Module-scope variables used in root finding. - double precision :: massHalf, radiusMassHalf - type (treeNode ), pointer :: node_ - class (blackHoleBinaryInitialSeparationTidalRadius), pointer :: self_ - !$omp threadprivate(radiusMassHalf,massHalf,node_,self_) + class (massDistributionClass), pointer :: massDistribution_ + double precision :: massHalf , radiusMassHalf + !$omp threadprivate(radiusMassHalf,massHalf,massDistribution_) contains @@ -78,29 +75,20 @@ function tidalRadiusConstructorParameters(parameters) result(self) implicit none type (blackHoleBinaryInitialSeparationTidalRadius) :: self type (inputParameters ), intent(inout) :: parameters - class(galacticStructureClass ), pointer :: galacticStructure_ - !![ - - !!] - self=blackHoleBinaryInitialSeparationTidalRadius(galacticStructure_) + self=blackHoleBinaryInitialSeparationTidalRadius() !![ - !!] return end function tidalRadiusConstructorParameters - function tidalRadiusConstructorInternal(galacticStructure_) result(self) + function tidalRadiusConstructorInternal() result(self) !!{ Internal constructor for the {\normalfont \ttfamily tidalRadius} black hole binary recoil class. !!} implicit none - type (blackHoleBinaryInitialSeparationTidalRadius) :: self - class(galacticStructureClass ), intent(in ), target :: galacticStructure_ - !![ - - !!] + type (blackHoleBinaryInitialSeparationTidalRadius) :: self self%finder=rootFinder( & & rootFunction=tidalRadiusRoot , & @@ -115,25 +103,12 @@ function tidalRadiusConstructorInternal(galacticStructure_) result(self) return end function tidalRadiusConstructorInternal - subroutine tidalRadiusDestructor(self) - !!{ - Destructor for the {\normalfont \ttfamily tidalRadius} black hole binary recoil class. - !!} - implicit none - type(blackHoleBinaryInitialSeparationTidalRadius), intent(inout) :: self - - !![ - - !!] - return - end subroutine tidalRadiusDestructor - double precision function tidalRadiusSeparationInitial(self,node,nodeHost) !!{ Returns an initial separation for a binary black holes through tidal disruption. !!} use :: Galactic_Structure_Options, only : massTypeGalactic - use :: Galacticus_Nodes , only : nodeComponentBlackHole, treeNode + use :: Galacticus_Nodes , only : nodeComponentBlackHole implicit none class(blackHoleBinaryInitialSeparationTidalRadius), intent(inout), target :: self type (treeNode ), intent(inout), target :: nodeHost , node @@ -146,22 +121,23 @@ double precision function tidalRadiusSeparationInitial(self,node,nodeHost) blackHole => node%blackHole(instance=1) ! If the primary black hole has zero mass (i.e. has been ejected), then return immediately. if (blackHole%mass() <= 0.0d0) return + ! Get the mass distribution. + massDistribution_ => node%massDistribution(massType=massTypeGalactic) ! Get the half-mass radius of the satellite galaxy. - radiusMassHalf=self%galacticStructure_%radiusEnclosingMass(node,massFractional=0.5d0 ,massType=massTypeGalactic) + radiusMassHalf=massDistribution_%radiusEnclosingMass (massFractional=0.5d0 ) ! Get the mass within the half-mass radius. - massHalf =self%galacticStructure_%massEnclosed (node, radiusMassHalf,massType=massTypeGalactic) - ! Return zero radius for massless galaxy. - if (radiusMassHalf <= 0.0d0 .or. massHalf <= 0.0d0) return + massHalf =massDistribution_%massEnclosedBySphere( radiusMassHalf) + !![ + + !!] ! Solve for the radius around the host at which the satellite gets disrupted. - self_ => self - node_ => nodeHost - tidalRadiusSeparationInitial = self%finder%find( & - & rootGuess=self%galacticStructure_%radiusEnclosingMass( & - & nodeHost , & - & massFractional=0.5d0 , & - & massType=massTypeGalactic & - & ) & - & ) + if (radiusMassHalf > 0.0d0 .and. massHalf > 0.0d0) then + massDistribution_ => nodeHost %massDistribution(massType =massTypeGalactic ) + tidalRadiusSeparationInitial = self %finder%find (rootGuess=massDistribution_%radiusEnclosingMass(massFractional=0.5d0)) + !![ + + !!] + end if return end function tidalRadiusSeparationInitial @@ -174,11 +150,11 @@ Root function used in solving for the radius of tidal disruption of a satellite double precision, intent(in ) :: radius ! Evaluate the root function. - tidalRadiusRoot=+self_%galacticStructure_%massEnclosed(node_,radius,massType=massTypeGalactic) & - & /massHalf & - & -( & - & +radius & - & /radiusMassHalf & + tidalRadiusRoot=+massDistribution_%massEnclosedBySphere(radius) & + & /massHalf & + & -( & + & +radius & + & /radiusMassHalf & & )**3 return end function tidalRadiusRoot diff --git a/source/black_holes.binaries.separation_growth_rate.standard.F90 b/source/black_holes.binaries.separation_growth_rate.standard.F90 index 1de1d2883f..53c21b2de7 100644 --- a/source/black_holes.binaries.separation_growth_rate.standard.F90 +++ b/source/black_holes.binaries.separation_growth_rate.standard.F90 @@ -25,7 +25,6 @@ !!} use :: Dark_Matter_Halo_Scales, only : darkMatterHaloScaleClass - use :: Galactic_Structure , only : galacticStructureClass !![ @@ -82,7 +81,6 @@ private logical :: stellarDensityChangeBinaryMotion , computeVelocityDispersion class (darkMatterHaloScaleClass), pointer :: darkMatterHaloScale_ => null() - class (galacticStructureClass ), pointer :: galacticStructure_ => null() contains final :: standardDestructor procedure :: growthRate => standardGrowthRate @@ -108,7 +106,6 @@ function standardConstructorParameters(parameters) result(self) type (blackHoleBinarySeparationGrowthRateStandard) :: self type (inputParameters ), intent(inout) :: parameters class (darkMatterHaloScaleClass ), pointer :: darkMatterHaloScale_ - class (galacticStructureClass ), pointer :: galacticStructure_ logical :: stellarDensityChangeBinaryMotion, computeVelocityDispersion !![ @@ -127,28 +124,25 @@ function standardConstructorParameters(parameters) result(self) parameters - !!] - self=blackHoleBinarySeparationGrowthRateStandard(stellarDensityChangeBinaryMotion,computeVelocityDispersion,darkMatterHaloScale_,galacticStructure_) + self=blackHoleBinarySeparationGrowthRateStandard(stellarDensityChangeBinaryMotion,computeVelocityDispersion,darkMatterHaloScale_) !![ - !!] return end function standardConstructorParameters - function standardConstructorInternal(stellarDensityChangeBinaryMotion,computeVelocityDispersion,darkMatterHaloScale_,galacticStructure_) result(self) + function standardConstructorInternal(stellarDensityChangeBinaryMotion,computeVelocityDispersion,darkMatterHaloScale_) result(self) !!{ Internal constructor for the {\normalfont \ttfamily standard} black hole binary separation growth class. !!} implicit none type (blackHoleBinarySeparationGrowthRateStandard) :: self class (darkMatterHaloScaleClass ), intent(in ), target :: darkMatterHaloScale_ - class (galacticStructureClass ), intent(in ), target :: galacticStructure_ logical , intent(in ) :: stellarDensityChangeBinaryMotion, computeVelocityDispersion !![ - + !!] return @@ -163,7 +157,6 @@ subroutine standardDestructor(self) !![ - !!] return end subroutine standardDestructor @@ -173,11 +166,13 @@ double precision function standardGrowthRate(self,blackHole) Returns an initial separation growth rate for a binary black holes that follows a modified version of \cite{volonteri_assembly_2003}. !!} + use :: Coordinates , only : coordinateCylindrical , assignment(=) use :: Display , only : displayIndent , displayMessage , displayUnindent - use :: Galactic_Structure_Options , only : componentTypeDarkHalo , componentTypeSpheroid , coordinateSystemCylindrical, massTypeDark, & - & massTypeGalactic , massTypeStellar + use :: Galactic_Structure_Options , only : componentTypeDarkHalo , componentTypeSpheroid , massTypeDark , massTypeGalactic,& + & massTypeStellar use :: Error , only : Error_Report use :: Galacticus_Nodes , only : nodeComponentBlackHole , nodeComponentSpheroid , treeNode + use :: Mass_Distributions , only : massDistributionClass , kinematicsDistributionClass use :: Numerical_Constants_Astronomical, only : Mpc_per_km_per_s_To_Gyr, gravitationalConstantGalacticus use :: Numerical_Constants_Math , only : Pi use :: Numerical_Constants_Physical , only : speedLight @@ -188,18 +183,22 @@ double precision function standardGrowthRate(self,blackHole) type (treeNode ), pointer :: node class (nodeComponentBlackHole ), pointer :: blackHoleCentral class (nodeComponentSpheroid ), pointer :: spheroid - double precision , parameter :: hardeningRateDimensionless =15.0d0 - double precision , parameter :: outerRadiusMultiplier =10.0d0 - double precision , parameter :: dynamicalFrictionMinimumRadius =0.1d0 - double precision :: coulombLogarithmDarkMatter , coulombLogarithmSpheroid , & - & densityDarkMatter , densitySpheroid , & - & densityStellar , dynamicalFrictionAcceleration , & - & dynamicalFrictionXDarkMatter , dynamicalFrictionXSpheroid , & - & radiusHardBinary , rateGravitationalWaves , & - & rateScattering , rateScatteringDynamicalFriction, & - & rateScatteringStars , rotationCurveGradient , & - & stellarDensityFractionRemaining , velocityDispersionDarkMatter , & - & velocityDispersionSpheroid + class (massDistributionClass ), pointer :: massDistributionSpheroidStellar_ , massDistributionDarkMatterHalo_ , & + & massDistributionGalactic_ , massDistribution_ + class (kinematicsDistributionClass ), pointer :: kinematicsDistributionSpheroidStellar_ , kinematicsDistributionDarkMatterHalo_ + double precision , parameter :: hardeningRateDimensionless =15.0d0 + double precision , parameter :: outerRadiusMultiplier =10.0d0 + double precision , parameter :: dynamicalFrictionMinimumRadius = 0.1d0 + double precision :: coulombLogarithmDarkMatter , coulombLogarithmSpheroid , & + & densityDarkMatter , densitySpheroid , & + & densityStellar , dynamicalFrictionAcceleration , & + & dynamicalFrictionXDarkMatter , dynamicalFrictionXSpheroid , & + & radiusHardBinary , rateGravitationalWaves , & + & rateScattering , rateScatteringDynamicalFriction , & + & rateScatteringStars , rotationCurveGradient , & + & stellarDensityFractionRemaining , velocityDispersionDarkMatter , & + & velocityDispersionSpheroid , velocityRotation + type (coordinateCylindrical ) :: coordinates character (len=24 ) :: message ! Get the host node. @@ -218,27 +217,28 @@ double precision function standardGrowthRate(self,blackHole) standardGrowthRate=0.0d0 return end if + ! Get required mass distributions. + massDistribution_ => node%massDistribution( ) + massDistributionSpheroidStellar_ => node%massDistribution(componentType=componentTypeSpheroid,massType=massTypeStellar ) + massDistributionDarkMatterHalo_ => node%massDistribution(componentType=componentTypeDarkHalo,massType=massTypeDark ) + massDistributionGalactic_ => node%massDistribution( massType=massTypeGalactic) ! Get the spheroid component. - spheroid => node%spheroid() - ! Compute the velocity dispersion of stars and dark matter. + spheroid => node%spheroid ( ) + ! Set coordinates of the black hole. + coordinates = [blackHole%radialPosition(),0.0d0,0.0d0] + ! Compute the velocity dispersion of stars and dark matter. if (self%computeVelocityDispersion) then - velocityDispersionSpheroid =self%galacticStructure_%velocityDispersion( & - & node, & - & blackHole %radialPosition( ), & - & spheroid %radius ( )*outerRadiusMultiplier, & - & componentTypeSpheroid , & - & massTypeStellar & - & ) - velocityDispersionDarkMatter=self%galacticStructure_%velocityDispersion( & - & node, & - & blackHole %radialPosition( ), & - & self %darkMatterHaloScale_%radiusVirial (node)*outerRadiusMultiplier, & - & componentTypeDarkHalo , & - & massTypeDark & - & ) + kinematicsDistributionSpheroidStellar_ => massDistributionSpheroidStellar_ %kinematicsDistribution( ) + kinematicsDistributionDarkMatterHalo_ => massDistributionDarkMatterHalo_ %kinematicsDistribution( ) + velocityDispersionSpheroid = kinematicsDistributionSpheroidStellar_%velocityDispersion1D (coordinates,massDistribution_) + velocityDispersionDarkMatter = kinematicsDistributionDarkMatterHalo_ %velocityDispersion1D (coordinates,massDistribution_) + !![ + + + !!] else - velocityDispersionSpheroid =spheroid %velocity ( ) - velocityDispersionDarkMatter=self %darkMatterHaloScale_%velocityVirial(node) + velocityDispersionSpheroid = spheroid %velocity ( ) + velocityDispersionDarkMatter = self %darkMatterHaloScale_%velocityVirial(node) end if ! Compute the separation growth rate due to emission of gravitational waves. rateGravitationalWaves=-( & @@ -295,12 +295,7 @@ double precision function standardGrowthRate(self,blackHole) ! Limit the density fraction to unity. stellarDensityFractionRemaining=min(stellarDensityFractionRemaining,1.0d0) ! Compute the stellar density, accounting for any loss. - densityStellar= self%galacticStructure_%density(node , & - & [blackHole%radialPosition(),0.0d0,0.0d0] , & - & coordinateSystem=coordinateSystemCylindrical, & - & componentType =componentTypeSpheroid , & - & massType =massTypeStellar & - & ) & + densityStellar=+massDistributionSpheroidStellar_%density(coordinates) & & *stellarDensityFractionRemaining ! Compute the hardening rate due to strong scattering of individual stars. if (velocityDispersionSpheroid > 0.0d0) then @@ -316,16 +311,8 @@ double precision function standardGrowthRate(self,blackHole) ! Check if the binary has sufficiently large separation that we should compute the rate of hardening due to dynamical friction. if (blackHole%radialPosition() > dynamicalFrictionMinimumRadius*radiusHardBinary) then ! Compute the total density, including dark matter. - densitySpheroid =self%galacticStructure_%density(node , & - & [blackHole%radialPosition(),0.0d0,0.0d0] , & - & coordinateSystem=coordinateSystemCylindrical, & - & massType =massTypeGalactic & - & ) - densityDarkMatter=self%galacticStructure_%density(node , & - & [blackHole%radialPosition(),0.0d0,0.0d0] , & - & coordinateSystem=coordinateSystemCylindrical, & - & massType =massTypeDark & - & ) + densitySpheroid =massDistributionGalactic_ %density(coordinates) + densityDarkMatter=massDistributionDarkMatterHalo_%density(coordinates) ! Compute the Coulomb logarithms for dynamical friction. coulombLogarithmSpheroid = ( & & blackHole %radialPosition() & @@ -352,70 +339,67 @@ double precision function standardGrowthRate(self,blackHole) ! Compute the rotation curve of the galaxy and the additional contribution from the active black hole. Add them in ! quadrature to get an estimate of the actual orbital speed of the black hole binary. ! Precompute the "X" term appearing in the dynamical friction formula. + velocityRotation=massDistribution_%rotationCurve(blackHole%radialPosition()) if (velocityDispersionSpheroid > 0.0d0) then - dynamicalFrictionXSpheroid = self%galacticStructure_%velocityRotation( & - & node , & - & blackHole%radialPosition() & - & ) & - & /sqrt(2.0d0) & - & /velocityDispersionSpheroid + dynamicalFrictionXSpheroid=+velocityRotation & + & /sqrt(2.0d0) & + & /velocityDispersionSpheroid else - dynamicalFrictionXSpheroid=0.0d0 + dynamicalFrictionXSpheroid=+0.0d0 end if - dynamicalFrictionXDarkMatter= self%galacticStructure_%velocityRotation( & - & node , & - & blackHole%radialPosition() & - & ) & - & /sqrt(2.0d0) & - & /velocityDispersionDarkMatter + dynamicalFrictionXDarkMatter =+velocityRotation & + & /sqrt(2.0d0) & + & /velocityDispersionDarkMatter ! Compute the acceleration due to dynamical friction. - dynamicalFrictionAcceleration=-4.0d0 & - & *Pi & - & *gravitationalConstantGalacticus**2 & - & *blackHole%mass() & - & *0.5d0 & - & /self%galacticStructure_%velocityRotation(node,blackHole%radialPosition())**2 & - & /Mpc_per_km_per_s_To_Gyr & - & *( & - & densitySpheroid & - & *log(1.0d0+coulombLogarithmSpheroid**2) & - & *( & - & erf(dynamicalFrictionXSpheroid) & - & -( & - & 2.0d0 & - & *dynamicalFrictionXSpheroid & - & /sqrt(Pi) & - & *exp(-dynamicalFrictionXSpheroid**2) & - & ) & - & ) & - & +densityDarkMatter & - & *log(1.0d0+coulombLogarithmDarkMatter**2) & - & *( & - & erf(dynamicalFrictionXDarkMatter) & - & -( & - & 2.0d0 & - & *dynamicalFrictionXDarkMatter & - & /sqrt(Pi) & - & *exp(-dynamicalFrictionXDarkMatter**2) & - & ) & - & ) & + dynamicalFrictionAcceleration=-4.0d0 & + & *Pi & + & *gravitationalConstantGalacticus**2 & + & *blackHole%mass() & + & *0.5d0 & + & /velocityRotation**2 & + & /Mpc_per_km_per_s_To_Gyr & + & *( & + & densitySpheroid & + & *log(1.0d0+coulombLogarithmSpheroid**2) & + & *( & + & erf(dynamicalFrictionXSpheroid) & + & -( & + & 2.0d0 & + & *dynamicalFrictionXSpheroid & + & /sqrt(Pi) & + & *exp(-dynamicalFrictionXSpheroid**2) & + & ) & + & ) & + & +densityDarkMatter & + & *log(1.0d0+coulombLogarithmDarkMatter**2) & + & *( & + & erf(dynamicalFrictionXDarkMatter) & + & -( & + & 2.0d0 & + & *dynamicalFrictionXDarkMatter & + & /sqrt(Pi) & + & *exp(-dynamicalFrictionXDarkMatter**2) & + & ) & + & ) & & ) ! Compute the radial inflow velocity due to dynamical friction. - rotationCurveGradient =( & - & +self%galacticStructure_%velocityRotation (node,blackHole%radialPosition()) & - & + blackHole%radialPosition() & - & *self%galacticStructure_%velocityRotationGradient(node,blackHole%radialPosition()) & + rotationCurveGradient =( & + & + velocityRotation & + & +0.5d0 & + & * blackHole%radialPosition() & + & / velocityRotation & + & *massDistribution_%rotationCurveGradient(blackHole%radialPosition()) & & ) if (rotationCurveGradient == 0.0d0) then call displayIndent('dynamical friction calculation report') write (message,'(a,i12) ') 'nodeIndex = ',node%index() - write (message,'(a,e12.6)') ' V(r) = ',self%galacticStructure_%velocityRotation (node,blackHole%radialPosition()) + write (message,'(a,e12.6)') ' V (r) = ', velocityRotation call displayMessage(trim(message)) - write (message,'(a,e12.6)') ' r = ', blackHole%radialPosition() + write (message,'(a,e12.6)') ' r = ', blackHole%radialPosition() call displayMessage(trim(message)) - write (message,'(a,e12.6)') ' dV(r)/dr = ',self%galacticStructure_%velocityRotationGradient(node,blackHole%radialPosition()) + write (message,'(a,e12.6)') ' dV²(r)/dr = ',massDistribution_%rotationCurveGradient (blackHole%radialPosition()) call displayMessage(trim(message)) - write (message,'(a,e12.6)') ' a_{df} = ',dynamicalFrictionAcceleration + write (message,'(a,e12.6)') ' a_{df} = ', dynamicalFrictionAcceleration call displayMessage(trim(message)) call displayUnindent('done') call Error_Report('rotation curve gradient is zero'//{introspection:location}) @@ -432,5 +416,12 @@ double precision function standardGrowthRate(self,blackHole) end if ! Sum the two contributions to the radial growth rate. standardGrowthRate=rateScattering+rateGravitationalWaves + ! Clean up. + !![ + + + + + !!] return end function standardGrowthRate diff --git a/source/cooling.cooling_radius.beta_profile.F90 b/source/cooling.cooling_radius.beta_profile.F90 index 1a638be189..047001e209 100644 --- a/source/cooling.cooling_radius.beta_profile.F90 +++ b/source/cooling.cooling_radius.beta_profile.F90 @@ -22,14 +22,12 @@ time scales as inverse density. !!} - use :: Cooling_Times , only : coolingTimeClass - use :: Cooling_Times_Available , only : coolingTimeAvailableClass - use :: Cosmology_Functions , only : cosmologyFunctions , cosmologyFunctionsClass - use :: Dark_Matter_Halo_Scales , only : darkMatterHaloScaleClass - use :: Hot_Halo_Mass_Distributions , only : hotHaloMassDistributionClass - use :: Hot_Halo_Temperature_Profiles, only : hotHaloTemperatureProfileClass - use :: Kind_Numbers , only : kind_int8 - use :: Radiation_Fields , only : radiationFieldCosmicMicrowaveBackground + use :: Cooling_Times , only : coolingTimeClass + use :: Cooling_Times_Available, only : coolingTimeAvailableClass + use :: Cosmology_Functions , only : cosmologyFunctions , cosmologyFunctionsClass + use :: Dark_Matter_Halo_Scales, only : darkMatterHaloScaleClass + use :: Kind_Numbers , only : kind_int8 + use :: Radiation_Fields , only : radiationFieldCosmicMicrowaveBackground !![ @@ -60,8 +58,6 @@ class (darkMatterHaloScaleClass ), pointer :: darkMatterHaloScale_ => null() class (coolingTimeAvailableClass ), pointer :: coolingTimeAvailable_ => null() class (coolingTimeClass ), pointer :: coolingTime_ => null() - class (hotHaloTemperatureProfileClass ), pointer :: hotHaloTemperatureProfile_ => null() - class (hotHaloMassDistributionClass ), pointer :: hotHaloMassDistribution_ => null() type (radiationFieldCosmicMicrowaveBackground), pointer :: radiation => null() integer (kind=kind_int8 ) :: lastUniqueID = -1 integer :: abundancesCount , chemicalsCount @@ -102,52 +98,42 @@ function betaProfileConstructorParameters(parameters) result(self) class(coolingTimeAvailableClass ), pointer :: coolingTimeAvailable_ class(coolingTimeClass ), pointer :: coolingTime_ class(darkMatterHaloScaleClass ), pointer :: darkMatterHaloScale_ - class(hotHaloTemperatureProfileClass), pointer :: hotHaloTemperatureProfile_ - class(hotHaloMassDistributionClass ), pointer :: hotHaloMassDistribution_ class(cosmologyFunctionsClass ), pointer :: cosmologyFunctions_ !![ - - - - - - + + + + !!] - self=coolingRadiusBetaProfile(cosmologyFunctions_,darkMatterHaloScale_,coolingTimeAvailable_,coolingTime_,hotHaloTemperatureProfile_,hotHaloMassDistribution_) + self=coolingRadiusBetaProfile(cosmologyFunctions_,darkMatterHaloScale_,coolingTimeAvailable_,coolingTime_) !![ - - - - - - + + + + !!] return end function betaProfileConstructorParameters - function betaProfileConstructorInternal(cosmologyFunctions_,darkMatterHaloScale_,coolingTimeAvailable_,coolingTime_,hotHaloTemperatureProfile_,hotHaloMassDistribution_) result(self) + function betaProfileConstructorInternal(cosmologyFunctions_,darkMatterHaloScale_,coolingTimeAvailable_,coolingTime_) result(self) !!{ Internal constructor for the $\beta$-profile cooling radius class. !!} - use :: Abundances_Structure , only : Abundances_Property_Count , abundances + use :: Abundances_Structure , only : Abundances_Property_Count, abundances use :: Array_Utilities , only : operator(.intersection.) use :: Chemical_Abundances_Structure, only : Chemicals_Property_Count - use :: Error , only : Component_List , Error_Report + use :: Error , only : Component_List , Error_Report use :: Galacticus_Nodes , only : defaultHotHaloComponent - use :: Hot_Halo_Mass_Distributions , only : hotHaloMassDistributionBetaProfile - use :: Hot_Halo_Temperature_Profiles, only : hotHaloTemperatureProfileVirial implicit none - type (coolingRadiusBetaProfile ) :: self - class(cosmologyFunctionsClass ), intent(in ), target :: cosmologyFunctions_ - class(darkMatterHaloScaleClass ), intent(in ), target :: darkMatterHaloScale_ - class(coolingTimeAvailableClass ), intent(in ), target :: coolingTimeAvailable_ - class(coolingTimeClass ), intent(in ), target :: coolingTime_ - class(hotHaloTemperatureProfileClass), intent(in ), target :: hotHaloTemperatureProfile_ - class(hotHaloMassDistributionClass ), intent(in ), target :: hotHaloMassDistribution_ + type (coolingRadiusBetaProfile ) :: self + class(cosmologyFunctionsClass ), intent(in ), target :: cosmologyFunctions_ + class(darkMatterHaloScaleClass ), intent(in ), target :: darkMatterHaloScale_ + class(coolingTimeAvailableClass), intent(in ), target :: coolingTimeAvailable_ + class(coolingTimeClass ), intent(in ), target :: coolingTime_ !![ - + !!] ! Initial state of stored solutions. @@ -185,24 +171,6 @@ function betaProfileConstructorInternal(cosmologyFunctions_,darkMatterHaloScale_ & ) // & & {introspection:location} & & ) - ! Check that assumptions are valid. Note that currently we can not check all assumptions. We check that: - ! * Hot halo temperature profile is isothermal; - ! * Hot halo density profile is a β-profile. - ! We do not check that: - ! * β=2/3; - ! * Cooling function is always proportional to ρ². - select type (hotHaloTemperatureProfile_ => self%hotHaloTemperatureProfile_) - class is (hotHaloTemperatureProfileVirial) - ! An isothermal profile - this is acceptable. - class default - call Error_Report('assumption of isothermal hot halo temperature profile is not met'//{introspection:location}) - end select - select type (hotHaloMassDistribution_ => self%hotHaloMassDistribution_ ) - class is (hotHaloMassDistributionBetaProfile) - ! A beta-model profile - this is acceptable. - class default - call Error_Report('assumption of β-model hot halo mass distribution is not met' //{introspection:location}) - end select return end function betaProfileConstructorInternal @@ -227,13 +195,11 @@ subroutine betaProfileDestructor(self) type(coolingRadiusBetaProfile), intent(inout) :: self !![ - - - - - - - + + + + + !!] if (calculationResetEvent%isAttached(self,betaProfileCalculationReset)) call calculationResetEvent%detach(self,betaProfileCalculationReset) return @@ -263,18 +229,24 @@ double precision function betaProfileRadiusGrowthRate(self,node) use :: Abundances_Structure , only : abundances use :: Chemical_Abundances_Structure , only : chemicalAbundances use :: Chemical_Reaction_Rates_Utilities, only : Chemicals_Mass_To_Fraction_Conversion - use :: Galacticus_Nodes , only : nodeComponentBasic , nodeComponentHotHalo, treeNode + use :: Galacticus_Nodes , only : nodeComponentBasic , nodeComponentHotHalo , treeNode + use :: Mass_Distributions , only : massDistributionClass , kinematicsDistributionClass + use :: Coordinates , only : coordinateSpherical , assignment(=) + use :: Galactic_Structure_Options , only : componentTypeHotHalo , massTypeGaseous implicit none - class (coolingRadiusBetaProfile), intent(inout) :: self - type (treeNode ), intent(inout) :: node - class (nodeComponentBasic ), pointer :: basic - class (nodeComponentHotHalo ), pointer :: hotHalo - double precision :: coolingTimeZero , timeAvailable , & - & densityZero , massToDensityConversion, & - & temperature , outerRadius , & - & densityOuter , coolingTimeOuter - type (abundances ) :: hotAbundances - type (chemicalAbundances ) :: chemicalFractions, chemicalMasses + class (coolingRadiusBetaProfile ), intent(inout) :: self + type (treeNode ), intent(inout) :: node + class (nodeComponentBasic ), pointer :: basic + class (nodeComponentHotHalo ), pointer :: hotHalo + class (massDistributionClass ), pointer :: massDistribution_ + class (kinematicsDistributionClass), pointer :: kinematicsDistribution_ + type (coordinateSpherical ) :: coordinates + double precision :: coolingTimeZero , timeAvailable , & + & densityZero , massToDensityConversion, & + & temperature , outerRadius , & + & densityOuter , coolingTimeOuter + type (abundances ) :: hotAbundances + type (chemicalAbundances ) :: chemicalFractions , chemicalMasses ! Check if node differs from previous one for which we performed calculations. if (node%uniqueID() /= self%lastUniqueID) call self%calculationReset(node,node%uniqueID()) @@ -305,13 +277,22 @@ double precision function betaProfileRadiusGrowthRate(self,node) call self%radiation%timeSet(basic%time()) ! Get the outer radius. outerRadius=hotHalo%outerRadius() + ! Get the mass distribution. + massDistribution_ => node %massDistribution (componentTypeHotHalo,massTypeGaseous) + kinematicsDistribution_ => massDistribution_%kinematicsDistribution( ) ! Get the temperature. - temperature=self%hotHaloTemperatureProfile_%temperature(node,outerRadius) + coordinates = [outerRadius,0.0d0,0.0d0] + temperature = kinematicsDistribution_ %temperature(coordinates ) ! Compute density and cooling time at outer radius and zero radius. - densityZero =self%hotHaloMassDistribution_ %density(node,0.0d0 ) - densityOuter =self%hotHaloMassDistribution_ %density(node,outerRadius) - coolingTimeZero =self%coolingTime_ %time (node,temperature,densityZero ,hotAbundances,chemicalFractions*densityZero,self%radiation) - coolingTimeOuter=self%coolingTime_ %time (node,temperature,densityOuter,hotAbundances,chemicalFractions*densityOuter,self%radiation) + densityOuter = massDistribution_ %density (coordinates ) + coordinates = [outerRadius,0.0d0,0.0d0] + densityZero = massDistribution_ %density (coordinates ) + coolingTimeZero = self %coolingTime_%time (node,temperature,densityZero ,hotAbundances,chemicalFractions*densityZero ,self%radiation) + coolingTimeOuter = self %coolingTime_%time (node,temperature,densityOuter,hotAbundances,chemicalFractions*densityOuter,self%radiation) + !![ + + + !!] if (coolingTimeOuter < timeAvailable .or. coolingTimeZero > timeAvailable) then ! Cooling radius is static. self%radiusGrowthRateStored=0.0d0 @@ -341,18 +322,24 @@ double precision function betaProfileRadius(self,node) use :: Abundances_Structure , only : abundances use :: Chemical_Abundances_Structure , only : chemicalAbundances use :: Chemical_Reaction_Rates_Utilities, only : Chemicals_Mass_To_Fraction_Conversion - use :: Galacticus_Nodes , only : nodeComponentBasic , nodeComponentHotHalo, treeNode + use :: Galacticus_Nodes , only : nodeComponentBasic , nodeComponentHotHalo , treeNode + use :: Mass_Distributions , only : massDistributionClass , kinematicsDistributionClass + use :: Coordinates , only : coordinateSpherical , assignment(=) + use :: Galactic_Structure_Options , only : componentTypeHotHalo , massTypeGaseous implicit none - class (coolingRadiusBetaProfile), intent(inout), target :: self - type (treeNode ), intent(inout), target :: node - class (nodeComponentBasic ) , pointer :: basic - class (nodeComponentHotHalo ) , pointer :: hotHalo - double precision :: coolingTimeZero , timeAvailable , & - & densityZero , massToDensityConversion, & - & temperature , outerRadius , & - & densityOuter , coolingTimeOuter - type (abundances ) :: hotAbundances - type (chemicalAbundances ) :: chemicalFractions, chemicalMasses + class (coolingRadiusBetaProfile ), intent(inout), target :: self + type (treeNode ), intent(inout), target :: node + class (nodeComponentBasic ) , pointer :: basic + class (nodeComponentHotHalo ) , pointer :: hotHalo + class (massDistributionClass ), pointer :: massDistribution_ + class (kinematicsDistributionClass), pointer :: kinematicsDistribution_ + type (coordinateSpherical ) :: coordinates + double precision :: coolingTimeZero , timeAvailable , & + & densityZero , massToDensityConversion, & + & temperature , outerRadius , & + & densityOuter , coolingTimeOuter + type (abundances ) :: hotAbundances + type (chemicalAbundances ) :: chemicalFractions , chemicalMasses ! Check if node differs from previous one for which we performed calculations. if (node%uniqueID() /= self%lastUniqueID) call self%calculationReset(node,node%uniqueID()) @@ -382,13 +369,22 @@ double precision function betaProfileRadius(self,node) call self%radiation%timeSet(basic%time()) ! Get the outer radius. outerRadius=hotHalo%outerRadius() + ! Get the mass distribution. + massDistribution_ => node %massDistribution (componentTypeHotHalo,massTypeGaseous) + kinematicsDistribution_ => massDistribution_%kinematicsDistribution( ) ! Get the temperature. - temperature=self%hotHaloTemperatureProfile_%temperature(node,outerRadius) + coordinates = [outerRadius,0.0d0,0.0d0] + temperature = kinematicsDistribution_ %temperature(coordinates ) ! Compute density and cooling time at outer radius and zero radius. - densityZero =self%hotHaloMassDistribution_ %density(node,0.0d0 ) - densityOuter =self%hotHaloMassDistribution_ %density(node,outerRadius ) - coolingTimeZero =self%coolingTime_ %time (node,temperature,densityZero ,hotAbundances,chemicalFractions*densityZero,self%radiation) - coolingTimeOuter=self%coolingTime_ %time (node,temperature,densityOuter,hotAbundances,chemicalFractions*densityOuter,self%radiation) + densityOuter = massDistribution_ %density (coordinates ) + coordinates = [outerRadius,0.0d0,0.0d0] + densityZero = massDistribution_ %density (coordinates ) + coolingTimeZero = self %coolingTime_%time (node,temperature,densityZero ,hotAbundances,chemicalFractions*densityZero ,self%radiation) + coolingTimeOuter = self %coolingTime_%time (node,temperature,densityOuter,hotAbundances,chemicalFractions*densityOuter,self%radiation) + !![ + + + !!] if (coolingTimeOuter < timeAvailable) then ! Cooling time available exceeds cooling time at virial radius, return virial radius. self%radiusStored=outerRadius diff --git a/source/cooling.cooling_radius.isothermal_profile.F90 b/source/cooling.cooling_radius.isothermal_profile.F90 index 38fbb2d5f2..c099c20526 100644 --- a/source/cooling.cooling_radius.isothermal_profile.F90 +++ b/source/cooling.cooling_radius.isothermal_profile.F90 @@ -22,13 +22,11 @@ time scales as inverse density. !!} - use :: Cooling_Times , only : coolingTimeClass - use :: Cooling_Times_Available , only : coolingTimeAvailableClass - use :: Cosmology_Functions , only : cosmologyFunctions , cosmologyFunctionsClass - use :: Dark_Matter_Halo_Scales , only : darkMatterHaloScaleClass - use :: Hot_Halo_Mass_Distributions , only : hotHaloMassDistributionClass - use :: Hot_Halo_Temperature_Profiles, only : hotHaloTemperatureProfileClass - use :: Kind_Numbers , only : kind_int8 + use :: Cooling_Times , only : coolingTimeClass + use :: Cooling_Times_Available, only : coolingTimeAvailableClass + use :: Cosmology_Functions , only : cosmologyFunctions , cosmologyFunctionsClass + use :: Dark_Matter_Halo_Scales, only : darkMatterHaloScaleClass + use :: Kind_Numbers , only : kind_int8 !![ @@ -69,8 +67,6 @@ class (darkMatterHaloScaleClass ), pointer :: darkMatterHaloScale_ => null() class (coolingTimeAvailableClass ), pointer :: coolingTimeAvailable_ => null() class (coolingTimeClass ), pointer :: coolingTime_ => null() - class (hotHaloTemperatureProfileClass ), pointer :: hotHaloTemperatureProfile_ => null() - class (hotHaloMassDistributionClass ), pointer :: hotHaloMassDistribution_ => null() type (radiationFieldCosmicMicrowaveBackground), pointer :: radiation => null() integer (kind=kind_int8 ) :: lastUniqueID = -1 integer :: abundancesCount , chemicalsCount @@ -106,37 +102,31 @@ function isothermalConstructorParameters(parameters) result(self) !!} use :: Input_Parameters, only : inputParameter, inputParameters implicit none - type (coolingRadiusIsothermal ) :: self - type (inputParameters ), intent(inout) :: parameters - class(coolingTimeAvailableClass ), pointer :: coolingTimeAvailable_ - class(coolingTimeClass ), pointer :: coolingTime_ - class(darkMatterHaloScaleClass ), pointer :: darkMatterHaloScale_ - class(hotHaloTemperatureProfileClass), pointer :: hotHaloTemperatureProfile_ - class(hotHaloMassDistributionClass ), pointer :: hotHaloMassDistribution_ - class(cosmologyFunctionsClass ), pointer :: cosmologyFunctions_ + type (coolingRadiusIsothermal ) :: self + type (inputParameters ), intent(inout) :: parameters + class(coolingTimeAvailableClass), pointer :: coolingTimeAvailable_ + class(coolingTimeClass ), pointer :: coolingTime_ + class(darkMatterHaloScaleClass ), pointer :: darkMatterHaloScale_ + class(cosmologyFunctionsClass ), pointer :: cosmologyFunctions_ !![ - - - - - - + + + + !!] - self=coolingRadiusIsothermal(cosmologyFunctions_,darkMatterHaloScale_,coolingTimeAvailable_,coolingTime_,hotHaloTemperatureProfile_,hotHaloMassDistribution_) + self=coolingRadiusIsothermal(cosmologyFunctions_,darkMatterHaloScale_,coolingTimeAvailable_,coolingTime_) !![ - - - - - - + + + + !!] return end function isothermalConstructorParameters - function isothermalConstructorInternal(cosmologyFunctions_,darkMatterHaloScale_,coolingTimeAvailable_,coolingTime_,hotHaloTemperatureProfile_,hotHaloMassDistribution_) result(self) + function isothermalConstructorInternal(cosmologyFunctions_,darkMatterHaloScale_,coolingTimeAvailable_,coolingTime_) result(self) !!{ Internal constructor for the isothermal cooling radius class. !!} @@ -146,15 +136,13 @@ function isothermalConstructorInternal(cosmologyFunctions_,darkMatterHaloScale_, use :: Error , only : Component_List , Error_Report use :: Galacticus_Nodes , only : defaultHotHaloComponent implicit none - type (coolingRadiusIsothermal ) :: self - class(cosmologyFunctionsClass ), intent(in ), target :: cosmologyFunctions_ - class(darkMatterHaloScaleClass ), intent(in ), target :: darkMatterHaloScale_ - class(coolingTimeAvailableClass ), intent(in ), target :: coolingTimeAvailable_ - class(coolingTimeClass ), intent(in ), target :: coolingTime_ - class(hotHaloTemperatureProfileClass), intent(in ), target :: hotHaloTemperatureProfile_ - class(hotHaloMassDistributionClass ), intent(in ), target :: hotHaloMassDistribution_ + type (coolingRadiusIsothermal ) :: self + class(cosmologyFunctionsClass ), intent(in ), target :: cosmologyFunctions_ + class(darkMatterHaloScaleClass ), intent(in ), target :: darkMatterHaloScale_ + class(coolingTimeAvailableClass), intent(in ), target :: coolingTimeAvailable_ + class(coolingTimeClass ), intent(in ), target :: coolingTime_ !![ - + !!] ! Initial state of stored solutions. @@ -216,13 +204,11 @@ subroutine isothermalDestructor(self) type(coolingRadiusIsothermal), intent(inout) :: self !![ - - - - - - - + + + + + !!] if (calculationResetEvent%isAttached(self,isothermalCalculationReset)) call calculationResetEvent%detach(self,isothermalCalculationReset) return @@ -288,17 +274,23 @@ double precision function isothermalRadius(self,node) use :: Abundances_Structure , only : abundances use :: Chemical_Abundances_Structure , only : chemicalAbundances use :: Chemical_Reaction_Rates_Utilities, only : Chemicals_Mass_To_Fraction_Conversion - use :: Galacticus_Nodes , only : nodeComponentBasic , nodeComponentHotHalo, treeNode + use :: Galacticus_Nodes , only : nodeComponentBasic , nodeComponentHotHalo , treeNode + use :: Mass_Distributions , only : massDistributionClass , kinematicsDistributionClass + use :: Coordinates , only : coordinateSpherical , assignment(=) + use :: Galactic_Structure_Options , only : componentTypeHotHalo , massTypeGaseous implicit none - class (coolingRadiusIsothermal), intent(inout), target :: self - type (treeNode ), intent(inout), target :: node - class (nodeComponentBasic ) , pointer :: basic - class (nodeComponentHotHalo ) , pointer :: hotHalo - double precision :: coolingTime , timeAvailable , & - & density , massToDensityConversion, & - & temperature , radiusVirial - type (abundances ) :: hotAbundances - type (chemicalAbundances ) :: chemicalFractions, chemicalMasses + class (coolingRadiusIsothermal ), intent(inout), target :: self + type (treeNode ), intent(inout), target :: node + class (nodeComponentBasic ) , pointer :: basic + class (nodeComponentHotHalo ) , pointer :: hotHalo + class (massDistributionClass ) , pointer :: massDistribution_ + class (kinematicsDistributionClass) , pointer :: kinematicsDistribution_ + type (coordinateSpherical ) :: coordinates + double precision :: coolingTime , timeAvailable , & + & density , massToDensityConversion, & + & temperature , radiusVirial + type (abundances ) :: hotAbundances + type (chemicalAbundances ) :: chemicalFractions , chemicalMasses ! Check if node differs from previous one for which we performed calculations. if (node%uniqueID() /= self%lastUniqueID) call self%calculationReset(node,node%uniqueID()) @@ -328,11 +320,19 @@ double precision function isothermalRadius(self,node) call self%radiation%timeSet(basic%time()) ! Get the virial radius. radiusVirial=self%darkMatterHaloScale_%radiusVirial(node) + ! Get the mass distribution. + massDistribution_ => node %massDistribution (componentTypeHotHalo,massTypeGaseous) + kinematicsDistribution_ => massDistribution_%kinematicsDistribution( ) ! Compute density, temperature and abundances. - density =self%hotHaloMassDistribution_ %density (node,radiusVirial) - temperature =self%hotHaloTemperatureProfile_%temperature(node,radiusVirial) + coordinates=[radiusVirial,0.0d0,0.0d0] + density =massDistribution_ %density (coordinates) + temperature=kinematicsDistribution_ %temperature(coordinates) + !![ + + + !!] ! Compute the cooling time at the virial radius. - coolingTime =self%coolingTime_ %time (node,temperature,density,hotAbundances,chemicalFractions*density,self%radiation) + coolingTime =self %coolingTime_%time (node,temperature,density,hotAbundances,chemicalFractions*density,self%radiation) if (coolingTime < timeAvailable) then ! Cooling time available exceeds cooling time at virial radius, return virial radius. self%radiusStored=radiusVirial diff --git a/source/cooling.cooling_radius.simple.F90 b/source/cooling.cooling_radius.simple.F90 index af3df8615b..490c61335e 100644 --- a/source/cooling.cooling_radius.simple.F90 +++ b/source/cooling.cooling_radius.simple.F90 @@ -26,8 +26,6 @@ use :: Cooling_Times , only : coolingTimeClass use :: Cooling_Times_Available , only : coolingTimeAvailableClass use :: Cosmology_Functions , only : cosmologyFunctions , cosmologyFunctionsClass - use :: Hot_Halo_Mass_Distributions , only : hotHaloMassDistributionClass - use :: Hot_Halo_Temperature_Profiles, only : hotHaloTemperatureProfileClass use :: Kind_Numbers , only : kind_int8 use :: Radiation_Fields , only : radiationFieldCosmicMicrowaveBackground use :: Root_Finder , only : rootFinder @@ -58,8 +56,6 @@ which the time available for cooling is increasing. This method assumes that the class (cosmologyFunctionsClass ), pointer :: cosmologyFunctions_ => null() class (coolingTimeClass ), pointer :: coolingTime_ => null() class (coolingTimeAvailableClass ), pointer :: coolingTimeAvailable_ => null() - class (hotHaloMassDistributionClass ), pointer :: hotHaloMassDistribution_ => null() - class (hotHaloTemperatureProfileClass ), pointer :: hotHaloTemperatureProfile_ => null() type (radiationFieldCosmicMicrowaveBackground), pointer :: radiation => null() type (rootFinder ) :: finder integer (kind=kind_int8 ) :: lastUniqueID = -1 @@ -104,34 +100,28 @@ function simpleConstructorParameters(parameters) result(self) !!} use :: Input_Parameters, only : inputParameter, inputParameters implicit none - type (coolingRadiusSimple ) :: self - type (inputParameters ), intent(inout) :: parameters - class(coolingTimeAvailableClass ), pointer :: coolingTimeAvailable_ - class(coolingTimeClass ), pointer :: coolingTime_ - class(hotHaloTemperatureProfileClass), pointer :: hotHaloTemperatureProfile_ - class(hotHaloMassDistributionClass ), pointer :: hotHaloMassDistribution_ - class(cosmologyFunctionsClass ), pointer :: cosmologyFunctions_ + type (coolingRadiusSimple ) :: self + type (inputParameters ), intent(inout) :: parameters + class(coolingTimeAvailableClass), pointer :: coolingTimeAvailable_ + class(coolingTimeClass ), pointer :: coolingTime_ + class(cosmologyFunctionsClass ), pointer :: cosmologyFunctions_ !![ - - - - - + + + !!] - self=coolingRadiusSimple(cosmologyFunctions_,coolingTimeAvailable_,coolingTime_,hotHaloTemperatureProfile_,hotHaloMassDistribution_) + self=coolingRadiusSimple(cosmologyFunctions_,coolingTimeAvailable_,coolingTime_) !![ - - - - - + + + !!] return end function simpleConstructorParameters - function simpleConstructorInternal(cosmologyFunctions_,coolingTimeAvailable_,coolingTime_,hotHaloTemperatureProfile_,hotHaloMassDistribution_) result(self) + function simpleConstructorInternal(cosmologyFunctions_,coolingTimeAvailable_,coolingTime_) result(self) !!{ Internal constructor for the simple cooling radius class. !!} @@ -141,15 +131,13 @@ function simpleConstructorInternal(cosmologyFunctions_,coolingTimeAvailable_,coo use :: Error , only : Component_List , Error_Report use :: Galacticus_Nodes , only : defaultHotHaloComponent implicit none - type (coolingRadiusSimple ) :: self - class (cosmologyFunctionsClass ), intent(in ), target :: cosmologyFunctions_ - class (coolingTimeAvailableClass ), intent(in ), target :: coolingTimeAvailable_ - class (coolingTimeClass ), intent(in ), target :: coolingTime_ - class (hotHaloTemperatureProfileClass), intent(in ), target :: hotHaloTemperatureProfile_ - class (hotHaloMassDistributionClass ), intent(in ), target :: hotHaloMassDistribution_ - double precision , parameter :: toleranceAbsolute =0.0d0, toleranceRelative=1.0d-6 + type (coolingRadiusSimple ) :: self + class (cosmologyFunctionsClass ), intent(in ), target :: cosmologyFunctions_ + class (coolingTimeAvailableClass), intent(in ), target :: coolingTimeAvailable_ + class (coolingTimeClass ), intent(in ), target :: coolingTime_ + double precision , parameter :: toleranceAbsolute =0.0d0, toleranceRelative=1.0d-6 !![ - + !!] ! Initial state of stored solutions. @@ -217,12 +205,10 @@ subroutine simpleDestructor(self) type(coolingRadiusSimple), intent(inout) :: self !![ - - - - - - + + + + !!] if (calculationResetEvent%isAttached(self,simpleCalculationReset)) call calculationResetEvent%detach(self,simpleCalculationReset) return @@ -249,18 +235,24 @@ double precision function simpleRadiusGrowthRate(self,node) !!{ Returns the cooling radius growth rate (in Mpc/Gyr) in the hot atmosphere. !!} - use :: Galacticus_Nodes, only : nodeComponentBasic, nodeComponentHotHalo, treeNode + use :: Galacticus_Nodes , only : nodeComponentBasic , nodeComponentHotHalo , treeNode + use :: Mass_Distributions , only : massDistributionClass, kinematicsDistributionClass + use :: Coordinates , only : coordinateSpherical , assignment(=) + use :: Galactic_Structure_Options, only : componentTypeHotHalo , massTypeGaseous implicit none - class (coolingRadiusSimple ), intent(inout) :: self - type (treeNode ), intent(inout) :: node - class (nodeComponentBasic ), pointer :: basic - class (nodeComponentHotHalo), pointer :: hotHalo - double precision :: coolingRadius , coolingTimeAvailable , & - & coolingTimeAvailableIncreaseRate, coolingTimeDensityLogSlope, & - & coolingTimeTemperatureLogSlope , density , & - & densityLogSlope , outerRadius , & - & temperature , temperatureLogSlope , & - & slope + class (coolingRadiusSimple ), intent(inout) :: self + type (treeNode ), intent(inout) :: node + class (nodeComponentBasic ), pointer :: basic + class (nodeComponentHotHalo ), pointer :: hotHalo + class (massDistributionClass ), pointer :: massDistribution_ + class (kinematicsDistributionClass), pointer :: kinematicsDistribution_ + type (coordinateSpherical ) :: coordinates + double precision :: coolingRadius , coolingTimeAvailable , & + & coolingTimeAvailableIncreaseRate, coolingTimeDensityLogSlope, & + & coolingTimeTemperatureLogSlope , density , & + & densityLogSlope , outerRadius , & + & temperature , temperatureLogSlope , & + & slope ! Check if node differs from previous one for which we performed calculations. if (node%uniqueID() /= self%lastUniqueID) call self%calculationReset(node,node%uniqueID()) @@ -282,10 +274,17 @@ double precision function simpleRadiusGrowthRate(self,node) basic => node%basic() call self%radiation%timeSet(basic%time()) ! Get density and temperature at the cooling radius, plus their gradients. - density =self%hotHaloMassDistribution_ %density (node,coolingRadius) - temperature =self%hotHaloTemperatureProfile_%temperature (node,coolingRadius) - densityLogSlope =self%hotHaloMassDistribution_ %densityLogSlope (node,coolingRadius) - temperatureLogSlope=self%hotHaloTemperatureProfile_%temperatureLogSlope(node,coolingRadius) + coordinates = [coolingRadius,0.0d0,0.0d0] + massDistribution_ => node %massDistribution (componentTypeHotHalo,massTypeGaseous) + kinematicsDistribution_ => massDistribution_ %kinematicsDistribution ( ) + density = massDistribution_ %density (coordinates ) + temperature = kinematicsDistribution_%temperature (coordinates ) + densityLogSlope = massDistribution_ %densityGradientRadial (coordinates,logarithmic=.true. ) + temperatureLogSlope = kinematicsDistribution_%temperatureGradientLogarithmic(coordinates ) + !![ + + + !!] ! Get the time available for cooling in node and its rate of increase. coolingTimeAvailable =self%coolingTimeAvailable_%timeAvailable (node) coolingTimeAvailableIncreaseRate=self%coolingTimeAvailable_%timeAvailableIncreaseRate(node) @@ -396,20 +395,37 @@ double precision function coolingRadiusRoot(radius) !!{ Root function which evaluates the difference between the cooling time at {\normalfont \ttfamily radius} and the time available for cooling. !!} + use :: Mass_Distributions , only : massDistributionClass, kinematicsDistributionClass + use :: Coordinates , only : coordinateSpherical , assignment(=) + use :: Galactic_Structure_Options, only : componentTypeHotHalo , massTypeGaseous implicit none - double precision , intent(in ) :: radius - double precision :: coolingTime , density, & - & temperature - type (chemicalAbundances), save :: densityChemicals + double precision , intent(in ) :: radius + double precision :: coolingTime , density, & + & temperature + class (massDistributionClass ), pointer :: massDistribution_ + class (kinematicsDistributionClass), pointer :: kinematicsDistribution_ + type (chemicalAbundances ), save :: densityChemicals !$omp threadprivate(densityChemicals) - + type (coordinateSpherical ) :: coordinates + ! Compute density, temperature and abundances. - density =self_%hotHaloMassDistribution_ %density (node_,radius ) - temperature =self_%hotHaloTemperatureProfile_%temperature(node_,radius ) + coordinates = [radius,0.0d0,0.0d0] + massDistribution_ => node_ %massDistribution (componentTypeHotHalo,massTypeGaseous) + kinematicsDistribution_ => massDistribution_ %kinematicsDistribution( ) + density = massDistribution_ %density (coordinates ) + if (associated(kinematicsDistribution_)) then + temperature = kinematicsDistribution_%temperature (coordinates ) + else + temperature = 0.0d0 + end if + !![ + + + !!] densityChemicals=fractionsChemical_ call densityChemicals%scale(density) ! Compute the cooling time at the specified radius. - coolingTime =self_%coolingTime_ %time (node_,temperature,density,abundancesGas_,densityChemicals,self_%radiation) + coolingTime=self_%coolingTime_%time(node_,temperature,density,abundancesGas_,densityChemicals,self_%radiation) ! Return the difference between cooling time and time available. coolingRadiusRoot=coolingTime-coolingTimeAvailable_ return diff --git a/source/cooling.cooling_rate.Cole2000.F90 b/source/cooling.cooling_rate.Cole2000.F90 index 57a9697ba5..4fabfdebc3 100644 --- a/source/cooling.cooling_rate.Cole2000.F90 +++ b/source/cooling.cooling_rate.Cole2000.F90 @@ -21,8 +21,7 @@ Implementation of a cooling rate class for the \cite{cole_hierarchical_2000} cooling rate calculation. !!} - use :: Cooling_Infall_Radii , only : coolingInfallRadiusClass - use :: Hot_Halo_Mass_Distributions, only : hotHaloMassDistributionClass + use :: Cooling_Infall_Radii, only : coolingInfallRadiusClass !![ @@ -43,8 +42,7 @@ Implementation of cooling rate class for the \cite{cole_hierarchical_2000} cooling rate calculation. !!} private - class(coolingInfallRadiusClass ), pointer :: coolingInfallRadius_ => null() - class(hotHaloMassDistributionClass), pointer :: hotHaloMassDistribution_ => null() + class(coolingInfallRadiusClass), pointer :: coolingInfallRadius_ => null() contains final :: cole2000Destructor procedure :: rate => cole2000Rate @@ -66,34 +64,30 @@ function cole2000ConstructorParameters(parameters) result(self) !!} use :: Input_Parameters, only : inputParameter, inputParameters implicit none - type (coolingRateCole2000 ) :: self - type (inputParameters ), intent(inout) :: parameters - class(coolingInfallRadiusClass ), pointer :: coolingInfallRadius_ - class(hotHaloMassDistributionClass), pointer :: hotHaloMassDistribution_ + type (coolingRateCole2000 ) :: self + type (inputParameters ), intent(inout) :: parameters + class(coolingInfallRadiusClass), pointer :: coolingInfallRadius_ !![ - - + !!] - self=coolingRateCole2000(coolingInfallRadius_,hotHaloMassDistribution_) + self=coolingRateCole2000(coolingInfallRadius_) !![ - - + !!] return end function cole2000ConstructorParameters - function cole2000ConstructorInternal(coolingInfallRadius_,hotHaloMassDistribution_) result(self) + function cole2000ConstructorInternal(coolingInfallRadius_) result(self) !!{ Internal constructor for the \cite{cole_hierarchical_2000} cooling rate class. !!} implicit none - type (coolingRateCole2000 ) :: self - class(coolingInfallRadiusClass ), intent(in ), target :: coolingInfallRadius_ - class(hotHaloMassDistributionClass), intent(in ), target :: hotHaloMassDistribution_ + type (coolingRateCole2000 ) :: self + class(coolingInfallRadiusClass), intent(in ), target :: coolingInfallRadius_ !![ - + !!] return @@ -107,8 +101,7 @@ subroutine cole2000Destructor(self) type(coolingRateCole2000), intent(inout) :: self !![ - - + !!] return end subroutine cole2000Destructor @@ -118,15 +111,20 @@ double precision function cole2000Rate(self,node) Returns the cooling rate (in $M_\odot$ Gyr$^{-1}$) in the hot atmosphere for the \cite{white_galaxy_1991} cooling rate model. !!} - use :: Galacticus_Nodes , only : nodeComponentBasic, nodeComponentHotHalo, treeNode - use :: Numerical_Constants_Math, only : Pi + use :: Galacticus_Nodes , only : nodeComponentBasic , nodeComponentHotHalo, treeNode + use :: Numerical_Constants_Math , only : Pi + use :: Mass_Distributions , only : massDistributionClass + use :: Coordinates , only : coordinateSpherical , assignment(=) + use :: Galactic_Structure_Options, only : componentTypeHotHalo , massTypeGaseous implicit none - class (coolingRateCole2000 ), intent(inout) :: self - type (treeNode ), intent(inout) :: node - class (nodeComponentBasic ), pointer :: basicFormation - class (nodeComponentHotHalo), pointer :: hotHaloFormation - double precision :: densityInfall , radiusInfall, & - & radiusInfallGrowthRate , radiusOuter + class (coolingRateCole2000 ), intent(inout) :: self + type (treeNode ), intent(inout) :: node + class (nodeComponentBasic ), pointer :: basicFormation + class (nodeComponentHotHalo ), pointer :: hotHaloFormation + class (massDistributionClass), pointer :: massDistribution_ + type (coordinateSpherical ) :: coordinates + double precision :: densityInfall , radiusInfall, & + & radiusInfallGrowthRate , radiusOuter !$GLC attributes unused :: self ! Get formation node components. @@ -146,9 +144,14 @@ double precision function cole2000Rate(self,node) cole2000Rate=0.0d0 else ! Find the density at the infall radius. - densityInfall = self%hotHaloMassDistribution_%density (node%formationNode,radiusInfall) + coordinates = [radiusInfall,0.0d0,0.0d0] + massDistribution_ => node %formationNode %massDistribution (componentTypeHotHalo,massTypeGaseous) + densityInfall = massDistribution_ %density (coordinates ) + !![ + + !!] ! Find infall radius growth rate. - radiusInfallGrowthRate = self%coolingInfallRadius_ %radiusIncreaseRate(node%formationNode ) + radiusInfallGrowthRate = self %coolingInfallRadius_%radiusIncreaseRate(node%formationNode ) ! Compute the infall rate. cole2000Rate = +4.0d0 & & *Pi & diff --git a/source/cooling.cooling_rate.White-Frenk.F90 b/source/cooling.cooling_rate.White-Frenk.F90 index 757f664b0f..31eb25082c 100644 --- a/source/cooling.cooling_rate.White-Frenk.F90 +++ b/source/cooling.cooling_rate.White-Frenk.F90 @@ -21,9 +21,8 @@ Implementation of a cooling rate class for the \cite{white_galaxy_1991} cooling rate calculation. !!} - use :: Cooling_Infall_Radii , only : coolingInfallRadiusClass - use :: Dark_Matter_Halo_Scales , only : darkMatterHaloScaleClass - use :: Hot_Halo_Mass_Distributions, only : hotHaloMassDistributionClass + use :: Cooling_Infall_Radii , only : coolingInfallRadiusClass + use :: Dark_Matter_Halo_Scales, only : darkMatterHaloScaleClass !![ @@ -44,10 +43,9 @@ Implementation of cooling rate class for the \cite{white_galaxy_1991} cooling rate calculation. !!} private - class (darkMatterHaloScaleClass ), pointer :: darkMatterHaloScale_ => null() - class (coolingInfallRadiusClass ), pointer :: coolingInfallRadius_ => null() - class (hotHaloMassDistributionClass), pointer :: hotHaloMassDistribution_ => null() - double precision :: velocityCutOff + class (darkMatterHaloScaleClass), pointer :: darkMatterHaloScale_ => null() + class (coolingInfallRadiusClass), pointer :: coolingInfallRadius_ => null() + double precision :: velocityCutOff contains final :: whiteFrenk1991Destructor procedure :: rate => whiteFrenk1991Rate @@ -69,12 +67,11 @@ function whiteFrenk1991ConstructorParameters(parameters) result(self) !!} use :: Input_Parameters, only : inputParameter, inputParameters implicit none - type (coolingRateWhiteFrenk1991 ) :: self - type (inputParameters ), intent(inout) :: parameters - class (darkMatterHaloScaleClass ), pointer :: darkMatterHaloScale_ - class (coolingInfallRadiusClass ), pointer :: coolingInfallRadius_ - class (hotHaloMassDistributionClass), pointer :: hotHaloMassDistribution_ - double precision :: velocityCutOff + type (coolingRateWhiteFrenk1991) :: self + type (inputParameters ), intent(inout) :: parameters + class (darkMatterHaloScaleClass ), pointer :: darkMatterHaloScale_ + class (coolingInfallRadiusClass ), pointer :: coolingInfallRadius_ + double precision :: velocityCutOff !![ @@ -83,21 +80,19 @@ function whiteFrenk1991ConstructorParameters(parameters) result(self) 1.0d4 The halo virial velocity (in km/s) above which cooling rates are forced to zero in the \cite{white_galaxy_1991} cooling rate model. - - - + + !!] - self=coolingRateWhiteFrenk1991(velocityCutOff,darkMatterHaloScale_,coolingInfallRadius_,hotHaloMassDistribution_) + self=coolingRateWhiteFrenk1991(velocityCutOff,darkMatterHaloScale_,coolingInfallRadius_) !![ - - - + + !!] return end function whiteFrenk1991ConstructorParameters - function whiteFrenk1991ConstructorInternal(velocityCutOff,darkMatterHaloScale_,coolingInfallRadius_,hotHaloMassDistribution_) result(self) + function whiteFrenk1991ConstructorInternal(velocityCutOff,darkMatterHaloScale_,coolingInfallRadius_) result(self) !!{ Internal constructor for the \cite{white_galaxy_1991} cooling rate class. !!} @@ -105,13 +100,12 @@ function whiteFrenk1991ConstructorInternal(velocityCutOff,darkMatterHaloScale_,c use :: Error , only : Component_List , Error_Report use :: Galacticus_Nodes, only : defaultHotHaloComponent implicit none - type (coolingRateWhiteFrenk1991 ) :: self - double precision , intent(in ) :: velocityCutOff - class (darkMatterHaloScaleClass ), intent(in ), target :: darkMatterHaloScale_ - class (coolingInfallRadiusClass ), intent(in ), target :: coolingInfallRadius_ - class (hotHaloMassDistributionClass), intent(in ), target :: hotHaloMassDistribution_ + type (coolingRateWhiteFrenk1991) :: self + double precision , intent(in ) :: velocityCutOff + class (darkMatterHaloScaleClass ), intent(in ), target :: darkMatterHaloScale_ + class (coolingInfallRadiusClass ), intent(in ), target :: coolingInfallRadius_ !![ - + !!] ! Check that the properties we need are gettable. @@ -143,9 +137,8 @@ subroutine whiteFrenk1991Destructor(self) type(coolingRateWhiteFrenk1991), intent(inout) :: self !![ - - - + + !!] return end subroutine whiteFrenk1991Destructor @@ -155,14 +148,19 @@ double precision function whiteFrenk1991Rate(self,node) Returns the cooling rate (in $M_\odot$ Gyr$^{-1}$) in the hot atmosphere for the \cite{white_galaxy_1991} cooling rate model. !!} - use :: Galacticus_Nodes , only : nodeComponentHotHalo, treeNode - use :: Numerical_Constants_Math, only : Pi + use :: Galacticus_Nodes , only : nodeComponentHotHalo , treeNode + use :: Numerical_Constants_Math , only : Pi + use :: Galactic_Structure_Options, only : componentTypeHotHalo , massTypeGaseous + use :: Mass_Distributions , only : massDistributionClass + use :: Coordinates , only : coordinateSpherical , assignment(=) implicit none class (coolingRateWhiteFrenk1991), intent(inout) :: self type (treeNode ), intent(inout) :: node class (nodeComponentHotHalo ), pointer :: hotHalo - double precision :: densityCooling , radiusInfall , & - & radiusOuter , velocityVirial, & + class (massDistributionClass ), pointer :: massDistribution_ + type (coordinateSpherical ) :: coordinates + double precision :: densityCooling , radiusInfall , & + & radiusOuter , velocityVirial, & & radiusInfallGrowthRate ! Get the virial velocity. @@ -182,10 +180,15 @@ double precision function whiteFrenk1991Rate(self,node) whiteFrenk1991Rate=+hotHalo %mass ( ) & & /self %darkMatterHaloScale_%timescaleDynamical(node) else - ! Find the density at the cooling radius. - densityCooling = self%hotHaloMassDistribution_%density (node,radiusInfall) + ! Find the density at the cooling radius. + coordinates = [radiusInfall,0.0d0,0.0d0] + massDistribution_ => node %massDistribution (componentTypeHotHalo,massTypeGaseous) + densityCooling = massDistribution_ %density (coordinates ) + !![ + + !!] ! Find infall radius growth rate. - radiusInfallGrowthRate = self%coolingInfallRadius_ %radiusIncreaseRate(node ) + radiusInfallGrowthRate = self %coolingInfallRadius_%radiusIncreaseRate(node ) ! Compute the infall rate. whiteFrenk1991Rate = +4.0d0 & & *Pi & diff --git a/source/cooling.cooling_rate.velocity_maximum_scaling.F90 b/source/cooling.cooling_rate.velocity_maximum_scaling.F90 index 55ba461778..b2259a76ed 100644 --- a/source/cooling.cooling_rate.velocity_maximum_scaling.F90 +++ b/source/cooling.cooling_rate.velocity_maximum_scaling.F90 @@ -238,21 +238,27 @@ double precision function velocityMaximumScalingRate(self,node) !!{ Returns the cooling rate (in $M_\odot$ Gyr$^{-1}$) in the hot atmosphere for a model in which this rate scales with the maximum circular velocity of the halo. !!} - use :: Galacticus_Nodes, only : nodeComponentBasic, nodeComponentHotHalo, treeNode + use :: Galacticus_Nodes , only : nodeComponentBasic , nodeComponentHotHalo, treeNode + use :: Mass_Distributions, only : massDistributionClass implicit none class (coolingRateVelocityMaximumScaling), intent(inout) :: self type (treeNode ), intent(inout) :: node double precision , parameter :: expArgumentMaximum=100.0d0 class (nodeComponentBasic ), pointer :: basic class (nodeComponentHotHalo ), pointer :: hotHalo + class (massDistributionClass ), pointer :: massDistribution_ double precision :: expFactor , expansionFactor, & & expArgument , velocityMaximum ! Compute expansion factor and maximum velocity. - basic => node %basic ( ) - hotHalo => node %hotHalo ( ) - expansionFactor = self%cosmologyFunctions_ %expansionFactor (basic%time()) - velocityMaximum = self%darkMatterProfileDMO_%circularVelocityMaximum(node ) + massDistribution_ => self %darkMatterProfileDMO_%get (node ) + basic => node %basic ( ) + hotHalo => node %hotHalo ( ) + expansionFactor = self%cosmologyFunctions_ %expansionFactor (basic%time()) + velocityMaximum = massDistribution_ %velocityRotationCurveMaximum( ) + !![ + + !!] if (expansionFactor /= self%expansionFactorPrevious .or. velocityMaximum /= self%velocityMaximumPrevious) then expArgument=log10( & & +velocityMaximum & diff --git a/source/cooling.freefall_radii.dark_matter_halo.F90 b/source/cooling.freefall_radii.dark_matter_halo.F90 index ee003ee650..fe8918f58f 100644 --- a/source/cooling.freefall_radii.dark_matter_halo.F90 +++ b/source/cooling.freefall_radii.dark_matter_halo.F90 @@ -108,37 +108,49 @@ subroutine darkMatterHaloDestructor(self) return end subroutine darkMatterHaloDestructor - double precision function darkMatterHaloRadiusGrowthRate(self,node) + double precision function darkMatterHaloRadiusGrowthRate(self,node) result(radiusGrowthRate) !!{ Returns the freefall radius growth rate (in Mpc/Gyr) in the hot atmosphere. !!} + use :: Mass_Distributions, only : massDistributionClass implicit none class (freefallRadiusDarkMatterHalo ), intent(inout) :: self type (treeNode ), intent(inout) :: node - double precision :: timeAvailable, timeAvailableIncreaseRate + class (massDistributionClass ), pointer :: massDistribution_ + double precision :: timeAvailable , timeAvailableIncreaseRate ! Get the time available for freefall. - timeAvailable =+self%freefallTimeAvailable_%timeAvailable (node ) + timeAvailable = +self %freefallTimeAvailable_ %timeAvailable (node ) ! Get the rate of increase of the time available for freefall. - timeAvailableIncreaseRate =+self%freefallTimeAvailable_%timeAvailableIncreaseRate (node ) + timeAvailableIncreaseRate = +self %freefallTimeAvailable_ %timeAvailableIncreaseRate(node ) ! Get freefall radius increase rate from dark matter profile. - darkMatterHaloRadiusGrowthRate=+self%darkMatterProfileDMO_ %freefallRadiusIncreaseRate(node,timeAvailable ) & - & * timeAvailableIncreaseRate + massDistribution_ => self %darkMatterProfileDMO_ %get (node ) + radiusGrowthRate = +massDistribution_%radiusFreefallIncreaseRate (timeAvailable ) & + & * timeAvailableIncreaseRate + !![ + + !!] return end function darkMatterHaloRadiusGrowthRate - double precision function darkMatterHaloRadius(self,node) + double precision function darkMatterHaloRadius(self,node) result(radius) !!{ Return the freefall radius in the darkMatterHalo model. !!} + use :: Mass_Distributions, only : massDistributionClass implicit none class (freefallRadiusDarkMatterHalo ), intent(inout) :: self type (treeNode ), intent(inout) :: node + class (massDistributionClass ), pointer :: massDistribution_ double precision :: timeAvailable ! Get the time available for freefall. - timeAvailable =self%freefallTimeAvailable_%timeAvailable (node ) + timeAvailable = self %freefallTimeAvailable_%timeAvailable(node ) ! Get freefall radius from dark matter profile. - darkMatterHaloRadius=self%darkMatterProfileDMO_ %freefallRadius(node,timeAvailable) + massDistribution_ => self %darkMatterProfileDMO_ %get (node ) + radius = massDistribution_%radiusFreefall (timeAvailable) + !![ + + !!] return end function darkMatterHaloRadius diff --git a/source/cooling.specific_angular_momentum.constant_rotation.F90 b/source/cooling.specific_angular_momentum.constant_rotation.F90 index 95fc1dfe8a..a43259478f 100644 --- a/source/cooling.specific_angular_momentum.constant_rotation.F90 +++ b/source/cooling.specific_angular_momentum.constant_rotation.F90 @@ -22,9 +22,9 @@ Implementation of a specific angular momentum of cooling gas class assuming a co radius. !!} - use :: Dark_Matter_Profiles_DMO , only : darkMatterProfileDMOClass - use :: Hot_Halo_Mass_Distributions, only : hotHaloMassDistributionClass - use :: Kind_Numbers , only : kind_int8 + use :: Dark_Matter_Profiles_DMO, only : darkMatterProfileDMOClass + use :: Dark_Matter_Halo_Scales , only : darkMatterHaloScaleClass + use :: Kind_Numbers , only : kind_int8 ! Enumeration for angular momentum source. !![ @@ -70,8 +70,8 @@ A cooling specific angular momentum class which assumes a constant rotation velo Implementation of the specific angular momentum of cooling gas class which assumes a constant rotation velocity as a function of radius. !!} private + class (darkMatterHaloScaleClass ), pointer :: darkMatterHaloScale_ => null() class (darkMatterProfileDMOClass ), pointer :: darkMatterProfileDMO_ => null() - class (hotHaloMassDistributionClass ), pointer :: hotHaloMassDistribution_ => null() integer (kind=kind_int8 ) :: lastUniqueID logical :: angularMomentumSpecificComputed double precision :: angularMomentumSpecificPrevious @@ -108,7 +108,7 @@ function constantRotationConstructorParameters(parameters) result(self) type (coolingSpecificAngularMomentumConstantRotation) :: self type (inputParameters ), intent(inout) :: parameters class (darkMatterProfileDMOClass ), pointer :: darkMatterProfileDMO_ - class (hotHaloMassDistributionClass ), pointer :: hotHaloMassDistribution_ + class (darkMatterHaloScaleClass ), pointer :: darkMatterHaloScale_ logical :: useInteriorMean type (varying_string ) :: sourceAngularMomentumSpecificMean, sourceNormalizationRotation @@ -137,36 +137,36 @@ function constantRotationConstructorParameters(parameters) result(self) Specifies whether to use the specific angular momentum at the cooling radius, or the mean specific angular momentum interior to that radius. parameters - - + + !!] self=coolingSpecificAngularMomentumConstantRotation( & + & darkMatterHaloScale_ , & & darkMatterProfileDMO_ , & - & hotHaloMassDistribution_ , & & enumerationAngularMomentumSourceEncode(char(sourceAngularMomentumSpecificMean),includesPrefix=.false.), & & enumerationAngularMomentumSourceEncode(char(sourceNormalizationRotation ),includesPrefix=.false.), & & useInteriorMean & & ) !![ - - + + !!] return end function constantRotationConstructorParameters - function constantRotationConstructorInternal(darkMatterProfileDMO_,hotHaloMassDistribution_,sourceAngularMomentumSpecificMean,sourceNormalizationRotation,useInteriorMean) result(self) + function constantRotationConstructorInternal(darkMatterHaloScale_,darkMatterProfileDMO_,sourceAngularMomentumSpecificMean,sourceNormalizationRotation,useInteriorMean) result(self) !!{ Internal constructor for the darkMatterHalo freefall radius class. !!} implicit none type (coolingSpecificAngularMomentumConstantRotation) :: self + class (darkMatterHaloScaleClass ), intent(in ), target :: darkMatterHaloScale_ class (darkMatterProfileDMOClass ), intent(in ), target :: darkMatterProfileDMO_ - class (hotHaloMassDistributionClass ), intent(in ), target :: hotHaloMassDistribution_ type (enumerationAngularMomentumSourceType ), intent(in ) :: sourceAngularMomentumSpecificMean, sourceNormalizationRotation logical , intent(in ) :: useInteriorMean !![ - + !!] self%lastUniqueID =-1_kind_int8 @@ -194,8 +194,8 @@ subroutine constantRotationDestructor(self) type(coolingSpecificAngularMomentumConstantRotation), intent(inout) :: self !![ - - + + !!] if (calculationResetEvent%isAttached(self,constantRotationCalculationReset)) call calculationResetEvent%detach(self,constantRotationCalculationReset) return @@ -208,6 +208,8 @@ double precision function constantRotationAngularMomentumSpecific(self,node,radi use :: Error , only : Error_Report use :: Galacticus_Nodes , only : nodeComponentBasic , nodeComponentHotHalo, nodeComponentSpin, treeNode use :: Numerical_Constants_Astronomical, only : gravitationalConstantGalacticus + use :: Mass_Distributions , only : massDistributionClass + use :: Galactic_Structure_Options , only : componentTypeHotHalo , massTypeGaseous implicit none class (coolingSpecificAngularMomentumConstantRotation), intent(inout) :: self type (treeNode ), intent(inout) :: node @@ -215,6 +217,7 @@ double precision function constantRotationAngularMomentumSpecific(self,node,radi class (nodeComponentBasic ), pointer :: basic class (nodeComponentSpin ), pointer :: spin class (nodeComponentHotHalo ), pointer :: hotHalo + class (massDistributionClass ), pointer :: massDistribution_ double precision :: angularMomentumSpecificMean, normalizationRotation ! Check if node differs from previous one for which we performed calculations. @@ -243,9 +246,20 @@ double precision function constantRotationAngularMomentumSpecific(self,node,radi ! Compute the rotation normalization. select case (self%sourceNormalizationRotation %ID) case (angularMomentumSourceDarkMatter%ID) - normalizationRotation=self%darkMatterProfileDMO_ %rotationNormalization(node) + massDistribution_ => self %darkMatterProfileDMO_%get (node ) + normalizationRotation = +massDistribution_ %densityRadialMoment(moment =2.0d0 ,radiusMaximum=self%darkMatterHaloScale_%radiusVirial(node)) & + & /massDistribution_ %densityRadialMoment(moment =3.0d0 ,radiusMaximum=self%darkMatterHaloScale_%radiusVirial(node)) + !![ + + !!] case (angularMomentumSourceHotGas %ID) - normalizationRotation=self%hotHaloMassDistribution_%rotationNormalization(node) + hotHalo => node %hotHalo ( ) + massDistribution_ => node %massDistribution (componentType=componentTypeHotHalo,massType =massTypeGaseous ) + normalizationRotation = +massDistribution_ %densityRadialMoment(moment =2.0d0 ,radiusMaximum=hotHalo %outerRadius ( )) & + & /massDistribution_ %densityRadialMoment(moment =3.0d0 ,radiusMaximum=hotHalo %outerRadius ( )) + !![ + + !!] case default normalizationRotation=0.0d0 call Error_Report('unknown profile type'//{introspection:location}) @@ -259,13 +273,17 @@ double precision function constantRotationAngularMomentumSpecific(self,node,radi ! Return the computed value. if (self%useInteriorMean) then ! Find the specific angular momentum interior to the specified radius. - constantRotationAngularMomentumSpecific=+self %angularMomentumSpecificPrevious & - & *self%hotHaloMassDistribution_%radialMoment (node,3.0d0,radius) & - & /self%hotHaloMassDistribution_%radialMoment (node,2.0d0,radius) + massDistribution_ => node %massDistribution (componentType=componentTypeHotHalo,massType=massTypeGaseous) + constantRotationAngularMomentumSpecific = +self %angularMomentumSpecificPrevious & + & *massDistribution_%densityRadialMoment (3.0d0,radius) & + & /massDistribution_%densityRadialMoment (2.0d0,radius) + !![ + + !!] else ! Find the specific angular momentum at the specified radius. - constantRotationAngularMomentumSpecific=+self %angularMomentumSpecificPrevious & - & * radius + constantRotationAngularMomentumSpecific = +self %angularMomentumSpecificPrevious & + & * radius end if else ! Radius is non-positive - return zero. diff --git a/source/cooling.time_available.Benson_Bower_2010.F90 b/source/cooling.time_available.Benson_Bower_2010.F90 index e546530da3..525bdb6738 100644 --- a/source/cooling.time_available.Benson_Bower_2010.F90 +++ b/source/cooling.time_available.Benson_Bower_2010.F90 @@ -21,12 +21,10 @@ Implementation of a time available for cooling class using the model of \cite{benson_galaxy_2010-1}. !!} - use :: Hot_Halo_Temperature_Profiles, only : hotHaloTemperatureProfileClass - use :: Radiation_Fields , only : radiationFieldCosmicMicrowaveBackground - use :: Galactic_Structure , only : galacticStructureClass - use :: Cooling_Functions , only : coolingFunctionClass - use :: Cosmology_Functions , only : cosmologyFunctionsClass - use :: Chemical_States , only : chemicalStateClass + use :: Radiation_Fields , only : radiationFieldCosmicMicrowaveBackground + use :: Cooling_Functions , only : coolingFunctionClass + use :: Cosmology_Functions, only : cosmologyFunctionsClass + use :: Chemical_States , only : chemicalStateClass !![ @@ -46,12 +44,10 @@ Implementation of a time available for cooling class using the model of \cite{benson_galaxy_2010-1}. !!} private - class (cosmologyFunctionsClass ), pointer :: cosmologyFunctions_ => null() - class (coolingFunctionClass ), pointer :: coolingFunction_ => null() - class (hotHaloTemperatureProfileClass ), pointer :: hotHaloTemperatureProfile_ => null() - class (chemicalStateClass ), pointer :: chemicalState_ => null() - class (galacticStructureClass ), pointer :: galacticStructure_ => null() - type (radiationFieldCosmicMicrowaveBackground), pointer :: radiation => null() + class (cosmologyFunctionsClass ), pointer :: cosmologyFunctions_ => null() + class (coolingFunctionClass ), pointer :: coolingFunction_ => null() + class (chemicalStateClass ), pointer :: chemicalState_ => null() + type (radiationFieldCosmicMicrowaveBackground), pointer :: radiation => null() integer :: energyRadiatedID contains @@ -78,32 +74,26 @@ function bensonBower2010ConstructorParameters(parameters) result(self) implicit none type (coolingTimeAvailableBensonBower2010) :: self type (inputParameters ), intent(inout) :: parameters - class(hotHaloTemperatureProfileClass ), pointer :: hotHaloTemperatureProfile_ class(cosmologyFunctionsClass ), pointer :: cosmologyFunctions_ class(coolingFunctionClass ), pointer :: coolingFunction_ class(chemicalStateClass ), pointer :: chemicalState_ - class(galacticStructureClass ), pointer :: galacticStructure_ !![ - - - - - + + + !!] - self=coolingTimeAvailableBensonBower2010(cosmologyFunctions_,coolingFunction_,hotHaloTemperatureProfile_,chemicalState_,galacticStructure_) + self=coolingTimeAvailableBensonBower2010(cosmologyFunctions_,coolingFunction_,chemicalState_) !![ - - - - - + + + !!] return end function bensonBower2010ConstructorParameters - function bensonBower2010ConstructorInternal(cosmologyFunctions_,coolingFunction_,hotHaloTemperatureProfile_,chemicalState_,galacticStructure_) result(self) + function bensonBower2010ConstructorInternal(cosmologyFunctions_,coolingFunction_,chemicalState_) result(self) !!{ Internal constructor for the \cite{benson_galaxy_2010-1} cooling rate class. !!} @@ -111,11 +101,9 @@ function bensonBower2010ConstructorInternal(cosmologyFunctions_,coolingFunction_ type (coolingTimeAvailableBensonBower2010) :: self class(cosmologyFunctionsClass ), intent(in ), target :: cosmologyFunctions_ class(coolingFunctionClass ), intent(in ), target :: coolingFunction_ - class(hotHaloTemperatureProfileClass ), intent(in ), target :: hotHaloTemperatureProfile_ class(chemicalStateClass ), intent(in ), target :: chemicalState_ - class(galacticStructureClass ), intent(in ), target :: galacticStructure_ !![ - + !!] allocate(self%radiation) @@ -134,12 +122,10 @@ subroutine bensonBower2010Destructor(self) type(coolingTimeAvailableBensonBower2010), intent(inout) :: self !![ - - - - - - + + + + !!] return end subroutine bensonBower2010Destructor @@ -152,8 +138,10 @@ double precision function bensonBower2010TimeAvailable(self,node) use :: Abundances_Structure , only : abundances use :: Chemical_Abundances_Structure , only : chemicalAbundances , Chemicals_Property_Count use :: Chemical_Reaction_Rates_Utilities, only : Chemicals_Mass_To_Density_Conversion - use :: Galactic_Structure_Options , only : radiusLarge , massTypeGalactic - use :: Numerical_Constants_Astronomical , only : gigaYear , massSolar , megaParsec + use :: Mass_Distributions , only : massDistributionClass , kinematicsDistributionClass + use :: Coordinates , only : coordinateSpherical , assignment(=) + use :: Galactic_Structure_Options , only : componentTypeHotHalo , massTypeGaseous , massTypeGalactic + use :: Numerical_Constants_Astronomical , only : gigaYear , massSolar , megaParsec use :: Numerical_Constants_Atomic , only : massHydrogenAtom use :: Numerical_Constants_Physical , only : boltzmannsConstant use :: Numerical_Constants_Prefixes , only : hecto , centi @@ -164,6 +152,9 @@ double precision function bensonBower2010TimeAvailable(self,node) type (treeNode ), intent(inout) :: node class (nodeComponentBasic ), pointer :: basic class (nodeComponentHotHalo ), pointer :: hotHalo + class (massDistributionClass ), pointer :: massDistribution_ + class (kinematicsDistributionClass ), pointer :: kinematicsDistribution_ + type (coordinateSpherical ) :: coordinates double precision :: density , temperature , & & massToDensityConversion, numberDensityHydrogen, & & numberDensityAllSpecies, coolingFunction , & @@ -171,22 +162,34 @@ double precision function bensonBower2010TimeAvailable(self,node) type (abundances ) :: abundances_ type (chemicalAbundances ) :: chemicalDensities_ , chemicalMasses_ - basic => node %basic ( ) - hotHalo => node %hotHalo ( ) - massNotional = +hotHalo %mass ( ) & - & +hotHalo %outflowedMass( ) & - & +self %galacticStructure_%massEnclosed (node,radiusLarge,massType=massTypeGalactic) + massDistribution_ => node %massDistribution(massType=massTypeGalactic) + basic => node %basic ( ) + hotHalo => node %hotHalo ( ) + massNotional = +hotHalo %mass ( ) & + & +hotHalo %outflowedMass ( ) & + & +massDistribution_%massTotal ( ) + !![ + + !!] if (massNotional <= 0.0d0) then bensonBower2010TimeAvailable=0.0d0 return end if + ! Get the mass distribution. + massDistribution_ => node %massDistribution (componentType=componentTypeHotHalo,massType=massTypeGaseous) + kinematicsDistribution_ => massDistribution_%kinematicsDistribution( ) ! Compute the mean density and temperature of the hot halo. density =+massNotional & & *3.0d0 & & /4.0d0 & & /Pi & & /hotHalo%outerRadius()**3 - temperature=self%hotHaloTemperatureProfile_%temperature(node,hotHalo%outerRadius()) + coordinates=[hotHalo%outerRadius(),0.0d0,0.0d0] + temperature=+kinematicsDistribution_%temperature(coordinates) + !![ + + + !!] ! Get the abundances for this node. abundances_=hotHalo%abundances() call abundances_%massToMassFraction(hotHalo%mass()) diff --git a/source/dark_matter_halos.spins.F90 b/source/dark_matter_halos.spins.F90 index 2ac4be7df2..876ae02070 100644 --- a/source/dark_matter_halos.spins.F90 +++ b/source/dark_matter_halos.spins.F90 @@ -64,22 +64,23 @@ subroutine assertPropertiesGettable() return end subroutine assertPropertiesGettable - double precision function Dark_Matter_Halo_Angular_Momentum_Scale(node,darkMatterProfileDMO_,darkMatterHaloScale_,useBullockDefinition) + double precision function Dark_Matter_Halo_Angular_Momentum_Scale(node,darkMatterHaloScale_,useBullockDefinition) result(angularMomentumScale) !!{ Returns the characteristic angular momentum scale of {\normalfont \ttfamily node} (as used in spin definitions) based on its mass, and energy. !!} - use :: Dark_Matter_Profiles_DMO , only : darkMatterProfileDMOClass use :: Dark_Matter_Halo_Scales , only : darkMatterHaloScaleClass use :: Galacticus_Nodes , only : nodeComponentBasic , treeNode use :: Error , only : Error_Report use :: Numerical_Constants_Astronomical, only : gravitationalConstantGalacticus + use :: Mass_Distributions , only : massDistributionClass + use :: Galactic_Structure_Options , only : componentTypeDarkMatterOnly , massTypeDark implicit none type (treeNode ), intent(inout) :: node - class (darkMatterProfileDMOClass), intent(inout), optional :: darkMatterProfileDMO_ - class (darkMatterHaloScaleClass ), intent(inout), optional :: darkMatterHaloScale_ + class (darkMatterHaloScaleClass ), intent(inout) :: darkMatterHaloScale_ logical , intent(in ), optional :: useBullockDefinition class (nodeComponentBasic ), pointer :: basic - !![ + class (massDistributionClass ), pointer :: massDistribution_ + !![ !!] @@ -88,19 +89,21 @@ double precision function Dark_Matter_Halo_Angular_Momentum_Scale(node,darkMatte if (useBullockDefinition_) then ! Use the halo angular momentum scale used in the Bullock et al. (2001; http://adsabs.harvard.edu/abs/2001ApJ...555..240B) ! definition of halo spin. - if (.not.present(darkMatterHaloScale_ )) call Error_Report('"darkMatterHaloScale_" must be supplied' //{introspection:location}) - Dark_Matter_Halo_Angular_Momentum_Scale=+sqrt(2.0d0) & - & *basic %mass ( ) & - & *darkMatterHaloScale_%velocityVirial(node) & - & *darkMatterHaloScale_%radiusVirial (node) + angularMomentumScale=+sqrt(2.0d0) & + & *basic %mass ( ) & + & *darkMatterHaloScale_%velocityVirial(node) & + & *darkMatterHaloScale_%radiusVirial (node) else ! Use the halo angular momentum scale used in the Peebles (1971; http://adsabs.harvard.edu/abs/1971A%26A....11..377P) ! definition of halo spin. - if (.not.present(darkMatterProfileDMO_)) call Error_Report('"darkMatterProfileDMO_" must be supplied'//{introspection:location}) - Dark_Matter_Halo_Angular_Momentum_Scale=+gravitationalConstantGalacticus & - & * basic %mass ( )**2.5d0 & - & /sqrt(abs(darkMatterProfileDMO_%energy(node) )) - end if + massDistribution_ => node%massDistribution(componentTypeDarkMatterOnly,massTypeDark) + angularMomentumScale = +gravitationalConstantGalacticus & + & * basic %mass ( ) **2.5d0 & + & /sqrt(abs(massDistribution_%energy(darkMatterHaloScale_%radiusVirial(node),massDistribution_))) + !![ + + !!] + end if return end function Dark_Matter_Halo_Angular_Momentum_Scale diff --git a/source/dark_matter_halos.spins.distributions.Bett2007.F90 b/source/dark_matter_halos.spins.distributions.Bett2007.F90 index 9c356f62b2..148a75e880 100644 --- a/source/dark_matter_halos.spins.distributions.Bett2007.F90 +++ b/source/dark_matter_halos.spins.distributions.Bett2007.F90 @@ -22,8 +22,8 @@ An implementation of the dark matter halo spin distribution which uses the fitti \cite{bett_spin_2007}. !!} - use :: Dark_Matter_Profiles_DMO, only : darkMatterProfileDMO, darkMatterProfileDMOClass - use :: Tables , only : table1D , table1DLogarithmicLinear + use :: Dark_Matter_Halo_Scales, only : darkMatterHaloScaleClass + use :: Tables , only : table1D , table1DLogarithmicLinear !![ @@ -39,7 +39,7 @@ An implementation of the dark matter halo spin distribution which uses the fitti A dark matter halo spin distribution class which assumes a \cite{bett_spin_2007} distribution. !!} private - class (darkMatterProfileDMOClass), pointer :: darkMatterProfileDMO_ => null() + class (darkMatterHaloScaleClass ), pointer :: darkMatterHaloScale_ => null() double precision :: alpha , lambda0, & & normalization type (table1DLogarithmicLinear ) :: distributionTable @@ -76,8 +76,8 @@ function bett2007ConstructorParameters(parameters) result(self) implicit none type (haloSpinDistributionBett2007) :: self type (inputParameters ), intent(inout) :: parameters - class (darkMatterProfileDMOClass ), pointer :: darkMatterProfileDMO_ - double precision :: lambda0 , alpha + class (darkMatterHaloScaleClass ), pointer :: darkMatterHaloScale_ + double precision :: lambda0 , alpha ! Check and read parameters. !![ @@ -95,17 +95,17 @@ function bett2007ConstructorParameters(parameters) result(self) \citep{bett_spin_2007} The parameter $\alpha$ in the halo spin distribution of \cite{bett_spin_2007}. - + !!] - self=haloSpinDistributionBett2007(lambda0,alpha,darkMatterProfileDMO_) + self=haloSpinDistributionBett2007(lambda0,alpha,darkMatterHaloScale_) !![ - + !!] return end function bett2007ConstructorParameters - function bett2007ConstructorInternal(lambda0,alpha,darkMatterProfileDMO_) result(self) + function bett2007ConstructorInternal(lambda0,alpha,darkMatterHaloScale_) result(self) !!{ Internal constructor for the {\normalfont \ttfamily bett2007} dark matter halo spin distribution class. @@ -114,12 +114,12 @@ function bett2007ConstructorInternal(lambda0,alpha,darkMatterProfileDMO_) result use :: Table_Labels , only : extrapolationTypeFix implicit none type (haloSpinDistributionBett2007) :: self - double precision , intent(in ) :: lambda0 , alpha - class (darkMatterProfileDMOClass ), intent(in ), target :: darkMatterProfileDMO_ - double precision :: spinDimensionless , tableMaximum + double precision , intent(in ) :: lambda0 , alpha + class (darkMatterHaloScaleClass ), intent(in ), target :: darkMatterHaloScale_ + double precision :: spinDimensionless , tableMaximum integer :: iSpin !![ - + !!] ! Compute the normalization. @@ -167,7 +167,7 @@ subroutine bett2007Destructor(self) type(haloSpinDistributionBett2007), intent(inout) :: self !![ - + !!] call self%distributionTable %destroy() if (allocated(self%distributionInverse)) call self%distributionInverse%destroy() @@ -207,8 +207,8 @@ Compute the spin parameter distribution for the given {\normalfont \ttfamily nod double precision :: spin_ spin => node%spin () - spin_ = +spin%angularMomentum() & - & /Dark_Matter_Halo_Angular_Momentum_Scale(node,self%darkMatterProfileDMO_) + spin_ = +spin%angularMomentum() & + & /Dark_Matter_Halo_Angular_Momentum_Scale(node,self%darkMatterHaloScale_) bett2007Distribution = +self%normalization & & *( & & +spin_ & diff --git a/source/dark_matter_halos.spins.distributions.N-body_errors.F90 b/source/dark_matter_halos.spins.distributions.N-body_errors.F90 index de49964069..d52db56372 100644 --- a/source/dark_matter_halos.spins.distributions.N-body_errors.F90 +++ b/source/dark_matter_halos.spins.distributions.N-body_errors.F90 @@ -25,7 +25,6 @@ use :: Cosmology_Functions , only : cosmologyFunctionsClass use :: Dark_Matter_Halo_Scales , only : darkMatterHaloScaleClass use :: Dark_Matter_Profile_Scales , only : darkMatterProfileScaleRadius, darkMatterProfileScaleRadiusClass - use :: Dark_Matter_Profiles_DMO , only : darkMatterProfileDMOClass use :: Halo_Mass_Functions , only : haloMassFunctionClass use :: Root_Finder , only : rootFinder use :: Statistics_NBody_Halo_Mass_Errors, only : nbodyHaloMassErrorClass @@ -49,7 +48,6 @@ class (nbodyHaloMassErrorClass ), pointer :: nbodyHaloMassError_ => null() class (haloMassFunctionClass ), pointer :: haloMassFunction_ => null() class (darkMatterHaloScaleClass ), pointer :: darkMatterHaloScale_ => null() - class (darkMatterProfileDMOClass ), pointer :: darkMatterProfileDMO_ => null() class (darkMatterProfileScaleRadiusClass), pointer :: darkMatterProfileScaleRadius_ => null() type (rootFinder ) :: finder double precision :: massParticle , time , & @@ -113,7 +111,6 @@ function nbodyErrorsConstructorParameters(parameters) result(self) class (cosmologyFunctionsClass ), pointer :: cosmologyFunctions_ class (haloMassFunctionClass ), pointer :: haloMassFunction_ class (darkMatterHaloScaleClass ), pointer :: darkMatterHaloScale_ - class (darkMatterProfileDMOClass ), pointer :: darkMatterProfileDMO_ class (darkMatterProfileScaleRadiusClass), pointer :: darkMatterProfileScaleRadius_ double precision :: massParticle , redshift , & & time , energyEstimateParticleCountMaximum, & @@ -154,7 +151,6 @@ function nbodyErrorsConstructorParameters(parameters) result(self) - !!] ! Find the time corresponding to the given redshift. @@ -175,7 +171,6 @@ function nbodyErrorsConstructorParameters(parameters) result(self) & cosmologyFunctions_ , & & haloMassFunction_ , & & darkMatterHaloScale_ , & - & darkMatterProfileDMO_ , & & darkMatterProfileScaleRadius_ & & ) !![ @@ -185,13 +180,12 @@ function nbodyErrorsConstructorParameters(parameters) result(self) - !!] return end function nbodyErrorsConstructorParameters - function nbodyErrorsConstructorInternal(distributionIntrinsic,massParticle,particleCountMinimum,energyEstimateParticleCountMaximum,logNormalRange,time,nbodyHaloMassError_,cosmologyFunctions_,haloMassFunction_,darkMatterHaloScale_,darkMatterProfileDMO_,darkMatterProfileScaleRadius_) result(self) + function nbodyErrorsConstructorInternal(distributionIntrinsic,massParticle,particleCountMinimum,energyEstimateParticleCountMaximum,logNormalRange,time,nbodyHaloMassError_,cosmologyFunctions_,haloMassFunction_,darkMatterHaloScale_,darkMatterProfileScaleRadius_) result(self) !!{ Internal constructor for the {\normalfont \ttfamily nbodyErrors} dark matter halo spin distribution class. !!} @@ -203,13 +197,12 @@ function nbodyErrorsConstructorInternal(distributionIntrinsic,massParticle,parti class (cosmologyFunctionsClass ), intent(in ), target :: cosmologyFunctions_ class (haloMassFunctionClass ), intent(in ), target :: haloMassFunction_ class (darkMatterHaloScaleClass ), intent(in ), target :: darkMatterHaloScale_ - class (darkMatterProfileDMOClass ), intent(in ), target :: darkMatterProfileDMO_ class (darkMatterProfileScaleRadiusClass), intent(in ), target :: darkMatterProfileScaleRadius_ double precision , intent(in ) :: massParticle , time , & & energyEstimateParticleCountMaximum, logNormalRange integer , intent(in ) :: particleCountMinimum !![ - + !!] ! Store the redshift. @@ -456,57 +449,67 @@ double precision function massSpinIntegral(massIntrinsic) !!{ Integral over the halo mass function, spin distribution, halo mass error distribution, and spin error distribution. !!} - use :: Display , only : displayMagenta , displayReset - use :: Error , only : Error_Report , Warn , errorStatusFail, errorStatusSuccess - use :: Input_Parameters , only : inputParameters - use :: Numerical_Constants_Math, only : Pi + use :: Display , only : displayMagenta , displayReset + use :: Error , only : Error_Report , Warn , errorStatusFail, errorStatusSuccess + use :: Input_Parameters , only : inputParameters + use :: Numerical_Constants_Math , only : Pi + use :: Mass_Distributions , only : massDistributionClass + use :: Galactic_Structure_Options, only : componentTypeDarkMatterOnly, massTypeDark + use :: Coordinates , only : coordinateSpherical , assignment(=) implicit none - double precision , intent(in ) :: massIntrinsic - double precision , parameter :: rangeIntegration =1.0000d1 ! Range of integration in units of error. - double precision , parameter :: radiusVelocityDispersionMeanOverSpinSpecificAngularMomentum=0.4175d0 ! Ratio of mean velocity dispersion-radius product to "spin" angular - ! momentum (i.e. the normalizing angular momentum appearing in the definition of - ! halo spin): γ = Mσⱼ/Jₛ, Jₛ = GM²˙⁵/|E⁰˙⁵|. This parameter corresponds to γ. - integer :: errorStatus - double precision :: logSpinMinimum, logSpinMaximum , & - & errorMaximum , scaleAbsolute , & - & tolerance , massSpinIntegralMass - character (len=8 ) :: label , labelMass , & - & labelSpin - type (inputParameters) :: descriptor + double precision , intent(in ) :: massIntrinsic + class (massDistributionClass), pointer :: massDistribution_ + double precision , parameter :: rangeIntegration =1.0000d1 ! Range of integration in units of error. + double precision , parameter :: radiusVelocityDispersionMeanOverSpinSpecificAngularMomentum=0.4175d0 ! Ratio of mean velocity dispersion-radius product to "spin" angular + ! momentum (i.e. the normalizing angular momentum appearing in the definition of + ! halo spin): γ = Mσⱼ/Jₛ, Jₛ = GM²˙⁵/|E⁰˙⁵|. This parameter corresponds to γ. + integer :: errorStatus + double precision :: logSpinMinimum , logSpinMaximum , & + & errorMaximum , scaleAbsolute , & + & tolerance , massSpinIntegralMass + character (len=8 ) :: label , labelMass , & + & labelSpin + type (inputParameters ) :: descriptor + type (coordinateSpherical ) :: coordinatesHalo ! Evaluate the halo mass part of the integrand, unless evaluating the distribution at a fixed point. if (self%fixedPoint) then - massSpinIntegralMass =1.0d0 + massSpinIntegralMass = 1.0d0 else - massSpinIntegralMass =massIntegral(massIntrinsic) + massSpinIntegralMass = massIntegral(massIntrinsic) end if ! Compute the particle number. - particleNumber =massIntrinsic /self%massParticle + particleNumber = massIntrinsic /self%massParticle ! Evaluate the root-variance of the spin-independent error term which arises from the random walk in angular momentum ! space. Note that the root-variance that goes into non-central χ-square distribution is the width of the Gaussian for a ! single dimension, leading to a factor of √3 in the following. - errorSpinIndependent =+radiusVelocityDispersionMeanOverSpinSpecificAngularMomentum & - & /sqrt(particleNumber) - errorSpinIndependent1D =+errorSpinIndependent & - & /sqrt(3.0d0) + errorSpinIndependent = +radiusVelocityDispersionMeanOverSpinSpecificAngularMomentum & + & /sqrt(particleNumber) + errorSpinIndependent1D = +errorSpinIndependent & + & /sqrt(3.0d0) ! Get the outer radius of the halo. - radiusHalo =+self%darkMatterHaloScale_ %radiusVirial(node ) + radiusHalo = +self%darkMatterHaloScale_ %radiusVirial(node) + coordinatesHalo = [radiusHalo,0.0d0,0.0d0] ! Get the density at the edge of the halo. - densityOuterRadius =+self%darkMatterProfileDMO_%density (node,radiusHalo) + massDistribution_ => node %massDistribution(componentTypeDarkMatterOnly,massTypeDark) + densityOuterRadius = +massDistribution_%density (coordinatesHalo ) + !![ + + !!] ! Find the ratio of the mean interior density in the halo to the density at the halo outer radius. - densityRatioInternalToSurface=+3.0d0 & - & *massIntrinsic & - & /4.0d0 & - & /Pi & - & /radiusHalo **3 & - & /densityOuterRadius + densityRatioInternalToSurface = +3.0d0 & + & *massIntrinsic & + & /4.0d0 & + & /Pi & + & /radiusHalo **3 & + & /densityOuterRadius ! Evaluate the distribution. if (self%fixedPoint) then massSpinIntegral=spinErrorIntegral(spinFixed) else ! Evaluate an estimate of the absolute scale of the spin distribution for use in setting an absolute precision level on the ! integration. - call nodeSpin%angularMomentumSet(spinMeasured*Dark_Matter_Halo_Angular_Momentum_Scale(node,self%darkMatterProfileDMO_)) + call nodeSpin%angularMomentumSet(spinMeasured*Dark_Matter_Halo_Angular_Momentum_Scale(node,self%darkMatterHaloScale_)) call Calculations_Reset(node) scaleAbsolute=self%distributionIntrinsic%distribution(node) ! Evaluate the integral over the spin distribution. We integrate from ±ασ around the measured spin, with σ being the larger @@ -578,7 +581,7 @@ double precision function spinIntegral(logSpinIntrinsic) ! Compute intrinsic spin. spinIntrinsic=exp(logSpinIntrinsic) ! Set the intrinsic spin. - call nodeSpin%angularMomentumSet(spinIntrinsic*Dark_Matter_Halo_Angular_Momentum_Scale(node,self%darkMatterProfileDMO_)) + call nodeSpin%angularMomentumSet(spinIntrinsic*Dark_Matter_Halo_Angular_Momentum_Scale(node,self%darkMatterHaloScale_)) call Calculations_Reset(node) ! Compute the integrand. spinIntegral=+self%distributionIntrinsic%distribution(node ) & ! Weight by the intrinsic spin distribution. @@ -795,7 +798,6 @@ subroutine nbodyErrorsDestructor(self) - !!] return @@ -836,8 +838,8 @@ double precision function nbodyErrorsDistribution(self,node) nodeBasic => node %basic () nodeSpin => node %spin () mass = nodeBasic%mass () - spin = +nodeSpin %angularMomentum() & - & /Dark_Matter_Halo_Angular_Momentum_Scale(node,self%darkMatterProfileDMO_) + spin = +nodeSpin %angularMomentum() & + & /Dark_Matter_Halo_Angular_Momentum_Scale(node,self%darkMatterHaloScale_) ! Ensure the table has sufficient extent. call self%tabulate(massRequired=mass,spinRequired=spin,tree=node%hostTree) ! Find the interpolating factors. @@ -878,8 +880,8 @@ double precision function nbodyErrorsDistributionFixedPoint(self,node,spinMeasur nodeBasic => node %basic () nodeSpin => node %spin () mass = nodeBasic%mass () - spin = +nodeSpin %angularMomentum() & - & /Dark_Matter_Halo_Angular_Momentum_Scale(node,self%darkMatterProfileDMO_) + spin = +nodeSpin %angularMomentum() & + & /Dark_Matter_Halo_Angular_Momentum_Scale(node,self%darkMatterHaloScale_) ! Ensure the table has sufficient extent. call self%tabulate(massFixed=mass,spinFixed=spin,spinFixedMeasuredMinimum=spinMeasuredMinimum,spinFixedMeasuredMaximum=spinMeasuredMaximum,tree=node%hostTree) ! Find the interpolating factors. diff --git a/source/dark_matter_halos.spins.distributions.lognormal.F90 b/source/dark_matter_halos.spins.distributions.lognormal.F90 index 01e84c75ac..ea7b84020f 100644 --- a/source/dark_matter_halos.spins.distributions.lognormal.F90 +++ b/source/dark_matter_halos.spins.distributions.lognormal.F90 @@ -22,7 +22,7 @@ log-normal distribution. !!} - use :: Dark_Matter_Profiles_DMO, only : darkMatterProfileDMO, darkMatterProfileDMOClass + use :: Dark_Matter_Halo_Scales, only : darkMatterHaloScaleClass !![ @@ -38,8 +38,8 @@ log-normal distribution. !!} private - class (darkMatterProfileDMOClass), pointer :: darkMatterProfileDMO_ => null() - double precision :: median , sigma + class (darkMatterHaloScaleClass), pointer :: darkMatterHaloScale_ => null() + double precision :: median , sigma contains final :: logNormalDestructor procedure :: sample => logNormalSample @@ -66,8 +66,8 @@ function logNormalConstructorParameters(parameters) result(self) implicit none type (haloSpinDistributionLogNormal) :: self type (inputParameters ), intent(inout) :: parameters - class (darkMatterProfileDMOClass ), pointer :: darkMatterProfileDMO_ - double precision :: median , sigma + class (darkMatterHaloScaleClass ), pointer :: darkMatterHaloScale_ + double precision :: median , sigma ! Check and read parameters. !![ @@ -87,27 +87,27 @@ function logNormalConstructorParameters(parameters) result(self) (\citealt{bett_spin_2007}; note that in this reference the value of $\sigma$ quoted is for $\log_{10}\lambda$, while here we use $\log\lambda$) The width of a log-normal spin distribution. - + !!] - self=haloSpinDistributionLogNormal(median,sigma,darkMatterProfileDMO_) + self=haloSpinDistributionLogNormal(median,sigma,darkMatterHaloScale_) !![ - + !!] return end function logNormalConstructorParameters - function logNormalConstructorInternal(median,sigma,darkMatterProfileDMO_) result(self) + function logNormalConstructorInternal(median,sigma,darkMatterHaloScale_) result(self) !!{ Internal constructor for the {\normalfont \ttfamily logNormal} dark matter halo spin distribution class. !!} implicit none type (haloSpinDistributionLogNormal) :: self - double precision , intent(in ) :: median , sigma - class (darkMatterProfileDMOClass ), intent(in ), target :: darkMatterProfileDMO_ + double precision , intent(in ) :: median , sigma + class (darkMatterHaloScaleClass ), intent(in ), target :: darkMatterHaloScale_ !![ - + !!] return @@ -122,7 +122,7 @@ subroutine lognormalDestructor(self) type(haloSpinDistributionLognormal), intent(inout) :: self !![ - + !!] return end subroutine lognormalDestructor @@ -160,8 +160,8 @@ double precision function logNormalDistribution(self,node) double precision :: spin_ spin => node%spin () - spin_ = +spin%angularMomentum() & - & /Dark_Matter_Halo_Angular_Momentum_Scale(node,self%darkMatterProfileDMO_) + spin_ = +spin%angularMomentum() & + & /Dark_Matter_Halo_Angular_Momentum_Scale(node,self%darkMatterHaloScale_) logNormalDistribution = +exp( & & -( & & +log(spin_) & diff --git a/source/dark_matter_profiles.F90 b/source/dark_matter_profiles.F90 index 23ba4184b9..773406e13f 100644 --- a/source/dark_matter_profiles.F90 +++ b/source/dark_matter_profiles.F90 @@ -25,185 +25,26 @@ module Dark_Matter_Profiles !!{ Provides an object that implements non-dark-matter-only dark matter halo profiles. !!} - use :: Dark_Matter_Profiles_Generic, only : darkMatterProfileGeneric - use :: Galacticus_Nodes , only : treeNode - use :: Galactic_Structure_Options , only : enumerationStructureErrorCodeType - use :: Kind_Numbers , only : kind_int8 + use :: Galacticus_Nodes , only : treeNode + use :: Mass_Distributions , only : massDistributionClass + use :: Galactic_Structure_Options, only : enumerationStructureErrorCodeType, enumerationWeightByType + use :: Kind_Numbers , only : kind_int8 private !![ darkMatterProfile - darkMatterProfileGeneric Dark Matter Halo Profiles Object providing dark matter halo profiles. adiabaticGnedin2004 - - Returns the density (in $M_\odot$ Mpc$^{-3}$) in the dark matter profile of {\normalfont \ttfamily node} at the given {\normalfont \ttfamily radius} (given in units of Mpc). - double precision - yes - type (treeNode), intent(inout) :: node - double precision , intent(in ) :: radius - - - Returns the logarithmic slope of the density profile in the dark matter profile of {\normalfont \ttfamily node} at the given {\normalfont \ttfamily radius} (given in units of Mpc). - double precision - yes - type (treeNode), intent(inout) :: node - double precision , intent(in ) :: radius - - - Returns the {\normalfont \ttfamily m}$^\mathrm{th}$ radial moment of the dark matter profile of {\normalfont \ttfamily node} optionally between the given {\normalfont \ttfamily radiusMinimum} and {\normalfont \ttfamily radiusMaximum} (given in units of Mpc). - double precision - yes - type (treeNode), intent(inout) :: node - double precision , intent(in ) :: moment - double precision , intent(in ), optional :: radiusMinimum, radiusMaximum - - - Return the total energy for the given {\normalfont \ttfamily node} in units of $M_\odot$ km$^2$ s$^{-2}$. - double precision - yes - type(treeNode), intent(inout) :: node - - - Returns the relation between specific angular momentum and rotation velocity (assuming a rotation velocity that is constant in radius) for the given {\normalfont \ttfamily node}. Specifically, the normalization, $A$, returned is such that $V_\mathrm{rot} = A J/M$ - double precision - yes - type (treeNode), intent(inout) :: node - - - Returns the radius (in Mpc) in the dark matter profile of {\normalfont \ttfamily node} at which the specific angular momentum of a circular orbit equals {\normalfont \ttfamily specificAngularMomentum} (specified in units of km s$^{-1}$ Mpc. - double precision - yes - yes - type (treeNode), intent(inout) :: node - double precision , intent(in ) :: specificAngularMomentum - - - Returns the circular velocity (in km/s) in the dark matter profile of {\normalfont \ttfamily node} at the given {\normalfont \ttfamily radius} (given in units of Mpc). - double precision - yes - type (treeNode), intent(inout) :: node - double precision , intent(in ) :: radius - - - Returns the radius (in Mpc) at which the maximum circular velocity is achieved in the dark matter profile of {\normalfont \ttfamily node}. - double precision - yes - type (treeNode), intent(inout) :: node - - - Returns the maximum circular velocity (in km/s) in the dark matter profile of {\normalfont \ttfamily node}. - double precision - yes - type (treeNode), intent(inout) :: node - - - Returns the radial velocity dispersion (in km/s) in the dark matter profile of {\normalfont \ttfamily node} at the given {\normalfont \ttfamily radius} (given in units of Mpc). - double precision - yes - type (treeNode), intent(inout) :: node - double precision , intent(in ) :: radius - - - Returns the gravitational potential (in (km/s)$^2$) in the dark matter profile of {\normalfont \ttfamily node} at the given {\normalfont \ttfamily radius} (given in units of Mpc). - double precision - yes - yes - type (treeNode ), intent(inout), target :: node - double precision , intent(in ) :: radius - type (enumerationStructureErrorCodeType), intent( out), optional :: status - - - Returns the enclosed mass (in $M_\odot$) in the dark matter profile of {\normalfont \ttfamily node} at the given {\normalfont \ttfamily radius} (given in units of Mpc). - double precision - yes - type (treeNode), intent(inout) :: node - double precision , intent(in ) :: radius - - - Returns the radius (in Mpc) enclosing a given density threshold (in $M_\odot \hbox{Mpc}^{-3}$) in the dark matter profile of {\normalfont \ttfamily node}. - double precision - yes - yes - type (treeNode), intent(inout), target :: node - double precision , intent(in ) :: density - - - Returns the normalized Fourier space density profile of the dark matter profile of {\normalfont \ttfamily node} at the given {\normalfont \ttfamily waveNumber} (given in units of Mpc$^{-1}$). - double precision - yes - type (treeNode), intent(inout), target :: node - double precision , intent(in ) :: wavenumber - - - Returns the freefall radius (in Mpc) corresponding to the given {\normalfont \ttfamily time} (in Gyr) in {\normalfont \ttfamily node}. - double precision - yes - type (treeNode), intent(inout) :: node - double precision , intent(in ) :: time - - - Returns the rate of increase of the freefall radius (in Mpc/Gyr) corresponding to the given {\normalfont \ttfamily time} (in Gyr) in {\normalfont \ttfamily node}. - double precision - yes - type (treeNode), intent(inout) :: node - double precision , intent(in ) :: time - - - Returns the radius (in Mpc) enclosing a given mass (in $M_\odot$) in the dark matter profile of {\normalfont \ttfamily node}. - double precision + + Return the mass distribution of the dark matter profile. + class(massDistributionClass) yes yes - type (treeNode), intent(inout), target :: node - double precision , intent(in ) :: mass - Root_Finder Kind_Numbers - - double precision :: radiusGuess - type (rootFinder), save :: finder - logical , save :: finderConstructed=.false. - double precision , save :: radiusPrevious =-huge(0.0d0) - integer (kind_int8 ), save :: uniqueIDPrevious =-1_kind_int8 - !$omp threadprivate(finder,finderConstructed,radiusPrevious,uniqueIDPrevious) - if(mass <= 0) then - darkMatterProfileRadiusEnclosingMass=0.0d0 - return - end if - ! Initialize the root finder. - if (.not.finderConstructed) then - finder=rootFinder( & - & rootFunction =enclosedMassRoot , & - & rangeExpandDownward =0.5d0 , & - & rangeExpandUpward =2.0d0 , & - & rangeExpandDownwardSignExpect=rangeExpandSignExpectNegative, & - & rangeExpandUpwardSignExpect =rangeExpandSignExpectPositive, & - & rangeExpandType =rangeExpandMultiplicative , & - & toleranceAbsolute =0.0d+0 , & - & toleranceRelative =1.0d-6 & - & ) - finderConstructed=.true. - end if - if (node%uniqueID() == uniqueIDPrevious) then - radiusGuess =radiusPrevious - else - radiusGuess =self%darkMatterHaloScale_%radiusVirial(node) - uniqueIDPrevious=node %uniqueID ( ) - end if - radiusPrevious=finder%find(rootGuess=radiusGuess) - darkMatterProfileRadiusEnclosingMass=radiusPrevious - return - contains - double precision function enclosedMassRoot(radius) - !!{ - Root function used in solving for the radius that encloses a given mass. - !!} - implicit none - double precision, intent(in) :: radius - enclosedMassRoot=self%enclosedMass(node,radius)-mass - return - end function enclosedMassRoot - + type (treeNode ), intent(inout), target :: node + type (enumerationWeightByType), intent(in ), optional :: weightBy + integer , intent(in ), optional :: weightIndex !!] diff --git a/source/dark_matter_profiles.SIDM.F90 b/source/dark_matter_profiles.SIDM.F90 deleted file mode 100644 index 2fea326714..0000000000 --- a/source/dark_matter_profiles.SIDM.F90 +++ /dev/null @@ -1,141 +0,0 @@ -!! Copyright 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, -!! 2019, 2020, 2021, 2022, 2023, 2024 -!! Andrew Benson -!! -!! This file is part of Galacticus. -!! -!! Galacticus is free software: you can redistribute it and/or modify -!! it under the terms of the GNU General Public License as published by -!! the Free Software Foundation, either version 3 of the License, or -!! (at your option) any later version. -!! -!! Galacticus is distributed in the hope that it will be useful, -!! but WITHOUT ANY WARRANTY; without even the implied warranty of -!! MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -!! GNU General Public License for more details. -!! -!! You should have received a copy of the GNU General Public License -!! along with Galacticus. If not, see . - - !!{ - An abstract dark matter halo profile class for SIDM profiles. - !!} - - use :: Dark_Matter_Particles, only : darkMatterParticleClass - - !![ - - - Abstract dark matter halo profile for self-interacting dark matter particles. Provides a method to compute the - characteristic radius for interactions, $r_1$, defined as (e.g. Jiang et al. 2020): - \begin{equation} - \frac{4}{\sqrt{\pi}} \rho_\mathrm{dm}(r_1) v_\mathrm{rms}(r_1) \frac{\sigma}{m} = \frac{1}{t_\mathrm{age}}, - \end{equation} - where the left-hand side is the scattering rate per particle, with $\rho_\mathrm{dm}(r)$ being the dark matter density - profile, $v_\mathrm{rms}$ the average relative velocity between DM particles (which is approximated by the 1D velocity - dispersion), and $\sigma/m$ is the self-interaction cross-section per unit mass. - - - !!] - type, abstract, extends(darkMatterProfileClass) :: darkMatterProfileSIDM - !!{ - An abstract dark matter halo profile class for SIDM profiles. - !!} - private - class (darkMatterProfileClass ), pointer :: darkMatterProfile_ => null() - class (darkMatterParticleClass), pointer :: darkMatterParticle_ => null() - integer (kind=kind_int8 ) :: uniqueIDPreviousSIDM - double precision :: radiusInteractivePrevious - contains - !![ - - - - !!] - procedure :: radiusInteraction => sidmRadiusInteraction - end type darkMatterProfileSIDM - - ! Submodule-scope variables used in root finding. - class (darkMatterProfileSIDM), pointer :: self_ - type (treeNode ), pointer :: node_ - double precision :: timeAge_, crossSection_ - !$omp threadprivate(self_,node_,timeAge_,crossSection_) - -contains - - double precision function sidmRadiusInteraction(self,node,timeAge) - !!{ - Returns the characteristic interaction radius (in Mpc) of the self-interacting dark matter profile of {\normalfont \ttfamily node}. - !!} - use :: Dark_Matter_Particles , only : darkMatterParticleSelfInteractingDarkMatter - use :: Error , only : Error_Report - use :: Galacticus_Nodes , only : nodeComponentBasic - use :: Numerical_Constants_Prefixes , only : centi , kilo - use :: Numerical_Constants_Astronomical, only : megaParsec , massSolar - use :: Root_Finder , only : rootFinder , rangeExpandMultiplicative, rangeExpandSignExpectNegative, rangeExpandSignExpectPositive - implicit none - class (darkMatterProfileSIDM), intent(inout), target :: self - type (treeNode ), intent(inout), target :: node - double precision , intent(in ), optional :: timeAge - class (nodeComponentBasic ) , pointer :: basic - type (rootFinder ) :: finder - double precision , parameter :: toleranceAbsolute=0.0d0, toleranceRelative=1.0d-3 - - if (node%uniqueID() /= self%uniqueIDPreviousSIDM .or. self%radiusInteractivePrevious < 0.0d0) then - self_ => self - node_ => node - if (present(timeAge)) then - timeAge_ = timeAge - else - basic => node %basic () - timeAge_ = basic%time () - end if - select type (darkMatterParticle_ => self%darkMatterParticle_) - class is (darkMatterParticleSelfInteractingDarkMatter) - crossSection_=+darkMatterParticle_%crossSectionSelfInteraction() & - & *centi **2 & - & /megaParsec**2 & - & *kilo & - & *massSolar - class default - call Error_Report('expected self-interacting dark matter particle'//{introspection:location}) - end select - finder=rootFinder( & - & rootFunction =sidmRadiusInteractionRoot, & - & toleranceAbsolute=toleranceAbsolute , & - & toleranceRelative=toleranceRelative & - & ) - call finder%rangeExpand( & - & rangeExpandUpward =2.0d0 , & - & rangeExpandDownward =0.5d0 , & - & rangeExpandDownwardSignExpect=rangeExpandSignExpectPositive, & - & rangeExpandUpwardSignExpect =rangeExpandSignExpectNegative, & - & rangeExpandType =rangeExpandMultiplicative & - & ) - self%radiusInteractivePrevious=finder%find (rootGuess=self%darkMatterHaloScale_%radiusVirial(node)) - self%uniqueIDPreviousSIDM =node %uniqueID( ) - end if - sidmRadiusInteraction=self%radiusInteractivePrevious - return - end function sidmRadiusInteraction - - double precision function sidmRadiusInteractionRoot(radius) - !!{ - Root function used in seeking the characteristic interaction radius in self-interacting dark matter profiles. - !!} - use :: Numerical_Constants_Astronomical, only : Mpc_per_km_per_s_To_Gyr - use :: Numerical_Constants_Math , only : Pi - implicit none - double precision, intent(in ) :: radius - - sidmRadiusInteractionRoot=+4.0d0 & - & /sqrt(Pi) & - & /Mpc_per_km_per_s_To_Gyr & - & *self_%darkMatterProfile_%density (node_,radius) & - & *self_%darkMatterProfile_%radialVelocityDispersion(node_,radius) & - & *crossSection_ & - & -1.0d0 & - & /timeAge_ - return - end function sidmRadiusInteractionRoot - diff --git a/source/dark_matter_profiles.SIDM.isothermal.F90 b/source/dark_matter_profiles.SIDM.isothermal.F90 index bb2478e5ed..fc0401e5ad 100644 --- a/source/dark_matter_profiles.SIDM.isothermal.F90 +++ b/source/dark_matter_profiles.SIDM.isothermal.F90 @@ -22,69 +22,26 @@ al. (2022), including the effects of a baryonic potential. !!} - use, intrinsic :: ISO_C_Binding , only : c_size_t - use :: Numerical_Interpolation, only : interpolator - use :: Numerical_ODE_Solvers , only : odeSolver - + use :: Dark_Matter_Particles, only : darkMatterParticleClass + !![ - Dark matter halo profiles for self-interacting dark matter following the ``isothermal'' model of Jiang et al. (2022). This - model assumes that the dark matter within the interaction radius, $r_1$, has thermalized and can therefore be described by a - constant velocity dispersion, $\sigma_0$. Under this assumption the spherical Jeans equation has a solution of the form: - \begin{equation} - \rho(r) = \rho_0 \exp\left[-\frac{\phi(r)}{\sigma_0^2}\right], - \end{equation} - where $\rho(r)$ is the density $\rho_0$ is the density at $r=0$, and the gravitational potential satisfies (Jiang et al. 2022): - \begin{equation} - \nabla^2 \phi(r) = 4 \pi \mathrm{G} \left[ \rho_0 \exp \left( - \frac{\phi(r)}{\sigma_0^2} \right) + \rho_\mathrm{b}(r) \right], - \end{equation} - where $\rho_\mathrm{b}(r)$ is the density of the baryonic component. This second-order differential equation is solved using the boundary conditions $\phi(r=0)=0$ and - $\mathrm{d}\phi/\mathrm{d}r(r=0)=0$. The values of $\rho_0$ and $\sigma_0$ are then found by minimizing a function - \begin{equation} - \delta^2(\rho_0,\sigma_0) = \left[ \frac{\rho(r_1)}{\rho^\prime(r_1)} - 1 \right]^2 + \left[ \frac{M(r_1)}{M^\prime(r_1)} - 1 \right]^2, - \end{equation} - where $M(r)$ is the mass contained within radius $r$, and primes indicate the profile prior to SIDM thermalization. + A dark matter halo profile class that builds \refClass{massDistributionSphericalSIDMIsothermalBaryons} objects for + isothermal SIDM profiles containing baryons. !!] - type, extends(darkMatterProfileSIDM) :: darkMatterProfileSIDMIsothermal + type, extends(darkMatterProfileClass) :: darkMatterProfileSIDMIsothermal !!{ A dark matter halo profile class implementing profiles for self-interacting dark matter following the ``isothermal'' model of Jiang et al. (2022). !!} private - integer (kind=kind_int8) :: uniqueIDPrevious - double precision :: velocityDispersionCentral - logical :: solutionsTabulated - class (* ), pointer :: galacticStructure_ => null() - type (interpolator ), allocatable :: densityProfile , massProfile + class(darkMatterProfileClass ), pointer :: darkMatterProfile_ => null() + class(darkMatterParticleClass), pointer :: darkMatterParticle_ => null() contains - !![ - - - - - !!] - final :: sidmIsothermalDestructor - procedure :: autoHook => sidmIsothermalAutoHook - procedure :: calculationReset => sidmIsothermalCalculationReset - procedure :: density => sidmIsothermalDensity - procedure :: densityLogSlope => sidmIsothermalDensityLogSlope - procedure :: radiusEnclosingDensity => sidmIsothermalRadiusEnclosingDensity - procedure :: radiusEnclosingMass => sidmIsothermalRadiusEnclosingMass - procedure :: radialMoment => sidmIsothermalRadialMoment - procedure :: enclosedMass => sidmIsothermalEnclosedMass - procedure :: potential => sidmIsothermalPotential - procedure :: circularVelocity => sidmIsothermalCircularVelocity - procedure :: circularVelocityMaximum => sidmIsothermalCircularVelocityMaximum - procedure :: radialVelocityDispersion => sidmIsothermalRadialVelocityDispersion - procedure :: radiusFromSpecificAngularMomentum => sidmIsothermalRadiusFromSpecificAngularMomentum - procedure :: rotationNormalization => sidmIsothermalRotationNormalization - procedure :: energy => sidmIsothermalEnergy - procedure :: kSpace => sidmIsothermalKSpace - procedure :: freefallRadius => sidmIsothermalFreefallRadius - procedure :: freefallRadiusIncreaseRate => sidmIsothermalFreefallRadiusIncreaseRate - procedure :: computeSolution => sidmIsothermalComputeSolution + final :: sidmIsothermalDestructor + procedure :: get => sidmIsothermalGet end type darkMatterProfileSIDMIsothermal interface darkMatterProfileSIDMIsothermal @@ -101,46 +58,37 @@ function sidmIsothermalConstructorParameters(parameters) result(self) !!{ Constructor for the {\normalfont \ttfamily sidmIsothermal} dark matter halo profile class which takes a parameter set as input. !!} - use :: Functions_Global, only : galacticStructureConstruct_, galacticStructureDestruct_ - use :: Input_Parameters, only : inputParameter , inputParameters + use :: Input_Parameters, only : inputParameters implicit none type (darkMatterProfileSIDMIsothermal) :: self type (inputParameters ), intent(inout) :: parameters - class(darkMatterHaloScaleClass ), pointer :: darkMatterHaloScale_ class(darkMatterParticleClass ), pointer :: darkMatterParticle_ class(darkMatterProfileClass ), pointer :: darkMatterProfile_ - class(* ), pointer :: galacticStructure_ !![ - - - + + !!] - call galacticStructureConstruct_(parameters,galacticStructure_) - self=darkMatterProfileSIDMIsothermal(darkMatterProfile_,darkMatterHaloScale_,darkMatterParticle_,galacticStructure_) + self=darkMatterProfileSIDMIsothermal(darkMatterProfile_,darkMatterParticle_) !![ - - - - + + + !!] - call galacticStructureDestruct_(self%galacticStructure_) return end function sidmIsothermalConstructorParameters - function sidmIsothermalConstructorInternal(darkMatterProfile_,darkMatterHaloScale_,darkMatterParticle_,galacticStructure_) result(self) + function sidmIsothermalConstructorInternal(darkMatterProfile_,darkMatterParticle_) result(self) !!{ Internal constructor for the {\normalfont \ttfamily sidmIsothermal} dark matter profile class. !!} use :: Dark_Matter_Particles, only : darkMatterParticleSelfInteractingDarkMatter implicit none type (darkMatterProfileSIDMIsothermal) :: self - class(darkMatterHaloScaleClass ), intent(in ), target :: darkMatterHaloScale_ class(darkMatterParticleClass ), intent(in ), target :: darkMatterParticle_ class(darkMatterProfileClass ), intent(in ), target :: darkMatterProfile_ - class(* ), intent(in ), target :: galacticStructure_ !![ - + !!] ! Validate the dark matter particle type. @@ -150,470 +98,122 @@ function sidmIsothermalConstructorInternal(darkMatterProfile_,darkMatterHaloScal class default call Error_Report('SIDM isothermal dark matter profile expects a self-interacting dark matter particle'//{introspection:location}) end select - self%uniqueIDPrevious =-1_kind_int8 - self%genericLastUniqueID =-1_kind_int8 - self%uniqueIDPreviousSIDM=-1_kind_int8 return end function sidmIsothermalConstructorInternal - subroutine sidmIsothermalAutoHook(self) - !!{ - Attach to the calculation reset event. - !!} - use :: Events_Hooks, only : calculationResetEvent, openMPThreadBindingAllLevels - implicit none - class(darkMatterProfileSIDMIsothermal), intent(inout) :: self - - call calculationResetEvent%attach(self,sidmIsothermalCalculationReset,openMPThreadBindingAllLevels,label='darkMatterProfileSIDMIsothermal') - return - end subroutine sidmIsothermalAutoHook - subroutine sidmIsothermalDestructor(self) !!{ Destructor for the {\normalfont \ttfamily sidmIsothermal} dark matter halo profile class. !!} - use :: Functions_Global, only : galacticStructureDestruct_ - use :: Events_Hooks , only : calculationResetEvent implicit none type(darkMatterProfileSIDMIsothermal), intent(inout) :: self !![ - - - + + !!] - if (associated(self%galacticStructure_)) call galacticStructureDestruct_(self%galacticStructure_) - if (calculationResetEvent%isAttached(self,sidmIsothermalCalculationReset)) call calculationResetEvent%detach(self,sidmIsothermalCalculationReset) return end subroutine sidmIsothermalDestructor - subroutine sidmIsothermalCalculationReset(self,node,uniqueID) - !!{ - Reset the dark matter profile calculation. - !!} - use :: Kind_Numbers, only : kind_int8 - implicit none - class (darkMatterProfileSIDMIsothermal), intent(inout) :: self - type (treeNode ), intent(inout) :: node - integer(kind_int8 ), intent(in ) :: uniqueID - !$GLC attributes unused :: node - - self%uniqueIDPrevious =uniqueID - self%genericLastUniqueID =uniqueID - self%uniqueIDPreviousSIDM =uniqueID - self%radiusInteractivePrevious =-1.0d0 - self%velocityDispersionCentral =-1.0d0 - self%genericEnclosedMassRadiusMinimum =+huge(0.0d0) - self%genericEnclosedMassRadiusMaximum =-huge(0.0d0) - self%genericVelocityDispersionRadialRadiusMinimum=+huge(0.0d0) - self%genericVelocityDispersionRadialRadiusMaximum=-huge(0.0d0) - if (allocated(self%densityProfile )) deallocate(self%densityProfile ) - if (allocated(self%massProfile )) deallocate(self%massProfile ) - if (allocated(self%genericVelocityDispersionRadialVelocity)) deallocate(self%genericVelocityDispersionRadialVelocity) - if (allocated(self%genericVelocityDispersionRadialRadius )) deallocate(self%genericVelocityDispersionRadialRadius ) - if (allocated(self%genericEnclosedMassMass )) deallocate(self%genericEnclosedMassMass ) - if (allocated(self%genericEnclosedMassRadius )) deallocate(self%genericEnclosedMassRadius ) - return - end subroutine sidmIsothermalCalculationReset - - subroutine sidmIsothermalComputeSolution(self,node) - !!{ - Compute a solution for the isothermal core of an SIDM halo. - !!} - use :: Numerical_ODE_Solvers , only : odeSolver - use :: Numerical_Ranges , only : Make_Range , rangeTypeLinear - use :: Numerical_Constants_Math , only : Pi - use :: Numerical_Constants_Astronomical, only : gravitationalConstantGalacticus - use :: Multidimensional_Minimizer , only : multiDMinimizer - implicit none - class (darkMatterProfileSIDMIsothermal), intent(inout) :: self - type (treeNode ), intent(inout) :: node - integer (c_size_t ), parameter :: propertyCount =2 - integer , parameter :: countTable =1000 - double precision , parameter :: odeToleranceAbsolute =1.0d-3, odeToleranceRelative =1.0d-3 - double precision , parameter :: fractionRadiusInitial =1.0d-6 - double precision , dimension(propertyCount+1) :: properties , propertyScales - double precision , dimension(countTable ) :: radiusTable , densityTable , & - & massTable - double precision , dimension(propertyCount ) :: locationMinimum - type (odeSolver ) :: odeSolver_ - type (multiDMinimizer ) :: minimizer_ - integer :: i , iteration - logical :: converged - double precision :: densityCentral , velocityDispersionCentral , & - & densityInteraction , massInteraction , & - & radiusInteraction , radius , & - & velocityDispersionInteraction , mass , & - & density - - ! Find the interaction radius. - radiusInteraction =self%radiusInteraction (node ) - ! Properties of the original density profile at the interaction radius. - densityInteraction =self%darkMatterProfile_%density (node,radiusInteraction) - massInteraction =self%darkMatterProfile_%enclosedMass (node,radiusInteraction) - ! Find the velocity dispersion scale. - velocityDispersionInteraction=sqrt(gravitationalConstantGalacticus*massInteraction/radiusInteraction) - ! Set ODE solver scales. - propertyScales =[velocityDispersionInteraction**2,velocityDispersionInteraction**2/radiusInteraction,massInteraction] - ! Construct an ODE solver. - odeSolver_ =odeSolver (propertyCount+1,sidmIsothermalODEs ,toleranceAbsolute=odeToleranceAbsolute,toleranceRelative=odeToleranceRelative,scale=propertyScales) - ! Construct a minimizer. - minimizer_ =multiDMinimizer(propertyCount ,sidmIsothermalFitMetric ) - ! Seek the solution. - call minimizer_%set(x=[0.0d0,1.0d0],stepSize=[1.0d0,1.0d0]) - iteration=0 - converged=.false. - do while (.not.converged .and. iteration < 100) - call minimizer_%iterate() - iteration=iteration+1 - converged=minimizer_%testSize(toleranceAbsolute=1.0d-3) - end do - locationMinimum =minimizer_%x() - densityCentral =exp(locationMinimum(1))*densityInteraction - velocityDispersionCentral= locationMinimum(2) *velocityDispersionInteraction - ! Tabulate solutions for density and mass. - radiusTable=Make_Range(rangeMinimum=0.0d0,rangeMaximum=radiusInteraction,rangeNumber=countTable,rangeType=rangeTypeLinear) - densityTable(1)=densityCentral - massTable (1)=0.0d0 - do i=2,countTable - radius =fractionRadiusInitial*radiusInteraction - properties=0.0d0 - call odeSolver_%solve(radius,radiusTable(i),properties) - densityTable(i)=+densityCentral & - & *exp( & - & -properties(1) & - & /velocityDispersionCentral**2 & - & ) - massTable (i)=+ properties(3) - end do - allocate(self%densityProfile) - allocate(self% massProfile) - self% densityProfile=interpolator(radiusTable, densityTable) - self% massProfile=interpolator(radiusTable, massTable) - self%velocityDispersionCentral= velocityDispersionCentral - return - - contains - - double precision function sidmIsothermalFitMetric(propertiesCentral) - !!{ - Evaluate the fit metric. - !!} - implicit none - double precision, intent(in ), dimension(:) :: propertiesCentral - double precision , dimension(propertyCount+1) :: properties - double precision :: radius - - ! Extract current parameters. - densityCentral =exp(propertiesCentral(1))*densityInteraction - velocityDispersionCentral= propertiesCentral(2) *velocityDispersionInteraction - ! Solve the ODE to r₁. - radius =fractionRadiusInitial*radiusInteraction - properties=0.0d0 - call odeSolver_%solve(radius,radiusInteraction,properties) - ! Extract density and mass at r₁. - density=+densityCentral & - & *exp( & - & -properties(1) & - & /velocityDispersionCentral**2 & - & ) - mass =+ properties(3) - ! Evaluate the fit metric. - sidmIsothermalFitMetric=+(density/densityInteraction-1.0d0)**2 & - & +( mass/ massInteraction-1.0d0)**2 - return - end function sidmIsothermalFitMetric - - integer function sidmIsothermalODEs(radius,properties,propertiesRateOfChange) - !!{ - Define the ODE system to solve for isothermal self-interacting dark matter cores. - !!} - use :: Functions_Global , only : galacticStructureDensity_ - use :: Galactic_Structure_Options , only : massTypeBaryonic - use :: Interface_GSL , only : GSL_Success - use :: Numerical_Constants_Math , only : Pi - use :: Numerical_Constants_Astronomical, only : gravitationalConstantGalacticus - implicit none - double precision, intent(in ) :: radius - double precision, intent(in ), dimension(:) :: properties - double precision, intent( out), dimension(:) :: propertiesRateOfChange - double precision :: densityDarkMatter , densityBaryons - - densityDarkMatter =+densityCentral & - & *exp( & - & -max(properties(1),0.0d0) & - & /velocityDispersionCentral**2 & - & ) - densityBaryons =+galacticStructureDensity_(self%galacticStructure_,node,position=[radius,0.0d0,0.0d0],massType=massTypeBaryonic) - propertiesRateOfChange (1)=+properties(2) - propertiesRateOfChange (2)=+4.0d0 & - & *Pi & - & *gravitationalConstantGalacticus & - & *( & - & +densityDarkMatter & - & +densityBaryons & - & ) - if (radius > 0.0d0) & - & propertiesRateOfChange(2)=+propertiesRateOfChange(2) & - & -2.0d0 & - & *properties (2) & - & /radius - propertiesRateOfChange (3)=+4.0d0 & - & *Pi & - & *radius**2 & - & *densityDarkMatter - sidmIsothermalODEs = GSL_Success - return - end function sidmIsothermalODEs - - end subroutine sidmIsothermalComputeSolution - - double precision function sidmIsothermalDensity(self,node,radius) - !!{ - Returns the density (in $M_\odot$ Mpc$^{-3}$) in the dark matter profile of {\normalfont \ttfamily node} at the given - {\normalfont \ttfamily radius} (given in units of Mpc). - !!} - implicit none - class (darkMatterProfileSIDMIsothermal), intent(inout) :: self - type (treeNode ), intent(inout) :: node - double precision , intent(in ) :: radius - - if (radius > self%radiusInteraction(node)) then - sidmIsothermalDensity=self%darkMatterProfile_%density(node,radius) - else - if (node%uniqueID() /= self%uniqueIDPrevious) call self%calculationReset(node,node%uniqueID()) - if (self%velocityDispersionCentral <= 0.0d0 ) call self%computeSolution (node ) - sidmIsothermalDensity=self%densityProfile%interpolate(radius) - end if - return - end function sidmIsothermalDensity - - double precision function sidmIsothermalDensityLogSlope(self,node,radius) - !!{ - Returns the logarithmic slope of the density in the dark matter profile of {\normalfont \ttfamily node} at the given - {\normalfont \ttfamily radius} (given in units of Mpc). - !!} - implicit none - class (darkMatterProfileSIDMIsothermal), intent(inout) :: self - type (treeNode ), intent(inout) :: node - double precision , intent(in ) :: radius - - if (radius > self%radiusInteraction(node)) then - sidmIsothermalDensityLogSlope=self%darkMatterProfile_%densityLogSlope(node,radius) - else - if (node%uniqueID() /= self%uniqueIDPrevious) call self%calculationReset(node,node%uniqueID()) - if (self%velocityDispersionCentral <= 0.0d0 ) call self%computeSolution (node ) - sidmIsothermalDensityLogSlope=self%densityProfile%derivative(radius)*radius/self%densityProfile%interpolate(radius) - end if - return - end function sidmIsothermalDensityLogSlope - - double precision function sidmIsothermalEnclosedMass(self,node,radius) - !!{ - Returns the enclosed mass (in $M_\odot$) in the dark matter profile of {\normalfont \ttfamily node} at the given {\normalfont \ttfamily radius} (given in - units of Mpc). - !!} - implicit none - class (darkMatterProfileSIDMIsothermal), intent(inout) :: self - type (treeNode ), intent(inout) :: node - double precision , intent(in ) :: radius - - if (radius > self%radiusInteraction(node)) then - sidmIsothermalEnclosedMass=self%darkMatterProfile_%enclosedMass(node,radius) - else - if (node%uniqueID() /= self%uniqueIDPrevious) call self%calculationReset(node,node%uniqueID()) - if (self%velocityDispersionCentral <= 0.0d0 ) call self%computeSolution (node ) - sidmIsothermalEnclosedMass=self%massProfile%interpolate(radius) - end if - return - end function sidmIsothermalEnclosedMass - - double precision function sidmIsothermalRadiusEnclosingDensity(self,node,density) - !!{ - Returns the radius (in Mpc) in the dark matter profile of {\normalfont \ttfamily node} which encloses the given - {\normalfont \ttfamily density} (given in units of $M_\odot/$Mpc$^{-3}$). - !!} - implicit none - class (darkMatterProfileSIDMIsothermal), intent(inout), target :: self - type (treeNode ), intent(inout), target :: node - double precision , intent(in ) :: density - - sidmIsothermalRadiusEnclosingDensity=self%radiusEnclosingDensityNumerical(node,density) - return - end function sidmIsothermalRadiusEnclosingDensity - - double precision function sidmIsothermalRadiusEnclosingMass(self,node,mass) - !!{ - Returns the radius (in Mpc) in the dark matter profile of {\normalfont \ttfamily node} which encloses the given - {\normalfont \ttfamily mass} (given in units of $M_\odot$). - !!} - implicit none - class (darkMatterProfileSIDMIsothermal), intent(inout), target :: self - type (treeNode ), intent(inout), target :: node - double precision , intent(in ) :: mass - - sidmIsothermalRadiusEnclosingMass=self%radiusEnclosingMassNumerical(node,mass) - return - end function sidmIsothermalRadiusEnclosingMass - - double precision function sidmIsothermalRadialMoment(self,node,moment,radiusMinimum,radiusMaximum) - !!{ - Returns the density (in $M_\odot$ Mpc$^{-3}$) in the dark matter profile of {\normalfont \ttfamily node} at the given - {\normalfont \ttfamily radius} (given in units of Mpc). - !!} - implicit none - class (darkMatterProfileSIDMIsothermal), intent(inout) :: self - type (treeNode ), intent(inout) :: node - double precision , intent(in ) :: moment - double precision , intent(in ), optional :: radiusMinimum, radiusMaximum - - sidmIsothermalRadialMoment=self%radialMomentNumerical(node,moment,radiusMinimum,radiusMaximum) - return - end function sidmIsothermalRadialMoment - - double precision function sidmIsothermalPotential(self,node,radius,status) - !!{ - Returns the potential (in (km/s)$^2$) in the dark matter profile of {\normalfont \ttfamily node} at the given {\normalfont - \ttfamily radius} (given in units of Mpc). - !!} - implicit none - class (darkMatterProfileSIDMIsothermal ), intent(inout), target :: self - type (treeNode ), intent(inout), target :: node - double precision , intent(in ) :: radius - type (enumerationStructureErrorCodeType), intent( out), optional :: status - - if (radius > self%radiusInteraction(node)) then - sidmIsothermalPotential=self%darkMatterProfile_%potential(node,radius) - else - if (node%uniqueID() /= self%uniqueIDPrevious) call self%calculationReset(node,node%uniqueID()) - if (self%velocityDispersionCentral <= 0.0d0 ) call self%computeSolution (node ) - sidmIsothermalPotential=self%darkMatterProfile_%potential(node,self%radiusInteraction(node))-self%velocityDispersionCentral**2*log(self%densityProfile%interpolate(radius)/self%densityProfile%interpolate(self%radiusInteraction(node))) - end if - return - end function sidmIsothermalPotential - - double precision function sidmIsothermalCircularVelocity(self,node,radius) - !!{ - Returns the circular velocity (in km/s) in the dark matter profile of {\normalfont \ttfamily node} at the given - {\normalfont \ttfamily radius} (given in units of Mpc). - !!} - implicit none - class (darkMatterProfileSIDMIsothermal), intent(inout) :: self - type (treeNode ), intent(inout) :: node - double precision , intent(in ) :: radius - - sidmIsothermalCircularVelocity=self%circularVelocityNumerical(node,radius) - return - end function sidmIsothermalCircularVelocity - - double precision function sidmIsothermalCircularVelocityMaximum(self,node) - !!{ - Returns the maximum circular velocity (in km/s) in the dark matter profile of {\normalfont \ttfamily node}. - !!} - implicit none - class(darkMatterProfileSIDMIsothermal), intent(inout) :: self - type (treeNode ), intent(inout) :: node - - sidmIsothermalCircularVelocityMaximum=self%circularVelocityMaximumNumerical(node) - return - end function sidmIsothermalCircularVelocityMaximum - - double precision function sidmIsothermalRadialVelocityDispersion(self,node,radius) - !!{ - Returns the radial velocity dispersion (in km/s) in the dark matter profile of {\normalfont \ttfamily node} at the given - {\normalfont \ttfamily radius} (given in units of Mpc). - !!} - implicit none - class (darkMatterProfileSIDMIsothermal), intent(inout) :: self - type (treeNode ), intent(inout) :: node - double precision , intent(in ) :: radius - - if (radius > self%radiusInteraction(node)) then - sidmIsothermalRadialVelocityDispersion=self%darkMatterProfile_%radialVelocityDispersion(node,radius) - else - if (node%uniqueID() /= self%uniqueIDPrevious) call self%calculationReset(node,node%uniqueID()) - if (self%velocityDispersionCentral <= 0.0d0 ) call self%computeSolution (node ) - sidmIsothermalRadialVelocityDispersion=self%velocityDispersionCentral - end if - return - end function sidmIsothermalRadialVelocityDispersion - - double precision function sidmIsothermalRadiusFromSpecificAngularMomentum(self,node,specificAngularMomentum) - !!{ - Returns the radius (in Mpc) in {\normalfont \ttfamily node} at which a circular orbit has the given {\normalfont \ttfamily specificAngularMomentum} (given - in units of km s$^{-1}$ Mpc). - !!} - implicit none - class (darkMatterProfileSIDMIsothermal), intent(inout), target :: self - type (treeNode ), intent(inout) :: node - double precision , intent(in ) :: specificAngularMomentum - - sidmIsothermalRadiusFromSpecificAngularMomentum=self%radiusFromSpecificAngularMomentumNumerical(node,specificAngularMomentum) - return - end function sidmIsothermalRadiusFromSpecificAngularMomentum - - double precision function sidmIsothermalRotationNormalization(self,node) - !!{ - Return the normalization of the rotation velocity vs. specific angular momentum relation. - !!} - implicit none - class(darkMatterProfileSIDMIsothermal), intent(inout) :: self - type (treeNode ), intent(inout) :: node - - sidmIsothermalRotationNormalization=self%rotationNormalizationNumerical(node) - return - end function sidmIsothermalRotationNormalization - - double precision function sidmIsothermalEnergy(self,node) - !!{ - Return the energy of a sidmIsothermal halo density profile. - !!} - implicit none - class(darkMatterProfileSIDMIsothermal), intent(inout) :: self - type (treeNode ), intent(inout) :: node - - sidmIsothermalEnergy=self%energyNumerical(node) - return - end function sidmIsothermalEnergy - - double precision function sidmIsothermalKSpace(self,node,waveNumber) - !!{ - Returns the Fourier transform of the sidmIsothermal density profile at the specified {\normalfont \ttfamily waveNumber} - (given in Mpc$^{-1}$). - !!} - implicit none - class (darkMatterProfileSIDMIsothermal), intent(inout) :: self - type (treeNode ), intent(inout), target :: node - double precision , intent(in ) :: waveNumber - - sidmIsothermalKSpace=self%kSpaceNumerical(node,waveNumber) - return - end function sidmIsothermalKSpace - - double precision function sidmIsothermalFreefallRadius(self,node,time) - !!{ - Returns the freefall radius in the sidmIsothermal density profile at the specified {\normalfont \ttfamily time} (given in - Gyr). - !!} - implicit none - class (darkMatterProfileSIDMIsothermal), intent(inout) :: self - type (treeNode ), intent(inout) :: node - double precision , intent(in ) :: time + function sidmIsothermalGet(self,node,weightBy,weightIndex) result(massDistribution_) + !!{ + Return the dark matter mass distribution for the given {\normalfont \ttfamily node}. + !!} + use :: Galacticus_Nodes , only : nodeComponentBasic + use :: Galactic_Structure_Options, only : componentTypeDarkHalo , massTypeDark , weightByMass + use :: Mass_Distributions , only : massDistributionSphericalSIDMIsothermalBaryons, kinematicsDistributionSIDMIsothermal, nonAnalyticSolversNumerical, massDistributionSpherical, & + & sphericalSIDMIsothermalBaryonsInitializor + implicit none + class (massDistributionClass ), pointer :: massDistribution_ + type (kinematicsDistributionSIDMIsothermal ), pointer :: kinematicsDistribution_ + class (darkMatterProfileSIDMIsothermal ), intent(inout), target :: self + type (treeNode ), intent(inout), target :: node + type (enumerationWeightByType ), intent(in ), optional :: weightBy + integer , intent(in ), optional :: weightIndex + class (massDistributionClass ), pointer :: massDistributionBaryonic, massDistributionDecorated + class (nodeComponentBasic ), pointer :: basic + procedure(sphericalSIDMIsothermalBaryonsInitializor), pointer :: initializationFunction + class (* ), pointer :: initializationSelf , initializationArgument + !![ + + !!] - sidmIsothermalFreefallRadius=self%freefallRadiusNumerical(node,time) + ! Assume a null distribution by default. + massDistribution_ => null() + ! If weighting is not by mass, return a null profile. + if (weightBy_ /= weightByMass) return + ! Create the mass distribution. + allocate(massDistributionSphericalSIDMIsothermalBaryons :: massDistribution_) + select type(massDistribution_) + type is (massDistributionSphericalSIDMIsothermalBaryons) + massDistributionDecorated => self%darkMatterProfile_%get (node,weightBy,weightIndex) + massDistributionBaryonic => null ( ) + basic => node %basic( ) + select type (massDistributionDecorated) + class is (massDistributionSpherical) + initializationFunction => sidmIsothermalInitialize + initializationSelf => self + initializationArgument => node + !![ + + + massDistributionSphericalSIDMIsothermalBaryons( & + & timeAge =basic%time (), & + & nonAnalyticSolver = nonAnalyticSolversNumerical , & + & massDistribution_ = massDistributionDecorated , & + & massDistributionBaryonic= massDistributionBaryonic , & + & darkMatterParticle_ =self %darkMatterParticle_ , & + & initializationFunction = initializationFunction , & + & initializationSelf = initializationSelf , & + & initializationArgument = initializationArgument , & + & componentType = componentTypeDarkHalo , & + & massType = massTypeDark & + & ) + + + !!] + class default + call Error_Report('expected a spherical mass distribution'//{introspection:location}) + end select + !![ + + !!] + end select + allocate(kinematicsDistribution_) + !![ + + + kinematicsDistributionSIDMIsothermal( & + & ) + + + !!] + call massDistribution_%setKinematicsDistribution(kinematicsDistribution_) + !![ + + !!] return - end function sidmIsothermalFreefallRadius + end function sidmIsothermalGet - double precision function sidmIsothermalFreefallRadiusIncreaseRate(self,node,time) + subroutine sidmIsothermalInitialize(self,node,massDistributionBaryonic) !!{ - Returns the rate of increase of the freefall radius in the sidmIsothermal density profile at the specified {\normalfont - \ttfamily time} (given in Gyr). + Initialize the dark matter mass distribution for the given {\normalfont \ttfamily node}. !!} + use :: Galactic_Structure_Options, only : massTypeBaryonic implicit none - class (darkMatterProfileSIDMIsothermal), intent(inout) :: self - type (treeNode ), intent(inout) :: node - double precision , intent(in ) :: time + class(* ), intent(inout), target :: self , node + class(massDistributionClass), intent( out), pointer :: massDistributionBaryonic - sidmIsothermalFreefallRadiusIncreaseRate=self%freefallRadiusIncreaseRateNumerical(node,time) + select type (self) + type is (darkMatterProfileSIDMIsothermal) + select type (node) + type is (treeNode) + massDistributionBaryonic => node%massDistribution(massType=massTypeBaryonic) + class default + call Error_Report('unexpected class'//{introspection:location}) + end select + class default + call Error_Report('unexpected class'//{introspection:location}) + end select return - end function sidmIsothermalFreefallRadiusIncreaseRate + end subroutine sidmIsothermalInitialize diff --git a/source/dark_matter_profiles.accelerator.F90 b/source/dark_matter_profiles.accelerator.F90 index 4b1a19a3f5..b8509c42aa 100644 --- a/source/dark_matter_profiles.accelerator.F90 +++ b/source/dark_matter_profiles.accelerator.F90 @@ -20,10 +20,7 @@ !!{ An accelerator class for non-dark-matter-only dark matter halo profiles. !!} - - use :: Binary_Search_Trees, only : binaryTree - use :: Kind_Numbers , only : kind_int8 - + !![ An accelerator class for non-dark-matter-only dark matter halo profiles. @@ -34,37 +31,11 @@ An accelerator class for non-dark-matter-only dark matter halo profiles. !!} private - integer (kind_int8 ), dimension(2) :: uniqueIDPrevious - class (darkMatterProfileClass), pointer :: darkMatterProfile_ => null() - type (binaryTree ), dimension(2) :: treeMassEnclosed - integer :: treePrevious - double precision :: toleranceRelative , factorRadiusMaximum, & - & factorRadiusLogarithmicMaximum + class (darkMatterProfileClass), pointer :: darkMatterProfile_ => null() + double precision :: toleranceRelative , factorRadiusMaximum contains - !![ - - - - !!] - final :: acceleratorDestructor - procedure :: autoHook => acceleratorAutoHook - procedure :: calculationReset => acceleratorCalculationReset - procedure :: density => acceleratorDensity - procedure :: densityLogSlope => acceleratorDensityLogSlope - procedure :: radiusEnclosingDensity => acceleratorRadiusEnclosingDensity - procedure :: radiusEnclosingMass => acceleratorRadiusEnclosingMass - procedure :: radialMoment => acceleratorRadialMoment - procedure :: enclosedMass => acceleratorEnclosedMass - procedure :: potential => acceleratorPotential - procedure :: circularVelocity => acceleratorCircularVelocity - procedure :: circularVelocityMaximum => acceleratorCircularVelocityMaximum - procedure :: radialVelocityDispersion => acceleratorRadialVelocityDispersion - procedure :: radiusFromSpecificAngularMomentum => acceleratorRadiusFromSpecificAngularMomentum - procedure :: rotationNormalization => acceleratorRotationNormalization - procedure :: energy => acceleratorEnergy - procedure :: kSpace => acceleratorKSpace - procedure :: freefallRadius => acceleratorFreefallRadius - procedure :: freefallRadiusIncreaseRate => acceleratorFreefallRadiusIncreaseRate + final :: acceleratorDestructor + procedure :: get => acceleratorGet end type darkMatterProfileAccelerator interface darkMatterProfileAccelerator @@ -87,8 +58,7 @@ function acceleratorConstructorParameters(parameters) result(self) type (darkMatterProfileAccelerator) :: self type (inputParameters ), intent(inout) :: parameters class (darkMatterProfileClass ), pointer :: darkMatterProfile_ - class (darkMatterHaloScaleClass ), pointer :: darkMatterHaloScale_ - double precision :: toleranceRelative , factorRadiusMaximum + double precision :: toleranceRelative , factorRadiusMaximum !![ @@ -103,358 +73,104 @@ function acceleratorConstructorParameters(parameters) result(self) parameters The maximum factor by which to interpolate in radius. - - + !!] - self=darkMatterProfileAccelerator(toleranceRelative,factorRadiusMaximum,darkMatterHaloScale_,darkMatterProfile_) + self=darkMatterProfileAccelerator(toleranceRelative,factorRadiusMaximum,darkMatterProfile_) !![ - - + !!] return end function acceleratorConstructorParameters - function acceleratorConstructorInternal(toleranceRelative,factorRadiusMaximum,darkMatterHaloScale_,darkMatterProfile_) result(self) + function acceleratorConstructorInternal(toleranceRelative,factorRadiusMaximum,darkMatterProfile_) result(self) !!{ Generic constructor for the {\normalfont \ttfamily accelerator} dark matter profile class. !!} implicit none type (darkMatterProfileAccelerator) :: self class (darkMatterProfileClass ), intent(in ), target :: darkMatterProfile_ - class (darkMatterHaloScaleClass ), intent(in ), target :: darkMatterHaloScale_ - double precision , intent(in) :: toleranceRelative , factorRadiusMaximum + double precision , intent(in) :: toleranceRelative , factorRadiusMaximum !![ - + !!] - self%uniqueIDPrevious =-1_kind_int8 - self%treePrevious =+1 - self%factorRadiusLogarithmicMaximum=+log(sqrt(factorRadiusMaximum)) return end function acceleratorConstructorInternal - subroutine acceleratorAutoHook(self) - !!{ - Attach to the calculation reset event. - !!} - use :: Events_Hooks, only : calculationResetEvent, openMPThreadBindingAllLevels - implicit none - class(darkMatterProfileAccelerator), intent(inout) :: self - - call calculationResetEvent%attach(self,acceleratorCalculationReset,openMPThreadBindingAllLevels,label='darkMatterProfileAccelerator') - return - end subroutine acceleratorAutoHook - subroutine acceleratorDestructor(self) !!{ Destructor for the {\normalfont \ttfamily accelerator} dark matter halo profile class. !!} - use :: Events_Hooks, only : calculationResetEvent implicit none type(darkMatterProfileAccelerator), intent(inout) :: self !![ - - + !!] - if (calculationResetEvent%isAttached(self,acceleratorCalculationReset)) call calculationResetEvent%detach(self,acceleratorCalculationReset) return end subroutine acceleratorDestructor - subroutine acceleratorCalculationReset(self,node,uniqueID) - !!{ - Reset the dark matter profile calculation. - !!} - use :: Kind_Numbers, only : kind_int8 - implicit none - class (darkMatterProfileAccelerator), intent(inout) :: self - type (treeNode ), intent(inout) :: node - integer(kind_int8 ), intent(in ) :: uniqueID - integer :: i - !$GLC attributes unused :: node - - ! Trees are maintained for two nodes - this is often advantageous as queries are often made for satellite and host nodes - ! together. If the current node is one for which we currently have a tree, invalidate that tree. Otherwise, if the current - ! node is a satellite in the current host node, place it into the second tree, otherwise, into the first. - if (uniqueID == self%uniqueIDPrevious(1)) then - i =+1 - else if (uniqueID == self%uniqueIDPrevious(2)) then - i =+2 - else - if (node%isSatellite() .and. node%parent%uniqueID() == self%uniqueIDPrevious(1)) then - i=2 - else - i=1 - end if - end if - self%uniqueIDPrevious(i)=uniqueID - self%treePrevious =i - if (associated(self%treeMassEnclosed(i)%root)) deallocate(self%treeMassEnclosed(i)%root) - return - end subroutine acceleratorCalculationReset - - double precision function acceleratorDensity(self,node,radius) - !!{ - Returns the density (in $M_\odot$ Mpc$^{-3}$) in the dark matter profile of {\normalfont \ttfamily node} at the given - {\normalfont \ttfamily radius} (given in units of Mpc). - !!} - implicit none - class (darkMatterProfileAccelerator), intent(inout) :: self - type (treeNode ), intent(inout) :: node - double precision , intent(in ) :: radius - - acceleratorDensity=self%darkMatterProfile_%density(node,radius) - return - end function acceleratorDensity - - double precision function acceleratorDensityLogSlope(self,node,radius) - !!{ - Returns the logarithmic slope of the density in the dark matter profile of {\normalfont \ttfamily node} at the given - {\normalfont \ttfamily radius} (given in units of Mpc). - !!} - implicit none - class (darkMatterProfileAccelerator), intent(inout) :: self - type (treeNode ), intent(inout) :: node - double precision , intent(in ) :: radius - - acceleratorDensityLogSlope=self%darkMatterProfile_%densityLogSlope(node,radius) - return - end function acceleratorDensityLogSlope - - double precision function acceleratorRadiusEnclosingDensity(self,node,density) - !!{ - Returns the radius (in Mpc) in the dark matter profile of {\normalfont \ttfamily node} which encloses the given - {\normalfont \ttfamily density} (given in units of $M_\odot/$Mpc$^{-3}$). - !!} - implicit none - class (darkMatterProfileAccelerator), intent(inout), target :: self - type (treeNode ), intent(inout), target :: node - double precision , intent(in ) :: density - - acceleratorRadiusEnclosingDensity=self%darkMatterProfile_%radiusEnclosingDensity(node,density) - return - end function acceleratorRadiusEnclosingDensity - - double precision function acceleratorRadiusEnclosingMass(self,node,mass) - !!{ - Returns the radius (in Mpc) in the dark matter profile of {\normalfont \ttfamily node} which encloses the given - {\normalfont \ttfamily mass} (given in units of $M_\odot$). - !!} - implicit none - class (darkMatterProfileAccelerator), intent(inout), target :: self - type (treeNode ), intent(inout), target :: node - double precision , intent(in ) :: mass - - acceleratorRadiusEnclosingMass=self%darkMatterProfile_%radiusEnclosingMass(node,mass) - return - end function acceleratorRadiusEnclosingMass - - double precision function acceleratorRadialMoment(self,node,moment,radiusMinimum,radiusMaximum) - !!{ - Returns the density (in $M_\odot$ Mpc$^{-3}$) in the dark matter profile of {\normalfont \ttfamily node} at the given - {\normalfont \ttfamily radius} (given in units of Mpc). - !!} - implicit none - class (darkMatterProfileAccelerator), intent(inout) :: self - type (treeNode ), intent(inout) :: node - double precision , intent(in ) :: moment - double precision , intent(in ), optional :: radiusMinimum, radiusMaximum - - acceleratorRadialMoment=self%darkMatterProfile_%radialMoment(node,moment,radiusMinimum,radiusMaximum) - return - end function acceleratorRadialMoment - - double precision function acceleratorEnclosedMass(self,node,radius) - !!{ - Returns the enclosed mass (in $M_\odot$) in the dark matter profile of {\normalfont \ttfamily node} at the given {\normalfont \ttfamily radius} (given in - units of Mpc). - !!} - use :: Binary_Search_Trees, only : binaryTreeNode - use :: Numerical_Comparison, only : Values_Agree - implicit none - class (darkMatterProfileAccelerator), intent(inout) :: self - type (treeNode ), intent(inout) :: node - double precision , intent(in ) :: radius - type (binaryTreeNode ), pointer :: left1 , left2 , & - & right1 , right2 - double precision :: massEnclosed1 , massEnclosed2, & - & radiusLogarithmic - logical :: found - integer :: i - - if (node%uniqueID() == self%uniqueIDPrevious(1)) then - i=1 - else if (node%uniqueID() == self%uniqueIDPrevious(2)) then - i=2 - else - call self%calculationReset(node,node%uniqueID()) - i=self%treePrevious - end if - found=.false. - radiusLogarithmic=log(radius) - call self%treeMassEnclosed(i)%bracket(radiusLogarithmic,left1,right1) - if (associated(left1).and.associated(right1)) then - if (associated(left1,right1)) then - acceleratorEnclosedMass=exp(left1%value) - found =.true. - else - if ( & - & +radiusLogarithmic- left1%key < self%factorRadiusLogarithmicMaximum & - & .and. & - & -radiusLogarithmic+right1%key < self%factorRadiusLogarithmicMaximum & - & ) then - left2 => left1%predecessor() - right2 => right1% successor() - if (associated(left2).and.associated(right2)) then - massEnclosed1=(radiusLogarithmic-left1%key)*(right1%value-left1%value)/(right1%key-left1%key)+left1%value - massEnclosed2=(radiusLogarithmic-left2%key)*(right2%value-left2%value)/(right2%key-left2%key)+left2%value - if (Values_Agree(massEnclosed1,massEnclosed2,relTol=self%toleranceRelative)) then - acceleratorEnclosedMass=exp(massEnclosed1) - found =.true. - end if - end if - end if - end if - end if - if (.not.found) then - acceleratorEnclosedMass=self%darkMatterProfile_%enclosedMass(node,radius) - call self%treeMassEnclosed(i)%insert(radiusLogarithmic,log(acceleratorEnclosedMass)) - end if - return - end function acceleratorEnclosedMass - - double precision function acceleratorPotential(self,node,radius,status) - !!{ - Returns the potential (in (km/s)$^2$) in the dark matter profile of {\normalfont \ttfamily node} at the given {\normalfont - \ttfamily radius} (given in units of Mpc). - !!} - implicit none - class (darkMatterProfileAccelerator ), intent(inout), target :: self - type (treeNode ), intent(inout), target :: node - double precision , intent(in ) :: radius - type (enumerationStructureErrorCodeType), intent( out), optional :: status - - acceleratorPotential=self%darkMatterProfile_%potential(node,radius,status) - return - end function acceleratorPotential - - double precision function acceleratorCircularVelocity(self,node,radius) + function acceleratorGet(self,node,weightBy,weightIndex) result(massDistribution_) !!{ - Returns the circular velocity (in km/s) in the dark matter profile of {\normalfont \ttfamily node} at the given - {\normalfont \ttfamily radius} (given in units of Mpc). + Return the dark matter mass distribution for the given {\normalfont \ttfamily node}. !!} + use :: Galactic_Structure_Options, only : componentTypeDarkHalo , massTypeDark , weightByMass + use :: Mass_Distributions , only : massDistributionSphericalAccelerator, kinematicsDistributionCollisionless, massDistributionSpherical, nonAnalyticSolversNumerical implicit none - class (darkMatterProfileAccelerator), intent(inout) :: self - type (treeNode ), intent(inout) :: node - double precision , intent(in ) :: radius - - acceleratorCircularVelocity=self%darkMatterProfile_%circularVelocity(node,radius) - return - end function acceleratorCircularVelocity - - double precision function acceleratorCircularVelocityMaximum(self,node) - !!{ - Returns the maximum circular velocity (in km/s) in the dark matter profile of {\normalfont \ttfamily node}. - !!} - implicit none - class(darkMatterProfileAccelerator), intent(inout) :: self - type (treeNode ), intent(inout) :: node - - acceleratorCircularVelocityMaximum=self%darkMatterProfile_%circularVelocityMaximum(node) - return - end function acceleratorCircularVelocityMaximum - - double precision function acceleratorRadialVelocityDispersion(self,node,radius) - !!{ - Returns the radial velocity dispersion (in km/s) in the dark matter profile of {\normalfont \ttfamily node} at the given - {\normalfont \ttfamily radius} (given in units of Mpc). - !!} - implicit none - class (darkMatterProfileAccelerator), intent(inout) :: self - type (treeNode ), intent(inout) :: node - double precision , intent(in ) :: radius - - acceleratorRadialVelocityDispersion=self%darkMatterProfile_%radialVelocityDispersion(node,radius) - return - end function acceleratorRadialVelocityDispersion - - double precision function acceleratorRadiusFromSpecificAngularMomentum(self,node,specificAngularMomentum) - !!{ - Returns the radius (in Mpc) in {\normalfont \ttfamily node} at which a circular orbit has the given {\normalfont \ttfamily specificAngularMomentum} (given - in units of km s$^{-1}$ Mpc). - !!} - implicit none - class (darkMatterProfileAccelerator), intent(inout), target :: self - type (treeNode ), intent(inout) :: node - double precision , intent(in ) :: specificAngularMomentum - - acceleratorRadiusFromSpecificAngularMomentum=self%darkMatterProfile_%radiusFromSpecificAngularMomentum(node,specificAngularMomentum) - return - end function acceleratorRadiusFromSpecificAngularMomentum - - double precision function acceleratorRotationNormalization(self,node) - !!{ - Return the normalization of the rotation velocity vs. specific angular momentum relation. - !!} - implicit none - class(darkMatterProfileAccelerator), intent(inout) :: self - type (treeNode ), intent(inout) :: node - - acceleratorRotationNormalization=self%darkMatterProfile_%rotationNormalization(node) - return - end function acceleratorRotationNormalization - - double precision function acceleratorEnergy(self,node) - !!{ - Return the energy of the dark matter halo density profile. - !!} - implicit none - class(darkMatterProfileAccelerator), intent(inout) :: self - type (treeNode ), intent(inout) :: node - - acceleratorEnergy=self%darkMatterProfile_%energy(node) - return - end function acceleratorEnergy - - double precision function acceleratorKSpace(self,node,waveNumber) - !!{ - Returns the Fourier transform of the dark matter halo density profile at the specified {\normalfont \ttfamily waveNumber} - (given in Mpc$^{-1}$). - !!} - implicit none - class (darkMatterProfileAccelerator), intent(inout) :: self - type (treeNode ), intent(inout), target :: node - double precision , intent(in ) :: waveNumber - - acceleratorKSpace=+self%darkMatterProfile_%kSpace(node,waveNumber) - return - end function acceleratorKSpace - - double precision function acceleratorFreefallRadius(self,node,time) - !!{ - Returns the freefall radius in the dark matter halo density profile at the specified {\normalfont \ttfamily time} (given in - Gyr). - !!} - implicit none - class (darkMatterProfileAccelerator), intent(inout) :: self - type (treeNode ), intent(inout) :: node - double precision , intent(in ) :: time - - acceleratorFreefallRadius=self%darkMatterProfile_%freefallRadius(node,time) - return - end function acceleratorFreefallRadius - - double precision function acceleratorFreefallRadiusIncreaseRate(self,node,time) - !!{ - Returns the freefall radius in the dark matter halo density profile at the specified {\normalfont \ttfamily time} (given in - Gyr). - !!} - implicit none - class (darkMatterProfileAccelerator), intent(inout) :: self - type (treeNode ), intent(inout) :: node - double precision , intent(in ) :: time + class (massDistributionClass ), pointer :: massDistribution_ + type (kinematicsDistributionCollisionless), pointer :: kinematicsDistribution_ + class (darkMatterProfileAccelerator ), intent(inout), target :: self + type (treeNode ), intent(inout), target :: node + type (enumerationWeightByType ), intent(in ), optional :: weightBy + integer , intent(in ), optional :: weightIndex + class (massDistributionClass ), pointer :: massDistributionDecorated + !![ + + !!] - acceleratorFreefallRadiusIncreaseRate=self%darkMatterProfile_%freefallRadiusIncreaseRate(node,time) + ! Assume a null distribution by default. + massDistribution_ => null() + ! If weighting is not by mass, return a null profile. + if (weightBy_ /= weightByMass) return + ! Create the mass distribution. + allocate(massDistributionSphericalAccelerator :: massDistribution_) + select type(massDistribution_) + type is (massDistributionSphericalAccelerator) + massDistributionDecorated => self%darkMatterProfile_%get(node,weightBy,weightIndex) + select type (massDistributionDecorated) + class is (massDistributionSpherical) + !![ + + + massDistributionSphericalAccelerator( & + & toleranceRelative =self%toleranceRelative , & + & factorRadiusMaximum=self%factorRadiusMaximum , & + & massDistribution_ = massDistributionDecorated , & + & nonAnalyticSolver = nonAnalyticSolversNumerical, & + & componentType = componentTypeDarkHalo , & + & massType = massTypeDark & + & ) + + + + !!] + class default + call Error_Report('expected a spherical mass distribution'//{introspection:location}) + end select + end select + allocate(kinematicsDistribution_) + !![ + + + kinematicsDistributionCollisionless( & + & ) + + + !!] + call massDistribution_%setKinematicsDistribution(kinematicsDistribution_) + !![ + + !!] return - end function acceleratorFreefallRadiusIncreaseRate + end function acceleratorGet diff --git a/source/dark_matter_profiles.adiabatic_Gnedin2004.F90 b/source/dark_matter_profiles.adiabatic_Gnedin2004.F90 index acf6d2d81d..22ed9c9985 100644 --- a/source/dark_matter_profiles.adiabatic_Gnedin2004.F90 +++ b/source/dark_matter_profiles.adiabatic_Gnedin2004.F90 @@ -21,79 +21,17 @@ An implementation of adiabaticGnedin2004 dark matter halo profiles. !!} - use :: Cosmology_Parameters , only : cosmologyParameters , cosmologyParametersClass - use :: Dark_Matter_Halo_Scales , only : darkMatterHaloScale , darkMatterHaloScaleClass - use :: Dark_Matter_Profiles_DMO , only : darkMatterProfileDMO , darkMatterProfileDMOClass - use :: Dark_Matter_Profiles_Generic, only : enumerationNonAnalyticSolversType, enumerationNonAnalyticSolversEncode, enumerationNonAnalyticSolversIsValid, nonAnalyticSolversFallThrough - use :: Galactic_Structure_Options , only : componentTypeAll , massTypeBaryonic , radiusLarge , weightByMass , & - & weightIndexNull , enumerationComponentTypeType , enumerationMassTypeType , enumerationWeightByType - use :: Math_Exponentiation , only : fastExponentiator - use :: Root_Finder , only : rootFinder + use :: Cosmology_Parameters , only : cosmologyParameters , cosmologyParametersClass + use :: Dark_Matter_Halo_Scales , only : darkMatterHaloScale , darkMatterHaloScaleClass + use :: Dark_Matter_Profiles_DMO , only : darkMatterProfileDMO , darkMatterProfileDMOClass + use :: Mass_Distributions , only : enumerationNonAnalyticSolversType - ! Number of previous radius solutions to store. !![ - - - - !!] - integer, parameter :: adiabaticGnedin2004StoreCount=10 - - !![ - + - A dark matter profile class which applies adiabatic contraction to the halo as it responds to the presence of - baryons. Adiabatic contraction follows the algorithm of \cite{gnedin_response_2004}. The parameters $A$ and $\omega$ of - that model are specified via input parameters {\normalfont \ttfamily A} and {\normalfont \ttfamily omega} respectively. - - Given the final radius, $r_\mathrm{f}$, the corresponding initial radius, $r_\mathrm{i}$, is found by solving: - \begin{equation} - f_\mathrm{i} M_\mathrm{total,0}(\bar{r}_\mathrm{i}) r_\mathrm{i} = f_\mathrm{f} M_\mathrm{total,0}(\bar{r}_\mathrm{i}) - r_\mathrm{f} + V^2_\mathrm{b}(\bar{r}_\mathrm{f}) \bar{r}_\mathrm{f} r_\mathrm{f}/ \mathrm{G}, - \label{eq:adiabaticContractionGnedinSolution} - \end{equation} - where $M_\mathrm{total,0}(r)$ is the initial total matter profile, $V_\mathrm{b}(r)$ is the baryonic contribution to the - rotation curve, $f_\mathrm{i}$, is the fraction of mass within the virial radius compared to the node mass\footnote{In - \protect\glc\ the ``node mass'' refers to the total mass of the node, assuming it has the universal complement of - baryons. Since some halos may contain less than the complete complement of baryons it is possible that $f_\mathrm{i}<1$.}, - $f_\mathrm{f}=(\Omega_\mathrm{M}-\Omega_\mathrm{b})/\Omega_\mathrm{M}+M_\mathrm{satellite, baryonic}/M_\mathrm{total}$, - $M_\mathrm{satellite, baryonic}$ is the baryonic mass in any satellite halos, $M_\mathrm{total}$ is the node mass, and - \begin{equation} - {\bar{r} \over r_0} = A \left({r \over r_0}\right)^\omega, - \label{eq:adiabaticContractionGnedinPowerLaw} - \end{equation} - where the pivot radius $r_0$ is set to $f_0 r_\mathrm{vir}$ where $f_0=${\normalfont \ttfamily [radiusFractionalPivot]}, and - $r_\mathrm{vir}$ is the virial radius. The original \cite{gnedin_response_2004} assumed $f_0=1$, but the revised model of - \cite{gnedin_halo_2011} found that $f_0=0.03$ lead to an improved model (less scatter in the best fit values of $(A,\omega)$ - when comparing to N-body simulations). - - Note that we explicitly assume that the initial, uncontracted total density profile has the same shape as the initial dark - matter density profile, that contraction of the halo occurs with no shell crossing, and that satellite halos trace the dark - matter profile of their host halo. The derivative, $\mathrm{d} r_\mathrm{f}/\mathrm{d}d_\mathrm{i}\equiv r^\prime_\mathrm{i}$ - is found by taking the derivative of eqn.~(\ref{eq:adiabaticContractionGnedinSolution}) to give: - \begin{eqnarray} - & & f_\mathrm{i} M_\mathrm{total,0}(\bar{r}_\mathrm{i}) r^\prime_\mathrm{i} + f_\mathrm{i} 4 \pi - \bar{r}_\mathrm{i}^2 \rho_\mathrm{total,0}(\bar{r}_\mathrm{i}) {\mathrm{d} \bar{r}_\mathrm{i}\over\mathrm{d} r_\mathrm{i}} - r_\mathrm{i} r^\prime_\mathrm{i} \nonumber \\ - & = & f_\mathrm{f} M_\mathrm{total,0}(\bar{r}_\mathrm{i}) + f_\mathrm{i} 4 \pi \bar{r}_\mathrm{i}^2 - \rho_\mathrm{total,0}(\bar{r}_\mathrm{i}) {\mathrm{d} \bar{r}_\mathrm{i}\over\mathrm{d} r_\mathrm{i}} r_\mathrm{f} - r^\prime_\mathrm{i} \nonumber \\ - & + & V^2_\mathrm{b}(\bar{r}_\mathrm{f}) \bar{r}_\mathrm{f} / \mathrm{G} + V^2_\mathrm{b}(\bar{r}_\mathrm{f}) - {\mathrm{d}\bar{r}_\mathrm{f}\over \mathrm{d} r_\mathrm{f}} r_\mathrm{f}/ \mathrm{G} + - {\mathrm{d}V^2_\mathrm{b}\over\mathrm{d} \bar{r}_\mathrm{f}}(\bar{r}_\mathrm{f}) {\mathrm{d}\bar{r}_\mathrm{f}\over - \mathrm{d} r_\mathrm{f}} \bar{r}_\mathrm{f} r_\mathrm{f}/ \mathrm{G}, - \end{eqnarray} - where - \begin{equation} - {\mathrm{d}\bar{r} \over \mathrm{d} r} = A \left({r \over r_0}\right)^{\omega-1}, - \end{equation} - and which can then be solved numerically for $r^\prime_\mathrm{i}$. + A dark matter profile class which builds \refClass{massDistributionSphericalAdiabaticGnedin2004} objects to apply adiabatic + contraction to other dark matter profiles. - - - - - - !!] type, extends(darkMatterProfileClass) :: darkMatterProfileAdiabaticGnedin2004 @@ -101,66 +39,16 @@ A dark matter halo profile class implementing adiabaticGnedin2004 dark matter halos. !!} private - logical :: isRecursive , parentDeferred - class (darkMatterProfileAdiabaticGnedin2004), pointer :: recursiveSelf => null() - class (cosmologyParametersClass ), pointer :: cosmologyParameters_ => null() - class (darkMatterProfileDMOClass ), pointer :: darkMatterProfileDMO_ => null() - class (* ), pointer :: galacticStructure_ => null() - type (enumerationNonAnalyticSolversType ) :: nonAnalyticSolver - type (rootFinder ) :: finder - ! Parameters of the adiabatic contraction algorithm. - double precision :: A , omega , & - & radiusFractionalPivot - ! Stored solutions for reuse. - integer (kind=kind_int8 ) :: lastUniqueID - integer :: radiusPreviousIndex , radiusPreviousIndexMaximum - double precision , dimension(adiabaticGnedin2004StoreCount) :: radiusPrevious , radiusInitialPrevious - type (fastExponentiator ) :: radiusExponentiator - ! Quantities used in solving the initial radius root function. - double precision :: baryonicFinalTerm , baryonicFinalTermDerivative, & - & darkMatterDistributedFraction , initialMassFraction , & - & radiusFinal , radiusFinalMean , & - & darkMatterFraction , radiusVirial , & - & toleranceRelative - logical :: massesComputed - contains - !![ - - - - - - - - - !!] - final :: adiabaticGnedin2004Destructor - procedure :: autoHook => adiabaticGnedin2004AutoHook - procedure :: calculationReset => adiabaticGnedin2004CalculationReset - procedure :: density => adiabaticGnedin2004Density - procedure :: densityLogSlope => adiabaticGnedin2004DensityLogSlope - procedure :: radiusEnclosingDensity => adiabaticGnedin2004RadiusEnclosingDensity - procedure :: radiusEnclosingMass => adiabaticGnedin2004RadiusEnclosingMass - procedure :: radialMoment => adiabaticGnedin2004RadialMoment - procedure :: enclosedMass => adiabaticGnedin2004EnclosedMass - procedure :: potential => adiabaticGnedin2004Potential - procedure :: circularVelocity => adiabaticGnedin2004CircularVelocity - procedure :: circularVelocityMaximum => adiabaticGnedin2004CircularVelocityMaximum - procedure :: radialVelocityDispersion => adiabaticGnedin2004RadialVelocityDispersion - procedure :: radiusFromSpecificAngularMomentum => adiabaticGnedin2004RadiusFromSpecificAngularMomentum - procedure :: rotationNormalization => adiabaticGnedin2004RotationNormalization - procedure :: energy => adiabaticGnedin2004Energy - procedure :: kSpace => adiabaticGnedin2004KSpace - procedure :: freefallRadius => adiabaticGnedin2004FreefallRadius - procedure :: freefallRadiusIncreaseRate => adiabaticGnedin2004FreefallRadiusIncreaseRate - procedure :: radiusInitial => adiabaticGnedin2004RadiusInitial - procedure :: radiusInitialDerivative => adiabaticGnedin2004RadiusInitialDerivative - procedure :: computeFactors => adiabaticGnedin2004ComputeFactors - procedure :: radiusOrbitalMean => adiabaticGnedin2004RadiusOrbitalMean - procedure :: radiusOrbitalMeanDerivative => adiabaticGnedin2004RadiusOrbitalMeanDerivative - procedure :: deepCopy => adiabaticGnedin2004DeepCopy - procedure :: deepCopyReset => adiabaticGnedin2004DeepCopyReset - procedure :: deepCopyFinalize => adiabaticGnedin2004DeepCopyFinalize + class (cosmologyParametersClass ), pointer :: cosmologyParameters_ => null() + class (darkMatterProfileDMOClass ), pointer :: darkMatterProfileDMO_ => null() + class (darkMatterHaloScaleClass ), pointer :: darkMatterHaloScale_ => null() + type (enumerationNonAnalyticSolversType) :: nonAnalyticSolver + double precision :: A , omega , & + & radiusFractionalPivot , toleranceRelative , & + & darkMatterFraction + contains + final :: adiabaticGnedin2004Destructor + procedure :: get => adiabaticGnedin2004Get end type darkMatterProfileAdiabaticGnedin2004 interface darkMatterProfileAdiabaticGnedin2004 @@ -170,32 +58,21 @@ module procedure adiabaticGnedin2004ConstructorParameters module procedure adiabaticGnedin2004ConstructorInternal end interface darkMatterProfileAdiabaticGnedin2004 - - ! Module-scope quantities used in solving the initial radius root function. - type (enumerationComponentTypeType ), parameter :: componentType =componentTypeAll - type (enumerationMassTypeType ), parameter :: massType =massTypeBaryonic - type (enumerationWeightByType ), parameter :: weightBy =weightByMass - integer , parameter :: weightIndex =weightIndexNull - double precision , parameter :: toleranceAbsolute=0.0d0 - type (treeNode ), pointer :: node_ - class (darkMatterProfileAdiabaticGnedin2004), pointer :: self_ - !$omp threadprivate(self_,node_) - + contains - recursive function adiabaticGnedin2004ConstructorParameters(parameters) result(self) + function adiabaticGnedin2004ConstructorParameters(parameters) result(self) !!{ Default constructor for the {\normalfont \ttfamily adiabaticGnedin2004} dark matter halo profile class. !!} - use :: Input_Parameters, only : inputParameters - use :: Functions_Global, only : galacticStructureConstruct_, galacticStructureDestruct_ + use :: Mass_Distributions, only : enumerationNonAnalyticSolversEncode + use :: Input_Parameters , only : inputParameters implicit none type (darkMatterProfileAdiabaticGnedin2004) :: self type (inputParameters ), intent(inout) :: parameters class (cosmologyParametersClass ), pointer :: cosmologyParameters_ class (darkMatterHaloScaleClass ), pointer :: darkMatterHaloScale_ class (darkMatterProfileDMOClass ), pointer :: darkMatterProfileDMO_ - class (* ), pointer :: galacticStructure_ type (varying_string ) :: nonAnalyticSolver double precision :: A , omega , & & radiusFractionalPivot, toleranceRelative @@ -238,23 +115,22 @@ recursive function adiabaticGnedin2004ConstructorParameters(parameters) result(s !!] - call galacticStructureConstruct_(parameters,galacticStructure_) - self=darkMatterProfileAdiabaticGnedin2004(A,omega,radiusFractionalPivot,toleranceRelative,enumerationNonAnalyticSolversEncode(char(nonAnalyticSolver),includesPrefix=.false.),cosmologyParameters_,darkMatterHaloScale_,darkMatterProfileDMO_,galacticStructure_) + self=darkMatterProfileAdiabaticGnedin2004(A,omega,radiusFractionalPivot,toleranceRelative,enumerationNonAnalyticSolversEncode(char(nonAnalyticSolver),includesPrefix=.false.),cosmologyParameters_,darkMatterHaloScale_,darkMatterProfileDMO_) !![ - + !!] - if (associated(galacticStructure_)) call galacticStructureDestruct_(galacticStructure_) return end function adiabaticGnedin2004ConstructorParameters - recursive function adiabaticGnedin2004ConstructorInternal(A,omega,radiusFractionalPivot,toleranceRelative,nonAnalyticSolver,cosmologyParameters_,darkMatterHaloScale_,darkMatterProfileDMO_,galacticStructure_) result(self) + function adiabaticGnedin2004ConstructorInternal(A,omega,radiusFractionalPivot,toleranceRelative,nonAnalyticSolver,cosmologyParameters_,darkMatterHaloScale_,darkMatterProfileDMO_) result(self) !!{ Generic constructor for the {\normalfont \ttfamily adiabaticGnedin2004} dark matter profile class. !!} - use :: Error, only : Error_Report + use :: Mass_Distributions, only : enumerationNonAnalyticSolversIsValid + use :: Error , only : Error_Report implicit none type (darkMatterProfileAdiabaticGnedin2004) :: self double precision , intent(in ) :: A , omega , & @@ -262,53 +138,24 @@ recursive function adiabaticGnedin2004ConstructorInternal(A,omega,radiusFraction class (cosmologyParametersClass ), intent(in ), target :: cosmologyParameters_ class (darkMatterProfileDMOClass ), intent(in ), target :: darkMatterProfileDMO_ class (darkMatterHaloScaleClass ), intent(in ), target :: darkMatterHaloScale_ - class (* ), intent(in ), target :: galacticStructure_ type (enumerationNonAnalyticSolversType ), intent(in ) :: nonAnalyticSolver !![ - + !!] ! Validate. if (.not.enumerationNonAnalyticSolversIsValid(nonAnalyticSolver)) call Error_Report('invalid non-analytic solver type'//{introspection:location}) - ! Construct the object. - self%lastUniqueID =-1_kind_int8 - self%genericLastUniqueID=-1_kind_int8 - self%massesComputed =.false. - self%radiusExponentiator=fastExponentiator(1.0d-3,1.0d0,omega,1.0d4,.false.) ! Evaluate the dark matter fraction. self%darkMatterFraction=+1.0d0 & & -self%cosmologyParameters_%OmegaBaryon() & & /self%cosmologyParameters_%OmegaMatter() - ! Construct a root finder. - self%finder=rootFinder( & - & rootFunction =adiabaticGnedin2004Solver, & - & toleranceAbsolute=toleranceAbsolute , & - & toleranceRelative=toleranceRelative & - & ) - ! Set recursive properties. - self%isRecursive =.false. - self%parentDeferred=.false. return end function adiabaticGnedin2004ConstructorInternal - subroutine adiabaticGnedin2004AutoHook(self) - !!{ - Attach to the calculation reset event. - !!} - use :: Events_Hooks, only : calculationResetEvent, openMPThreadBindingAllLevels - implicit none - class(darkMatterProfileAdiabaticGnedin2004), intent(inout) :: self - - call calculationResetEvent%attach(self,adiabaticGnedin2004CalculationReset,openMPThreadBindingAllLevels,label='darkMatterProfileAdiabaticGnedin2004') - return - end subroutine adiabaticGnedin2004AutoHook - subroutine adiabaticGnedin2004Destructor(self) !!{ Destructor for the {\normalfont \ttfamily adiabaticGnedin2004} dark matter halo profile class. !!} - use :: Events_Hooks , only : calculationResetEvent - use :: Functions_Global, only : galacticStructureDestruct_ implicit none type(darkMatterProfileAdiabaticGnedin2004), intent(inout) :: self @@ -317,690 +164,149 @@ subroutine adiabaticGnedin2004Destructor(self) !!] - if (associated (self%galacticStructure_ )) call galacticStructureDestruct_ (self%galacticStructure_ ) - if (calculationResetEvent%isAttached(self,adiabaticGnedin2004CalculationReset)) call calculationResetEvent%detach(self,adiabaticGnedin2004CalculationReset) return end subroutine adiabaticGnedin2004Destructor - subroutine adiabaticGnedin2004CalculationReset(self,node,uniqueID) - !!{ - Reset the dark matter profile calculation. - !!} - use :: Kind_Numbers, only : kind_int8 - implicit none - class (darkMatterProfileAdiabaticGnedin2004), intent(inout) :: self - type (treeNode ), intent(inout) :: node - integer(kind_int8 ), intent(in ) :: uniqueID - !$GLC attributes unused :: node - - ! Reset calculations for this profile. - self%lastUniqueID =uniqueID - self%genericLastUniqueID =uniqueID - self%radiusPreviousIndex = 0 - self%radiusPreviousIndexMaximum = 0 - self%radiusPrevious =-1.0d0 - self%massesComputed =.false. - self%genericEnclosedMassRadiusMinimum =+huge(0.0d0) - self%genericEnclosedMassRadiusMaximum =-huge(0.0d0) - self%genericVelocityDispersionRadialRadiusMinimum=+huge(0.0d0) - self%genericVelocityDispersionRadialRadiusMaximum=-huge(0.0d0) - if (allocated(self%genericVelocityDispersionRadialVelocity)) deallocate(self%genericVelocityDispersionRadialVelocity) - if (allocated(self%genericVelocityDispersionRadialRadius )) deallocate(self%genericVelocityDispersionRadialRadius ) - if (allocated(self%genericEnclosedMassMass )) deallocate(self%genericEnclosedMassMass ) - if (allocated(self%genericEnclosedMassRadius )) deallocate(self%genericEnclosedMassRadius ) - return - end subroutine adiabaticGnedin2004CalculationReset - - double precision function adiabaticGnedin2004EnclosedMass(self,node,radius) - !!{ - Returns the enclosed mass (in $M_\odot$) in the dark matter profile of {\normalfont \ttfamily node} at the given {\normalfont \ttfamily radius} (given in - units of Mpc). - !!} - implicit none - class (darkMatterProfileAdiabaticGnedin2004), intent(inout) :: self - type (treeNode ), intent(inout) :: node - double precision , intent(in ) :: radius - - ! Use recursive self if necessary. - if (self%isRecursive) then - adiabaticGnedin2004EnclosedMass=self%recursiveSelf%enclosedMass(node,radius) - return - end if - adiabaticGnedin2004EnclosedMass=+self%darkMatterFraction & - & *self%darkMatterProfileDMO_%enclosedMass(node,self%radiusInitial(node,radius)) - return - end function adiabaticGnedin2004EnclosedMass - - double precision function adiabaticGnedin2004Density(self,node,radius) - !!{ - Returns the density (in $M_\odot$ Mpc$^{-3}$) in the dark matter profile of {\normalfont \ttfamily node} at the given - {\normalfont \ttfamily radius} (given in units of Mpc). - !!} - implicit none - class (darkMatterProfileAdiabaticGnedin2004), intent(inout) :: self - type (treeNode ), intent(inout) :: node - double precision , intent(in ) :: radius - double precision :: radiusInitial , radiusInitialDerivative, & - & densityInitial - - ! Use recursive self if necessary. - if (self%isRecursive) then - adiabaticGnedin2004Density=self%recursiveSelf%density(node,radius) - return - end if - radiusInitial =self %radiusInitial(node,radius ) - densityInitial=self%darkMatterProfileDMO_%density (node,radiusInitial) - if (radius == radiusInitial) then - adiabaticGnedin2004Density=+self%darkMatterFraction & - & *densityInitial - else - radiusInitialDerivative = self%radiusInitialDerivative(node,radius) - adiabaticGnedin2004Density=+self%darkMatterFraction & - & *densityInitial & - & *( & - & +radiusInitial & - & /radius & - & ) **2 & - & *radiusInitialDerivative - end if - return - end function adiabaticGnedin2004Density - - double precision function adiabaticGnedin2004DensityLogSlope(self,node,radius) - !!{ - Returns the logarithmic slope of the density in the dark matter profile of {\normalfont \ttfamily node} at the given - {\normalfont \ttfamily radius} (given in units of Mpc). - !!} - implicit none - class (darkMatterProfileAdiabaticGnedin2004), intent(inout) :: self - type (treeNode ), intent(inout) :: node - double precision , intent(in ) :: radius - logical :: fallThrough - - ! Use recursive self if necessary. - if (self%isRecursive) then - adiabaticGnedin2004DensityLogSlope=self%recursiveSelf%densityLogSlope(node,radius) - return - end if - fallThrough=self%nonAnalyticSolver == nonAnalyticSolversFallThrough - if (.not.fallThrough) fallThrough=radius == self%radiusInitial(node,radius) - if (fallThrough) then - adiabaticGnedin2004DensityLogSlope=self%darkMatterProfileDMO_%densityLogSlope (node,radius) - else - adiabaticGnedin2004DensityLogSlope=self %densityLogSlopeNumerical(node,radius) - end if - return - end function adiabaticGnedin2004DensityLogSlope - - double precision function adiabaticGnedin2004RadiusEnclosingDensity(self,node,density) - !!{ - Returns the radius (in Mpc) in the dark matter profile of {\normalfont \ttfamily node} which encloses the given - {\normalfont \ttfamily density} (given in units of $M_\odot/$Mpc$^{-3}$). - !!} - implicit none - class (darkMatterProfileAdiabaticGnedin2004), intent(inout), target :: self - type (treeNode ), intent(inout), target :: node - double precision , intent(in ) :: density - - ! Use recursive self if necessary. - if (self%isRecursive) then - adiabaticGnedin2004RadiusEnclosingDensity=self%recursiveSelf%radiusEnclosingDensity(node,density) - return - end if - if (self%nonAnalyticSolver == nonAnalyticSolversFallThrough) then - adiabaticGnedin2004RadiusEnclosingDensity=self%darkMatterProfileDMO_%radiusEnclosingDensity (node,density) - else - adiabaticGnedin2004RadiusEnclosingDensity=self %radiusEnclosingDensityNumerical(node,density) - end if - return - end function adiabaticGnedin2004RadiusEnclosingDensity - - double precision function adiabaticGnedin2004RadiusEnclosingMass(self,node,mass) - !!{ - Returns the radius (in Mpc) in the dark matter profile of {\normalfont \ttfamily node} which encloses the given - {\normalfont \ttfamily mass} (given in units of $M_\odot$). - !!} - implicit none - class (darkMatterProfileAdiabaticGnedin2004), intent(inout), target :: self - type (treeNode ), intent(inout), target :: node - double precision , intent(in ) :: mass - - ! Use recursive self if necessary. - if (self%isRecursive) then - adiabaticGnedin2004RadiusEnclosingMass=self%recursiveSelf %radiusEnclosingMass (node,mass) - return - end if - if (self%nonAnalyticSolver == nonAnalyticSolversFallThrough) then - adiabaticGnedin2004RadiusEnclosingMass=self%darkMatterProfileDMO_%radiusEnclosingMass (node,mass) - else - adiabaticGnedin2004RadiusEnclosingMass=self %radiusEnclosingMassNumerical(node,mass) - end if - return - end function adiabaticGnedin2004RadiusEnclosingMass - - double precision function adiabaticGnedin2004RadialMoment(self,node,moment,radiusMinimum,radiusMaximum) - !!{ - Returns the radial moment of the density in the dark matter profile of {\normalfont \ttfamily node} between the given - {\normalfont \ttfamily radiusMinimum} and {\normalfont \ttfamily radiusMaximum} (given in units of Mpc). - !!} - implicit none - class (darkMatterProfileAdiabaticGnedin2004), intent(inout) :: self - type (treeNode ), intent(inout) :: node - double precision , intent(in ) :: moment - double precision , intent(in ), optional :: radiusMinimum, radiusMaximum - - ! Use recursive self if necessary. - if (self%isRecursive) then - adiabaticGnedin2004RadialMoment=self%recursiveSelf%radialMoment(node,moment,radiusMinimum,radiusMaximum) - return - end if - if (self%nonAnalyticSolver == nonAnalyticSolversFallThrough) then - adiabaticGnedin2004RadialMoment=self%darkMatterProfileDMO_%radialMoment (node,moment,radiusMinimum,radiusMaximum) - else - adiabaticGnedin2004RadialMoment=self %radialMomentNumerical(node,moment,radiusMinimum,radiusMaximum) - end if - return - end function adiabaticGnedin2004RadialMoment - - double precision function adiabaticGnedin2004Potential(self,node,radius,status) - !!{ - Returns the potential (in (km/s)$^2$) in the dark matter profile of {\normalfont \ttfamily node} at the given {\normalfont - \ttfamily radius} (given in units of Mpc). - !!} - implicit none - class (darkMatterProfileAdiabaticGnedin2004), intent(inout), target :: self - type (treeNode ), intent(inout), target :: node - double precision , intent(in ) :: radius - type (enumerationStructureErrorCodeType ), intent( out), optional :: status - logical :: fallThrough - - ! Use recursive self if necessary. - if (self%isRecursive) then - adiabaticGnedin2004Potential=self%recursiveSelf%potential(node,radius,status) - return - end if - fallThrough=self%nonAnalyticSolver == nonAnalyticSolversFallThrough - if (.not.fallThrough) fallThrough=radius == self%radiusInitial(node,radius) - if (fallThrough) then - ! No adiabatic contraction - use the dark-matter-only result. - adiabaticGnedin2004Potential=+self%darkMatterFraction & - & *self%darkMatterProfileDMO_%potential (node,radius,status) - else - ! Adiabatic contraction is present - fall back to using a numerical calculation. - adiabaticGnedin2004Potential=+self %potentialNumerical(node,radius,status) - end if - return - end function adiabaticGnedin2004Potential - - double precision function adiabaticGnedin2004CircularVelocity(self,node,radius) - !!{ - Returns the circular velocity (in km/s) in the dark matter profile of {\normalfont \ttfamily node} at the given - {\normalfont \ttfamily radius} (given in units of Mpc). - !!} - implicit none - class (darkMatterProfileAdiabaticGnedin2004), intent(inout) :: self - type (treeNode ), intent(inout) :: node - double precision , intent(in ) :: radius - - ! Use recursive self if necessary. - if (self%isRecursive) then - adiabaticGnedin2004CircularVelocity=self%recursiveSelf%circularVelocity(node,radius) - return - end if - if (self%nonAnalyticSolver == nonAnalyticSolversFallThrough) then - adiabaticGnedin2004CircularVelocity=self%darkMatterProfileDMO_%circularVelocity (node,radius) - else - adiabaticGnedin2004CircularVelocity=self %circularVelocityNumerical(node,radius) - end if - return - end function adiabaticGnedin2004CircularVelocity - - double precision function adiabaticGnedin2004CircularVelocityMaximum(self,node) - !!{ - Returns the maximum circular velocity (in km/s) in the dark matter profile of {\normalfont \ttfamily node}. - !!} - implicit none - class(darkMatterProfileAdiabaticGnedin2004), intent(inout) :: self - type (treeNode ), intent(inout) :: node - - if (self%nonAnalyticSolver == nonAnalyticSolversFallThrough) then - adiabaticGnedin2004CircularVelocityMaximum=self%darkMatterProfileDMO_%circularVelocityMaximum (node) - else - adiabaticGnedin2004CircularVelocityMaximum=self %circularVelocityMaximumNumerical(node) - end if - return - end function adiabaticGnedin2004CircularVelocityMaximum - - double precision function adiabaticGnedin2004RadialVelocityDispersion(self,node,radius) - !!{ - Returns the radial velocity dispersion (in km/s) in the dark matter profile of {\normalfont \ttfamily node} at the given - {\normalfont \ttfamily radius} (given in units of Mpc). - !!} - implicit none - class (darkMatterProfileAdiabaticGnedin2004), intent(inout) :: self - type (treeNode ), intent(inout) :: node - double precision , intent(in ) :: radius - - ! Use recursive self if necessary. - if (self%isRecursive) then - adiabaticGnedin2004RadialVelocityDispersion=self%recursiveSelf%radialVelocityDispersion(node,radius) - return - end if - if (self%nonAnalyticSolver == nonAnalyticSolversFallThrough) then - adiabaticGnedin2004RadialVelocityDispersion=self%darkMatterProfileDMO_%radialVelocityDispersion (node,radius) - else - adiabaticGnedin2004RadialVelocityDispersion=self %radialVelocityDispersionNumerical(node,radius) - end if - return - end function adiabaticGnedin2004RadialVelocityDispersion - - double precision function adiabaticGnedin2004RadiusFromSpecificAngularMomentum(self,node,specificAngularMomentum) - !!{ - Returns the radius (in Mpc) in {\normalfont \ttfamily node} at which a circular orbit has the given {\normalfont \ttfamily specificAngularMomentum} (given - in units of km s$^{-1}$ Mpc). - !!} - implicit none - class (darkMatterProfileAdiabaticGnedin2004), intent(inout), target :: self - type (treeNode ), intent(inout) :: node - double precision , intent(in ) :: specificAngularMomentum - - ! Use recursive self if necessary. - if (self%isRecursive) then - adiabaticGnedin2004RadiusFromSpecificAngularMomentum=self%recursiveSelf%radiusFromSpecificAngularMomentum(node,specificAngularMomentum) - return - end if - if (self%nonAnalyticSolver == nonAnalyticSolversFallThrough) then - adiabaticGnedin2004RadiusFromSpecificAngularMomentum=self%darkMatterProfileDMO_%radiusFromSpecificAngularMomentum (node,specificAngularMomentum) - else - adiabaticGnedin2004RadiusFromSpecificAngularMomentum=self %radiusFromSpecificAngularMomentumNumerical(node,specificAngularMomentum) - end if - return - end function adiabaticGnedin2004RadiusFromSpecificAngularMomentum - - double precision function adiabaticGnedin2004RotationNormalization(self,node) - !!{ - Return the normalization of the rotation velocity vs. specific angular momentum relation. - !!} - implicit none - class(darkMatterProfileAdiabaticGnedin2004), intent(inout) :: self - type (treeNode ), intent(inout) :: node - - ! Use recursive self if necessary. - if (self%isRecursive) then - adiabaticGnedin2004RotationNormalization=self%recursiveSelf%rotationNormalization(node) - return - end if - if (self%nonAnalyticSolver == nonAnalyticSolversFallThrough) then - adiabaticGnedin2004RotationNormalization=self%darkMatterProfileDMO_%rotationNormalization (node) - else - adiabaticGnedin2004RotationNormalization=self %rotationNormalizationNumerical(node) - end if - return - end function adiabaticGnedin2004RotationNormalization - - double precision function adiabaticGnedin2004Energy(self,node) - !!{ - Return the energy of a adiabaticGnedin2004 halo density profile. - !!} - implicit none - class(darkMatterProfileAdiabaticGnedin2004), intent(inout) :: self - type (treeNode ), intent(inout) :: node - - ! Use recursive self if necessary. - if (self%isRecursive) then - adiabaticGnedin2004Energy=self%recursiveSelf%energy(node) - return - end if - if (self%nonAnalyticSolver == nonAnalyticSolversFallThrough) then - adiabaticGnedin2004Energy=self%darkMatterProfileDMO_%energy (node) - else - adiabaticGnedin2004Energy=self %energyNumerical(node) - end if - return - end function adiabaticGnedin2004Energy - - double precision function adiabaticGnedin2004KSpace(self,node,waveNumber) - !!{ - Returns the Fourier transform of the adiabaticGnedin2004 density profile at the specified {\normalfont \ttfamily waveNumber} - (given in Mpc$^{-1}$), using the expression given in \citeauthor{cooray_halo_2002}~(\citeyear{cooray_halo_2002}; eqn.~81). - !!} - implicit none - class (darkMatterProfileAdiabaticGnedin2004), intent(inout) :: self - type (treeNode ), intent(inout), target :: node - double precision , intent(in ) :: waveNumber - - ! Use recursive self if necessary. - if (self%isRecursive) then - adiabaticGnedin2004KSpace=self%recursiveSelf%kSpace(node,wavenumber) - return - end if - if (self%nonAnalyticSolver == nonAnalyticSolversFallThrough) then - adiabaticGnedin2004KSpace=self%darkMatterProfileDMO_%kSpace (node,waveNumber) - else - adiabaticGnedin2004KSpace=self %kSpaceNumerical(node,waveNumber) - end if - return - end function adiabaticGnedin2004KSpace - - double precision function adiabaticGnedin2004FreefallRadius(self,node,time) - !!{ - Returns the freefall radius in the adiabaticGnedin2004 density profile at the specified {\normalfont \ttfamily time} (given in - Gyr). - !!} - implicit none - class (darkMatterProfileAdiabaticGnedin2004), intent(inout) :: self - type (treeNode ), intent(inout) :: node - double precision , intent(in ) :: time - - ! Use recursive self if necessary. - if (self%isRecursive) then - adiabaticGnedin2004FreefallRadius=self%recursiveSelf%freefallRadius(node,time) - return - end if - if (self%nonAnalyticSolver == nonAnalyticSolversFallThrough) then - adiabaticGnedin2004FreefallRadius=self%darkMatterProfileDMO_%freefallRadius (node,time) - else - adiabaticGnedin2004FreefallRadius=self %freefallRadiusNumerical(node,time) - end if - return - end function adiabaticGnedin2004FreefallRadius - - double precision function adiabaticGnedin2004FreefallRadiusIncreaseRate(self,node,time) - !!{ - Returns the rate of increase of the freefall radius in the adiabaticGnedin2004 density profile at the specified - {\normalfont \ttfamily time} (given in Gyr). - !!} - implicit none - class (darkMatterProfileAdiabaticGnedin2004), intent(inout) :: self - type (treeNode ), intent(inout) :: node - double precision , intent(in ) :: time - - ! Use recursive self if necessary. - if (self%isRecursive) then - adiabaticGnedin2004FreefallRadiusIncreaseRate=self%recursiveSelf%freefallRadiusIncreaseRate(node,time) - return - end if - if (self%nonAnalyticSolver == nonAnalyticSolversFallThrough) then - adiabaticGnedin2004FreefallRadiusIncreaseRate=self%darkMatterProfileDMO_%freefallRadiusIncreaseRate (node,time) - else - adiabaticGnedin2004FreefallRadiusIncreaseRate=self %freefallRadiusIncreaseRateNumerical(node,time) - end if - return - end function adiabaticGnedin2004FreefallRadiusIncreaseRate - - double precision function adiabaticGnedin2004RadiusInitial(self,node,radius) - !!{ - Compute the initial radius in the dark matter halo using the adiabatic contraction algorithm of - \cite{gnedin_response_2004}. - !!} - use :: Root_Finder, only : rangeExpandMultiplicative, rangeExpandSignExpectNegative, rangeExpandSignExpectPositive, rootFinder - implicit none - class (darkMatterProfileAdiabaticGnedin2004), intent(inout) :: self - type (treeNode ), intent(inout) :: node - double precision , intent(in ) :: radius - integer :: i , j , & - & iMod - double precision :: radiusUpperBound, massEnclosed + function adiabaticGnedin2004Get(self,node,weightBy,weightIndex) result(massDistribution_) + !!{ + Return the dark matter mass distribution for the given {\normalfont \ttfamily node}. + !!} + use :: Galactic_Structure_Options, only : componentTypeDarkHalo , massTypeDark , massTypeBaryonic , weightByMass + use :: Mass_Distributions , only : massDistributionSphericalAdiabaticGnedin2004, kinematicsDistributionCollisionless, massDistributionSpherical , kinematicsDistributionClass, & + & sphericalAdiabaticGnedin2004Initializor , kinematicsDistributionUndecorator , nonAnalyticSolversFallThrough + implicit none + class (massDistributionClass ), pointer :: massDistribution_ + class (kinematicsDistributionClass ), pointer :: kinematicsDistribution_ , kinematicsDistribution__ + class (darkMatterProfileAdiabaticGnedin2004 ), intent(inout), target :: self + type (treeNode ), intent(inout), target :: node + type (enumerationWeightByType ), intent(in ), optional :: weightBy + integer , intent(in ), optional :: weightIndex + class (massDistributionClass ), pointer :: massDistributionDecorated , massDistributionBaryonic + double precision :: massBaryonicSelfTotal , massBaryonicTotal , & + & darkMatterDistributedFraction, initialMassFraction + procedure (sphericalAdiabaticGnedin2004Initializor), pointer :: initializationFunction + class (* ), pointer :: initializationSelf , initializationArgument + !![ + + !!] - ! Reset stored solutions if the node has changed. - if (node%uniqueID() /= self%lastUniqueID) call self%calculationReset(node,node%uniqueID()) - ! Check for a previously computed solution. - if (self%radiusPreviousIndexMaximum > 0 .and. any(self%radiusPrevious(1:self%radiusPreviousIndexMaximum) == radius)) then - adiabaticGnedin2004RadiusInitial=0.0d0 - do i=1,self%radiusPreviousIndexMaximum - if (self%radiusPrevious(i) == radius) then - adiabaticGnedin2004RadiusInitial=self%radiusInitialPrevious(i) - exit - end if - end do - return - end if - ! Get the virial radius of the node. - self%radiusVirial=self%darkMatterHaloScale_%radiusVirial(node) - ! Return radius unchanged if larger than the virial radius. - if (radius >= self%radiusVirial) then - adiabaticGnedin2004RadiusInitial=radius - return - end if - ! Compute the various factors needed by this calculation. - call self%computeFactors(node,radius,computeGradientFactors=.false.) - !! Note that even if no baryons are present at this radius we can not assume that the initial radius is unchanged because it - !! is possible that the initial fraction of baryons and the fraction of mass distributed as the dark matter are not equal, fᵢ - !! ≠ fᵪ. - ! Check that solution is within bounds. - if (adiabaticGnedin2004Solver(self%radiusVirial) < 0.0d0) then - adiabaticGnedin2004RadiusInitial=self%radiusVirial - return - end if - j=-1 - if (self%radiusPreviousIndexMaximum > 0) then - ! No exact match exists, look for approximate matches. - do i=1,self%radiusPreviousIndexMaximum - iMod=modulo(self%radiusPreviousIndex-i,adiabaticGnedin2004StoreCount)+1 - if (abs(radius-self%radiusPrevious(iMod))/self%radiusPrevious(iMod) < self%toleranceRelative) then - j=iMod - exit + ! Assume a null distribution by default. + massDistribution_ => null() + ! If weighting is not by mass, return a null profile. + if (weightBy_ /= weightByMass) return + ! Set the baryonic component to zero - we will compute this later during initialization. + massDistributionBaryonic => null() + massBaryonicTotal = 0.0d0 + massBaryonicSelfTotal = 0.0d0 + darkMatterDistributedFraction = 0.0d0 + initialMassFraction = 0.0d0 + ! Create the mass distribution. + allocate(massDistributionSphericalAdiabaticGnedin2004 :: massDistribution_) + select type(massDistribution_) + type is (massDistributionSphericalAdiabaticGnedin2004) + massDistributionDecorated => self%darkMatterProfileDMO_%get(node,weightBy,weightIndex) + select type (massDistributionDecorated) + class is (massDistributionSpherical) + initializationFunction => adiabaticGnedin2004Initialize + initializationSelf => self + initializationArgument => node + !![ + + + massDistributionSphericalAdiabaticGnedin2004( & + & A =self %A , & + & omega =self %omega , & + & radiusVirial =self%darkMatterHaloScale_%radiusVirial (node), & + & radiusFractionalPivot =self %radiusFractionalPivot , & + & darkMatterFraction =self %darkMatterFraction , & + & darkMatterDistributedFraction= darkMatterDistributedFraction , & + & massFractionInitial = initialMassFraction , & + & nonAnalyticSolver =self %nonAnalyticSolver , & + & toleranceRelative =self %toleranceRelative , & + & massDistribution_ = massDistributionDecorated , & + & massDistributionBaryonic = massDistributionBaryonic , & + & initializationFunction = initializationFunction , & + & initializationSelf = initializationSelf , & + & initializationArgument = initializationArgument , & + & componentType = componentTypeDarkHalo , & + & massType = massTypeDark & + & ) + + + !!] + kinematicsDistribution__ => massDistributionDecorated%kinematicsDistribution() + if (self%nonAnalyticSolver == nonAnalyticSolversFallThrough) then + allocate(kinematicsDistributionUndecorator :: kinematicsDistribution_) + select type (kinematicsDistribution_) + type is (kinematicsDistributionUndecorator ) + !![ + + + kinematicsDistributionUndecorator(kinematicsDistribution__) + + + !!] + end select + else + allocate(kinematicsDistributionCollisionless :: kinematicsDistribution_) + select type (kinematicsDistribution_) + type is (kinematicsDistributionCollisionless) + !![ + + + kinematicsDistributionCollisionless(kinematicsDistribution__) + + + !!] + end select end if - end do - end if - ! Find the solution for initial radius. - if (j == -1) then - ! No previous solution to use as an initial guess. Instead, we make an estimate of the initial radius under the - ! assumption that the mass of dark matter (in the initial profile) enclosed within the mean initial radius is the - ! same as enclosed within the mean final radius. Since the initial and final radii are typically not too - ! different, and since the mean radius is a weak (ѡ<1) function of the radius this is a useful - ! approximation. Furthermore, since it will underestimate the actual mass within the initial mean radius it gives - ! an overestimate of the initial radius. This means that we have a bracketing of the initial radius which we can - ! use in the solver. - massEnclosed=+self%darkMatterProfileDMO_%enclosedMass(node,self%radiusOrbitalMean(self%radiusFinal)) - if (massEnclosed > 0.0d0) then - radiusUpperBound=+( & - & +self%baryonicFinalTerm & - & / massEnclosed & - & +self%darkMatterDistributedFraction & - & *self%radiusFinal & - & ) & - & / self%initialMassFraction - if (radiusUpperBound < radius) radiusUpperBound=radius - else - radiusUpperBound=radius - end if - call self%finder%rangeExpand( & - & rangeExpandUpward =1.1d0 , & - & rangeExpandDownward =0.9d0 , & - & rangeExpandUpwardSignExpect =rangeExpandSignExpectPositive, & - & rangeExpandDownwardSignExpect=rangeExpandSignExpectNegative, & - & rangeExpandType =rangeExpandMultiplicative & - & ) - adiabaticGnedin2004RadiusInitial=self%finder%find(rootRange=[radius,radiusUpperBound]) - else - ! Use previous solution as an initial guess. - call self%finder%rangeExpand( & - & rangeExpandDownward =1.0d0/sqrt(1.0d0+self%toleranceRelative), & - & rangeExpandUpward =1.0d0*sqrt(1.0d0+self%toleranceRelative), & - & rangeExpandDownwardSignExpect=rangeExpandSignExpectNegative , & - & rangeExpandUpwardSignExpect =rangeExpandSignExpectPositive , & - & rangeExpandType =rangeExpandMultiplicative & - & ) - adiabaticGnedin2004RadiusInitial=self%finder%find( & - & rootRange=[ & - & self%radiusInitialPrevious(j)/sqrt(1.0d0+self%toleranceRelative), & - & self%radiusInitialPrevious(j)*sqrt(1.0d0+self%toleranceRelative) & - & ] & - & ) - end if - ! Store this solution. - self%radiusPreviousIndex =modulo(self%radiusPreviousIndex ,adiabaticGnedin2004StoreCount)+1 - self%radiusPreviousIndexMaximum =min (self%radiusPreviousIndexMaximum+1,adiabaticGnedin2004StoreCount) - self%radiusPrevious (self%radiusPreviousIndex)=radius - self%radiusInitialPrevious (self%radiusPreviousIndex)=adiabaticGnedin2004RadiusInitial - return - end function adiabaticGnedin2004RadiusInitial - - double precision function adiabaticGnedin2004RadiusInitialDerivative(self,node,radius) - !!{ - Compute the derivative of the initial radius in the dark matter halo using the adiabatic contraction algorithm of - \cite{gnedin_response_2004}. - !!} - use :: Display , only : displayMessage, displayIndent, displayUnindent - use :: Error , only : Error_Report - use :: Numerical_Constants_Math, only : Pi - implicit none - class (darkMatterProfileAdiabaticGnedin2004), intent(inout) :: self - type (treeNode ), intent(inout) :: node - double precision , intent(in ) :: radius - type (varying_string ), save :: message - !$omp threadprivate(message) - character (len=12 ) :: label - double precision :: radiusInitial , radiusInitialMean , & - & massDarkMatterInitial , densityDarkMatterInitial , & - & radiusInitialMeanSelfDerivative, radiusFinalMeanSelfDerivative, & - & numerator , denominator - - ! Reset stored solutions if the node has changed. - if (node%uniqueID() /= self%lastUniqueID) call self%calculationReset(node,node%uniqueID()) - ! Compute the various factors needed by this calculation. - call self%computeFactors(node,radius,computeGradientFactors=.true.) - ! Return unit derivative if radius is larger than the virial radius. - if (radius >= self%radiusVirial) then - adiabaticGnedin2004RadiusInitialDerivative=1.0d0 - return - end if - ! Validate. - if (radius <= 0.0d0) call Error_Report('non-positive radius') - ! Compute initial radius, and derivatives of initial and final mean radii. - radiusInitial =self %radiusInitial (node,radius ) - radiusInitialMeanSelfDerivative=self %radiusOrbitalMeanDerivative( radiusInitial ) - radiusFinalMeanSelfDerivative =self %radiusOrbitalMeanDerivative( radius ) - ! Find the initial mean orbital radius. - radiusInitialMean =self %radiusOrbitalMean ( radiusInitial ) - ! Get the mass of dark matter inside the initial radius. - massDarkMatterInitial =self%darkMatterProfileDMO_%enclosedMass (node,radiusInitialMean) - ! Get the density of dark matter at the initial radius. - densityDarkMatterInitial =self%darkMatterProfileDMO_%density (node,radiusInitialMean) - ! Find the solution for the derivative of the initial radius. - numerator =+( & - & +massDarkMatterInitial & - & *self%darkMatterDistributedFraction & - & +self%baryonicFinalTerm & - & *( & - & +1.0d0 / radius & - & +radiusFinalMeanSelfDerivative/self%radiusFinalMean & - & ) & - & +self%baryonicFinalTermDerivative & - & ) - denominator =+( & - & +massDarkMatterInitial*self%initialMassFraction & - & +( & - & +self%initialMassFraction *radiusInitial & - & -self%darkMatterDistributedFraction*radius & - & ) & - & *4.0d0 & - & *Pi & - & *radiusInitialMean**2 & - & *densityDarkMatterInitial & - & *radiusInitialMeanSelfDerivative & - & ) - if (exponent(numerator)-exponent(denominator) > maxExponent(0.0d0)) then - call displayIndent ('Radius derivative calculation') - write (label,'(e12.6)') radius - message='r_final = '//label//' Mpc' - call displayMessage (message) - write (label,'(e12.6)') radiusInitial - message='r_initial = '//label//' Mpc' - call displayMessage (message) - write (label,'(e12.6)') self%radiusFinalMean - message='⟨r_final⟩ = '//label//' Mpc' - call displayMessage (message) - write (label,'(e12.6)') radiusInitialMean - message='⟨r_initial⟩ = '//label//' Mpc' - call displayMessage (message) - write (label,'(e12.6)') radiusInitialMeanSelfDerivative - message='d⟨r_initial⟩/dr_initial = '//label - call displayMessage (message) - write (label,'(e12.6)') radiusFinalMeanSelfDerivative - message='d⟨r_final⟩/dr_final = '//label - call displayMessage (message) - write (label,'(e12.6)') massDarkMatterInitial - message='M_dark,initial = '//label//' M☉' - call displayMessage (message) - write (label,'(e12.6)') self%baryonicFinalTerm - message='M_baryonic,final = '//label//' M☉' - call displayMessage (message) - write (label,'(e12.6)') self%baryonicFinalTermDerivative - message='dM_braryonic,final/dr_initial = '//label//' M☉/Mpc' - call displayMessage (message) - call displayUnindent('' ) - call Error_Report('Overflow in initial radius derivative calculation'//{introspection:location}) - end if - adiabaticGnedin2004RadiusInitialDerivative=+numerator & - & /denominator + call massDistribution_%setKinematicsDistribution(kinematicsDistribution_) + !![ + + + + !!] + class default + call Error_Report('expected a spherical mass distribution'//{introspection:location}) + end select + end select return - end function adiabaticGnedin2004RadiusInitialDerivative + end function adiabaticGnedin2004Get - subroutine adiabaticGnedin2004ComputeFactors(self,node,radius,computeGradientFactors) + subroutine adiabaticGnedin2004Initialize(self,node,massDistributionBaryonic,darkMatterDistributedFraction,massFractionInitial) !!{ - Compute various factors needed when solving for the initial radius in the dark matter halo using the adiabatic contraction - algorithm of \cite{gnedin_response_2004}. + Initialize the dark matter mass distribution for the given {\normalfont \ttfamily node}. !!} - use :: Galacticus_Nodes , only : nodeComponentBasic , optimizeForEnclosedMassSummation , optimizeForRotationCurveGradientSummation , optimizeForRotationCurveSummation, & - & reductionSummation , treeNode - use :: Numerical_Constants_Astronomical, only : gravitationalConstantGalacticus - use :: Functions_Global , only : galacticStructureMassEnclosed_ , galacticStructureVelocityRotation_, galacticStructureVelocityRotationGradient_ + use :: Galacticus_Nodes , only : nodeComponentBasic + use :: Galactic_Structure_Options, only : massTypeBaryonic + use :: Mass_Distributions , only : massDistributionSphericalAdiabaticGnedin2004 + use :: Error , only : Error_Report implicit none - class (darkMatterProfileAdiabaticGnedin2004), intent(inout), target :: self - type (treeNode ), intent(inout), target :: node - double precision , intent(in ) :: radius - logical , intent(in ) :: computeGradientFactors - type (treeNode ) , pointer :: nodeCurrent , nodeHost - class (nodeComponentBasic ) , pointer :: basic - double precision :: massBaryonicSelfTotal , massBaryonicTotal , & - & velocityCircularSquaredGradient, velocityCircularSquared - - ! Set module-scope pointers to node and self. - node_ => node - self_ => self - ! Store the final radius and its orbit-averaged mean. - self%radiusFinal = radius - self%radiusFinalMean=self%radiusOrbitalMean(radius) - ! Compute the baryonic contribution to the rotation curve. - velocityCircularSquared=galacticStructureVelocityRotation_(self%galacticStructure_,node,self%radiusFinalMean,componentType,massType)**2 - self%baryonicFinalTerm=velocityCircularSquared*self%radiusFinalMean*self%radiusFinal/gravitationalConstantGalacticus - ! Compute the baryonic contribution to the rotation curve. - if (computeGradientFactors) then - velocityCircularSquaredGradient =+galacticStructureVelocityRotationGradient_(self%galacticStructure_,node,self%radiusFinalMean,componentType,massType) & - & *2.0d0 & - & *sqrt(velocityCircularSquared) - self%baryonicFinalTermDerivative=+ velocityCircularSquaredGradient & - & *self%radiusOrbitalMeanDerivative(self%radiusFinal) & - & *self%radiusFinalMean & - & *self%radiusFinal & - & / gravitationalConstantGalacticus - end if - ! Compute the initial baryonic contribution from this halo, and any satellites. - if (.not.self%massesComputed) then - massBaryonicTotal = 0.0d0 - massBaryonicSelfTotal = 0.0d0 - nodeCurrent => node - nodeHost => node - do while (associated(nodeCurrent)) - massBaryonicTotal=+massBaryonicTotal & - & +galacticStructureMassEnclosed_( & - & self%galacticStructure_, & - & nodeCurrent , & - & radiusLarge , & - & componentType , & - & massType , & - & weightBy , & - & weightIndex & - & ) - if (associated(nodeCurrent,nodeHost)) then - massBaryonicSelfTotal=massBaryonicTotal - do while (associated(nodeCurrent%firstSatellite)) - nodeCurrent => nodeCurrent%firstSatellite - end do - if (associated(nodeCurrent,nodeHost)) nodeCurrent => null() - else + class (* ), intent(inout), target :: self , node + class (massDistributionClass), intent( out), pointer :: massDistributionBaryonic + double precision , intent( out) :: darkMatterDistributedFraction, massFractionInitial + type (treeNode ) , pointer :: nodeCurrent + class (nodeComponentBasic ) , pointer :: basic + double precision :: massBaryonicSelf , massBaryonicTotal , & + & massBaryonicSubhalos + + select type (self) + type is (darkMatterProfileAdiabaticGnedin2004) + select type (node) + type is (treeNode) + ! Compute the initial baryonic contribution from this halo, and any satellites. + massDistributionBaryonic => node%massDistribution(massType=massTypeBaryonic) + massBaryonicSelf = node%massBaryonic ( ) + ! Compute baryonic mass in subhalos. + massBaryonicSubhalos = 0.0d0 + nodeCurrent => node + do while (associated(nodeCurrent%firstSatellite)) + nodeCurrent => nodeCurrent%firstSatellite + end do + if (associated(nodeCurrent,node)) nodeCurrent => null() + do while (associated(nodeCurrent)) + massBaryonicSubhalos=+ massBaryonicSubhalos & + & +nodeCurrent%massBaryonic () if (associated(nodeCurrent%sibling)) then nodeCurrent => nodeCurrent%sibling do while (associated(nodeCurrent%firstSatellite)) @@ -1010,279 +316,23 @@ subroutine adiabaticGnedin2004ComputeFactors(self,node,radius,computeGradientFac nodeCurrent => nodeCurrent%parent if (associated(nodeCurrent,node)) nodeCurrent => null() end if - end if - end do - ! Limit masses to physical values. - massBaryonicSelfTotal=max(massBaryonicSelfTotal,0.0d0) - massBaryonicTotal =max(massBaryonicTotal ,0.0d0) - ! Compute the fraction of matter assumed to be distributed like the dark matter. - basic => node%basic() - self%darkMatterDistributedFraction =min((self%cosmologyParameters_%OmegaMatter()-self%cosmologyParameters_%OmegaBaryon())/self%cosmologyParameters_%OmegaMatter()+(massBaryonicTotal-massBaryonicSelfTotal)/basic%mass(),1.0d0) - ! Compute the initial mass fraction. - self%initialMassFraction =min((self%cosmologyParameters_%OmegaMatter()-self%cosmologyParameters_%OmegaBaryon())/self%cosmologyParameters_%OmegaMatter()+ massBaryonicTotal /basic%mass(),1.0d0) - ! Record that masses (and mass fractions) have been computed. - self%massesComputed=.true. - end if - return - end subroutine adiabaticGnedin2004ComputeFactors - - double precision function adiabaticGnedin2004RadiusOrbitalMean(self,radius) - !!{ - Returns the orbit averaged radius for dark matter corresponding the given {\normalfont \ttfamily radius} using the model of - \cite{gnedin_response_2004}. - !!} - implicit none - class (darkMatterProfileAdiabaticGnedin2004), intent(inout) :: self - double precision , intent(in ) :: radius - - adiabaticGnedin2004RadiusOrbitalMean=+self%A & - & *self%radiusFractionalPivot & - & *self%radiusVirial & - & *self%radiusExponentiator%exponentiate( & - & + radius & - & /self%radiusFractionalPivot & - & /self%radiusVirial & - & ) - return - end function adiabaticGnedin2004RadiusOrbitalMean - - double precision function adiabaticGnedin2004RadiusOrbitalMeanDerivative(self,radius) - !!{ - Returns the derivative of the orbit averaged radius for dark matter corresponding the given {\normalfont \ttfamily radius} using the model of - \cite{gnedin_response_2004}. - !!} - implicit none - class (darkMatterProfileAdiabaticGnedin2004), intent(inout) :: self - double precision , intent(in ) :: radius - - adiabaticGnedin2004RadiusOrbitalMeanDerivative=+self%A & - & *self%omega & - & *( & - & + radius & - & /self%radiusFractionalPivot & - & /self%radiusVirial & - & )**(self%omega-1.0d0) - return - end function adiabaticGnedin2004RadiusOrbitalMeanDerivative - - double precision function adiabaticGnedin2004Solver(radiusInitial) - !!{ - Root function used in finding the initial radius in the dark matter halo when solving for adiabatic contraction. - !!} - implicit none - double precision, intent(in ) :: radiusInitial - double precision :: massDarkMatterInitial, radiusInitialMean - - ! Find the initial mean orbital radius. - radiusInitialMean =self_ %radiusOrbitalMean( radiusInitial ) - ! Get the mass of dark matter inside the initial radius. - massDarkMatterInitial=self_%darkMatterProfileDMO_%enclosedMass (node_,radiusInitialMean) - ! Compute the root function. - adiabaticGnedin2004Solver=+massDarkMatterInitial & - & *( & - & +self_%initialMassFraction* radiusInitial & - & -self_%darkMatterDistributedFraction *self_%radiusFinal & - & ) & - & -self_%baryonicFinalTerm - return - end function adiabaticGnedin2004Solver - - subroutine adiabaticGnedin2004DeepCopyReset(self) - !!{ - Perform a deep copy reset of the object. - !!} - use :: Functions_Global, only : galacticStructureDeepCopyReset_ - implicit none - class(darkMatterProfileAdiabaticGnedin2004), intent(inout) :: self - - self % copiedSelf => null() - if (.not.self%isRecursive) self%recursiveSelf => null() - if (associated(self%cosmologyParameters_ )) call self%cosmologyParameters_ %deepCopyReset() - if (associated(self%darkMatterHaloScale_ )) call self%darkMatterHaloScale_ %deepCopyReset() - if (associated(self%darkMatterProfileDMO_)) call self%darkMatterProfileDMO_%deepCopyReset() - if (associated(self%galacticStructure_ )) call galacticStructureDeepCopyReset_(self%galacticStructure_) - return - end subroutine adiabaticGnedin2004DeepCopyReset - - subroutine adiabaticGnedin2004DeepCopyFinalize(self) - !!{ - Finalize a deep reset of the object. - !!} - use :: Functions_Global, only : galacticStructureDeepCopyFinalize_ - implicit none - class(darkMatterProfileAdiabaticGnedin2004), intent(inout) :: self - - if (self%isRecursive) call adiabaticGnedin2004FindParent(self) - if (associated(self%cosmologyParameters_ )) call self%cosmologyParameters_ %deepCopyFinalize() - if (associated(self%darkMatterHaloScale_ )) call self%darkMatterHaloScale_ %deepCopyFinalize() - if (associated(self%darkMatterProfileDMO_)) call self%darkMatterProfileDMO_%deepCopyFinalize() - if (associated(self%galacticStructure_ )) call galacticStructureDeepCopyFinalize_(self%galacticStructure_) - return - end subroutine adiabaticGnedin2004DeepCopyFinalize - - subroutine adiabaticGnedin2004DeepCopy(self,destination) - !!{ - Perform a deep copy of the object. - !!} - use :: Error , only : Error_Report - use :: Functions_Global , only : galacticStructureDeepCopy_ -#ifdef OBJECTDEBUG - use :: Display , only : displayMessage , verbosityLevelSilent - use :: MPI_Utilities , only : mpiSelf - use :: Function_Classes , only : debugReporting - use :: ISO_Varying_String, only : operator(//) , var_str - use :: String_Handling , only : operator(//) -#endif - implicit none - class(darkMatterProfileAdiabaticGnedin2004), intent(inout), target :: self - class(darkMatterProfileClass ), intent(inout) :: destination - - call self%darkMatterProfileClass%deepCopy(destination) - select type (destination) - type is (darkMatterProfileAdiabaticGnedin2004) - destination%finder =self%finder - destination%A =self%A - destination%omega =self%omega - destination%radiusFractionalPivot =self%radiusFractionalPivot - destination%lastUniqueID =self%lastUniqueID - destination%radiusPreviousIndex =self%radiusPreviousIndex - destination%radiusPreviousIndexMaximum =self%radiusPreviousIndexMaximum - destination%radiusPrevious =self%radiusPrevious - destination%radiusInitialPrevious =self%radiusInitialPrevious - destination%radiusExponentiator =self%radiusExponentiator - destination%baryonicFinalTerm =self%baryonicFinalTerm - destination%baryonicFinalTermDerivative =self%baryonicFinalTermDerivative - destination%darkMatterDistributedFraction =self%darkMatterDistributedFraction - destination%initialMassFraction =self%initialMassFraction - destination%radiusFinal =self%radiusFinal - destination%radiusFinalMean =self%radiusFinalMean - destination%radiusVirial =self%radiusVirial - destination%darkMatterFraction =self%darkMatterFraction - destination%massesComputed =self%massesComputed - destination%isRecursive =self%isRecursive - destination%parentDeferred =.false. - if (self%isRecursive) then - if (associated(self%recursiveSelf%recursiveSelf)) then - ! If the parent self's recursiveSelf pointer is set, it indicates that it was deep-copied, and the pointer points to - ! that copy. In that case we set the parent self of our destination to that copy. - destination%recursiveSelf => self%recursiveSelf%recursiveSelf - else - ! The parent self does not appear to have been deep-copied yet. Retain the same parent self pointer in our copy, but - ! indicate that we need to look for the new parent later. - destination%recursiveSelf => self%recursiveSelf - destination%parentDeferred = .true. - end if - else - ! This is a parent of a recursively-constructed object. Record the location of our copy so that it can be used to set - ! the parent in deep copies of the child object. - call adiabaticGnedin2004DeepCopyAssign(self,destination) - destination%recursiveSelf => null() - end if - nullify(destination%cosmologyParameters_) - if (associated(self%cosmologyParameters_)) then - if (associated(self%cosmologyParameters_%copiedSelf)) then - select type(s => self%cosmologyParameters_%copiedSelf) - class is (cosmologyParametersClass) - destination%cosmologyParameters_ => s - class default - call Error_Report('copiedSelf has incorrect type'//{introspection:location}) - end select - call self%cosmologyParameters_%copiedSelf%referenceCountIncrement() - else - allocate(destination%cosmologyParameters_,mold=self%cosmologyParameters_) - call self%cosmologyParameters_%deepCopy(destination%cosmologyParameters_) - self%cosmologyParameters_%copiedSelf => destination%cosmologyParameters_ - call destination%cosmologyParameters_%autoHook() - end if -#ifdef OBJECTDEBUG - if (debugReporting.and.mpiSelf%isMaster()) call displayMessage(var_str('functionClass[own] (class : ownerName : ownerLoc : objectLoc : sourceLoc): cosmologyparameters : [destination] : ')//loc(destination)//' : '//loc(destination%cosmologyParameters_)//' : '//{introspection:location:compact},verbosityLevelSilent) -#endif - end if - nullify(destination%darkMatterProfileDMO_) - if (associated(self%darkMatterProfileDMO_)) then - if (associated(self%darkMatterProfileDMO_%copiedSelf)) then - select type(s => self%darkMatterProfileDMO_%copiedSelf) - class is (darkMatterProfileDMOClass) - destination%darkMatterProfileDMO_ => s - class default - call Error_Report('copiedSelf has incorrect type'//{introspection:location}) - end select - call self%darkMatterProfileDMO_%copiedSelf%referenceCountIncrement() - else - allocate(destination%darkMatterProfileDMO_,mold=self%darkMatterProfileDMO_) - call self%darkMatterProfileDMO_%deepCopy(destination%darkMatterProfileDMO_) - self%darkMatterProfileDMO_%copiedSelf => destination%darkMatterProfileDMO_ - call destination%darkMatterProfileDMO_%autoHook() - end if -#ifdef OBJECTDEBUG - if (debugReporting.and.mpiSelf%isMaster()) call displayMessage(var_str('functionClass[own] (class : ownerName : ownerLoc : objectLoc : sourceLoc): darkmatterprofiledmo_ : [destination] : ')//loc(destination)//' : '//loc(destination%darkMatterProfileDMO_)//' : '//{introspection:location:compact},verbosityLevelSilent) -#endif - end if - nullify(destination%galacticStructure_) - if (associated(self%galacticStructure_)) then - allocate(destination%galacticStructure_,mold=self%galacticStructure_) - call galacticStructureDeepCopy_(self%galacticStructure_,destination%galacticStructure_) -#ifdef OBJECTDEBUG - if (debugReporting.and.mpiSelf%isMaster()) call displayMessage(var_str('functionClass[own] (class : ownerName : ownerLoc : objectLoc : sourceLoc): galacticstructure : [destination] : ')//loc(destination)//' : '//loc(destination%galacticStructure_)//' : '//{introspection:location:compact},verbosityLevelSilent) -#endif - end if - nullify(destination%darkMatterHaloScale_) - if (associated(self%darkMatterHaloScale_)) then - if (associated(self%darkMatterHaloScale_%copiedSelf)) then - select type(s => self%darkMatterHaloScale_%copiedSelf) - class is (darkMatterHaloScaleClass) - destination%darkMatterHaloScale_ => s - class default - call Error_Report('copiedSelf has incorrect type'//{introspection:location}) - end select - call self%darkMatterHaloScale_%copiedSelf%referenceCountIncrement() - else - allocate(destination%darkMatterHaloScale_,mold=self%darkMatterHaloScale_) - call self%darkMatterHaloScale_%deepCopy(destination%darkMatterHaloScale_) - self%darkMatterHaloScale_%copiedSelf => destination%darkMatterHaloScale_ - call destination%darkMatterHaloScale_%autoHook() - end if -#ifdef OBJECTDEBUG - if (debugReporting.and.mpiSelf%isMaster()) call displayMessage(var_str('functionClass[own] (class : ownerName : ownerLoc : objectLoc : sourceLoc): darkmatterhaloscale : [destination] : ')//loc(destination)//' : '//loc(destination%darkMatterHaloScale_)//' : '//{introspection:location:compact},verbosityLevelSilent) -#endif - end if - call destination%finder%deepCopyActions() + end do + ! Compute the total baryonic mass. + massBaryonicTotal=+massBaryonicSubhalos & + & +massBaryonicSelf + ! Limit masses to physical values. + massBaryonicSelf =max(massBaryonicSelf ,0.0d0) + massBaryonicTotal=max(massBaryonicTotal,0.0d0) + ! Compute the fraction of matter assumed to be distributed like the dark matter. + basic => node%basic() + darkMatterDistributedFraction = min(self%darkMatterFraction+(massBaryonicTotal-massBaryonicSelf)/basic%mass(),1.0d0) + ! Compute the initial mass fraction. + massFractionInitial = min(self%darkMatterFraction+ massBaryonicTotal /basic%mass(),1.0d0) + class default + call Error_Report('unexpected class'//{introspection:location}) + end select class default - call Error_Report('destination and source types do not match'//{introspection:location}) - end select - return - end subroutine adiabaticGnedin2004DeepCopy - - subroutine adiabaticGnedin2004DeepCopyAssign(self,destination) - !!{ - Perform pointer assignment during a deep copy of the object. - !!} - implicit none - class(darkMatterProfileAdiabaticGnedin2004), intent(inout) :: self - class(darkMatterProfileClass ), intent(inout), target :: destination - - select type (destination) - type is (darkMatterProfileAdiabaticGnedin2004) - self%recursiveSelf => destination + call Error_Report('unexpected class'//{introspection:location}) end select return - end subroutine adiabaticGnedin2004DeepCopyAssign - - subroutine adiabaticGnedin2004FindParent(self) - !!{ - Find the deep-copied parent of a recursive child. - !!} - use :: Error, only : Error_Report - implicit none - class(darkMatterProfileAdiabaticGnedin2004), intent(inout) :: self - - if (self%parentDeferred) then - if (associated(self%recursiveSelf%recursiveSelf)) then - self%recursiveSelf => self%recursiveSelf%recursiveSelf - else - call Error_Report("recursive child's parent was not copied"//{introspection:location}) - end if - self%parentDeferred=.false. - end if - return - end subroutine adiabaticGnedin2004FindParent + end subroutine adiabaticGnedin2004Initialize diff --git a/source/dark_matter_profiles.dark_matter_only.F90 b/source/dark_matter_profiles.dark_matter_only.F90 index a08600f6df..290f5a4486 100644 --- a/source/dark_matter_profiles.dark_matter_only.F90 +++ b/source/dark_matter_profiles.dark_matter_only.F90 @@ -21,8 +21,8 @@ An implementation of non-dark-matter-only dark matter halo profiles which are unchanged from their dark-matter-only counterpart. !!} - use :: Cosmology_Parameters , only : cosmologyParameters , cosmologyParametersClass - use :: Dark_Matter_Profiles_DMO, only : darkMatterProfileDMO, darkMatterProfileDMOClass + use :: Cosmology_Parameters , only : cosmologyParametersClass + use :: Dark_Matter_Profiles_DMO, only : darkMatterProfileDMOClass !![ @@ -38,23 +38,8 @@ class (darkMatterProfileDMOClass), pointer :: darkMatterProfileDMO_ => null() double precision :: darkMatterFraction contains - final :: darkMatterOnlyDestructor - procedure :: density => darkMatterOnlyDensity - procedure :: densityLogSlope => darkMatterOnlyDensityLogSlope - procedure :: radiusEnclosingDensity => darkMatterOnlyRadiusEnclosingDensity - procedure :: radiusEnclosingMass => darkMatterOnlyRadiusEnclosingMass - procedure :: radialMoment => darkMatterOnlyRadialMoment - procedure :: enclosedMass => darkMatterOnlyEnclosedMass - procedure :: potential => darkMatterOnlyPotential - procedure :: circularVelocity => darkMatterOnlyCircularVelocity - procedure :: circularVelocityMaximum => darkMatterOnlyCircularVelocityMaximum - procedure :: radialVelocityDispersion => darkMatterOnlyRadialVelocityDispersion - procedure :: radiusFromSpecificAngularMomentum => darkMatterOnlyRadiusFromSpecificAngularMomentum - procedure :: rotationNormalization => darkMatterOnlyRotationNormalization - procedure :: energy => darkMatterOnlyEnergy - procedure :: kSpace => darkMatterOnlyKSpace - procedure :: freefallRadius => darkMatterOnlyFreefallRadius - procedure :: freefallRadiusIncreaseRate => darkMatterOnlyFreefallRadiusIncreaseRate + final :: darkMatterOnlyDestructor + procedure :: get => darkMatterOnlyGet end type darkMatterProfileDarkMatterOnly interface darkMatterProfileDarkMatterOnly @@ -72,30 +57,27 @@ function darkMatterOnlyConstructorParameters(parameters) result(self) Constructor for the {\normalfont \ttfamily darkMatterOnly} non-dark-matter-only dark matter halo profile class which takes a parameter set as input. !!} - use :: Input_Parameters, only : inputParameter, inputParameters + use :: Input_Parameters, only : inputParameters implicit none type (darkMatterProfileDarkMatterOnly) :: self type (inputParameters ), intent(inout) :: parameters class(cosmologyParametersClass ), pointer :: cosmologyParameters_ class(darkMatterProfileDMOClass ), pointer :: darkMatterProfileDMO_ - class(darkMatterHaloScaleClass ), pointer :: darkMatterHaloScale_ !![ - !!] - self=darkMatterProfileDarkMatterOnly(cosmologyParameters_,darkMatterHaloScale_,darkMatterProfileDMO_) + self=darkMatterProfileDarkMatterOnly(cosmologyParameters_,darkMatterProfileDMO_) !![ - !!] return end function darkMatterOnlyConstructorParameters - function darkMatterOnlyConstructorInternal(cosmologyParameters_,darkMatterHaloScale_,darkMatterProfileDMO_) result(self) + function darkMatterOnlyConstructorInternal(cosmologyParameters_,darkMatterProfileDMO_) result(self) !!{ Generic constructor for the {\normalfont \ttfamily darkMatterOnly} dark matter profile class. !!} @@ -103,9 +85,8 @@ function darkMatterOnlyConstructorInternal(cosmologyParameters_,darkMatterHaloSc type (darkMatterProfileDarkMatterOnly) :: self class(cosmologyParametersClass ), intent(in ), target :: cosmologyParameters_ class(darkMatterProfileDMOClass ), intent(in ), target :: darkMatterProfileDMO_ - class(darkMatterHaloScaleClass ), intent(in ), target :: darkMatterHaloScale_ !![ - + !!] ! Evaluate the dark matter fraction. @@ -124,238 +105,79 @@ subroutine darkMatterOnlyDestructor(self) !![ - !!] return end subroutine darkMatterOnlyDestructor - double precision function darkMatterOnlyDensity(self,node,radius) + function darkMatterOnlyGet(self,node,weightBy,weightIndex) result(massDistribution_) !!{ - Returns the density (in $M_\odot$ Mpc$^{-3}$) in the dark matter profile of {\normalfont \ttfamily node} at the given - {\normalfont \ttfamily radius} (given in units of Mpc). + Return the dark matter mass distribution for the given {\normalfont \ttfamily node}. !!} + use :: Galactic_Structure_Options, only : weightByMass + use :: Mass_Distributions , only : massDistributionSpherical, massDistributionSphericalScaler, kinematicsDistributionSphericalScaler, kinematicsDistributionClass + use :: Error , only : Error_Report implicit none - class (darkMatterProfileDarkMatterOnly), intent(inout) :: self - type (treeNode ), intent(inout) :: node - double precision , intent(in ) :: radius - - darkMatterOnlyDensity=+self%darkMatterFraction & - & *self%darkMatterProfileDMO_%density(node,radius) - return - end function darkMatterOnlyDensity - - double precision function darkMatterOnlyDensityLogSlope(self,node,radius) - !!{ - Returns the logarithmic slope of the density in the dark matter profile of {\normalfont \ttfamily node} at the given - {\normalfont \ttfamily radius} (given in units of Mpc). - !!} - implicit none - class (darkMatterProfileDarkMatterOnly), intent(inout) :: self - type (treeNode ), intent(inout) :: node - double precision , intent(in ) :: radius - - darkMatterOnlyDensityLogSlope=self%darkMatterProfileDMO_%densityLogSlope(node,radius) - return - end function darkMatterOnlyDensityLogSlope - - double precision function darkMatterOnlyRadiusEnclosingDensity(self,node,density) - !!{ - Returns the radius (in Mpc) in the dark matter profile of {\normalfont \ttfamily node} which encloses the given - {\normalfont \ttfamily density} (given in units of $M_\odot/$Mpc$^{-3}$). - !!} - implicit none - class (darkMatterProfileDarkMatterOnly), intent(inout), target :: self - type (treeNode ), intent(inout), target :: node - double precision , intent(in ) :: density - - darkMatterOnlyRadiusEnclosingDensity=self%darkMatterProfileDMO_%radiusEnclosingDensity(node,density/self%darkMatterFraction) - return - end function darkMatterOnlyRadiusEnclosingDensity - - double precision function darkMatterOnlyRadiusEnclosingMass(self,node,mass) - !!{ - Returns the radius (in Mpc) in the dark matter profile of {\normalfont \ttfamily node} which encloses the given - {\normalfont \ttfamily mass} (given in units of $M_\odot$). - !!} - implicit none - class (darkMatterProfileDarkMatterOnly), intent(inout), target :: self - type (treeNode ), intent(inout), target :: node - double precision , intent(in ) :: mass - - darkMatterOnlyRadiusEnclosingMass=self%darkMatterProfileDMO_%radiusEnclosingMass(node,mass/self%darkMatterFraction) - return - end function darkMatterOnlyRadiusEnclosingMass - - double precision function darkMatterOnlyRadialMoment(self,node,moment,radiusMinimum,radiusMaximum) - !!{ - Returns the density (in $M_\odot$ Mpc$^{-3}$) in the dark matter profile of {\normalfont \ttfamily node} at the given - {\normalfont \ttfamily radius} (given in units of Mpc). - !!} - implicit none - class (darkMatterProfileDarkMatterOnly), intent(inout) :: self - type (treeNode ), intent(inout) :: node - double precision , intent(in ) :: moment - double precision , intent(in ), optional :: radiusMinimum, radiusMaximum - - darkMatterOnlyRadialMoment=+self%darkMatterFraction & - & *self%darkMatterProfileDMO_%radialMoment(node,moment,radiusMinimum,radiusMaximum) - return - end function darkMatterOnlyRadialMoment - - double precision function darkMatterOnlyEnclosedMass(self,node,radius) - !!{ - Returns the enclosed mass (in $M_\odot$) in the dark matter profile of {\normalfont \ttfamily node} at the given {\normalfont \ttfamily radius} (given in - units of Mpc). - !!} - implicit none - class (darkMatterProfileDarkMatterOnly), intent(inout) :: self - type (treeNode ), intent(inout) :: node - double precision , intent(in ) :: radius - - darkMatterOnlyEnclosedMass=+self%darkMatterFraction & - & *self%darkMatterProfileDMO_%enclosedMass(node,radius) - return - end function darkMatterOnlyEnclosedMass - - double precision function darkMatterOnlyPotential(self,node,radius,status) - !!{ - Returns the potential (in (km/s)$^2$) in the dark matter profile of {\normalfont \ttfamily node} at the given {\normalfont - \ttfamily radius} (given in units of Mpc). - !!} - implicit none - class (darkMatterProfileDarkMatterOnly ), intent(inout), target :: self - type (treeNode ), intent(inout), target :: node - double precision , intent(in ) :: radius - type (enumerationStructureErrorCodeType), intent( out), optional :: status - - darkMatterOnlyPotential=+self%darkMatterFraction & - & *self%darkMatterProfileDMO_%potential(node,radius,status) - return - end function darkMatterOnlyPotential - - double precision function darkMatterOnlyCircularVelocity(self,node,radius) - !!{ - Returns the circular velocity (in km/s) in the dark matter profile of {\normalfont \ttfamily node} at the given - {\normalfont \ttfamily radius} (given in units of Mpc). - !!} - implicit none - class (darkMatterProfileDarkMatterOnly), intent(inout) :: self - type (treeNode ), intent(inout) :: node - double precision , intent(in ) :: radius - - darkMatterOnlyCircularVelocity=+sqrt(self%darkMatterFraction ) & - & * self%darkMatterProfileDMO_%circularVelocity(node,radius) - return - end function darkMatterOnlyCircularVelocity - - double precision function darkMatterOnlyCircularVelocityMaximum(self,node) - !!{ - Returns the maximum circular velocity (in km/s) in the dark matter profile of {\normalfont \ttfamily node}. - !!} - implicit none - class(darkMatterProfileDarkMatterOnly), intent(inout) :: self - type (treeNode ), intent(inout) :: node - - darkMatterOnlyCircularVelocityMaximum=+sqrt(self%darkMatterFraction ) & - & * self%darkMatterProfileDMO_%circularVelocityMaximum(node) - return - end function darkMatterOnlyCircularVelocityMaximum - - double precision function darkMatterOnlyRadialVelocityDispersion(self,node,radius) - !!{ - Returns the radial velocity dispersion (in km/s) in the dark matter profile of {\normalfont \ttfamily node} at the given - {\normalfont \ttfamily radius} (given in units of Mpc). - !!} - implicit none - class (darkMatterProfileDarkMatterOnly), intent(inout) :: self - type (treeNode ), intent(inout) :: node - double precision , intent(in ) :: radius - - darkMatterOnlyRadialVelocityDispersion=+sqrt(self%darkMatterFraction ) & - & * self%darkMatterProfileDMO_%radialVelocityDispersion(node,radius) - return - end function darkMatterOnlyRadialVelocityDispersion - - double precision function darkMatterOnlyRadiusFromSpecificAngularMomentum(self,node,specificAngularMomentum) - !!{ - Returns the radius (in Mpc) in {\normalfont \ttfamily node} at which a circular orbit has the given {\normalfont \ttfamily specificAngularMomentum} (given - in units of km s$^{-1}$ Mpc). - !!} - implicit none - class (darkMatterProfileDarkMatterOnly), intent(inout), target :: self - type (treeNode ), intent(inout) :: node - double precision , intent(in ) :: specificAngularMomentum - - darkMatterOnlyRadiusFromSpecificAngularMomentum=self%darkMatterProfileDMO_%radiusFromSpecificAngularMomentum(node,specificAngularMomentum/sqrt(self%darkMatterFraction)) - return - end function darkMatterOnlyRadiusFromSpecificAngularMomentum - - double precision function darkMatterOnlyRotationNormalization(self,node) - !!{ - Return the normalization of the rotation velocity vs. specific angular momentum relation. - !!} - implicit none - class(darkMatterProfileDarkMatterOnly), intent(inout) :: self - type (treeNode ), intent(inout) :: node - - darkMatterOnlyRotationNormalization=self%darkMatterProfileDMO_%rotationNormalization(node) - return - end function darkMatterOnlyRotationNormalization - - double precision function darkMatterOnlyEnergy(self,node) - !!{ - Return the energy of the dark matter halo density profile. - !!} - implicit none - class(darkMatterProfileDarkMatterOnly), intent(inout) :: self - type (treeNode ), intent(inout) :: node - - darkMatterOnlyEnergy=+self%darkMatterFraction **2 & - & *self%darkMatterProfileDMO_%energy(node) - return - end function darkMatterOnlyEnergy - - double precision function darkMatterOnlyKSpace(self,node,waveNumber) - !!{ - Returns the Fourier transform of the dark matter halo density profile at the specified {\normalfont \ttfamily waveNumber} - (given in Mpc$^{-1}$). - !!} - implicit none - class (darkMatterProfileDarkMatterOnly), intent(inout) :: self - type (treeNode ), intent(inout), target :: node - double precision , intent(in ) :: waveNumber - - ! This is normalized by mass, so no need to include the dark matter fraction. - darkMatterOnlyKSpace=+self%darkMatterProfileDMO_%kSpace(node,waveNumber) - return - end function darkMatterOnlyKSpace - - double precision function darkMatterOnlyFreefallRadius(self,node,time) - !!{ - Returns the freefall radius in the dark matter halo density profile at the specified {\normalfont \ttfamily time} (given in - Gyr). - !!} - implicit none - class (darkMatterProfileDarkMatterOnly), intent(inout) :: self - type (treeNode ), intent(inout) :: node - double precision , intent(in ) :: time - - darkMatterOnlyFreefallRadius=self%darkMatterProfileDMO_%freefallRadius(node,time*sqrt(self%darkMatterFraction)) - return - end function darkMatterOnlyFreefallRadius - - double precision function darkMatterOnlyFreefallRadiusIncreaseRate(self,node,time) - !!{ - Returns the freefall radius in the dark matter halo density profile at the specified {\normalfont \ttfamily time} (given in - Gyr). - !!} - implicit none - class (darkMatterProfileDarkMatterOnly), intent(inout) :: self - type (treeNode ), intent(inout) :: node - double precision , intent(in ) :: time + class (massDistributionClass ), pointer :: massDistribution_ + type (kinematicsDistributionSphericalScaler), pointer :: kinematicsDistribution_ + class (kinematicsDistributionClass ), pointer :: kinematicsDistributionDMO + class (darkMatterProfileDarkMatterOnly ), intent(inout), target :: self + type (treeNode ), intent(inout), target :: node + type (enumerationWeightByType ), intent(in ), optional :: weightBy + integer , intent(in ), optional :: weightIndex + class (massDistributionClass ), pointer :: massDistributionDMO + !![ + + !!] - darkMatterOnlyFreefallRadiusIncreaseRate=+ sqrt(self%darkMatterFraction) & - & *self%darkMatterProfileDMO_%freefallRadiusIncreaseRate(node,time*sqrt(self%darkMatterFraction)) + ! Assume a null distribution by default. + massDistribution_ => null() + ! If weighting is not by mass, return a null profile. + if (weightBy_ /= weightByMass) return + ! Get the dark matter-only mass distribution. + massDistributionDMO => self %darkMatterProfileDMO_%get (node,weightBy,weightIndex) + kinematicsDistributionDMO => massDistributionDMO %kinematicsDistribution( ) + if (.not.associated(massDistributionDMO)) return + select type (massDistributionDMO) + class is (massDistributionSpherical) + ! Create the mass distribution. + allocate(massDistributionSphericalScaler :: massDistribution_) + select type(massDistribution_) + type is (massDistributionSphericalScaler) + !![ + + + massDistributionSphericalScaler( & + & factorScalingLength= 1.0d0 , & + & factorScalingMass =self%darkMatterFraction , & + & massDistribution_ = massDistributionDMO & + & ) + + + !!] + end select + allocate(kinematicsDistribution_) + !![ + + + kinematicsDistributionSphericalScaler( & + & factorScalingLength = 1.0d0 , & + & factorScalingMass =self%darkMatterFraction , & + & kinematicsDistribution_= kinematicsDistributionDMO & + & ) + + + !!] + call massDistribution_%setKinematicsDistribution(kinematicsDistribution_) + !![ + + !!] + class default + call Error_Report('a spherical mass distribution is required'//{introspection:location}) + end select + !![ + + + !!] return - end function darkMatterOnlyFreefallRadiusIncreaseRate + end function darkMatterOnlyGet diff --git a/source/dark_matter_profiles.generic.F90 b/source/dark_matter_profiles.generic.F90 deleted file mode 100644 index 41e5264338..0000000000 --- a/source/dark_matter_profiles.generic.F90 +++ /dev/null @@ -1,1309 +0,0 @@ -!! Copyright 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, -!! 2019, 2020, 2021, 2022, 2023, 2024 -!! Andrew Benson -!! -!! This file is part of Galacticus. -!! -!! Galacticus is free software: you can redistribute it and/or modify -!! it under the terms of the GNU General Public License as published by -!! the Free Software Foundation, either version 3 of the License, or -!! (at your option) any later version. -!! -!! Galacticus is distributed in the hope that it will be useful, -!! but WITHOUT ANY WARRANTY; without even the implied warranty of -!! MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -!! GNU General Public License for more details. -!! -!! You should have received a copy of the GNU General Public License -!! along with Galacticus. If not, see . - -!!{ -Contains a module which implements a base class for dark matter profiles from which both dark-matter-only and -non-dark-matter-only profiles inherit. -!!} - -module Dark_Matter_Profiles_Generic - !!{ - A base class for dark matter profiles from which both dark-matter-only and non-dark-matter-only profiles inherit. Implements - numerical calculations of certain halo properties which are to be used as a fall-back option when no analytical solution - exists. - !!} - use :: Dark_Matter_Halo_Scales, only : darkMatterHaloScaleClass - use :: Function_Classes , only : functionClass - use :: Galacticus_Nodes , only : nodeComponentBasic , nodeComponentDarkMatterProfile, treeNode - use :: Kind_Numbers , only : kind_int8 - use :: Numerical_Interpolation, only : interpolator - private - public :: darkMatterProfileGeneric - - !![ - - !!] - type, extends(functionClass), abstract :: darkMatterProfileGeneric - !!{ - A dark matter halo profile class implementing numerical calculations for generic dark matter halos. - !!} - ! Note that the following components can not be "private", as private components of parent types which are accessed through a - ! "USE" association are inaccessible to the child type - ! (e.g. https://www.ibm.com/support/knowledgecenter/SSGH4D_15.1.3/com.ibm.xlf1513.aix.doc/language_ref/extensible.html). - class (darkMatterHaloScaleClass), pointer :: darkMatterHaloScale_ => null() - ! Tolerances used in numerical solutions. - double precision :: toleranceRelativeVelocityDispersion = 1.0d-6 - double precision :: toleranceRelativeVelocityDispersionMaximum = 1.0d-3 - double precision :: toleranceRelativePotential = 1.0d-6 - ! Unique ID for memoization - integer (kind_int8 ) :: genericLastUniqueID - ! Memoized solutions for the radial velocity dispersion. - double precision , allocatable, dimension(:) :: genericVelocityDispersionRadialVelocity , genericVelocityDispersionRadialRadius - double precision :: genericVelocityDispersionRadialRadiusMinimum , genericVelocityDispersionRadialRadiusMaximum, & - & genericVelocityDispersionRadialRadiusOuter - type (interpolator ), allocatable :: genericVelocityDispersionRadial - ! Memoized solutions for the enclosed mass. - double precision , allocatable, dimension(:) :: genericEnclosedMassMass , genericEnclosedMassRadius - double precision :: genericEnclosedMassRadiusMinimum , genericEnclosedMassRadiusMaximum - type (interpolator ), allocatable :: genericEnclosedMass - contains - !![ - - - - - - - - - - - - - - - - - - - - - - - - - - - - !!] - procedure(genericDensityInterface ), deferred :: density - procedure(genericEnclosedMassNumerical), deferred :: enclosedMass - procedure :: enclosedMassNumerical => genericEnclosedMassNumerical - procedure :: enclosedMassDifferenceNumerical => genericEnclosedMassDifferenceNumerical - procedure :: potentialNumerical => genericPotentialNumerical - procedure :: potentialDifferenceNumerical => genericPotentialDifferenceNumerical - procedure :: circularVelocityNumerical => genericCircularVelocityNumerical - procedure :: radialVelocityDispersionNumerical => genericRadialVelocityDispersionNumerical - procedure :: jeansEquationIntegrand => genericJeansEquationIntegrand - procedure :: jeansEquationRadius => genericJeansEquationRadius - procedure :: radialMomentNumerical => genericRadialMomentNumerical - procedure :: rotationNormalizationNumerical => genericRotationNormalizationNumerical - procedure :: kSpaceNumerical => genericKSpaceNumerical - procedure :: energyNumerical => genericEnergyNumerical - procedure :: freefallRadiusNumerical => genericFreefallRadiusNumerical - procedure :: freefallRadiusIncreaseRateNumerical => genericFreefallRadiusIncreaseRateNumerical - procedure :: radiusEnclosingDensityNumerical => genericRadiusEnclosingDensityNumerical - procedure :: radiusEnclosingMassNumerical => genericRadiusEnclosingMassNumerical - procedure :: circularVelocityMaximumNumerical => genericCircularVelocityMaximumNumerical - procedure :: radiusCircularVelocityMaximumNumerical => genericRadiusCircularVelocityMaximumNumerical - procedure :: radiusFromSpecificAngularMomentumNumerical => genericRadiusFromSpecificAngularMomentumNumerical - procedure :: densityLogSlopeNumerical => genericDensityLogSlopeNumerical - procedure :: solverSet => genericSolverSet - procedure , nopass :: solverUnset => genericSolverUnset - procedure :: calculationResetGeneric => genericCalculationResetGeneric - end type darkMatterProfileGeneric - - abstract interface - double precision function genericDensityInterface(self,node,radius) - !!{ - Returns the density (in $M_\odot/$Mpc$^3$) in the dark matter profile of {\normalfont \ttfamily node} at the given {\normalfont \ttfamily radius} (given in - units of Mpc). - !!} - import darkMatterProfileGeneric, treeNode - class (darkMatterProfileGeneric), intent(inout) :: self - type (treeNode ), intent(inout) :: node - double precision , intent(in ) :: radius - end function genericDensityInterface - end interface - - ! Module-scope pointers used in integrand functions and root finding. - type :: genericSolver - class(darkMatterProfileGeneric), pointer :: self => null() - type (treeNode ), pointer :: node => null() - end type genericSolver - type (genericSolver ), allocatable, dimension(:) :: solvers - integer , parameter :: solversIncrement =10 - integer :: solversCount = 0 - class (nodeComponentBasic ), pointer :: genericBasic - class (nodeComponentDarkMatterProfile), pointer :: genericDarkMatterProfile - double precision :: genericTime , genericRadiusFreefall , genericDensity , genericMass , & - & genericSpecificAngularMomentum , genericMassGrowthRate , genericScaleGrowthRate, genericScale, & - & genericShape , genericShapeGrowthRate - !$omp threadprivate(solvers,solversCount,genericBasic,genericTime,genericRadiusFreefall,genericDensity,genericMass,genericSpecificAngularMomentum,genericMassGrowthRate,genericDarkMatterProfile,genericScaleGrowthRate,genericScale,genericShape,genericShapeGrowthRate) - - !![ - - nonAnalyticSolvers - Used to specify the type of solution to use when no analytic solution is available. - yes - public - yes - - - - !!] - -contains - - double precision function genericEnclosedMassNumerical(self,node,radius) - !!{ - Returns the enclosed mass (in $M_\odot$) in the dark matter profile of {\normalfont \ttfamily node} at the given {\normalfont \ttfamily radius} (given in - units of Mpc). - !!} - implicit none - class (darkMatterProfileGeneric ), intent(inout) :: self - type (treeNode ), intent(inout) :: node - double precision , intent(in ) :: radius - - genericEnclosedMassNumerical=self%enclosedMassDifferenceNumerical(node,0.0d0,radius) - return - end function genericEnclosedMassNumerical - - double precision function genericEnclosedMassDifferenceNumerical(self,node,radiusLower,radiusUpper) - !!{ - Returns the enclosed mass difference (in $M_\odot$) in the dark matter profile of {\normalfont \ttfamily node} between the - given {\normalfont \ttfamily radiusLower} and {\normalfont \ttfamily radiusUpper} (given in units of Mpc) using a numerical - calculation. - !!} - use, intrinsic :: ISO_C_Binding , only : c_size_t - use :: Numerical_Integration , only : integrator - use :: Numerical_Ranges , only : Make_Range , rangeTypeLogarithmic - use :: Table_Labels , only : extrapolationTypeExtrapolate - use :: Numerical_Interpolation, only : gsl_interp_linear - use :: Error , only : Error_Report - implicit none - class (darkMatterProfileGeneric), intent(inout), target :: self - type (treeNode ), intent(inout), target :: node - double precision , intent(in ) :: radiusLower , radiusUpper - double precision , parameter :: countPointsPerOctave =4.0d+00 - double precision , parameter :: radiusVirialFractionSmall=1.0d-12 - double precision , dimension(:) , allocatable :: masses , radii - type (integrator ), save :: integrator_ - logical , save :: initialized =.false. - !$omp threadprivate(integrator_,initialized) - integer (c_size_t ) :: countRadii , iMinimum , & - & iMaximum , i - logical :: remakeTable - double precision :: radiusIntegralLower , radiusIntegralUpper, & - & radiusMinimum , radiusMaximum , & - & radiusVirial - - ! Validate input. - if (radiusUpper < radiusLower) call Error_Report('radiusUpper ≥ radiusLower is required'//{introspection:location}) - if (radiusUpper <= 0.0d0) then - genericEnclosedMassDifferenceNumerical=0.0d0 - return - end if - ! Reset calculations if necessary. - if (node%uniqueID() /= self%genericLastUniqueID) call self%calculationResetGeneric(node,node%uniqueID()) - ! Determine if the table must be rebuilt. - remakeTable=.false. - if (.not.allocated(self%genericEnclosedMassMass)) then - remakeTable=.true. - else - remakeTable= radiusLower < self%genericEnclosedMassRadiusMinimum & - & .or. & - & radiusUpper > self%genericEnclosedMassRadiusMaximum - end if - if (remakeTable) then - ! Initialize integrator if necessary. - if (.not.initialized) then - integrator_=integrator(genericMassIntegrand,toleranceRelative=1.0d-2) - initialized=.true. - end if - ! Find the range of radii at which to compute the enclosed mass, and construct the arrays. - call self%solverSet (node) - radiusVirial =self%darkMatterHaloScale_%radiusVirial(node) - !! Set an initial range of radii that brackets the requested radii. - if (radiusLower <= 0.0d0) then - radiusMinimum=max(0.5d0*radiusUpper,radiusVirial*radiusVirialFractionSmall) - else - radiusMinimum=max(0.5d0*radiusLower,radiusVirial*radiusVirialFractionSmall) - end if - radiusMaximum=2.0d0*radiusUpper - !! Round to the nearest factor of 2. - radiusMinimum=2.0d0**floor (log(radiusMinimum)/log(2.0d0)) - radiusMaximum=2.0d0**ceiling(log(radiusMaximum)/log(2.0d0)) - !! Expand to encompass any pre-existing range. - if (allocated(self%genericEnclosedMassRadius)) then - radiusMinimum=min(radiusMinimum,self%genericEnclosedMassRadiusMinimum) - radiusMaximum=max(radiusMaximum,self%genericEnclosedMassRadiusMaximum) - end if - !! Construct arrays. - countRadii=nint(log(radiusMaximum/radiusMinimum)/log(2.0d0)*countPointsPerOctave+1.0d0) - allocate(radii (countRadii)) - allocate(masses(countRadii)) - radii=Make_Range(radiusMinimum,radiusMaximum,int(countRadii),rangeTypeLogarithmic) - ! Copy in any usable results from any previous solution. - !! Assume by default that no previous solutions are usable. - iMinimum=+huge(0_c_size_t) - iMaximum=-huge(0_c_size_t) - !! Check that a pre-existing solution exists. - if (allocated(self%genericEnclosedMassRadius)) then - iMinimum=nint(log(self%genericEnclosedMassRadiusMinimum/radiusMinimum)/log(2.0d0)*countPointsPerOctave)+1_c_size_t - iMaximum=nint(log(self%genericEnclosedMassRadiusMaximum/radiusMinimum)/log(2.0d0)*countPointsPerOctave)+1_c_size_t - masses(iMinimum:iMaximum)=self%genericEnclosedMassMass - end if - ! Solve for the enclosed mass where old results were unavailable. - do i=1,countRadii - ! Skip cases for which we have a pre-existing solution. - if (i >= iMinimum .and. i <= iMaximum) cycle - ! Find the limits for the integral. - if (i == 1) then - radiusIntegralLower=0.0d0 - else - radiusIntegralLower=radii(i-1) - end if - radiusIntegralUpper =radii(i ) - ! Evaluate the integral. - masses (i)= integrator_%integrate(radiusIntegralLower,radiusIntegralUpper) - if (i > 1) masses(i)=+masses(i ) & - & +masses(i-1) - end do - call self%solverUnset( ) - ! Build the interpolator. - if (allocated(self%genericEnclosedMass)) deallocate(self%genericEnclosedMass) - allocate(self%genericEnclosedMass) - self%genericEnclosedMass=interpolator(log(radii),log(masses),interpolationType=gsl_interp_linear,extrapolationType=extrapolationTypeExtrapolate) - ! Store the current results for future re-use. - if (allocated(self%genericEnclosedMassRadius)) deallocate(self%genericEnclosedMassRadius) - if (allocated(self%genericEnclosedMassMass )) deallocate(self%genericEnclosedMassMass ) - allocate(self%genericEnclosedMassRadius(countRadii)) - allocate(self%genericEnclosedMassMass (countRadii)) - self%genericEnclosedMassRadius =radii - self%genericEnclosedMassMass =masses - self%genericEnclosedMassRadiusMinimum=radiusMinimum - self%genericEnclosedMassRadiusMaximum=radiusMaximum - end if - ! Interpolate in the table to find the mass difference. - genericEnclosedMassDifferenceNumerical =+exp(self%genericEnclosedMass %interpolate(log(radiusUpper))) - if (radiusLower > 0.0d0) & - & genericEnclosedMassDifferenceNumerical=+ genericEnclosedMassDifferenceNumerical & - & +exp(self%genericEnclosedMass %interpolate(log(radiusLower))) - return - end function genericEnclosedMassDifferenceNumerical - - double precision function genericMassIntegrand(radius) - !!{ - Integrand for mass in generic dark matter profiles. - !!} - use :: Numerical_Constants_Math, only : Pi - implicit none - double precision, intent(in ) :: radius - - if (radius > 0.0d0) then - genericMassIntegrand=4.0d0*Pi*radius**2*solvers(solversCount)%self%density(solvers(solversCount)%node,radius) - else - genericMassIntegrand=0.0d0 - end if - return - end function genericMassIntegrand - - double precision function genericPotentialNumerical(self,node,radius,status) - !!{ - Returns the potential (in (km/s)$^2$) in the dark matter profile of {\normalfont \ttfamily node} at the given {\normalfont - \ttfamily radius} (given in units of Mpc) using a numerical calculation. - !!} - use :: Galactic_Structure_Options , only : enumerationStructureErrorCodeType, structureErrorCodeSuccess - use :: Numerical_Constants_Astronomical, only : gravitationalConstantGalacticus - use :: Numerical_Integration , only : integrator - implicit none - class (darkMatterProfileGeneric ), intent(inout), target :: self - type (treeNode ), intent(inout), target :: node - double precision , intent(in ) :: radius - type (enumerationStructureErrorCodeType), intent( out), optional :: status - double precision , parameter :: radiusMaximumFactor=1.0d2 - type (integrator ), save :: integrator_ - logical , save :: initialized =.false. - !$omp threadprivate(integrator_,initialized) - double precision :: radiusMaximum - - if (present(status)) status=structureErrorCodeSuccess - if (.not.initialized) then - integrator_=integrator(integrandPotential,toleranceRelative=self%toleranceRelativePotential) - initialized=.true. - end if - call self%solverSet (node) - radiusMaximum = +radiusMaximumFactor & - & *self%darkMatterHaloScale_%radiusVirial(node) - if (radius < radiusMaximum) then - genericPotentialNumerical = integrator_%integrate( & - & radius , & - & radiusMaximum & - & ) - else - ! Beyond some large radius approximate as a point mass. - genericPotentialNumerical=+gravitationalConstantGalacticus & - & *solvers(solversCount)%self%enclosedMass(solvers(solversCount)%node,radiusMaximum) & - & *( & - & +1.0d0/radiusMaximum & - & -1.0d0/radius & - & ) - end if - call self%solverUnset( ) - return - end function genericPotentialNumerical - - double precision function genericPotentialDifferenceNumerical(self,node,radiusLower,radiusUpper) - !!{ - Returns the potential difference (in (km/s)$^2$) in the dark matter profile of {\normalfont \ttfamily node} between the - given {\normalfont \ttfamily radiusLower} and {\normalfont \ttfamily radiusUpper} (given in units of Mpc) using a numerical - calculation. - !!} - use :: Galactic_Structure_Options, only : structureErrorCodeSuccess - use :: Numerical_Integration , only : integrator - implicit none - class (darkMatterProfileGeneric), intent(inout), target :: self - type (treeNode ), intent(inout), pointer :: node - double precision , intent(in ) :: radiusLower , radiusUpper - type (integrator ), save :: integrator_ - logical , save :: initialized=.false. - !$omp threadprivate(integrator_,initialized) - - if (.not.initialized) then - integrator_= integrator(integrandPotential,toleranceRelative=1.0d-6) - initialized=.true. - end if - call self%solverSet (node) - genericPotentialDifferenceNumerical=integrator_%integrate( & - & radiusLower, & - & radiusUpper & - & ) - call self%solverUnset( ) - return - end function genericPotentialDifferenceNumerical - - double precision function integrandPotential(radius) - !!{ - Integrand for gravitational potential in a generic dark matter profile. - !!} - use :: Numerical_Constants_Astronomical, only : gravitationalConstantGalacticus - implicit none - double precision, intent(in ) :: radius - - if (radius > 0.0d0) then - integrandPotential=-gravitationalConstantGalacticus & - & *solvers(solversCount)%self%enclosedMass(solvers(solversCount)%node,radius) & - & / radius **2 - else - integrandPotential=0.0d0 - end if - return - end function integrandPotential - - double precision function genericCircularVelocityNumerical(self,node,radius) - !!{ - Returns the circular velocity (in km/s) in the dark matter profile of {\normalfont \ttfamily node} at the given - {\normalfont \ttfamily radius} (given in units of Mpc). - !!} - use :: Numerical_Constants_Astronomical, only : gravitationalConstantGalacticus - implicit none - class (darkMatterProfileGeneric), intent(inout) :: self - type (treeNode ), intent(inout) :: node - double precision , intent(in ) :: radius - - if (radius > 0.0d0) then - genericCircularVelocityNumerical=sqrt( & - & +gravitationalConstantGalacticus & - & *self%enclosedMass(node,radius) & - & / radius & - & ) - else - genericCircularVelocityNumerical=0.0d0 - end if - return - end function genericCircularVelocityNumerical - - double precision function genericRadialVelocityDispersionNumerical(self,node,radius,radiusOuter) - !!{ - Returns the radial velocity dispersion (in km/s) in the dark matter profile of {\normalfont \ttfamily node} at the given - {\normalfont \ttfamily radius} (given in units of Mpc). - !!} - use, intrinsic :: ISO_C_Binding , only : c_size_t - use :: Error , only : Error_Report , errorStatusSuccess - use :: Numerical_Integration , only : integrator - use :: Numerical_Ranges , only : Make_Range , rangeTypeLogarithmic - use :: Table_Labels , only : extrapolationTypeFix - use :: Numerical_Interpolation, only : gsl_interp_linear - implicit none - class (darkMatterProfileGeneric), intent(inout), target :: self - type (treeNode ), intent(inout), target :: node - double precision , intent(in ) :: radius - double precision , intent(in ), optional :: radiusOuter - double precision , parameter :: radiusTinyFactor =1.0d-9 , radiusLargeFactor=5.0d2 - double precision , parameter :: countPointsPerOctave =2.0d0 - double precision , parameter :: toleranceFactor =2.0d0 - double precision , dimension(:) , allocatable :: velocityDispersions , radii - double precision :: radiusMinimum , radiusMaximum , & - & radiusVirial , density , & - & jeansIntegral , radiusOuter_ , & - & radiusLower , radiusUpper , & - & radiusLowerJeansEquation , radiusUpperJeansEquation, & - & jeansIntegralPrevious , toleranceRelative - integer (c_size_t ) :: countRadii , iMinimum , & - & iMaximum , i - integer :: status - type (integrator ), save :: integrator_ - logical , save :: initialized =.false. - logical :: remakeTable - !$omp threadprivate(integrator_,initialized) - - ! Reset calculations if necessary. - if (node%uniqueID() /= self%genericLastUniqueID) call self%calculationResetGeneric(node,node%uniqueID()) - ! Determine if the table must be rebuilt. - remakeTable=.false. - if (.not.allocated(self%genericVelocityDispersionRadialVelocity)) then - remakeTable=.true. - else - remakeTable= radius < self%genericVelocityDispersionRadialRadiusMinimum & - & .or. & - & radius > self%genericVelocityDispersionRadialRadiusMaximum - end if - if (remakeTable) then - ! Initialize integrator if necessary. - if (.not.initialized) then - integrator_=integrator(genericJeansEquationIntegrand_,toleranceRelative=self%toleranceRelativeVelocityDispersion) - initialized=.true. - end if - ! Find the range of radii at which to compute the velocity dispersion, and construct the arrays. - call self%solverSet (node) - radiusVirial =self%darkMatterHaloScale_%radiusVirial(node) - !! Set an initial range of radii that brackets the requested radius, but avoids tiny radii. - radiusMinimum=max(0.5d0*radius,radiusTinyFactor*radiusVirial) - radiusMaximum=max(2.0d0*radius, 2.0d0*radiusVirial) - !! Round to the nearest factor of 2. - radiusMinimum=2.0d0**floor (log(radiusMinimum)/log(2.0d0)) - radiusMaximum=2.0d0**ceiling(log(radiusMaximum)/log(2.0d0)) - !! Expand to encompass any pre-existing range. - if (allocated(self%genericVelocityDispersionRadialRadius)) then - radiusMinimum=min(radiusMinimum,self%genericVelocityDispersionRadialRadiusMinimum) - radiusMaximum=max(radiusMaximum,self%genericVelocityDispersionRadialRadiusMaximum) - end if - !! Set a suitable outer radius for integration. - if (present(radiusOuter)) then - radiusOuter_=radiusOuter - else - radiusOuter_=max(10.0d0*radiusMaximum,radiusLargeFactor*radiusVirial) - end if - !! Construct arrays. - countRadii=nint(log(radiusMaximum/radiusMinimum)/log(2.0d0)*countPointsPerOctave+1.0d0) - allocate(radii (countRadii)) - allocate(velocityDispersions(countRadii)) - radii=Make_Range(radiusMinimum,radiusMaximum,int(countRadii),rangeTypeLogarithmic) - ! Copy in any usable results from any previous solution. - !! Assume by default that no previous solutions are usable. - iMinimum=+huge(0_c_size_t) - iMaximum=-huge(0_c_size_t) - !! Check that a pre-existing solution exists. - if (allocated(self%genericVelocityDispersionRadialRadius)) then - !! Check that the outer radius for integration has not changed - if it has we need to recompute the full solution for - !! consistency. - if (radiusOuter_ == self%genericVelocityDispersionRadialRadiusOuter) then - iMinimum=nint(log(self%genericVelocityDispersionRadialRadiusMinimum/radiusMinimum)/log(2.0d0)*countPointsPerOctave)+1_c_size_t - iMaximum=nint(log(self%genericVelocityDispersionRadialRadiusMaximum/radiusMinimum)/log(2.0d0)*countPointsPerOctave)+1_c_size_t - velocityDispersions(iMinimum:iMaximum)=self%genericVelocityDispersionRadialVelocity - end if - end if - ! Solve for the velocity dispersion where old results were unavailable. - jeansIntegralPrevious=0.0d0 - do i=countRadii,1,-1 - ! Skip cases for which we have a pre-existing solution. - if (i >= iMinimum .and. i <= iMaximum) cycle - ! Find the limits for the integral. - if (i == countRadii) then - radiusUpper=radiusOuter_ - else - radiusUpper=radii(i+1) - end if - radiusLower =radii(i ) - ! Reset the accumulated Jeans integral if necessary. - if (i == iMinimum-1) jeansIntegralPrevious=+ velocityDispersions( iMinimum )**2 & - & *self%density (node,radii(iMinimum)) - ! If the interval is wholly outside of the outer radius, the integral is zero. - if (radiusLower > radiusOuter_) then - jeansIntegral =0.0d0 - velocityDispersions(i)=0.0d0 - else - ! Evaluate the integral. - density =self %density (node,radiusLower ) - radiusLowerJeansEquation=self %jeansEquationRadius(node,radiusLower ) - radiusUpperJeansEquation=self %jeansEquationRadius(node,radiusUpper ) - jeansIntegral =integrator_%integrate ( radiusLowerJeansEquation,radiusUpperJeansEquation,status) - if (status /= errorStatusSuccess) then - ! Integration failed. - toleranceRelative=+ toleranceFactor & - & *self%toleranceRelativeVelocityDispersion - do while (toleranceRelative < self%toleranceRelativeVelocityDispersionMaximum) - call integrator_%toleranceSet(toleranceRelative=toleranceRelative) - jeansIntegral=integrator_%integrate(radiusLowerJeansEquation,radiusUpperJeansEquation,status) - if (status == errorStatusSuccess) then - exit - else - toleranceRelative=+toleranceFactor & - & *toleranceRelative - end if - end do - if (status /= errorStatusSuccess) call Error_Report('integration of Jeans equation failed'//{introspection:location}) - call integrator_%toleranceSet(toleranceRelative=self%toleranceRelativeVelocityDispersion) - end if - if (density <= 0.0d0) then - ! Density is zero - the velocity dispersion is undefined. If the Jeans integral is also zero this is acceptable - we've - ! been asked for the velocity dispersion in a region of zero density, so we simply return zero dispersion as it should have - ! no consequence. If the Jeans integral is non-zero however, then something has gone wrong. - velocityDispersions(i)=0.0d0 - if (jeansIntegral+jeansIntegralPrevious > 0.0d0) call Error_Report('undefined velocity dispersion'//{introspection:location}) - else - velocityDispersions(i)=sqrt( & - & +( & - & +jeansIntegral & - & +jeansIntegralPrevious & - & ) & - & /density & - & ) - end if - end if - jeansIntegralPrevious=+jeansIntegralPrevious & - & +jeansIntegral - end do - call self%solverUnset( ) - ! Build the interpolator. - if (allocated(self%genericVelocityDispersionRadial)) deallocate(self%genericVelocityDispersionRadial) - allocate(self%genericVelocityDispersionRadial) - self%genericVelocityDispersionRadial=interpolator(log(radii),velocityDispersions,interpolationType=gsl_interp_linear,extrapolationType=extrapolationTypeFix) - ! Store the current results for future re-use. - if (allocated(self%genericVelocityDispersionRadialRadius )) deallocate(self%genericVelocityDispersionRadialRadius ) - if (allocated(self%genericVelocityDispersionRadialVelocity)) deallocate(self%genericVelocityDispersionRadialVelocity) - allocate(self%genericVelocityDispersionRadialRadius (countRadii)) - allocate(self%genericVelocityDispersionRadialVelocity(countRadii)) - self%genericVelocityDispersionRadialRadius =radii - self%genericVelocityDispersionRadialVelocity =velocityDispersions - self%genericVelocityDispersionRadialRadiusMinimum=radiusMinimum - self%genericVelocityDispersionRadialRadiusMaximum=radiusMaximum - self%genericVelocityDispersionRadialRadiusOuter =radiusOuter_ - end if - ! Interpolate in the table to find the velocity dispersion. - genericRadialVelocityDispersionNumerical=self%genericVelocityDispersionRadial%interpolate(log(radius)) - return - end function genericRadialVelocityDispersionNumerical - - double precision function genericJeansEquationIntegrand_(radius) - !!{ - Integrand for generic dark matter profile Jeans equation. - !!} - implicit none - double precision, intent(in ) :: radius - - genericJeansEquationIntegrand_=solvers(solversCount)%self%jeansEquationIntegrand(solvers(solversCount)%node,radius) - return - end function genericJeansEquationIntegrand_ - - double precision function genericJeansEquationIntegrand(self,node,radius) - !!{ - Integrand for generic dark matter profile Jeans equation. - !!} - use :: Numerical_Constants_Astronomical, only : gravitationalConstantGalacticus - implicit none - class (darkMatterProfileGeneric), intent(inout) :: self - type (treeNode ), intent(inout) :: node - double precision , intent(in ) :: radius - - if (radius > 0.0d0) then - genericJeansEquationIntegrand=+gravitationalConstantGalacticus & - & *self%enclosedMass(node,radius) & - & *self%density (node,radius) & - & / radius **2 - else - genericJeansEquationIntegrand=0.0d0 - end if - return - end function genericJeansEquationIntegrand - - double precision function genericJeansEquationRadius(self,node,radius) - !!{ - Return the radius variable used in solving the Jeans equation that corresponds to a given physical radius. - In some cases, it is easier to do the integration with respect to another variable which is a function of - the physical radius. - !!} - implicit none - class (darkMatterProfileGeneric), intent(inout) :: self - type (treeNode ), intent(inout) :: node - double precision , intent(in ) :: radius - !$GLC attributes unused :: self, node - - genericJeansEquationRadius=radius - return - end function genericJeansEquationRadius - - double precision function genericRadialMomentNumerical(self,node,moment,radiusMinimum,radiusMaximum) - !!{ - Returns the radial moment of the density in the dark matter profile of {\normalfont \ttfamily node} between the given - {\normalfont \ttfamily radiusMinimum} and {\normalfont \ttfamily radiusMaximum} (given in units of Mpc). - !!} - use :: Numerical_Integration, only : integrator - implicit none - class (darkMatterProfileGeneric), intent(inout) :: self - type (treeNode ), intent(inout) :: node - double precision , intent(in ) :: moment - double precision , intent(in ), optional :: radiusMinimum , radiusMaximum - type (integrator ) :: integrator_ - double precision :: radiusMinimumActual, radiusMaximumActual - - radiusMinimumActual=0.0d0 - radiusMaximumActual=self%darkMatterHaloScale_%radiusVirial(node) - if (present(radiusMinimum)) radiusMinimumActual=radiusMinimum - if (present(radiusMaximum)) radiusMaximumActual=radiusMaximum - integrator_=integrator(integrandRadialMoment,toleranceRelative=1.0d-3) - genericRadialMomentNumerical=integrator_%integrate(radiusMinimumActual,radiusMaximumActual) - return - - contains - - double precision function integrandRadialMoment(radius) - !!{ - Integrand for radial moment in a generic dark matter profile. - !!} - implicit none - double precision, intent(in ) :: radius - - if (radius > 0.0d0) then - integrandRadialMoment=+ radius **moment & - & *self%density(node,radius) - else - integrandRadialMoment=0.0d0 - end if - return - end function integrandRadialMoment - - end function genericRadialMomentNumerical - - double precision function genericRotationNormalizationNumerical(self,node) - !!{ - Return the normalization of the rotation velocity vs. specific angular momentum relation. - !!} - use :: Numerical_Constants_Math, only : Pi - implicit none - class (darkMatterProfileGeneric), intent(inout) :: self - type (treeNode ), intent(inout) :: node - double precision :: radiusVirial - - radiusVirial =+self%darkMatterHaloScale_%radiusVirial ( & - & node & - & ) - genericRotationNormalizationNumerical=+self %enclosedMass ( & - & node , & - & radiusVirial & - & ) & - & /4.0d0 & - & /Pi & - & /self %radialMomentNumerical( & - & node , & - & moment =3.0d0 , & - & radiusMinimum=0.0d0 , & - & radiusMaximum=radiusVirial & - & ) - return - end function genericRotationNormalizationNumerical - - double precision function genericKSpaceNumerical(self,node,waveNumber) - !!{ - Returns the Fourier transform of the dark matter density profile at the specified {\normalfont \ttfamily waveNumber} - (given in Mpc$^{-1}$). - !!} - use :: Numerical_Integration, only : integrator - implicit none - class (darkMatterProfileGeneric), intent(inout) :: self - type (treeNode ), intent(inout), target :: node - double precision , intent(in ) :: waveNumber - type (integrator ) :: integrator_ - double precision :: radiusVirial - - radiusVirial =+self %darkMatterHaloScale_%radiusVirial(node ) - integrator_ = integrator (integrandFourierTransform,toleranceRelative=1.0d-3) - genericKSpaceNumerical=+integrator_%integrate (0.0d0 ,radiusVirial ) & - & /self %enclosedMass(node ,radiusVirial ) - return - - contains - - double precision function integrandFourierTransform(radius) - !!{ - Integrand for Fourier transform of the generic dark matter profile. - !!} - use :: Numerical_Constants_Math, only : Pi - implicit none - double precision, intent(in ) :: radius - - if (radius > 0.0d0) then - integrandFourierTransform=+4.0d0 & - & *Pi & - & * radius **2 & - & *sin(wavenumber*radius) & - & / (waveNumber*radius) & - & *self%density(node,radius) - else - integrandFourierTransform=0.0d0 - end if - return - end function integrandFourierTransform - - end function genericKSpaceNumerical - - double precision function genericEnergyNumerical(self,node) - !!{ - Return the energy of a generic dark matter density profile. - !!} - use :: Numerical_Constants_Math , only : Pi - use :: Numerical_Constants_Astronomical, only : gravitationalConstantGalacticus - use :: Numerical_Integration , only : integrator - implicit none - class (darkMatterProfileGeneric ), intent(inout) :: self - type (treeNode ), intent(inout) :: node - double precision , parameter :: multiplierRadius =100.0d0 - type (integrator ) :: integratorPotential , integratorKinetic, & - & integratorPressure - double precision :: radiusVirial , radiusLarge , & - & energyPotential , energyKinetic , & - & pseudoPressure - - integratorPotential=integrator(integrandEnergyPotential,toleranceRelative=1.0d-3) - integratorKinetic =integrator(integrandEnergyKinetic ,toleranceRelative=1.0d-3) - integratorPressure =integrator(integrandPseudoPressure ,toleranceRelative=1.0d-3) - radiusVirial =+self%darkMatterHaloScale_%radiusVirial(node) - radiusLarge =+multiplierRadius & - & *radiusVirial - energyPotential =+integratorPotential%integrate(0.0d0 ,radiusVirial) - energyKinetic =+integratorKinetic %integrate(0.0d0 ,radiusVirial) - pseudoPressure =+integratorPressure %integrate(radiusVirial,radiusLarge ) - genericEnergyNumerical=-0.5d0 & - & *gravitationalConstantGalacticus & - & *( & - & +energyPotential & - & +self%enclosedMass(node,radiusVirial)**2 & - & / radiusVirial & - & ) & - & +2.0d0 & - & *Pi & - & *gravitationalConstantGalacticus & - & *( & - & +radiusVirial**3 & - & *pseudoPressure & - & +energyKinetic & - & ) - return - - contains - - double precision function integrandEnergyPotential(radius) - !!{ - Integrand for potential energy of the halo. - !!} - implicit none - double precision, intent(in ) :: radius - - if (radius > 0.0d0) then - integrandEnergyPotential=( & - & +self%enclosedMass(node,radius) & - & / radius & - & )**2 - else - integrandEnergyPotential=0.0d0 - end if - return - end function integrandEnergyPotential - - double precision function integrandEnergyKinetic(radius) - !!{ - Integrand for kinetic energy of the halo. - !!} - implicit none - double precision, intent(in ) :: radius - - if (radius > 0.0d0) then - integrandEnergyKinetic=+self%enclosedMass(node,radius) & - & *self%density (node,radius) & - & * radius - else - integrandEnergyKinetic=0.0d0 - end if - return - end function integrandEnergyKinetic - - double precision function integrandPseudoPressure(radius) - !!{ - Integrand for pseudo-pressure ($\rho(r) \sigma^2(r)$) of the halo. - !!} - implicit none - double precision, intent(in ) :: radius - - if (radius > 0.0d0) then - integrandPseudoPressure=+self%enclosedMass(node,radius) & - & *self%density (node,radius) & - & / radius **2 - else - integrandPseudoPressure=0.0d0 - end if - return - end function integrandPseudoPressure - - end function genericEnergyNumerical - - double precision function genericEnergyEvaluate(timeLogarithmic) - !!{ - GSL-callable function to evaluate the energy of the dark matter profile. - !!} - use :: Functions_Global, only : Calculations_Reset_ - implicit none - double precision, intent(in ), value :: timeLogarithmic - double precision :: time - - time=exp(timeLogarithmic) - call genericBasic %timeSet ( time ) - call genericBasic %timeLastIsolatedSet( time ) - call genericBasic %massSet (genericMass +genericMassGrowthRate *(time-genericTime)) - call genericDarkMatterProfile%scaleSet (genericScale+genericScaleGrowthRate*(time-genericTime)) - call genericDarkMatterProfile%shapeSet (genericShape+genericShapeGrowthRate*(time-genericTime)) - call Calculations_Reset_(solvers(solversCount)%node) - genericEnergyEvaluate=solvers(solversCount)%self%energyNumerical(solvers(solversCount)%node) - return - end function genericEnergyEvaluate - - double precision function genericFreefallRadiusNumerical(self,node,time) - !!{ - Returns the freefall radius in the adiabaticGnedin2004 density profile at the specified {\normalfont \ttfamily time} (given in - Gyr). - !!} - use :: Root_Finder, only : rangeExpandMultiplicative, rangeExpandSignExpectNegative, rangeExpandSignExpectPositive, rootFinder - implicit none - class (darkMatterProfileGeneric), intent(inout), target :: self - type (treeNode ), intent(inout), target :: node - double precision , intent(in ) :: time - double precision , parameter :: toleranceAbsolute=0.0d0, toleranceRelative=1.0d-3 - type (rootFinder ) :: finder - - call self%solverSet (node) - genericTime = time - finder = rootFinder( & - & rootFunction =rootRadiusFreefall , & - & toleranceAbsolute =toleranceAbsolute , & - & toleranceRelative =toleranceRelative , & - & rangeExpandDownward =0.5d0 , & - & rangeExpandUpward =2.0d0 , & - & rangeExpandType =rangeExpandMultiplicative , & - & rangeExpandUpwardSignExpect =rangeExpandSignExpectPositive, & - & rangeExpandDownwardSignExpect=rangeExpandSignExpectNegative & - & ) - genericFreefallRadiusNumerical=finder%find(rootGuess=self%darkMatterHaloScale_%radiusVirial(node)) - call self%solverUnset( ) - return - end function genericFreefallRadiusNumerical - - double precision function rootRadiusFreefall(radiusFreefall) - !!{ - Root function used in finding the radius corresponding to a given freefall time. - !!} - use :: Numerical_Integration, only : integrator - implicit none - double precision , intent(in ) :: radiusFreefall - type (integrator) :: integrator_ - - genericRadiusFreefall=+radiusFreefall - integrator_ = integrator (integrandTimeFreefall,toleranceRelative=1.0d-3) - rootRadiusFreefall =+integrator_ %integrate(0.0d0 ,radiusFreefall ) & - & -genericTime - return - end function rootRadiusFreefall - - double precision function integrandTimeFreefall(radius) - !!{ - Integrand for freefall time in the halo. - !!} - use :: Numerical_Constants_Astronomical, only : Mpc_per_km_per_s_To_Gyr - implicit none - double precision, intent(in ) :: radius - double precision :: potentialDifference - - potentialDifference=+solvers(solversCount)%self%potentialDifferenceNumerical(solvers(solversCount)%node,radius,genericRadiusFreefall) - if (potentialDifference < 0.0d0) then - integrandTimeFreefall=+Mpc_per_km_per_s_To_Gyr & - & /sqrt( & - & -2.0d0 & - & *potentialDifference & - & ) - else - ! Avoid floating point errors arising from rounding errors. - integrandTimeFreefall=0.0d0 - end if - return - end function integrandTimeFreefall - - double precision function genericFreefallRadiusIncreaseRateNumerical(self,node,time) - !!{ - Returns the rate of increase of the freefall radius in the dark matter density profile at the specified {\normalfont - \ttfamily time} (given in Gyr). - !!} - use :: Numerical_Differentiation, only : differentiator - implicit none - class (darkMatterProfileGeneric), intent(inout), target :: self - type (treeNode ), intent(inout), target :: node - double precision , intent(in ) :: time - double precision , parameter :: timeLogarithmicStep=0.1d0 - type (differentiator ) :: differentiator_ - - call self%solverSet (node) - differentiator_ = differentiator (genericFreefallRadiusEvaluate ) - genericFreefallRadiusIncreaseRateNumerical = +differentiator_%derivative(log(time) ,timeLogarithmicStep) & - & / time - call self%solverUnset( ) - return - end function genericFreefallRadiusIncreaseRateNumerical - - double precision function genericFreefallRadiusEvaluate(timeLogarithmic) - !!{ - GSL-callable function to evaluate the freefall radius of the dark matter profile. - !!} - implicit none - double precision, intent(in ), value :: timeLogarithmic - - genericFreefallRadiusEvaluate=solvers(solversCount)%self%freefallRadiusNumerical(solvers(solversCount)%node,exp(timeLogarithmic)) - return - end function genericFreefallRadiusEvaluate - - double precision function genericRadiusEnclosingDensityNumerical(self,node,density) - !!{ - Returns the radius (in Mpc) in the dark matter profile of {\normalfont \ttfamily node} which encloses the given - {\normalfont \ttfamily density} (given in units of $M_\odot/$Mpc$^{-3}$). - !!} - use :: Root_Finder, only : rangeExpandMultiplicative, rangeExpandSignExpectNegative, rangeExpandSignExpectPositive, rootFinder - implicit none - class (darkMatterProfileGeneric), intent(inout), target :: self - type (treeNode ), intent(inout), target :: node - double precision , intent(in ) :: density - double precision , parameter :: toleranceAbsolute=0.0d0, toleranceRelative=1.0d-3 - type (rootFinder ) :: finder - - call self%solverSet (node) - genericDensity=density - finder =rootFinder( & - & rootFunction =rootDensity , & - & toleranceAbsolute =toleranceAbsolute , & - & toleranceRelative =toleranceRelative , & - & rangeExpandDownward =0.5d0 , & - & rangeExpandUpward =2.0d0 , & - & rangeExpandType =rangeExpandMultiplicative , & - & rangeExpandUpwardSignExpect =rangeExpandSignExpectNegative, & - & rangeExpandDownwardSignExpect=rangeExpandSignExpectPositive & - & ) - genericRadiusEnclosingDensityNumerical=finder%find(rootGuess=self%darkMatterHaloScale_%radiusVirial(node)) - call self%solverUnset( ) - return - end function genericRadiusEnclosingDensityNumerical - - double precision function rootDensity(radius) - !!{ - Root function used in finding the radius enclosing a given mean density. - !!} - use :: Numerical_Constants_Math, only : Pi - implicit none - double precision, intent(in ) :: radius - - rootDensity=+3.0d0 & - & *solvers(solversCount)%self%enclosedMass(solvers(solversCount)%node,radius) & - & /4.0d0 & - & /Pi & - & / radius **3 & - & -genericDensity - return - end function rootDensity - - double precision function genericRadiusEnclosingMassNumerical(self,node,mass) - !!{ - Returns the radius (in Mpc) in the dark matter profile of {\normalfont \ttfamily node} which encloses the given - {\normalfont \ttfamily mass} (given in units of $M_\odot$). - !!} - use :: Root_Finder, only : rangeExpandMultiplicative, rangeExpandSignExpectNegative, rangeExpandSignExpectPositive, rootFinder - implicit none - class (darkMatterProfileGeneric), intent(inout), target :: self - type (treeNode ), intent(inout), target :: node - double precision , intent(in ) :: mass - double precision , parameter :: toleranceAbsolute=0.0d0, toleranceRelative=1.0d-3 - type (rootFinder ) :: finder - - call self%solverSet (node) - genericMass = mass - finder = rootFinder( & - & rootFunction =rootMass , & - & toleranceAbsolute =toleranceAbsolute , & - & toleranceRelative =toleranceRelative , & - & rangeExpandUpward =2.0d0 , & - & rangeExpandType =rangeExpandMultiplicative , & - & rangeExpandUpwardSignExpect =rangeExpandSignExpectPositive, & - & rangeExpandDownwardSignExpect=rangeExpandSignExpectNegative & - & ) - genericRadiusEnclosingMassNumerical=finder%find(rootRange=[0.0d0,self%darkMatterHaloScale_%radiusVirial(node)]) - call self%solverUnset( ) - return - end function genericRadiusEnclosingMassNumerical - - double precision function rootMass(radius) - !!{ - Root function used in finding the radius enclosing a given mass. - !!} - implicit none - double precision, intent(in ) :: radius - - rootMass=+solvers(solversCount)%self%enclosedMass(solvers(solversCount)%node,radius) & - & - genericMass - return - end function rootMass - - double precision function genericRadiusCircularVelocityMaximumNumerical(self,node) - !!{ - Returns the radius (in Mpc) at which the maximum circular velocity is achieved in the dark matter profile of {\normalfont \ttfamily node}. - !!} - use :: Galacticus_Nodes , only : nodeComponentBasic - use :: Numerical_Comparison, only : Values_Agree - use :: Root_Finder , only : rangeExpandMultiplicative, rangeExpandSignExpectNegative, rangeExpandSignExpectPositive, rootFinder - implicit none - class (darkMatterProfileGeneric), intent(inout) :: self - type (treeNode ), intent(inout) :: node - double precision , parameter :: toleranceAbsolute=0.0d0, toleranceRelative=1.0d-6 - class (nodeComponentBasic ), pointer :: basic - type (rootFinder ) :: finder - - call self%solverSet (node) - finder = rootFinder( & - & rootFunction =rootCircularVelocityMaximum , & - & toleranceAbsolute =toleranceAbsolute , & - & toleranceRelative =toleranceRelative , & - & rangeExpandDownward =0.5d0 , & - & rangeExpandUpward =2.0d0 , & - & rangeExpandType =rangeExpandMultiplicative , & - & rangeExpandUpwardSignExpect =rangeExpandSignExpectNegative, & - & rangeExpandDownwardSignExpect=rangeExpandSignExpectPositive & - & ) - ! Isothermal profiles have dVc²/dr=0 everywhere. To handle these profiles, first test if the root function is sufficiently - ! close to zero at a few points throughout the halo (which it will be for an isothermal profile), and return the circular - ! velocity at the virial radius if so. Otherwise solve for the radius corresponding to the maximum circular velocity. - basic => node%basic() - if ( & - & Values_Agree( & - & +rootCircularVelocityMaximum(1.0d+0*self%darkMatterHaloScale_%radiusVirial(node)), & - & +0.0d0 , & - & absTol=+toleranceRelative & - & *basic%mass ( ) & - & ) & - & .and. & - & Values_Agree( & - & +rootCircularVelocityMaximum(3.0d-1*self%darkMatterHaloScale_%radiusVirial(node)), & - & +0.0d0 , & - & absTol=+toleranceRelative & - & *basic%mass ( ) & - & ) & - & .and. & - & Values_Agree( & - & +rootCircularVelocityMaximum(1.0d-1*self%darkMatterHaloScale_%radiusVirial(node)), & - & +0.0d0 , & - & absTol=+toleranceRelative & - & *basic%mass ( ) & - & ) & - & .and. & - & Values_Agree( & - & +rootCircularVelocityMaximum(3.0d-2*self%darkMatterHaloScale_%radiusVirial(node)), & - & +0.0d0 , & - & absTol=+toleranceRelative & - & *basic%mass ( ) & - & ) & - & ) then - genericRadiusCircularVelocityMaximumNumerical= self%darkMatterHaloScale_%radiusVirial(node) - else - genericRadiusCircularVelocityMaximumNumerical=finder%find(rootGuess=self%darkMatterHaloScale_%radiusVirial(node)) - end if - call self%solverUnset( ) - return - end function genericRadiusCircularVelocityMaximumNumerical - - double precision function genericCircularVelocityMaximumNumerical(self,node) - !!{ - Returns the maximum circular velocity (in km/s) in the dark matter profile of {\normalfont \ttfamily node}. - !!} - implicit none - class(darkMatterProfileGeneric), intent(inout) :: self - type (treeNode ), intent(inout) :: node - - genericCircularVelocityMaximumNumerical=self%circularVelocityNumerical(node,self%radiusCircularVelocityMaximumNumerical(node)) - return - end function genericCircularVelocityMaximumNumerical - - double precision function rootCircularVelocityMaximum(radius) - !!{ - Root function used in finding the radius at which the maximum circular velocity occurs. Since for a spherical profile $V_\mathrm{c}^2(r)=\mathrm{G}M(r)/r$, then - \begin{equation} - {\mathrm{d} V_\mathrm{c}^2 \over \mathrm{d} r} = - {\mathrm{G} M(r) \over r^2} + 4 \pi \mathrm{G} \rho(r) r. - \end{equation} - Therefore, the peak of the rotation curve satisfies $4 \pi r^3 \rho(r) - M(r)=0$. - !!} - use :: Numerical_Constants_Math, only : Pi - implicit none - double precision, intent(in ) :: radius - - rootCircularVelocityMaximum=+4.0d0 & - & *Pi & - & * radius **3 & - & *solvers(solversCount)%self%density (solvers(solversCount)%node,radius) & - & -solvers(solversCount)%self%enclosedMass(solvers(solversCount)%node,radius) - return - end function rootCircularVelocityMaximum - - double precision function genericRadiusFromSpecificAngularMomentumNumerical(self,node,specificAngularMomentum) - !!{ - Returns the radius (in Mpc) in {\normalfont \ttfamily node} at which a circular orbit has the given {\normalfont \ttfamily specificAngularMomentum} (given - in units of km s$^{-1}$ Mpc). - !!} - use :: Root_Finder, only : rangeExpandMultiplicative, rangeExpandSignExpectNegative, rangeExpandSignExpectPositive, rootFinder - implicit none - class (darkMatterProfileGeneric), intent(inout), target :: self - type (treeNode ), intent(inout), target :: node - double precision , intent(in ) :: specificAngularMomentum - double precision , parameter :: toleranceAbsolute=0.0d0, toleranceRelative=1.0d-3 - type (rootFinder ) :: finder - - call self%solverSet (node) - genericSpecificAngularMomentum = specificAngularMomentum - finder = rootFinder( & - & rootFunction =rootSpecificAngularMomentum , & - & toleranceAbsolute =toleranceAbsolute , & - & toleranceRelative =toleranceRelative , & - & rangeExpandUpward =2.0d0 , & - & rangeExpandType =rangeExpandMultiplicative , & - & rangeExpandUpwardSignExpect =rangeExpandSignExpectPositive, & - & rangeExpandDownwardSignExpect=rangeExpandSignExpectNegative & - & ) - genericRadiusFromSpecificAngularMomentumNumerical=finder%find(rootRange=[0.0d0,self%darkMatterHaloScale_%radiusVirial(node)]) - call self%solverUnset( ) - return - end function genericRadiusFromSpecificAngularMomentumNumerical - - double precision function rootSpecificAngularMomentum(radius) - !!{ - Root function used in finding the radius enclosing a given specific angular momentum. - !!} - implicit none - double precision, intent(in ) :: radius - - rootSpecificAngularMomentum=+solvers(solversCount)%self%circularVelocityNumerical(solvers(solversCount)%node,radius) & - & * radius & - & -genericSpecificAngularMomentum - return - end function rootSpecificAngularMomentum - - double precision function genericDensityLogSlopeNumerical(self,node,radius) - !!{ - Returns the logarithmic slope of the density in the dark matter profile of {\normalfont \ttfamily node} at the given - {\normalfont \ttfamily radius} (given in units of Mpc). - !!} - use :: Numerical_Differentiation, only : differentiator - implicit none - class (darkMatterProfileGeneric), intent(inout), target :: self - type (treeNode ), intent(inout), target :: node - double precision , intent(in ) :: radius - double precision , parameter :: radiusLogarithmicStep=0.1d0 - type (differentiator ) :: differentiator_ - - call self%solverSet (node) - differentiator_ = differentiator (genericDensityEvaluate ) - genericDensityLogSlopeNumerical= +differentiator_ %derivative(log(radius) ,radiusLogarithmicStep) & - & /genericDensityEvaluate (log(radius) ) - call self%solverUnset( ) - return - end function genericDensityLogSlopeNumerical - - double precision function genericDensityEvaluate(radiusLogarithmic) - !!{ - GSL-callable function to evaluate the density of the dark matter profile. - !!} - implicit none - double precision, intent(in ), value :: radiusLogarithmic - - genericDensityEvaluate=solvers(solversCount)%self%density(solvers(solversCount)%node,exp(radiusLogarithmic)) - return - end function genericDensityEvaluate - - subroutine genericSolverSet(self,node) - !!{ - Set a sub-module scope pointers on a stack to allow recursive calls to functions. - !!} - implicit none - class (darkMatterProfileGeneric), intent(inout), target :: self - type (treeNode ), intent(inout), target :: node - type (genericSolver ), allocatable , dimension(:) :: solvers_ - integer :: i - - ! Increment the state counter. This is necessary to ensure that this function can be called recursively. - if (allocated(solvers)) then - if (solversCount == size(solvers)) then - call move_alloc(solvers,solvers_) - allocate(solvers(size(solvers_)+solversIncrement)) - solvers(1:size(solvers_))=solvers_ - do i=1,size(solvers_) - nullify(solvers_(i)%self) - nullify(solvers_(i)%node) - end do - deallocate(solvers_) - end if - else - allocate(solvers(solversIncrement)) - end if - solversCount=solversCount+1 - solvers(solversCount)%self => self - solvers(solversCount)%node => node - return - end subroutine genericSolverSet - - subroutine genericSolverUnset() - !!{ - Unset a sub-module scope pointers on the stack. - !!} - implicit none - - solvers(solversCount)%self => null() - solvers(solversCount)%node => null() - solversCount=solversCount-1 - return - end subroutine genericSolverUnset - - subroutine genericCalculationResetGeneric(self,node,uniqueID) - !!{ - Reset generic profile memoized data. - !!} - use :: Kind_Numbers, only : kind_int8 - implicit none - class (darkMatterProfileGeneric), intent(inout) :: self - type (treeNode ), intent(inout) :: node - integer(kind_int8 ), intent(in ) :: uniqueID - !$GLC attributes unused :: node - - self%genericLastUniqueID=uniqueID - if (allocated(self%genericVelocityDispersionRadialVelocity)) deallocate(self%genericVelocityDispersionRadialVelocity) - if (allocated(self%genericVelocityDispersionRadialRadius )) deallocate(self%genericVelocityDispersionRadialRadius ) - if (allocated(self%genericEnclosedMassMass )) deallocate(self%genericEnclosedMassMass ) - if (allocated(self%genericEnclosedMassRadius )) deallocate(self%genericEnclosedMassRadius ) - return - end subroutine genericCalculationResetGeneric - -end module Dark_Matter_Profiles_Generic diff --git a/source/dark_matter_profiles.heating.impulsive_outflow.F90 b/source/dark_matter_profiles.heating.impulsive_outflow.F90 index 494a0a479d..5c3ce54377 100644 --- a/source/dark_matter_profiles.heating.impulsive_outflow.F90 +++ b/source/dark_matter_profiles.heating.impulsive_outflow.F90 @@ -24,19 +24,12 @@ !![ - A dark matter profile heating model which accounts for heating due to impulsive outflows---i.e. outflows occurring on - timescales that are small relative to the dynamical time of the halo. The model assumed is that the energy injection is given by + A dark matter profile heating model which accounts for heating due to impulsive outflows. The quantity \begin{equation} - \dot{\epsilon}(r) = \alpha \frac{\mathrm{G} \dot{M}_\mathrm{outflow}(r)}{r} f\left( \frac{t_\phi}{t_\mathrm{dyn}} \right), - \end{equation} - where $\alpha$ is a normalization factor, $t_\phi = M_\mathrm{gas}/\dot{M}_\mathrm{outflow}$ is the timescale for the - outflow, and $t_\mathrm{dyn} = r_{1/2}/v_{1/2}$ is the dynamical time at the half-mass radius. - - In practice, the quantity - \begin{equation} - \dot{\epsilon}^\prime = \dot{M}_\mathrm{outflow} f\left( \frac{t_\phi}{t_\mathrm{dyn}} \right), - \end{equation} - has been accumulated by the \refClass{nodeOperatorImpulsiveOutflowEnergy} object---radially-dependent factors are then applied here. + \dot{\epsilon}^\prime = \dot{M}_\mathrm{outflow} f\left( \frac{t_\phi}{t_\mathrm{dyn}} \right), + \end{equation} + has been accumulated by the \refClass{nodeOperatorImpulsiveOutflowEnergy} object---radially-dependent factors are then applied + in the \refClass{massDistributionHeatingImpulsiveOutflow} object returned from our factory. !!] @@ -45,17 +38,10 @@ A dark matter profile heating class which accounts for heating arising from impulsive outflows. !!} private - integer :: energyImpulsiveOutflowDiskID , energyImpulsiveOutflowSpheroidID - double precision :: impulsiveEnergyFactor - class (*), pointer :: galacticStructure_ => null() + integer :: energyImpulsiveOutflowDiskID, energyImpulsiveOutflowSpheroidID + double precision :: impulsiveEnergyFactor contains - final :: impulsiveOutflowDestructor - procedure :: specificEnergy => impulsiveOutflowSpecificEnergy - procedure :: specificEnergyGradient => impulsiveOutflowSpecificEnergyGradient - procedure :: specificEnergyIsEverywhereZero => impulsiveOutflowSpecificEnergyIsEverywhereZero - procedure :: deepCopyReset => impulsiveOutflowDeepCopyReset - procedure :: deepCopy => impulsiveOutflowDeepCopy - procedure :: deepCopyFinalize => impulsiveOutflowDeepCopyFinalize + procedure :: get => impulsiveOutflowGet end type darkMatterProfileHeatingImpulsiveOutflow interface darkMatterProfileHeatingImpulsiveOutflow @@ -72,13 +58,12 @@ function impulsiveOutflowConstructorParameters(parameters) result(self) !!{ Constructor for the {\normalfont \ttfamily impulsiveOutflow} dark matter profile heating scales class which takes a parameter set as input. !!} - use :: Functions_Global, only : galacticStructureConstruct_, galacticStructureDestruct_ use :: Input_Parameters, only : inputParameters implicit none type (darkMatterProfileHeatingImpulsiveOutflow), target :: self type (inputParameters ), intent(inout) :: parameters - class (* ), pointer :: galacticStructure_ double precision :: impulsiveEnergyFactor + !![ impulsiveEnergyFactor @@ -87,25 +72,22 @@ function impulsiveOutflowConstructorParameters(parameters) result(self) parameters !!] - call galacticStructureConstruct_(parameters,galacticStructure_) - self=darkMatterProfileHeatingImpulsiveOutflow(impulsiveEnergyFactor,galacticStructure_) + self=darkMatterProfileHeatingImpulsiveOutflow(impulsiveEnergyFactor) !![ - + !!] - call galacticStructureDestruct_(galacticStructure_) return end function impulsiveOutflowConstructorParameters - function impulsiveOutflowConstructorInternal(impulsiveEnergyFactor,galacticStructure_) result(self) + function impulsiveOutflowConstructorInternal(impulsiveEnergyFactor) result(self) !!{ Internal constructor for the {\normalfont \ttfamily impulsiveOutflow} dark matter profile heating scales class. !!} implicit none - type (darkMatterProfileHeatingImpulsiveOutflow) :: self - class (* ), intent(in ), target :: galacticStructure_ - double precision , intent(in ) :: impulsiveEnergyFactor + type (darkMatterProfileHeatingImpulsiveOutflow) :: self + double precision , intent(in ) :: impulsiveEnergyFactor !![ - + !!] !![ @@ -115,204 +97,34 @@ function impulsiveOutflowConstructorInternal(impulsiveEnergyFactor,galacticStruc return end function impulsiveOutflowConstructorInternal - subroutine impulsiveOutflowDestructor(self) - !!{ - Destructor for the {\normalfont \ttfamily impulsiveOutflow} node operator class. - !!} - use :: Functions_Global, only : galacticStructureDestruct_ - implicit none - type(darkMatterProfileHeatingImpulsiveOutflow), intent(inout) :: self - - if (associated(self%galacticStructure_)) call galacticStructureDestruct_(self%galacticStructure_) - return - end subroutine impulsiveOutflowDestructor - - double precision function impulsiveOutflowSpecificEnergy(self,node,radius,darkMatterProfileDMO_) - !!{ - Returns the specific energy of heating in the given {\normalfont \ttfamily node}. - !!} - use :: Galacticus_Nodes , only : nodeComponentDarkMatterProfile - use :: Galactic_Structure_Options , only : componentTypeDisk , componentTypeSpheroid, radiusLarge,massTypeDark - use :: Numerical_Constants_Astronomical, only : gravitationalConstantGalacticus - use :: Functions_Global , only : galacticStructureMassEnclosed_ - implicit none - class (darkMatterProfileHeatingImpulsiveOutflow), intent(inout) :: self - type (treeNode ), intent(inout) :: node - class (darkMatterProfileDMOClass ), intent(inout) :: darkMatterProfileDMO_ - double precision , intent(in ) :: radius - class (nodeComponentDarkMatterProfile ), pointer :: darkMatterProfile - double precision :: massTotalDisk , massTotalSpheroid , & - & fractionMassDisk , fractionMassSpheroid - - darkMatterProfile => node%darkMatterProfile ( ) - massTotalDisk = galacticStructureMassEnclosed_(self%galacticStructure_,node,radiusLarge,componentType=componentTypeDisk ) - massTotalSpheroid = galacticStructureMassEnclosed_(self%galacticStructure_,node,radiusLarge,componentType=componentTypeSpheroid) - if (massTotalDisk > 0.0d0) then - fractionMassDisk =+galacticStructureMassEnclosed_(self%galacticStructure_,node,radius,componentType=componentTypeDisk ) & - & /massTotalDisk - else - fractionMassDisk =+0.0d0 - end if - if (massTotalSpheroid > 0.0d0) then - fractionMassSpheroid=+galacticStructureMassEnclosed_(self%galacticStructure_,node,radius,componentType=componentTypeSpheroid) & - & /massTotalSpheroid - else - fractionMassSpheroid=+0.0d0 - end if - impulsiveOutflowSpecificEnergy = +self %impulsiveEnergyFactor & - & *gravitationalConstantGalacticus & - & *( & - & +darkMatterProfile %floatRank0MetaPropertyGet(self%energyImpulsiveOutflowDiskID ) & - & *fractionMassDisk & - & +darkMatterProfile %floatRank0MetaPropertyGet(self%energyImpulsiveOutflowSpheroidID) & - & *fractionMassSpheroid & - & ) & - & /radius - return - end function impulsiveOutflowSpecificEnergy - - double precision function impulsiveOutflowSpecificEnergyGradient(self,node,radius,darkMatterProfileDMO_) - !!{ - Returns the gradient of the specific energy of heating in the given {\normalfont \ttfamily node}. - !!} - use :: Galacticus_Nodes , only : nodeComponentDarkMatterProfile - use :: Galactic_Structure_Options , only : componentTypeDisk , componentTypeSpheroid, radiusLarge, coordinateSystemSpherical - use :: Numerical_Constants_Astronomical, only : gravitationalConstantGalacticus - use :: Numerical_Constants_Math , only : Pi - use :: Functions_Global , only : galacticStructureMassEnclosed_ , galacticStructureDensity_ - implicit none - class (darkMatterProfileHeatingImpulsiveOutflow), intent(inout) :: self - type (treeNode ), intent(inout) :: node - class (darkMatterProfileDMOClass ), intent(inout) :: darkMatterProfileDMO_ - double precision , intent(in ) :: radius - class (nodeComponentDarkMatterProfile ), pointer :: darkMatterProfile - double precision :: massTotalDisk , massTotalSpheroid , & - & fractionMassDisk , fractionMassSpheroid , & - & fractionDensityDisk , fractionDensitySpheroid - - darkMatterProfile => node%darkMatterProfile ( ) - massTotalDisk = galacticStructureMassEnclosed_(self%galacticStructure_,node,radiusLarge,componentType=componentTypeDisk ) - massTotalSpheroid = galacticStructureMassEnclosed_(self%galacticStructure_,node,radiusLarge,componentType=componentTypeSpheroid) - if (massTotalDisk > 0.0d0) then - fractionMassDisk =+galacticStructureMassEnclosed_(self%galacticStructure_,node, radius ,componentType=componentTypeDisk ) & - & /massTotalDisk - fractionDensityDisk =+galacticStructureDensity_ (self%galacticStructure_,node,[radius,0.0d0,0.0d0],componentType=componentTypeDisk ,coordinateSystem=coordinateSystemSpherical) & - & /massTotalDisk - else - fractionMassDisk =+0.0d0 - fractionDensityDisk =+0.0d0 - end if - if (massTotalSpheroid > 0.0d0) then - fractionMassSpheroid =+galacticStructureMassEnclosed_(self%galacticStructure_,node, radius ,componentType=componentTypeSpheroid ) & - & /massTotalSpheroid - fractionDensitySpheroid=+galacticStructureDensity_ (self%galacticStructure_,node,[radius,0.0d0,0.0d0],componentType=componentTypeSpheroid,coordinateSystem=coordinateSystemSpherical) & - & /massTotalSpheroid - else - fractionMassSpheroid =+0.0d0 - fractionDensitySpheroid=+0.0d0 - end if - impulsiveOutflowSpecificEnergyGradient = +self%impulsiveEnergyFactor & - & *gravitationalConstantGalacticus & - & *( & - & +( & - & +darkMatterProfile %floatRank0MetaPropertyGet(self%energyImpulsiveOutflowDiskID ) & - & *fractionDensityDisk & - & +darkMatterProfile %floatRank0MetaPropertyGet(self%energyImpulsiveOutflowSpheroidID) & - & *fractionDensitySpheroid & - & ) & - & *4.0d0 & - & *Pi & - & *radius & - & -( & - & +darkMatterProfile %floatRank0MetaPropertyGet(self%energyImpulsiveOutflowDiskID ) & - & *fractionMassDisk & - & +darkMatterProfile %floatRank0MetaPropertyGet(self%energyImpulsiveOutflowSpheroidID) & - & *fractionMassSpheroid & - & ) & - & /radius**2 & - & ) - return - end function impulsiveOutflowSpecificEnergyGradient - - logical function impulsiveOutflowSpecificEnergyIsEverywhereZero(self,node,darkMatterProfileDMO_) + function impulsiveOutflowGet(self,node) result(massDistributionHeating_) !!{ - Returns true if the specific energy is everywhere zero in the given {\normalfont \ttfamily node}. + Return the dark matter mass distribution heating for the given {\normalfont \ttfamily node}. !!} - use :: Galacticus_Nodes, only : nodeComponentDarkMatterProfile + use :: Galacticus_Nodes , only : nodeComponentDarkMatterProfile + use :: Mass_Distributions, only : massDistributionHeatingImpulsiveOutflow implicit none + class(massDistributionHeatingClass ), pointer :: massDistributionHeating_ class(darkMatterProfileHeatingImpulsiveOutflow), intent(inout) :: self type (treeNode ), intent(inout) :: node - class(darkMatterProfileDMOClass ), intent(inout) :: darkMatterProfileDMO_ class(nodeComponentDarkMatterProfile ), pointer :: darkMatterProfile - !$GLC attributes unused :: darkMatterProfileDMO_ - - darkMatterProfile => node %darkMatterProfile ( ) - impulsiveOutflowSpecificEnergyIsEverywhereZero = darkMatterProfile%floatRank0MetaPropertyGet(self%energyImpulsiveOutflowDiskID ) <= 0.0d0 & - & .and. & - & darkMatterProfile%floatRank0MetaPropertyGet(self%energyImpulsiveOutflowSpheroidID) <= 0.0d0 - - return - end function impulsiveOutflowSpecificEnergyIsEverywhereZero - - subroutine impulsiveOutflowDeepCopyReset(self) - !!{ - Perform a deep copy reset of the object. - !!} - use :: Functions_Global, only : galacticStructureDeepCopyReset_ - implicit none - class(darkMatterProfileHeatingImpulsiveOutflow), intent(inout) :: self - - self%copiedSelf => null() - if (associated(self%galacticStructure_)) call galacticStructureDeepCopyReset_(self%galacticStructure_) - return - end subroutine impulsiveOutflowDeepCopyReset - - subroutine impulsiveOutflowDeepCopyFinalize(self) - !!{ - Finalize a deep reset of the object. - !!} - use :: Functions_Global, only : galacticStructureDeepCopyFinalize_ - implicit none - class(darkMatterProfileHeatingImpulsiveOutflow), intent(inout) :: self - - if (associated(self%galacticStructure_)) call galacticStructureDeepCopyFinalize_(self%galacticStructure_) - return - end subroutine impulsiveOutflowDeepCopyFinalize - - subroutine impulsiveOutflowDeepCopy(self,destination) - !!{ - Perform a deep copy of the object. - !!} - use :: Error , only : Error_Report - use :: Functions_Global , only : galacticStructureDeepCopy_ -#ifdef OBJECTDEBUG - use :: Display , only : displayMessage , verbosityLevelSilent - use :: MPI_Utilities , only : mpiSelf - use :: Function_Classes , only : debugReporting - use :: ISO_Varying_String, only : operator(//) , var_str - use :: String_Handling , only : operator(//) -#endif - implicit none - class(darkMatterProfileHeatingImpulsiveOutflow), intent(inout), target :: self - class(darkMatterProfileHeatingClass ), intent(inout) :: destination - - call self%darkMatterProfileHeatingClass%deepCopy(destination) - select type (destination) - type is (darkMatterProfileHeatingImpulsiveOutflow) - destination%energyImpulsiveOutflowDiskID =self%energyImpulsiveOutflowDiskID - destination%energyImpulsiveOutflowSpheroidID=self%energyImpulsiveOutflowSpheroidID - destination%impulsiveEnergyFactor =self%impulsiveEnergyFactor - nullify(destination%galacticStructure_) - if (associated(self%galacticStructure_)) then - allocate(destination%galacticStructure_,mold=self%galacticStructure_) - call galacticStructureDeepCopy_(self%galacticStructure_,destination%galacticStructure_) -#ifdef OBJECTDEBUG - if (debugReporting.and.mpiSelf%isMaster()) call displayMessage(var_str('functionClass[own] (class : ownerName : ownerLoc : objectLoc : sourceLoc): galacticstructure : [destination] : ')//loc(destination)//' : '//loc(destination%galacticStructure_)//' : '//{introspection:location:compact},verbosityLevelSilent) -#endif - destination%referenceCount=1 - end if - class default - call Error_Report('destination and source types do not match'//{introspection:location}) + + ! Create the mass distribution. + allocate(massDistributionHeatingImpulsiveOutflow :: massDistributionHeating_) + select type(massDistributionHeating_) + type is (massDistributionHeatingImpulsiveOutflow) + darkMatterProfile => node%darkMatterProfile() + !![ + + + massDistributionHeatingImpulsiveOutflow( & + & energyImpulsiveOutflowDisk =darkMatterProfile%floatRank0MetaPropertyGet(self%energyImpulsiveOutflowDiskID ), & + & energyImpulsiveOutflowSpheroid=darkMatterProfile%floatRank0MetaPropertyGet(self%energyImpulsiveOutflowSpheroidID), & + & impulsiveEnergyFactor =self %impulsiveEnergyFactor & + & ) + + + !!] end select return - end subroutine impulsiveOutflowDeepCopy + end function impulsiveOutflowGet diff --git a/source/dark_matter_profiles.heating.monotonic.F90 b/source/dark_matter_profiles.heating.monotonic.F90 index d242ac98b9..ca43778743 100644 --- a/source/dark_matter_profiles.heating.monotonic.F90 +++ b/source/dark_matter_profiles.heating.monotonic.F90 @@ -23,39 +23,23 @@ A dark matter halo profile heating class which takes another heating source and enforces monotonic heating energy perturbation. !!} - use :: Root_Finder, only : rootFinder - !![ - A dark matter profile heating model which takes another heating source and enforces monotonic heating energy perturbation. + + A dark matter profile heating model builds \refClass{massDistributionHeatingMonotonic} objects to enforce monotonic heating + energy perturbations. + !!] - type, extends(darkMatterProfileHeatingClass) :: darkMatterProfileHeatingMonotonic !!{ A dark matter profile heating class which takes another heating source and enforces monotonic heating energy perturbation. !!} private - class (darkMatterProfileHeatingClass), pointer :: darkMatterProfileHeating_ => null() - type (rootFinder ) :: finder - double precision :: radiusShellCrossing , energyPerturbationShellCrossing - integer (kind_int8 ) :: lastUniqueID + class(darkMatterProfileHeatingClass), pointer :: darkMatterProfileHeating_ => null() contains - !![ - - - - - - !!] - final :: monotonicDestructor - procedure :: autoHook => monotonicAutoHook - procedure :: calculationReset => monotonicCalculationReset - procedure :: specificEnergy => monotonicSpecificEnergy - procedure :: specificEnergyGradient => monotonicSpecificEnergyGradient - procedure :: specificEnergyIsEverywhereZero => monotonicSpecificEnergyIsEverywhereZero - procedure :: noShellCrossingIsValid => monotonicNoShellCrossingIsValid - procedure :: computeRadiusShellCrossing => monotonicComputeRadiusShellCrossing + final :: monotonicDestructor + procedure :: get => monotonicGet end type darkMatterProfileHeatingMonotonic interface darkMatterProfileHeatingMonotonic @@ -66,12 +50,6 @@ module procedure monotonicConstructorInternal end interface darkMatterProfileHeatingMonotonic - ! Global variables used in root solving. - type (treeNode ), pointer :: node_ - type (darkMatterProfileHeatingMonotonic), pointer :: self_ - class(darkMatterProfileDMOClass ), pointer :: darkMatterProfileDMO__ - !$omp threadprivate(node_,self_,darkMatterProfileDMO__) - contains function monotonicConstructorParameters(parameters) result(self) @@ -80,9 +58,9 @@ function monotonicConstructorParameters(parameters) result(self) !!} use :: Input_Parameters, only : inputParameter, inputParameters implicit none - type (darkMatterProfileHeatingMonotonic), target :: self - type (inputParameters ), intent(inout) :: parameters - class (darkMatterProfileHeatingClass ), pointer :: darkMatterProfileHeating_ + type (darkMatterProfileHeatingMonotonic), target :: self + type (inputParameters ), intent(inout) :: parameters + class(darkMatterProfileHeatingClass ), pointer :: darkMatterProfileHeating_ !![ @@ -100,276 +78,54 @@ function monotonicConstructorInternal(darkMatterProfileHeating_) result(self) Internal constructor for the ``monotonic'' dark matter profile heating class. !!} implicit none - type (darkMatterProfileHeatingMonotonic) :: self - class (darkMatterProfileHeatingClass ), target, intent(in ) :: darkMatterProfileHeating_ - double precision , parameter :: toleranceAbsolute =0.0d0, toleranceRelative=1.0d-6 + type (darkMatterProfileHeatingMonotonic) :: self + class(darkMatterProfileHeatingClass ), target, intent(in ) :: darkMatterProfileHeating_ !![ !!] - self%radiusShellCrossing =-1.0d0 - self%energyPerturbationShellCrossing=-1.0d0 - self%lastUniqueID =-1_kind_int8 - self%finder =rootFinder( & - & rootFunction =monotonicRadiusShellCrossingRoot, & - & toleranceAbsolute=toleranceAbsolute , & - & toleranceRelative=toleranceRelative & - & ) return end function monotonicConstructorInternal - subroutine monotonicAutoHook(self) - !!{ - Attach to the calculation reset event. - !!} - use :: Events_Hooks, only : calculationResetEvent, openMPThreadBindingAllLevels - implicit none - class(darkMatterProfileHeatingMonotonic), intent(inout) :: self - - call calculationResetEvent%attach(self,monotonicCalculationReset,openMPThreadBindingAllLevels,label='darkMatterProfileHeatingMonotonic') - return - end subroutine monotonicAutoHook - - subroutine monotonicCalculationReset(self,node,uniqueID) - !!{ - Reset the stored shell crossing radius. - !!} - implicit none - class (darkMatterProfileHeatingMonotonic), intent(inout) :: self - type (treeNode ), intent(inout) :: node - integer(kind_int8 ), intent(in ) :: uniqueID - - self%radiusShellCrossing =-1.0d0 - self%energyPerturbationShellCrossing=-1.0d0 - self%lastUniqueID =uniqueID - return - end subroutine monotonicCalculationReset - subroutine monotonicDestructor(self) !!{ - Destructor for the ``monotonic'' dark matter profile heating class. + Destructor for the {\normalfont \ttfamily monotonic} dark matter profile heating class. !!} - use :: Events_Hooks, only : calculationResetEvent implicit none type(darkMatterProfileHeatingMonotonic), intent(inout) :: self !![ !!] - if (calculationResetEvent%isAttached(self,monotonicCalculationReset)) call calculationResetEvent%detach(self,monotonicCalculationReset) return end subroutine monotonicDestructor - double precision function monotonicSpecificEnergy(self,node,radius,darkMatterProfileDMO_) + function monotonicGet(self,node) result(massDistributionHeating_) !!{ - Returns the specific energy of heating in the given {\normalfont \ttfamily node}. - !!} - use :: Numerical_Constants_Astronomical, only : gravitationalConstantGalacticus - implicit none - class (darkMatterProfileHeatingMonotonic), intent(inout) :: self - type (treeNode ), intent(inout) :: node - class (darkMatterProfileDMOClass ), intent(inout) :: darkMatterProfileDMO_ - double precision , intent(in ) :: radius - integer (kind_int8 ) :: uniqueID - - if (self%noShellCrossingIsValid(node,radius,darkMatterProfileDMO_)) then - monotonicSpecificEnergy=self%darkMatterProfileHeating_%specificEnergy( & - & node , & - & radius , & - & darkMatterProfileDMO_ & - & ) - else - uniqueID=node%uniqueID() - if (uniqueID /= self%lastUniqueID) call self%calculationReset(node,uniqueID) - if (self%energyPerturbationShellCrossing < 0.0d0) then - call self%computeRadiusShellCrossing ( & - & node , & - & radius , & - & darkMatterProfileDMO_ & - & ) - end if - monotonicSpecificEnergy=+self%energyPerturbationShellCrossing & - & *0.5d0 & - & *gravitationalConstantGalacticus & - & *darkMatterProfileDMO_ %enclosedMass(node,radius) & - & /radius - end if - return - end function monotonicSpecificEnergy - - double precision function monotonicSpecificEnergyGradient(self,node,radius,darkMatterProfileDMO_) - !!{ - Returns the gradient of the specific energy of heating in the given {\normalfont \ttfamily node}. - !!} - use :: Numerical_Constants_Astronomical, only : gravitationalConstantGalacticus - use :: Numerical_Constants_Math , only : Pi - implicit none - class (darkMatterProfileHeatingMonotonic), intent(inout) :: self - type (treeNode ), intent(inout) :: node - class (darkMatterProfileDMOClass ), intent(inout) :: darkMatterProfileDMO_ - double precision , intent(in ) :: radius - integer (kind_int8 ) :: uniqueID - - if (self%noShellCrossingIsValid(node,radius,darkMatterProfileDMO_)) then - monotonicSpecificEnergyGradient=self%darkMatterProfileHeating_%specificEnergyGradient( & - & node , & - & radius , & - & darkMatterProfileDMO_ & - & ) - else - uniqueID=node%uniqueID() - if (uniqueID /= self%lastUniqueID) call self%calculationReset(node,uniqueID) - if (self%energyPerturbationShellCrossing < 0.0d0) then - call self%computeRadiusShellCrossing ( & - & node , & - & radius , & - & darkMatterProfileDMO_ & - & ) - end if - monotonicSpecificEnergyGradient=+self%energyPerturbationShellCrossing & - & *0.5d0 & - & *gravitationalConstantGalacticus & - & *( & - & +4.0d0 & - & *Pi & - & * radius & - & *darkMatterProfileDMO_%density (node,radius) & - & -darkMatterProfileDMO_%enclosedMass(node,radius) & - & /radius**2 & - & ) - end if - return - end function monotonicSpecificEnergyGradient - - logical function monotonicSpecificEnergyIsEverywhereZero(self,node,darkMatterProfileDMO_) - !!{ - Returns true if the specific energy is everywhere zero in the given {\normalfont \ttfamily node}. + Return the dark matter mass distribution heating for the given {\normalfont \ttfamily node}. !!} + use :: Mass_Distributions, only : massDistributionHeatingMonotonic implicit none + class(massDistributionHeatingClass ), pointer :: massDistributionHeating_ class(darkMatterProfileHeatingMonotonic), intent(inout) :: self type (treeNode ), intent(inout) :: node - class(darkMatterProfileDMOClass ), intent(inout) :: darkMatterProfileDMO_ - - monotonicSpecificEnergyIsEverywhereZero=self%darkMatterProfileHeating_%specificEnergyIsEverywhereZero(node,darkMatterProfileDMO_) - return - end function monotonicSpecificEnergyIsEverywhereZero - - logical function monotonicNoShellCrossingIsValid(self,node,radius,darkMatterProfileDMO_) - !!{ - Determines if the no shell crossing assumption is valid. - !!} - use :: Numerical_Constants_Math, only : Pi - implicit none - class (darkMatterProfileHeatingMonotonic), intent(inout) :: self - type (treeNode ), intent(inout) :: node - class (darkMatterProfileDMOClass ), intent(inout) :: darkMatterProfileDMO_ - double precision , intent(in ) :: radius - double precision :: massEnclosed - - massEnclosed = darkMatterProfileDMO_%enclosedMass(node,radius) - if (massEnclosed > 0.0d0) then - monotonicNoShellCrossingIsValid=+self%darkMatterProfileHeating_%specificEnergyGradient( & - & node , & - & radius , & - & darkMatterProfileDMO_ & - & ) & - & * radius & - & +self%darkMatterProfileHeating_%specificEnergy ( & - & node , & - & radius , & - & darkMatterProfileDMO_ & - & ) & - & *( & - & +1.0d0 & - & -4.0d0 & - & *Pi & - & * radius**3 & - & *darkMatterProfileDMO_ %density ( & - & node , & - & radius & - & ) & - & /massEnclosed & - & ) & - & >=0.0d0 - else - monotonicNoShellCrossingIsValid=.true. - end if - return - end function monotonicNoShellCrossingIsValid - - subroutine monotonicComputeRadiusShellCrossing(self,node,radius,darkMatterProfileDMO_) - !!{ - Determines if the no shell crossing assumption is valid. - !!} - use :: Root_Finder , only : rangeExpandMultiplicative , rangeExpandSignExpectNegative, rangeExpandSignExpectPositive - use :: Numerical_Constants_Astronomical, only : gravitationalConstantGalacticus - implicit none - class (darkMatterProfileHeatingMonotonic), intent(inout), target :: self - type (treeNode ), intent(inout), target :: node - class (darkMatterProfileDMOClass ), intent(inout), target :: darkMatterProfileDMO_ - double precision , intent(in ) :: radius - integer (kind_int8 ) :: uniqueID - - uniqueID=node%uniqueID() - if (uniqueID /= self%lastUniqueID) call self%calculationReset(node,uniqueID) - if (self%energyPerturbationShellCrossing < 0.0d0) then - self_ => self - node_ => node - darkMatterProfileDMO__ => darkMatterProfileDMO_ - call self%finder%rangeExpand( & - & rangeExpandUpward =2.0d0 , & - & rangeExpandDownward =1.0d0 , & - & rangeExpandDownwardSignExpect=rangeExpandSignExpectNegative, & - & rangeExpandUpwardSignExpect =rangeExpandSignExpectPositive, & - & rangeExpandType =rangeExpandMultiplicative & - & ) - self%radiusShellCrossing =+self%finder%find(rootGuess=radius) - self%energyPerturbationShellCrossing =+self%darkMatterProfileHeating_%specificEnergy(node,self%radiusShellCrossing,darkMatterProfileDMO_) & - & /( & - & +0.5d0 & - & *gravitationalConstantGalacticus & - & *darkMatterProfileDMO_ %enclosedMass (node,self%radiusShellCrossing ) & - & / self%radiusShellCrossing & - & ) - end if - return - end subroutine monotonicComputeRadiusShellCrossing - - double precision function monotonicRadiusShellCrossingRoot(radius) - !!{ - Root function used in finding the radius where shell crossing happens. - !!} - use :: Numerical_Constants_Math, only : Pi - implicit none - double precision, intent(in ) :: radius - double precision :: massEnclosed - - massEnclosed = darkMatterProfileDMO__%enclosedMass(node_,radius) - if (massEnclosed > 0.0d0) then - monotonicRadiusShellCrossingRoot=+self_%darkMatterProfileHeating_%specificEnergyGradient( & - & node_ , & - & radius , & - & darkMatterProfileDMO__ & - & ) & - & * radius & - & +self_%darkMatterProfileHeating_%specificEnergy ( & - & node_ , & - & radius , & - & darkMatterProfileDMO__ & - & ) & - & *( & - & +1.0d0 & - & -4.0d0 & - & *Pi & - & * radius**3 & - & *darkMatterProfileDMO__ %density ( & - & node_ , & - & radius & - & ) & - & /massEnclosed & - & ) - else - monotonicRadiusShellCrossingRoot=0.0d0 - end if + class(massDistributionHeatingClass ), pointer :: massDistributionHeatingDecorated + + ! Create the mass distribution. + allocate(massDistributionHeatingMonotonic :: massDistributionHeating_) + select type(massDistributionHeating_) + type is (massDistributionHeatingMonotonic) + massDistributionHeatingDecorated => self%darkMatterProfileHeating_%get(node) + !![ + + + massDistributionHeatingMonotonic( & + & massDistributionHeating_=massDistributionHeatingDecorated & + & ) + + + + !!] + end select return - end function monotonicRadiusShellCrossingRoot + end function monotonicGet diff --git a/source/dark_matter_profiles.heating.null.F90 b/source/dark_matter_profiles.heating.null.F90 index 7e1fcce015..fa651c5cce 100644 --- a/source/dark_matter_profiles.heating.null.F90 +++ b/source/dark_matter_profiles.heating.null.F90 @@ -23,7 +23,9 @@ !![ - A dark matter profile heating model in which the heating is always zero. + + A dark matter profile heating model which constructs \refClass{massDistributionHeatingNull} objects to provide zero heating. + !!] @@ -33,9 +35,7 @@ !!} private contains - procedure :: specificEnergy => nullSpecificEnergy - procedure :: specificEnergyGradient => nullSpecificEnergyGradient - procedure :: specificEnergyIsEverywhereZero => nullSpecificEnergyIsEverywhereZero + procedure :: get => nullGet end type darkMatterProfileHeatingNull interface darkMatterProfileHeatingNull @@ -63,46 +63,28 @@ function nullConstructorParameters(parameters) result(self) return end function nullConstructorParameters - double precision function nullSpecificEnergy(self,node,radius,darkMatterProfileDMO_) + function nullGet(self,node) result(massDistributionHeating_) !!{ - Returns the specific energy of heating in the given {\normalfont \ttfamily node}. - !!} - implicit none - class (darkMatterProfileHeatingNull), intent(inout) :: self - type (treeNode ), intent(inout) :: node - class (darkMatterProfileDMOClass ), intent(inout) :: darkMatterProfileDMO_ - double precision , intent(in ) :: radius - !$GLC attributes unused :: self, node, radius, darkMatterProfileDMO_ - - nullSpecificEnergy=0.0d0 - return - end function nullSpecificEnergy - - double precision function nullSpecificEnergyGradient(self,node,radius,darkMatterProfileDMO_) - !!{ - Returns the gradient of the specific energy of heating in the given {\normalfont \ttfamily node}. - !!} - implicit none - class (darkMatterProfileHeatingNull), intent(inout) :: self - type (treeNode ), intent(inout) :: node - class (darkMatterProfileDMOClass ), intent(inout) :: darkMatterProfileDMO_ - double precision , intent(in ) :: radius - !$GLC attributes unused :: self, node, darkMatterProfileDMO_, radius - - nullSpecificEnergyGradient=0.0d0 - return - end function nullSpecificEnergyGradient - - logical function nullSpecificEnergyIsEverywhereZero(self,node,darkMatterProfileDMO_) - !!{ - Returns true if the specific energy is everywhere zero in the given {\normalfont \ttfamily node}. + Return the dark matter mass distribution heating for the given {\normalfont \ttfamily node}. !!} + use :: Mass_Distributions, only : massDistributionHeatingNull implicit none + class(massDistributionHeatingClass), pointer :: massDistributionHeating_ class(darkMatterProfileHeatingNull), intent(inout) :: self type (treeNode ), intent(inout) :: node - class(darkMatterProfileDMOClass ), intent(inout) :: darkMatterProfileDMO_ - !$GLC attributes unused :: self, node, darkMatterProfileDMO_ - - nullSpecificEnergyIsEverywhereZero=.true. + + ! Create the mass distribution. + allocate(massDistributionHeatingNull :: massDistributionHeating_) + select type(massDistributionHeating_) + type is (massDistributionHeatingNull) + !![ + + + massDistributionHeatingNull() + + + !!] + end select return - end function nullSpecificEnergyIsEverywhereZero + end function nullGet + diff --git a/source/dark_matter_profiles.heating.summation.F90 b/source/dark_matter_profiles.heating.summation.F90 index 7d87090bf0..c34a66daa1 100644 --- a/source/dark_matter_profiles.heating.summation.F90 +++ b/source/dark_matter_profiles.heating.summation.F90 @@ -40,10 +40,8 @@ private type(heatSourceList), pointer :: heatSources => null() contains - final :: summationDestructor - procedure :: specificEnergy => summationSpecificEnergy - procedure :: specificEnergyGradient => summationSpecificEnergyGradient - procedure :: specificEnergyIsEverywhereZero => summationSpecificEnergyIsEverywhereZero + final :: summationDestructor + procedure :: get => summationGet end type darkMatterProfileHeatingSummation interface darkMatterProfileHeatingSummation @@ -126,74 +124,45 @@ subroutine summationDestructor(self) return end subroutine summationDestructor - double precision function summationSpecificEnergy(self,node,radius,darkMatterProfileDMO_) + function summationGet(self,node) result(massDistributionHeating_) !!{ - Returns the specific energy of heating in the given {\normalfont \ttfamily node}. - !!} - implicit none - class (darkMatterProfileHeatingSummation), intent(inout) :: self - type (treeNode ), intent(inout) :: node - class (darkMatterProfileDMOClass ), intent(inout) :: darkMatterProfileDMO_ - double precision , intent(in ) :: radius - type (heatSourceList ), pointer :: heatSource - - summationSpecificEnergy = 0.0d0 - heatSource => self%heatSources - do while (associated(heatSource)) - summationSpecificEnergy=+summationSpecificEnergy & - & +heatSource%heatSource%specificEnergy( & - & node , & - & radius , & - & darkMatterProfileDMO_ & - & ) - heatSource => heatSource%next - end do - return - end function summationSpecificEnergy - - double precision function summationSpecificEnergyGradient(self,node,radius,darkMatterProfileDMO_) - !!{ - Returns the gradient of the specific energy of heating in the given {\normalfont \ttfamily node}. - !!} - implicit none - class (darkMatterProfileHeatingSummation), intent(inout) :: self - type (treeNode ), intent(inout) :: node - class (darkMatterProfileDMOClass ), intent(inout) :: darkMatterProfileDMO_ - double precision , intent(in ) :: radius - type (heatSourceList ), pointer :: heatSource - - summationSpecificEnergyGradient = 0.0d0 - heatSource => self%heatSources - do while (associated(heatSource)) - summationSpecificEnergyGradient=+summationSpecificEnergyGradient & - & +heatSource%heatSource%specificEnergyGradient( & - & node , & - & radius , & - & darkMatterProfileDMO_ & - & ) - heatSource => heatSource%next - end do - return - end function summationSpecificEnergyGradient - - logical function summationSpecificEnergyIsEverywhereZero(self,node,darkMatterProfileDMO_) - !!{ - Returns true if the specific energy is everywhere zero in the given {\normalfont \ttfamily node}. + Return the dark matter mass distribution heating for the given {\normalfont \ttfamily node}. !!} + use :: Mass_Distributions, only : massDistributionHeatingSummation, massDistributionHeatingList implicit none + class(massDistributionHeatingClass ), pointer :: massDistributionHeating_ class(darkMatterProfileHeatingSummation), intent(inout) :: self type (treeNode ), intent(inout) :: node - class(darkMatterProfileDMOClass ), intent(inout) :: darkMatterProfileDMO_ + type (massDistributionHeatingList ), pointer :: massDistributionHeatings, massDistributionHeating__ type (heatSourceList ), pointer :: heatSource - summationSpecificEnergyIsEverywhereZero = .true. - heatSource => self%heatSources - do while (associated(heatSource)) - if (.not.heatSource%heatSource%specificEnergyIsEverywhereZero(node,darkMatterProfileDMO_)) then - summationSpecificEnergyIsEverywhereZero=.false. - exit - end if - heatSource => heatSource%next - end do + ! Create the mass distribution. + allocate(massDistributionHeatingSummation :: massDistributionHeating_) + select type(massDistributionHeating_) + type is (massDistributionHeatingSummation) + heatSource => self%heatSources + massDistributionHeating__ => null() + massDistributionHeatings => null() + do while (associated(heatSource)) + if (associated(massDistributionHeatings)) then + allocate(massDistributionHeating__%next) + massDistributionHeating__ => massDistributionHeating__%next + else + allocate(massDistributionHeatings ) + massDistributionHeating__ => massDistributionHeatings + end if + massDistributionHeating__%massDistributionHeating_ => heatSource%heatSource%get(node) + heatSource => heatSource%next + end do + !![ + + + massDistributionHeatingSummation( & + & massDistributionHeatings=massDistributionHeatings & + & ) + + + !!] + end select return - end function summationSpecificEnergyIsEverywhereZero + end function summationGet diff --git a/source/dark_matter_profiles.heating.tidal.F90 b/source/dark_matter_profiles.heating.tidal.F90 index 6cf02e7c48..a6c5d4a7f0 100644 --- a/source/dark_matter_profiles.heating.tidal.F90 +++ b/source/dark_matter_profiles.heating.tidal.F90 @@ -21,25 +21,11 @@ A dark matter halo profile heating class which accounts for heating from tidal shocking. !!} - use :: Kind_Numbers, only : kind_int8 - !![ - A dark matter profile heating model which accounts for heating due to tidal shocking. The model follows the general - approach of \cite{gnedin_tidal_1999}. The change in the specific energy of particles at radius $r$ in a halo is given by - $\Delta \epsilon = \Delta \epsilon_1 + \Delta \epsilon_2$, where $\Delta \epsilon_1$, and $\Delta \epsilon_2$ are the first - and second order perturbations respectively. The first order term is given by $\Delta \epsilon_1 = Q r^2$ where $Q$ is the - tidal tensor integrated along the orbital path (see, for example, \citealt{taylor_dynamics_2001}), while the second order - term is given by $\Delta \epsilon_2 = (2/3) f \sigma_\mathrm{rms} (1+\chi_\mathrm{r,v}) \sqrt{\Delta \epsilon_1}$ - \citep[][eqn.~20, see also \protect\citealt{gnedin_self-consistent_1999}; eqn.~18a,b]{gnedin_tidal_1999}. For the particle - velocity dispersion, $v_\mathrm{rms}$, we use $\sqrt{3} \sigma_\mathrm{r}(r)$, the radial velocity dispersion in the dark - matter profile scaled to the total velocity dispersion assuming an isotropic velocity distribution. The position-velocity - correlation function, $\chi_\mathrm{r,v}$, is taken to be a constant given by the parameter {\normalfont \ttfamily - [correlationVelocityRadius]}. The coefficient, $f=${\normalfont \ttfamily [coefficientSecondOrder]} is introduced to allow - some freedom to adjust the contribution of the second order term. It is degenerate with the value of $\chi_\mathrm{r,v}$ - but is introduced to allow for possible future promotion of $\chi_\mathrm{r,v}$ from a constant to a function of the dark - matter profile potential \citep[see, for example,][appendix~B]{gnedin_self-consistent_1999}. + A dark matter profile heating class that constructs \refClass{massDistributionHeatingTidal} objects to compute heating due to + tidal shocks. !!] @@ -48,26 +34,10 @@ but is introduced to allow for possible future promotion of $\chi_\mathrm{r,v}$ A dark matter profile heating class which accounts for heating due to tidal shocking. !!} private - double precision :: specificEnergyOverRadiusSquared_, specificEnergyOverRadiusSquaredParent_, & - & correlationVelocityRadius , coefficientSecondOrder0 , & - & coefficientSecondOrder1 , coefficientSecondOrder2 - integer (kind_int8) :: lastUniqueID , parentUniqueID - contains - !![ - - - - - - !!] - final :: tidalDestructor - procedure :: autoHook => tidalAutoHook - procedure :: calculationReset => tidalCalculationReset - procedure :: specificEnergy => tidalSpecificEnergy - procedure :: specificEnergyGradient => tidalSpecificEnergyGradient - procedure :: specificEnergyIsEverywhereZero => tidalSpecificEnergyIsEverywhereZero - procedure :: specificEnergyOverRadiusSquared => tidalSpecificEnergyOverRadiusSquared - procedure :: specificEnergyTerms => tidalSpecificEnergyTerms + double precision :: correlationVelocityRadius, coefficientSecondOrder0, & + & coefficientSecondOrder1 , coefficientSecondOrder2 + contains + procedure :: get => tidalGet end type darkMatterProfileHeatingTidal interface darkMatterProfileHeatingTidal @@ -136,205 +106,45 @@ function tidalConstructorInternal(coefficientSecondOrder0,coefficientSecondOrder !!] - self%specificEnergyOverRadiusSquared_ =-1.0d0 - self%specificEnergyOverRadiusSquaredParent_=-1.0d0 - self%lastUniqueID =-1_kind_int8 - self%parentUniqueID =-1_kind_int8 return end function tidalConstructorInternal - subroutine tidalAutoHook(self) - !!{ - Attach to the calculation reset event. - !!} - use :: Events_Hooks, only : calculationResetEvent, openMPThreadBindingAllLevels - implicit none - class(darkMatterProfileHeatingTidal), intent(inout) :: self - - call calculationResetEvent%attach(self,tidalCalculationReset,openMPThreadBindingAllLevels,label='darkMatterProfileHeatingTidal') - return - end subroutine tidalAutoHook - - subroutine tidalCalculationReset(self,node,uniqueID) - !!{ - Reset the stored tidal radii. - !!} - use :: Kind_Numbers, only : kind_int8 - implicit none - class (darkMatterProfileHeatingTidal), intent(inout) :: self - type (treeNode ), intent(inout) :: node - integer(kind_int8 ), intent(in ) :: uniqueID - - self %specificEnergyOverRadiusSquared_ =-1.0d0 - self %specificEnergyOverRadiusSquaredParent_=-1.0d0 - self %lastUniqueID = uniqueID - if (associated(node%parent)) then - self%parentUniqueID =node%parent%uniqueID() - else - self%parentUniqueID =-1_kind_int8 - end if - return - end subroutine tidalCalculationReset - - subroutine tidalDestructor(self) - !!{ - Destructor for the {\normalfont \ttfamily tidal} dark matter profile heating class. - !!} - use :: Events_Hooks, only : calculationResetEvent - implicit none - type(darkMatterProfileHeatingTidal), intent(inout) :: self - - if (calculationResetEvent%isAttached(self,tidalCalculationReset)) call calculationResetEvent%detach(self,tidalCalculationReset) - return - end subroutine tidalDestructor - - double precision function tidalSpecificEnergy(self,node,radius,darkMatterProfileDMO_) - !!{ - Returns the specific energy of heating in the given {\normalfont \ttfamily node}. - !!} - implicit none - class (darkMatterProfileHeatingTidal), intent(inout) :: self - type (treeNode ), intent(inout) :: node - double precision , intent(in ) :: radius - class (darkMatterProfileDMOClass ), intent(inout) :: darkMatterProfileDMO_ - double precision :: energyPerturbationFirstOrder, energyPerturbationSecondOrder - - call self%specificEnergyTerms(node,radius,energyPerturbationFirstOrder,energyPerturbationSecondOrder,darkMatterProfileDMO_) - tidalSpecificEnergy=+energyPerturbationFirstOrder & - & +energyPerturbationSecondOrder - return - end function tidalSpecificEnergy - - double precision function tidalSpecificEnergyGradient(self,node,radius,darkMatterProfileDMO_) - !!{ - Returns the gradient of the specific energy of heating in the given {\normalfont \ttfamily node}. - !!} - use :: Numerical_Constants_Astronomical, only : gravitationalConstantGalacticus - implicit none - class (darkMatterProfileHeatingTidal), intent(inout) :: self - type (treeNode ), intent(inout) :: node - double precision , intent(in ) :: radius - class (darkMatterProfileDMOClass ), intent(inout) :: darkMatterProfileDMO_ - double precision :: energyPerturbationFirstOrder, energyPerturbationSecondOrder - - - if (radius > 0.0d0) then - call self%specificEnergyTerms(node,radius,energyPerturbationFirstOrder,energyPerturbationSecondOrder,darkMatterProfileDMO_) - if (energyPerturbationSecondOrder > 0.0d0) then - tidalSpecificEnergyGradient=+( & - & +energyPerturbationFirstOrder * 2.0d0 & ! dlog[r² ]/dlog(r) term - & +energyPerturbationSecondOrder*( & - & -0.5d0 & ! ⎧ dlog[σ_r(r)]/dlog[r] term - & *darkMatterProfileDMO_%densityLogSlope (node,radius) & ! ⎥ - & -0.5d0 & ! ⎥ Assumes the Jeans equation in - & *gravitationalConstantGalacticus & ! ⎥ spherical symmetry with anisotropy - & *darkMatterProfileDMO_%enclosedMass (node,radius) & ! ⎥ parameter β=0. Would be better to - & / radius & ! ⎥ have this provided by the - & /darkMatterProfileDMO_%radialVelocityDispersion(node,radius)**2 & ! ⎩ darkMatterProfileDMO class. - & +1.0d0 & ! dlog[r ]/dlog(r) term - & ) & - & ) & - & /radius - else - tidalSpecificEnergyGradient=+ energyPerturbationFirstOrder * 2.0d0 & ! dlog[r² ]/dlog(r) term - & /radius - end if - else - tidalSpecificEnergyGradient=+0.0d0 - end if - return - end function tidalSpecificEnergyGradient - - subroutine tidalSpecificEnergyTerms(self,node,radius,energyPerturbationFirstOrder,energyPerturbationSecondOrder,darkMatterProfileDMO_) + function tidalGet(self,node) result(massDistributionHeating_) !!{ - Compute the first and second order perturbations to the energy. + Return the dark matter mass distribution heating for the given {\normalfont \ttfamily node}. !!} + use :: Galacticus_Nodes , only : nodeComponentSatellite + use :: Mass_Distributions, only : massDistributionHeatingTidal implicit none + class (massDistributionHeatingClass ), pointer :: massDistributionHeating_ class (darkMatterProfileHeatingTidal), intent(inout) :: self type (treeNode ), intent(inout) :: node - double precision , intent(in ) :: radius - double precision , intent( out) :: energyPerturbationFirstOrder, energyPerturbationSecondOrder - class (darkMatterProfileDMOClass ), intent(inout) :: darkMatterProfileDMO_ - double precision :: coefficientSecondOrder, densityLogSlope - - energyPerturbationFirstOrder=+self%specificEnergyOverRadiusSquared(node) & - & *radius **2 - if ( & - & self%coefficientSecondOrder0 /= 0.0d0 & - & .or. & - & self%coefficientSecondOrder1 /= 0.0d0 & - & .or. & - & self%coefficientSecondOrder2 /= 0.0d0 & - & ) then - ! Compute the coefficient for the second order term. - densityLogSlope =+darkMatterProfileDMO_%densityLogSlope(node,radius) - coefficientSecondOrder=+self%coefficientSecondOrder0 & - & +self%coefficientSecondOrder1*densityLogSlope & - & +self%coefficientSecondOrder2*densityLogSlope**2 - ! Compute the second order energy perturbation. - energyPerturbationSecondOrder=+sqrt(2.0d0) & - & *coefficientSecondOrder & - & *( & - & +1.0d0 & - & +self%correlationVelocityRadius & - & ) & - & *sqrt(energyPerturbationFirstOrder) & - & *darkMatterProfileDMO_%radialVelocityDispersion(node,radius) - else - energyPerturbationSecondOrder=+0.0d0 - end if - return - end subroutine tidalSpecificEnergyTerms - - logical function tidalSpecificEnergyIsEverywhereZero(self,node,darkMatterProfileDMO_) - !!{ - Returns true if the specific energy is everywhere zero in the given {\normalfont \ttfamily node}. - !!} - implicit none - class(darkMatterProfileHeatingTidal), intent(inout) :: self - type (treeNode ), intent(inout) :: node - class(darkMatterProfileDMOClass ), intent(inout) :: darkMatterProfileDMO_ - !$GLC attributes unused :: darkMatterProfileDMO_ - - tidalSpecificEnergyIsEverywhereZero=self%specificEnergyOverRadiusSquared(node) <= 0.0d0 + class (nodeComponentSatellite ), pointer :: satellite + double precision :: heatSpecificNormalized + + ! Create the mass distribution. + allocate(massDistributionHeatingTidal :: massDistributionHeating_) + select type(massDistributionHeating_) + type is (massDistributionHeatingTidal) + satellite => node %satellite () + heatSpecificNormalized = max( & + & +0.0d0 , & + & +satellite%tidalHeatingNormalized() & + & ) + !![ + + + massDistributionHeatingTidal( & + & heatSpecificNormalized =heatSpecificNormalized , & + & coefficientSecondOrder0 =self%coefficientSecondOrder0 , & + & coefficientSecondOrder1 =self%coefficientSecondOrder1 , & + & coefficientSecondOrder2 =self%coefficientSecondOrder2 , & + & correlationVelocityRadius=self%correlationVelocityRadius & + & ) + + + !!] + end select return - end function tidalSpecificEnergyIsEverywhereZero - - double precision function tidalSpecificEnergyOverRadiusSquared(self,node) - !!{ - Compute $Q = E / r^2$. - !!} - use :: Galacticus_Nodes, only : nodeComponentSatellite, treeNode - implicit none - class (darkMatterProfileHeatingTidal), intent(inout) :: self - type (treeNode ), intent(inout) :: node - class (nodeComponentSatellite ), pointer :: satellite - integer(kind_int8 ) :: uniqueID + end function tidalGet - uniqueID=node%uniqueID() - if ( & - & uniqueID /= self%parentUniqueID & - & .and. & - & uniqueID /= self%lastUniqueID & - & ) call self%calculationReset(node,uniqueID) - if (uniqueID == self%parentUniqueID) then - if (self%specificEnergyOverRadiusSquaredParent_ < 0.0d0) then - satellite => node %satellite () - self%specificEnergyOverRadiusSquaredParent_ = max( & - & +0.0d0 , & - & +satellite%tidalHeatingNormalized() & - & ) - end if - tidalSpecificEnergyOverRadiusSquared=self%specificEnergyOverRadiusSquaredParent_ - else - if (self%specificEnergyOverRadiusSquared_ < 0.0d0) then - satellite => node %satellite () - self%specificEnergyOverRadiusSquared_ = max( & - & +0.0d0 , & - & +satellite%tidalHeatingNormalized() & - & ) - end if - tidalSpecificEnergyOverRadiusSquared=self%specificEnergyOverRadiusSquared_ - end if - return - end function tidalSpecificEnergyOverRadiusSquared diff --git a/source/dark_matter_profiles.heating.two_body_relaxation.F90 b/source/dark_matter_profiles.heating.two_body_relaxation.F90 index c0673ea864..839d5cf187 100644 --- a/source/dark_matter_profiles.heating.two_body_relaxation.F90 +++ b/source/dark_matter_profiles.heating.two_body_relaxation.F90 @@ -23,7 +23,10 @@ !![ - A dark matter profile heating model which computes heating due to two-body relaxation. + + A dark matter profile heating class which returns a \refClass{massDistributionHeatingTwoBodyRelaxation} objects to compute + heating due to two-body relaxation. + !!] @@ -35,9 +38,7 @@ double precision :: massParticle, lengthSoftening, & & timeStart , efficiency contains - procedure :: specificEnergy => twoBodyRelaxationSpecificEnergy - procedure :: specificEnergyGradient => twoBodyRelaxationSpecificEnergyGradient - procedure :: specificEnergyIsEverywhereZero => twoBodyRelaxationSpecificEnergyIsEverywhereZero + procedure :: get => twoBodyRelaxationGet end type darkMatterProfileHeatingTwoBodyRelaxation interface darkMatterProfileHeatingTwoBodyRelaxation @@ -105,154 +106,36 @@ function twoBodyRelaxationConstructorInternal(massParticle,lengthSoftening,timeS return end function twoBodyRelaxationConstructorInternal - double precision function twoBodyRelaxationSpecificEnergy(self,node,radius,darkMatterProfileDMO_) + function twoBodyRelaxationGet(self,node) result(massDistributionHeating_) !!{ - Returns the specific energy of heating in the given {\normalfont \ttfamily node}. The assumption here is that the mean - fractional change in energy for a particle per crossing time is $8 \log \Lambda / N$ where $N$ is the number of particles - within radius $r=${\normalfont \ttfamily radius}. The crossing time is approximated by $r/V(r)$ where $V(r)$ is the - circular velocity at $r$. The Coulomb logarithm is given by $\log\Lambda=\hbox{max}(\epsilon,b_{90})$ where $\epsilon$ is - the softening length, $b_{90}=2\mathrm{G}m_\mathrm{p}/V^2(r)$, and $m_\mathrm{p}$ is the particle mass. Finally, the - specific energy is assumed to be $\sigma^2(r)/2\approx V^2(r)/4$. + Return the dark matter mass distribution heating for the given {\normalfont \ttfamily node}. !!} - use :: Galacticus_Nodes , only : nodeComponentBasic, treeNode - use :: Numerical_Constants_Astronomical, only : gigaYear , megaParsec, gravitationalConstantGalacticus - use :: Numerical_Constants_Prefixes , only : kilo + use :: Galacticus_Nodes , only : nodeComponentBasic + use :: Mass_Distributions, only : massDistributionHeatingTwoBodyRelaxation implicit none + class (massDistributionHeatingClass ), pointer :: massDistributionHeating_ class (darkMatterProfileHeatingTwoBodyRelaxation), intent(inout) :: self type (treeNode ), intent(inout) :: node - class (darkMatterProfileDMOClass ), intent(inout) :: darkMatterProfileDMO_ - double precision , intent(in ) :: radius class (nodeComponentBasic ), pointer :: basic - double precision :: particleCount , velocity , & - & logarithmCoulomb , impactParameterCritical - - basic => node%basic() - if (basic%time() > self%timeStart) then - velocity =+darkMatterProfileDMO_ %circularVelocity(node,radius) - impactParameterCritical =+2.0d0 & - & *gravitationalConstantGalacticus & - & *self %massParticle & - & /velocity **2 - logarithmCoulomb =+0.5d0 & - & *log( & - & +1.0d0 & - & +( & - & +radius & - & /max( & - & self %lengthSoftening , & - & impactParameterCritical & - & ) & - & ) **2 & - & ) - particleCount =+darkMatterProfileDMO_ %enclosedMass (node,radius) & - & /self %massParticle - twoBodyRelaxationSpecificEnergy=+2.0d0 & - & *self %efficiency & - & *logarithmCoulomb & - & *( & - & +basic %time ( ) & - & -self %timeStart & - & ) & - & *velocity **3 & - & /radius & - & /particleCount & - & *kilo & - & *gigaYear & - & /megaParsec - else - twoBodyRelaxationSpecificEnergy=+0.0d0 - end if - return - end function twoBodyRelaxationSpecificEnergy - - double precision function twoBodyRelaxationSpecificEnergyGradient(self,node,radius,darkMatterProfileDMO_) - !!{ - Returns the gradient of the specific energy of heating in the given {\normalfont \ttfamily node}. - !!} - use :: Galacticus_Nodes , only : nodeComponentBasic , treeNode - use :: Numerical_Constants_Astronomical, only : gravitationalConstantGalacticus - implicit none - class (darkMatterProfileHeatingTwoBodyRelaxation), intent(inout) :: self - type (treeNode ), intent(inout) :: node - class (darkMatterProfileDMOClass ), intent(inout) :: darkMatterProfileDMO_ - double precision , intent(in ) :: radius - class (nodeComponentBasic ), pointer :: basic - double precision :: particleCount , velocity , & - & logarithmCoulomb , impactParameterCritical, & - & gradientCoulomb - - basic => node%basic() - if (basic%time() > self%timeStart) then - velocity =+darkMatterProfileDMO_ %circularVelocity(node,radius) - impactParameterCritical =+2.0d0 & - & *gravitationalConstantGalacticus & - & *self %massParticle & - & /velocity **2 - logarithmCoulomb =+0.5d0 & - & *log( & - & +1.0d0 & - & +( & - & +radius & - & /max( & - & self %lengthSoftening , & - & impactParameterCritical & - & ) & - & ) **2 & - & ) - particleCount =+darkMatterProfileDMO_ %enclosedMass (node,radius) & - & /self %massParticle - if (self%lengthSoftening > impactParameterCritical) then - gradientCoulomb=+radius & - & /self %lengthSoftening - else - gradientCoulomb=+2.0d0 & - & *radius & - & /impactParameterCritical & - & *( & - & -1.0d0 & - & +8.0d0 & - & *Pi & - & *gravitationalConstantGalacticus & - & *radius **2 & - & *darkMatterProfileDMO_ %density (node,radius) & - & /velocity **2 & - & ) - end if - twoBodyRelaxationSpecificEnergyGradient=+self %specificEnergy(node,radius,darkMatterProfileDMO_) & - & / radius & - & *( & - & -2.5d0 & - & +6.0d0 & - & *Pi & - & *gravitationalConstantGalacticus & - & *darkMatterProfileDMO_ %density (node,radius ) & - & *radius **2 & - & /velocity **2 & - & - radius & - & * gradientCoulomb & - & / logarithmCoulomb & - & *sqrt(exp(2.0d0*logarithmCoulomb)-1.0d0) & - & / exp(2.0d0*logarithmCoulomb) & - & ) - else - twoBodyRelaxationSpecificEnergyGradient=0.0d0 - end if - return - end function twoBodyRelaxationSpecificEnergyGradient - - logical function twoBodyRelaxationSpecificEnergyIsEverywhereZero(self,node,darkMatterProfileDMO_) - !!{ - Returns true if the specific energy is everywhere zero in the given {\normalfont \ttfamily node}. - !!} - use :: Galacticus_Nodes, only : nodeComponentBasic, treeNode - implicit none - class(darkMatterProfileHeatingTwoBodyRelaxation), intent(inout) :: self - type (treeNode ), intent(inout) :: node - class(darkMatterProfileDMOClass ), intent(inout) :: darkMatterProfileDMO_ - class(nodeComponentBasic ), pointer :: basic - !$GLC attributes unused :: self, darkMatterProfileDMO_ - - basic => node %basic() - twoBodyRelaxationSpecificEnergyIsEverywhereZero = basic%time () <= self%timeStart + + ! Create the mass distribution. + allocate(massDistributionHeatingTwoBodyRelaxation :: massDistributionHeating_) + select type(massDistributionHeating_) + type is (massDistributionHeatingTwoBodyRelaxation) + basic => node%basic() + !![ + + + massDistributionHeatingTwoBodyRelaxation( & + & massParticle =+self %massParticle , & + & lengthSoftening=+self %lengthSoftening , & + & timeRelaxing =+basic%time () & + & -self %timeStart , & + & efficiency =+self %efficiency & + & ) + + + !!] + end select return - end function twoBodyRelaxationSpecificEnergyIsEverywhereZero + end function twoBodyRelaxationGet diff --git a/source/dark_matter_profiles.structure.concentration.Bullock2001.F90 b/source/dark_matter_profiles.structure.concentration.Bullock2001.F90 index 21245b6d19..4a74e5a808 100644 --- a/source/dark_matter_profiles.structure.concentration.Bullock2001.F90 +++ b/source/dark_matter_profiles.structure.concentration.Bullock2001.F90 @@ -24,7 +24,6 @@ use :: Cosmological_Density_Field, only : cosmologicalMassVarianceClass, criticalOverdensityClass use :: Cosmology_Functions , only : cosmologyFunctionsClass use :: Cosmology_Parameters , only : cosmologyParametersClass - use :: Dark_Matter_Profiles_DMO , only : darkMatterProfileDMOClass , darkMatterProfileDMONFW use :: Virial_Density_Contrast , only : virialDensityContrastClass , virialDensityContrastSphericalCollapseClsnlssMttrCsmlgclCnstnt !![ @@ -48,7 +47,6 @@ class (criticalOverdensityClass ), pointer :: criticalOverdensity_ => null() class (cosmologicalMassVarianceClass ), pointer :: cosmologicalMassVariance_ => null() class (virialDensityContrastClass ), pointer :: virialDensityContrast_ => null() - class (darkMatterProfileDMOClass ), pointer :: darkMatterProfileDMO_ => null() type (virialDensityContrastSphericalCollapseClsnlssMttrCsmlgclCnstnt), pointer :: virialDensityContrastDefinition_ => null() type (darkMatterProfileDMONFW ), pointer :: darkMatterProfileDMODefinition_ => null() double precision :: F , K @@ -83,7 +81,6 @@ function bullock2001ConstructorParameters(parameters) result(self) class (criticalOverdensityClass ), pointer :: criticalOverdensity_ class (cosmologicalMassVarianceClass ), pointer :: cosmologicalMassVariance_ class (virialDensityContrastClass ), pointer :: virialDensityContrast_ - class (darkMatterProfileDMOClass ), pointer :: darkMatterProfileDMO_ double precision :: F , K ! Check and read parameters. @@ -107,9 +104,8 @@ function bullock2001ConstructorParameters(parameters) result(self) - !!] - self=darkMatterProfileConcentrationBullock2001(F,K,cosmologyParameters_,cosmologyFunctions_,criticalOverdensity_,cosmologicalMassVariance_,virialDensityContrast_,darkMatterProfileDMO_) + self=darkMatterProfileConcentrationBullock2001(F,K,cosmologyParameters_,cosmologyFunctions_,criticalOverdensity_,cosmologicalMassVariance_,virialDensityContrast_) !![ @@ -117,29 +113,27 @@ function bullock2001ConstructorParameters(parameters) result(self) - !!] return end function bullock2001ConstructorParameters - function bullock2001ConstructorInternal(F,K,cosmologyParameters_,cosmologyFunctions_,criticalOverdensity_,cosmologicalMassVariance_,virialDensityContrast_,darkMatterProfileDMO_) result(self) + function bullock2001ConstructorInternal(F,K,cosmologyParameters_,cosmologyFunctions_,criticalOverdensity_,cosmologicalMassVariance_,virialDensityContrast_) result(self) !!{ Constructor for the {\normalfont \ttfamily bullock2001} dark matter halo profile concentration class. !!} use :: Dark_Matter_Halo_Scales, only : darkMatterHaloScaleVirialDensityContrastDefinition implicit none - type (darkMatterProfileConcentrationBullock2001 ) :: self - double precision , intent(in ) :: F , K - class (cosmologyParametersClass ), intent(in ), target :: cosmologyParameters_ - class (cosmologyFunctionsClass ), intent(in ), target :: cosmologyFunctions_ - class (criticalOverdensityClass ), intent(in ), target :: criticalOverdensity_ - class (cosmologicalMassVarianceClass ), intent(in ), target :: cosmologicalMassVariance_ - class (virialDensityContrastClass ), intent(in ), target :: virialDensityContrast_ - class (darkMatterProfileDMOClass ), intent(in ), target :: darkMatterProfileDMO_ + type (darkMatterProfileConcentrationBullock2001 ) :: self + double precision , intent(in ) :: F , K + class (cosmologyParametersClass ), intent(in ), target :: cosmologyParameters_ + class (cosmologyFunctionsClass ), intent(in ), target :: cosmologyFunctions_ + class (criticalOverdensityClass ), intent(in ), target :: criticalOverdensity_ + class (cosmologicalMassVarianceClass ), intent(in ), target :: cosmologicalMassVariance_ + class (virialDensityContrastClass ), intent(in ), target :: virialDensityContrast_ type (darkMatterHaloScaleVirialDensityContrastDefinition) , pointer :: darkMatterHaloScaleDefinition_ !![ - + !!] allocate(self%darkMatterProfileDMODefinition_ ) @@ -189,7 +183,6 @@ subroutine bullock2001Destructor(self) - !!] @@ -226,7 +219,6 @@ double precision function bullock2001Concentration(self,node) & densityContrast , & & cosmologyParameters_ =self %cosmologyParameters_ , & & cosmologyFunctions_ =self %cosmologyFunctions_ , & - & darkMatterProfileDMO_ =self %darkMatterProfileDMO_ , & & virialDensityContrast_=self %virialDensityContrast_ & & ) massHaloFormation = +self%F*massHalo diff --git a/source/dark_matter_profiles.structure.mass_definitions.F90 b/source/dark_matter_profiles.structure.mass_definitions.F90 index 4e73aeac79..3213c802ba 100644 --- a/source/dark_matter_profiles.structure.mass_definitions.F90 +++ b/source/dark_matter_profiles.structure.mass_definitions.F90 @@ -30,19 +30,20 @@ module Dark_Matter_Profile_Mass_Definitions contains - function Dark_Matter_Profile_Mass_Definition(node,densityContrast,radius,velocity,cosmologyParameters_,cosmologyFunctions_,darkMatterProfileDMO_,virialDensityContrast_,useLastIsolatedTime) result(massHalo) + function Dark_Matter_Profile_Mass_Definition(node,densityContrast,radius,velocity,cosmologyParameters_,cosmologyFunctions_,virialDensityContrast_,useLastIsolatedTime) result(massHalo) !!{ Compute the mass of {\normalfont \ttfamily node} under the given density contrast definition. !!} use :: Cosmology_Functions , only : cosmologyFunctionsClass use :: Cosmology_Parameters , only : cosmologyParametersClass - use :: Dark_Matter_Profiles_DMO , only : darkMatterProfileDMOClass use :: Galacticus_Nodes , only : nodeComponentBasic , treeNode + use :: Mass_Distributions , only : massDistributionClass use :: Math_Exponentiation , only : cubeRoot use :: Numerical_Comparison , only : Values_Agree use :: Numerical_Constants_Math , only : Pi use :: Numerical_Constants_Astronomical, only : gravitationalConstantGalacticus use :: Virial_Density_Contrast , only : virialDensityContrastClass + use :: Galactic_Structure_Options , only : componentTypeDarkMatterOnly , massTypeDark implicit none double precision :: massHalo type (treeNode ) , intent(inout) :: node @@ -51,8 +52,8 @@ function Dark_Matter_Profile_Mass_Definition(node,densityContrast,radius,velocit logical , optional, intent(in ) :: useLastIsolatedTime class (cosmologyParametersClass ) , intent(inout) :: cosmologyParameters_ class (cosmologyFunctionsClass ) , intent(inout) :: cosmologyFunctions_ - class (darkMatterProfileDMOClass ) , intent(inout) :: darkMatterProfileDMO_ class (virialDensityContrastClass) , intent(inout) :: virialDensityContrast_ + class (massDistributionClass ), pointer :: massDistribution_ class (nodeComponentBasic ), pointer :: basic double precision :: radiusHalo , density , & & time @@ -94,7 +95,11 @@ function Dark_Matter_Profile_Mass_Definition(node,densityContrast,radius,velocit else ! Mismatched density contrast definitions - compute the mass directly. ! Get the radius in the halo enclosing this density. - radiusHalo=+darkMatterProfileDMO_%radiusEnclosingDensity(node,density) + massDistribution_ => node %massDistribution (componentTypeDarkMatterOnly,massTypeDark) + radiusHalo = massDistribution_%radiusEnclosingDensity(density ) + !![ + + !!] ! Find the mass within that radius - this is computable directly from the mean density and the radius enclosing that mean ! density. massHalo =+4.0d0 & diff --git a/source/dark_matter_profiles.structure.scale.Johnson2021.F90 b/source/dark_matter_profiles.structure.scale.Johnson2021.F90 index 296dc2ea8d..0882cd009f 100644 --- a/source/dark_matter_profiles.structure.scale.Johnson2021.F90 +++ b/source/dark_matter_profiles.structure.scale.Johnson2021.F90 @@ -21,7 +21,6 @@ Implements a dark matter profile scale radius class using the energy-based model of \cite{johnson_random_2021}. !!} - use :: Dark_Matter_Profiles_DMO , only : darkMatterProfileDMOClass use :: Dark_Matter_Halo_Scales , only : darkMatterHaloScaleClass use :: Galacticus_Nodes , only : nodeComponentDarkMatterProfile use :: Virial_Orbits , only : virialOrbitClass @@ -42,7 +41,6 @@ !!} private class (darkMatterProfileScaleRadiusClass), pointer :: darkMatterProfileScaleRadius_ => null() - class (darkMatterProfileDMOClass ), pointer :: darkMatterProfileDMO_ => null() class (darkMatterHaloScaleClass ), pointer :: darkMatterHaloScale_ => null() class (virialOrbitClass ), pointer :: virialOrbit_ => null() class (mergerTreeMassResolutionClass ), pointer :: mergerTreeMassResolution_ => null() @@ -80,7 +78,6 @@ function darkMatterProfileScaleJohnson2021ConstructorParameters(parameters) resu type (darkMatterProfileScaleRadiusJohnson2021) :: self type (inputParameters ), intent(inout) :: parameters class (darkMatterProfileScaleRadiusClass ), pointer :: darkMatterProfileScaleRadius_ - class (darkMatterProfileDMOClass ), pointer :: darkMatterProfileDMO_ class (darkMatterHaloScaleClass ), pointer :: darkMatterHaloScale_ class (virialOrbitClass ), pointer :: virialOrbit_ class (mergerTreeMassResolutionClass ), pointer :: mergerTreeMassResolution_ @@ -110,16 +107,14 @@ function darkMatterProfileScaleJohnson2021ConstructorParameters(parameters) resu Factor multiplying the estimate of the internal energy of unresolved accretion. - !!] - self=darkMatterProfileScaleRadiusJohnson2021(massExponent,energyBoost,unresolvedEnergy,darkMatterProfileScaleRadius_,darkMatterProfileDMO_,darkMatterHaloScale_,virialOrbit_,mergerTreeMassResolution_) + self=darkMatterProfileScaleRadiusJohnson2021(massExponent,energyBoost,unresolvedEnergy,darkMatterProfileScaleRadius_,darkMatterHaloScale_,virialOrbit_,mergerTreeMassResolution_) !![ - @@ -127,21 +122,20 @@ function darkMatterProfileScaleJohnson2021ConstructorParameters(parameters) resu return end function darkMatterProfileScaleJohnson2021ConstructorParameters - function darkMatterProfileScaleJohnson2021ConstructorInternal(massExponent,energyBoost,unresolvedEnergy,darkMatterProfileScaleRadius_,darkMatterProfileDMO_,darkMatterHaloScale_,virialOrbit_,mergerTreeMassResolution_) result(self) + function darkMatterProfileScaleJohnson2021ConstructorInternal(massExponent,energyBoost,unresolvedEnergy,darkMatterProfileScaleRadius_,darkMatterHaloScale_,virialOrbit_,mergerTreeMassResolution_) result(self) !!{ Internal constructor for the {\normalfont \ttfamily randomWalk} dark matter profile scale radius class. !!} implicit none type (darkMatterProfileScaleRadiusJohnson2021) :: self class (darkMatterProfileScaleRadiusClass ), intent(in ), target :: darkMatterProfileScaleRadius_ - class (darkMatterProfileDMOClass ), intent(in ), target :: darkMatterProfileDMO_ class (darkMatterHaloScaleClass ), intent(in ), target :: darkMatterHaloScale_ class (virialOrbitClass ), intent(in ), target :: virialOrbit_ class (mergerTreeMassResolutionClass ), intent(in ), target :: mergerTreeMassResolution_ double precision , intent(in ) :: massExponent , energyBoost, & & unresolvedEnergy !![ - + !!] return @@ -156,7 +150,6 @@ subroutine darkMatterProfileScaleJohnson2021Destructor(self) !![ - @@ -174,7 +167,9 @@ double precision function darkMatterProfileScaleJohnson2021Radius(self,node) res use :: Kepler_Orbits , only : keplerOrbit use :: Numerical_Constants_Astronomical, only : gravitationalConstantGalacticus use :: Beta_Functions , only : Beta_Function , Beta_Function_Incomplete_Normalized - use :: Hypergeometric_Functions + use :: Hypergeometric_Functions , only : Hypergeometric_2F1 + use :: Mass_Distributions , only : massDistributionClass + use :: Galactic_Structure_Options , only : componentTypeDarkMatterOnly , massTypeDark implicit none class (darkMatterProfileScaleRadiusJohnson2021), intent(inout), target :: self type (treeNode ), intent(inout), target :: node @@ -185,6 +180,7 @@ double precision function darkMatterProfileScaleJohnson2021Radius(self,node) res class (nodeComponentDarkMatterProfile ) , pointer :: darkMatterProfile , darkMatterProfileSibling , & & darkMatterProfileChild , darkMatterProfileUnresolved class (nodeComponentSatellite ) , pointer :: satelliteSibling + class (massDistributionClass ) , pointer :: massDistribution_ double precision , parameter :: massFunctionSlopeLogarithmic =1.90d0 double precision , parameter :: energyInternalFormFactorSlopeLogarithmic=0.03d0 double precision :: energyOrbital , massRatio , & @@ -193,7 +189,8 @@ double precision function darkMatterProfileScaleJohnson2021Radius(self,node) res & radiusScaleUnresolved , massResolution , & & energyPotentialSubresolutionFactor , energyKineticSubresolutionFactor , & & a , b , & - & energyPotential , energyKinetic + & energyPotential , energyKinetic , & + & radiusVirial type (rootFinder ) :: finder type (keplerOrbit ) :: orbit @@ -224,42 +221,52 @@ double precision function darkMatterProfileScaleJohnson2021Radius(self,node) res massUnresolved = +basic %mass () & & -basicChild %mass () ! Iterate over progenitors and sum their energies. - nodeSibling => nodeChild - energyTotal = self%darkMatterProfileDMO_%energy(nodeSibling) + nodeSibling => nodeChild + massDistribution_ => nodeSibling %massDistribution (componentTypeDarkMatterOnly,massTypeDark ) + radiusVirial = self %darkMatterHaloScale_ %radiusVirial(nodeSibling ) + energyTotal = massDistribution_%energy (radiusVirial ,massDistribution_) + !![ + + !!] do while (associated(nodeSibling%sibling)) nodeSibling => nodeSibling %sibling - basicSibling => nodeSibling %basic ( ) - darkMatterProfileSibling => nodeSibling %darkMatterProfile( ) - satelliteSibling => nodeSibling %satellite (autoCreate=.true. ) - orbit = satelliteSibling %virialOrbit ( ) - massRatio = +basicSibling %mass ( ) & - & /basicChild %mass ( ) - energyOrbital = +orbit %energy ( ) & - & *basicSibling %mass ( ) & - & /( & - & +1.0d0 & - & +massRatio & + basicSibling => nodeSibling %basic ( ) + darkMatterProfileSibling => nodeSibling %darkMatterProfile ( ) + satelliteSibling => nodeSibling %satellite (autoCreate=.true. ) + massDistribution_ => nodeSibling %massDistribution (componentTypeDarkMatterOnly,massTypeDark) + radiusVirial = self %darkMatterHaloScale_ %radiusVirial(nodeSibling ) + orbit = satelliteSibling %virialOrbit ( ) + massRatio = +basicSibling %mass ( ) & + & /basicChild %mass ( ) + energyOrbital = +orbit %energy ( ) & + & *basicSibling %mass ( ) & + & /( & + & +1.0d0 & + & +massRatio & & )**self%massExponent - massUnresolved = +massUnresolved & - & -basicSibling %mass ( ) + massUnresolved = +massUnresolved & + & -basicSibling %mass ( ) ! Add orbital energy of this sibling. - energyTotal = +energyTotal & - & +energyOrbital & - & *( & - & +1.0d0 & - & +self%energyBoost & + energyTotal = +energyTotal & + & +energyOrbital & + & *( & + & +1.0d0 & + & +self%energyBoost & & ) ! Add the internal energy of the sibling. - energyTotal = +energyTotal & - & +self%darkMatterProfileDMO_%energy ( nodeSibling) & - & /( & - & +1.0d0 & - & +massRatio & - & )**self%massExponent & - & *( & - & +1.0d0 & - & +self%energyBoost & + energyTotal = +energyTotal & + & +massDistribution_ %energy (radiusVirial,massDistribution_ ) & + & /( & + & +1.0d0 & + & +massRatio & + & )**self%massExponent & + & *( & + & +1.0d0 & + & +self%energyBoost & & ) + !![ + + !!] end do ! Account for unresolved accretion. We assume that unresolved halos are accreted with the mean orbital energy of ! the virial orbital parameter distribution, plus an internal energy corresponding to that of a halo with mass @@ -316,30 +323,35 @@ double precision function darkMatterProfileScaleJohnson2021Radius(self,node) res & 8.0d0/3.0d0-(massFunctionSlopeLogarithmic+energyInternalFormFactorSlopeLogarithmic) & & ) ! Determine the orbital and internal energies. - energyKinetic =+0.5d0 & - & *self%virialOrbit_%velocityTotalRootMeanSquared(nodeUnresolved,nodeChild)**2 & - & /(1.0d0+massRatio) - energyPotential=+self%virialOrbit_%energyMean (nodeUnresolved,nodeChild) & - & - energyKinetic - energyOrbital =+energyPotential & - & *energyPotentialSubresolutionFactor & - & +energyKinetic & - & *energyKineticSubresolutionFactor - energyTotal =+energyTotal & - & +massUnresolved & - & *self%unresolvedEnergy & - & *( & - & +energyOrbital & - & +energyInternalSubresolutionFactor & - & *self%darkMatterProfileDMO_%energy(nodeUnresolved) & - & /massResolution & - & ) & - & *( & - & +1.0d0 & - & +self%energyBoost & - & ) + massDistribution_ => nodeUnresolved%massDistribution(componentTypeDarkMatterOnly,massTypeDark) + radiusVirial = self%darkMatterHaloScale_ %radiusVirial(nodeUnresolved) + energyKinetic = +0.5d0 & + & *self%virialOrbit_%velocityTotalRootMeanSquared(nodeUnresolved,nodeChild)**2 & + & /(1.0d0+massRatio) + energyPotential = +self%virialOrbit_%energyMean (nodeUnresolved,nodeChild) & + & - energyKinetic + energyOrbital = +energyPotential & + & *energyPotentialSubresolutionFactor & + & +energyKinetic & + & *energyKineticSubresolutionFactor + energyTotal = +energyTotal & + & +massUnresolved & + & *self%unresolvedEnergy & + & *( & + & +energyOrbital & + & +energyInternalSubresolutionFactor & + & *massDistribution_%energy(radiusVirial,massDistribution_) & + & /massResolution & + & ) & + & *( & + & +1.0d0 & + & +self%energyBoost & + & ) call nodeUnresolved%destroy() deallocate(nodeUnresolved) + !![ + + !!] end if ! Add mutual gravitational binding energy of any sibling halo and any unresolved mass. if (associated(nodeChild%sibling)) & @@ -370,13 +382,20 @@ double precision function radiusScaleRoot(radiusScale) !!{ Function used in root-finding to compute the scale radius of a dark matter profile as a given energy. !!} - use :: Calculations_Resets, only : Calculations_Reset + use :: Calculations_Resets , only : Calculations_Reset + use :: Mass_Distributions , only : massDistributionClass + use :: Galactic_Structure_Options, only : componentTypeDarkMatterOnly, massTypeDark implicit none - double precision, intent(in ) :: radiusScale - + double precision , intent(in ) :: radiusScale + class (massDistributionClass), pointer :: massDistribution_ + call darkMatterProfile_%scaleSet(radiusScale) call Calculations_Reset(node_) - radiusScaleRoot=+ energyTotal & - & -self_%darkMatterProfileDMO_%energy (node_) + massDistribution_ => node_ %massDistribution(componentTypeDarkMatterOnly,massTypeDark) + radiusScaleRoot = + energyTotal & + & -massDistribution_%energy (self_%darkMatterHaloScale_%radiusVirial(node_),massDistribution_) + !![ + + !!] return end function radiusScaleRoot diff --git a/source/dark_matter_profiles.structure.scale.Ludlow2014.F90 b/source/dark_matter_profiles.structure.scale.Ludlow2014.F90 index 5f6389778c..ee460373c7 100644 --- a/source/dark_matter_profiles.structure.scale.Ludlow2014.F90 +++ b/source/dark_matter_profiles.structure.scale.Ludlow2014.F90 @@ -69,7 +69,7 @@ function ludlow2014ConstructorParameters(parameters) result(self) return end function ludlow2014ConstructorParameters - function ludlow2014ConstructorInternal(C,f,timeFormationSeekDelta,cosmologyFunctions_,cosmologyParameters_,darkMatterProfileScaleRadius_,darkMatterProfileDMO_,virialDensityContrast_) result(self) + function ludlow2014ConstructorInternal(C,f,timeFormationSeekDelta,cosmologyFunctions_,cosmologyParameters_,darkMatterProfileScaleRadius_,virialDensityContrast_) result(self) !!{ Constructor for the {\normalfont \ttfamily ludlow2014} dark matter halo profile concentration class. !!} @@ -80,10 +80,9 @@ function ludlow2014ConstructorInternal(C,f,timeFormationSeekDelta,cosmologyFunct class (cosmologyFunctionsClass ), intent(in ), target :: cosmologyFunctions_ class (cosmologyParametersClass ), intent(in ), target :: cosmologyParameters_ class (darkMatterProfileScaleRadiusClass ), intent(in ), target :: darkMatterProfileScaleRadius_ - class (darkMatterProfileDMOClass ), intent(in ), target :: darkMatterProfileDMO_ class (virialDensityContrastClass ), intent(in ), target :: virialDensityContrast_ - self%darkMatterProfileScaleRadiusLudlow2016=darkMatterProfileScaleRadiusLudlow2016(C,f,timeFormationSeekDelta,cosmologyFunctions_,cosmologyParameters_,darkMatterProfileScaleRadius_,darkMatterProfileDMO_,virialDensityContrast_) + self%darkMatterProfileScaleRadiusLudlow2016=darkMatterProfileScaleRadiusLudlow2016(C,f,timeFormationSeekDelta,cosmologyFunctions_,cosmologyParameters_,darkMatterProfileScaleRadius_,virialDensityContrast_) return end function ludlow2014ConstructorInternal @@ -146,7 +145,6 @@ Function used to find the formation time of a halo in the {\normalfont \ttfamily & * states(stateCount)%cosmologyFunctions_%expansionFactor ( basicSibling%time())**3, & & cosmologyParameters_ =states(stateCount)%self%cosmologyParameters_ , & & cosmologyFunctions_ =states(stateCount)%self%cosmologyFunctions_ , & - & darkMatterProfileDMO_ =states(stateCount)%self%darkMatterProfileDMO_ , & & virialDensityContrast_=states(stateCount)%self%virialDensityContrast_ & & ) nodeSibling => nodeSibling %sibling @@ -162,7 +160,6 @@ Function used to find the formation time of a halo in the {\normalfont \ttfamily & * states(stateCount)%cosmologyFunctions_%expansionFactor ( basicBranch%time())**3, & & cosmologyParameters_ =states(stateCount)%self%cosmologyParameters_ , & & cosmologyFunctions_ =states(stateCount)%self%cosmologyFunctions_ , & - & darkMatterProfileDMO_ =states(stateCount)%self%darkMatterProfileDMO_ , & & virialDensityContrast_=states(stateCount)%self%virialDensityContrast_ & & ) & & -massSiblings & @@ -181,7 +178,6 @@ Function used to find the formation time of a halo in the {\normalfont \ttfamily & * states(stateCount)%cosmologyFunctions_%expansionFactor ( basicChild%time())**3, & & cosmologyParameters_ =states(stateCount)%self%cosmologyParameters_ , & & cosmologyFunctions_ =states(stateCount)%self%cosmologyFunctions_ , & - & darkMatterProfileDMO_ =states(stateCount)%self%darkMatterProfileDMO_ , & & virialDensityContrast_=states(stateCount)%self%virialDensityContrast_ & & ) & & +massAccretionRate & @@ -206,7 +202,6 @@ Function used to find the formation time of a halo in the {\normalfont \ttfamily & * states(stateCount)%cosmologyFunctions_%expansionFactor ( basicBranch%time())**3, & & cosmologyParameters_ =states(stateCount)%self%cosmologyParameters_ , & & cosmologyFunctions_ =states(stateCount)%self%cosmologyFunctions_ , & - & darkMatterProfileDMO_ =states(stateCount)%self%darkMatterProfileDMO_ , & & virialDensityContrast_=states(stateCount)%self%virialDensityContrast_ & & ) if (massProgenitor >= states(stateCount)%massLimit) & diff --git a/source/dark_matter_profiles.structure.scale.Ludlow2016.F90 b/source/dark_matter_profiles.structure.scale.Ludlow2016.F90 index 4967d9e179..1d50e60e96 100644 --- a/source/dark_matter_profiles.structure.scale.Ludlow2016.F90 +++ b/source/dark_matter_profiles.structure.scale.Ludlow2016.F90 @@ -22,11 +22,10 @@ algorithm. !!} - use :: Cosmology_Functions , only : cosmologyFunctionsClass - use :: Cosmology_Parameters , only : cosmologyParametersClass - use :: Dark_Matter_Profiles_DMO, only : darkMatterProfileDMOClass - use :: Virial_Density_Contrast , only : virialDensityContrastClass - use :: Root_Finder , only : rootFinder + use :: Cosmology_Functions , only : cosmologyFunctionsClass + use :: Cosmology_Parameters , only : cosmologyParametersClass + use :: Virial_Density_Contrast, only : virialDensityContrastClass + use :: Root_Finder , only : rootFinder !![ @@ -48,7 +47,6 @@ class (cosmologyFunctionsClass ), pointer :: cosmologyFunctions_ => null() class (cosmologyParametersClass ), pointer :: cosmologyParameters_ => null() class (darkMatterProfileScaleRadiusClass), pointer :: darkMatterProfileScaleRadius_ => null() - class (darkMatterProfileDMOClass ), pointer :: darkMatterProfileDMO_ => null() class (virialDensityContrastClass ), pointer :: virialDensityContrast_ => null() double precision :: C , f , & & timeFormationSeekDelta , densityContrast @@ -102,7 +100,6 @@ function ludlow2016ConstructorParameters(parameters) result(self) class (cosmologyFunctionsClass ), pointer :: cosmologyFunctions_ class (cosmologyParametersClass ), pointer :: cosmologyParameters_ class (darkMatterProfileScaleRadiusClass ), pointer :: darkMatterProfileScaleRadius_ - class (darkMatterProfileDMOClass ), pointer :: darkMatterProfileDMO_ class (virialDensityContrastClass ), pointer :: virialDensityContrast_ double precision :: C , f, & & timeFormationSeekDelta @@ -130,22 +127,20 @@ function ludlow2016ConstructorParameters(parameters) result(self) - !!] - self=darkMatterProfileScaleRadiusLudlow2016(C,f,timeFormationSeekDelta,cosmologyFunctions_,cosmologyParameters_,darkMatterProfileScaleRadius_,darkMatterProfileDMO_,virialDensityContrast_) + self=darkMatterProfileScaleRadiusLudlow2016(C,f,timeFormationSeekDelta,cosmologyFunctions_,cosmologyParameters_,darkMatterProfileScaleRadius_,virialDensityContrast_) !![ - !!] return end function ludlow2016ConstructorParameters - function ludlow2016ConstructorInternal(C,f,timeFormationSeekDelta,cosmologyFunctions_,cosmologyParameters_,darkMatterProfileScaleRadius_,darkMatterProfileDMO_,virialDensityContrast_) result(self) + function ludlow2016ConstructorInternal(C,f,timeFormationSeekDelta,cosmologyFunctions_,cosmologyParameters_,darkMatterProfileScaleRadius_,virialDensityContrast_) result(self) !!{ Constructor for the {\normalfont \ttfamily ludlow2016} dark matter halo profile concentration class. !!} @@ -156,10 +151,9 @@ function ludlow2016ConstructorInternal(C,f,timeFormationSeekDelta,cosmologyFunct class (cosmologyFunctionsClass ), intent(in ), target :: cosmologyFunctions_ class (cosmologyParametersClass ), intent(in ), target :: cosmologyParameters_ class (darkMatterProfileScaleRadiusClass ), intent(in ), target :: darkMatterProfileScaleRadius_ - class (darkMatterProfileDMOClass ), intent(in ), target :: darkMatterProfileDMO_ class (virialDensityContrastClass ), intent(in ), target :: virialDensityContrast_ !![ - + !!] ! Find the density contrast as used to define masses by Ludlow et al. (2016). @@ -178,7 +172,6 @@ subroutine ludlow2016Destructor(self) - !!] return @@ -194,6 +187,8 @@ double precision function ludlow2016Radius(self,node) use :: Display , only : displayGreen , displayReset use :: Error , only : Error_Report , errorStatusSuccess use :: Galacticus_Nodes , only : nodeComponentBasic , nodeComponentDarkMatterProfile, treeNode + use :: Galactic_Structure_Options , only : componentTypeDarkMatterOnly , massTypeDark + use :: Mass_Distributions , only : massDistributionClass use :: Merger_Tree_Walkers , only : mergerTreeWalkerIsolatedNodesBranch use :: Numerical_Comparison , only : Values_Agree use :: Numerical_Constants_Math , only : Pi @@ -204,6 +199,7 @@ double precision function ludlow2016Radius(self,node) type (treeNode ) , pointer :: nodeBranch class (nodeComponentBasic ) , pointer :: basic , basicBranch class (nodeComponentDarkMatterProfile ) , pointer :: darkMatterProfile_ , darkMatterProfileChild_ + class (massDistributionClass ), pointer :: massDistribution_ integer , parameter :: iterationCountMaximum =100 type (ludlow2016State ), allocatable , dimension(:) :: statesTmp type (mergerTreeWalkerIsolatedNodesBranch ) :: treeWalker @@ -264,14 +260,18 @@ double precision function ludlow2016Radius(self,node) iterationCount=iterationCount+1 ! Compute the characteristic halo mass, M₋₂. if (iterationCount == 1) then - basic => node %basic ( ) + basic => node %basic ( ) states(stateCount)%self => self states(stateCount)%node => node - states(stateCount)%hubbleParameterPresent = self%cosmologyFunctions_ %hubbleParameterEpochal(expansionFactor=1.0d0 ) + states(stateCount)%hubbleParameterPresent = self%cosmologyFunctions_%hubbleParameterEpochal(expansionFactor=1.0d0 ) states(stateCount)%timePrevious = -1.0d0 states(stateCount)%densityContrast = -huge(0.0d0) end if - massHaloCharacteristic = +self%darkMatterProfileDMO_%enclosedMass (node,darkMatterProfile_%scale()) + massDistribution_ => node %massDistribution (componentTypeDarkMatterOnly,massTypeDark) + massHaloCharacteristic = +massDistribution_ %massEnclosedBySphere (darkMatterProfile_%scale() ) + !![ + + !!] states(stateCount)%massHaloCharacteristic = massHaloCharacteristic states(stateCount)%massLimit = +self%f & & *Dark_Matter_Profile_Mass_Definition( & @@ -279,7 +279,6 @@ double precision function ludlow2016Radius(self,node) & ludlow2016DensityContrast(states(stateCount),basic%time()), & & cosmologyParameters_ =self%cosmologyParameters_ , & & cosmologyFunctions_ =self%cosmologyFunctions_ , & - & darkMatterProfileDMO_ =self%darkMatterProfileDMO_ , & & virialDensityContrast_=self%virialDensityContrast_ & & ) ! Find the earliest time in the branch. Also estimate the earliest and latest times between which the formation time will lie. @@ -448,7 +447,6 @@ Function used to find the formation time of a halo in the {\normalfont \ttfamily & ludlow2016DensityContrast(states(stateCount),basicChild%time()), & & cosmologyParameters_ =states(stateCount)%self%cosmologyParameters_ , & & cosmologyFunctions_ =states(stateCount)%self%cosmologyFunctions_ , & - & darkMatterProfileDMO_ =states(stateCount)%self%darkMatterProfileDMO_ , & & virialDensityContrast_=states(stateCount)%self%virialDensityContrast_ & & ) if (nodeChild%isPrimaryProgenitor()) then @@ -463,7 +461,6 @@ Function used to find the formation time of a halo in the {\normalfont \ttfamily & ludlow2016DensityContrast(states(stateCount),basicSibling%time()), & & cosmologyParameters_ =states(stateCount)%self%cosmologyParameters_ , & & cosmologyFunctions_ =states(stateCount)%self%cosmologyFunctions_ , & - & darkMatterProfileDMO_ =states(stateCount)%self%darkMatterProfileDMO_ , & & virialDensityContrast_=states(stateCount)%self%virialDensityContrast_ & & ) nodeSibling => nodeSibling %sibling @@ -474,7 +471,6 @@ Function used to find the formation time of a halo in the {\normalfont \ttfamily & ludlow2016DensityContrast(states(stateCount),basicBranch %time()), & & cosmologyParameters_ =states(stateCount)%self%cosmologyParameters_ , & & cosmologyFunctions_ =states(stateCount)%self%cosmologyFunctions_ , & - & darkMatterProfileDMO_ =states(stateCount)%self%darkMatterProfileDMO_ , & & virialDensityContrast_=states(stateCount)%self%virialDensityContrast_ & & ) & & -massSiblings & @@ -507,7 +503,6 @@ Function used to find the formation time of a halo in the {\normalfont \ttfamily & ludlow2016DensityContrast(states(stateCount),basicBranch%time()), & & cosmologyParameters_ =states(stateCount)%self%cosmologyParameters_ , & & cosmologyFunctions_ =states(stateCount)%self%cosmologyFunctions_ , & - & darkMatterProfileDMO_ =states(stateCount)%self%darkMatterProfileDMO_ , & & virialDensityContrast_=states(stateCount)%self%virialDensityContrast_ & & ) if (massProgenitor >= states(stateCount)%massLimit) & diff --git a/source/dark_matter_profiles.structure.scale.concentration.F90 b/source/dark_matter_profiles.structure.scale.concentration.F90 index 41ac1753b3..8f662d37e0 100644 --- a/source/dark_matter_profiles.structure.scale.concentration.F90 +++ b/source/dark_matter_profiles.structure.scale.concentration.F90 @@ -315,11 +315,13 @@ double precision function concentrationMassRoot(massDefinitionTrial) Root function used to find the mass of a halo corresponding to the definition used for a particular concentration class. !!} use :: Calculations_Resets, only : Calculations_Reset + use :: Mass_Distributions , only : massDistributionClass implicit none - double precision, intent(in ) :: massDefinitionTrial - double precision :: radiusOuterDefinition, concentrationDefinition, & - & radiusCore , massOuter , & - & radiusOuter , densityOuter + double precision , intent(in ) :: massDefinitionTrial + class (massDistributionClass), pointer :: massDistribution_ + double precision :: radiusOuterDefinition, concentrationDefinition, & + & radiusCore , massOuter , & + & radiusOuter , densityOuter ! Set the mass of the worker node. call state_(stateCount)%basic%massSet(massDefinitionTrial) @@ -343,12 +345,17 @@ Root function used to find the mass of a halo corresponding to the definition us call state_(stateCount)%darkMatterProfile%scaleSet(radiusCore) call Calculations_Reset(state_(stateCount)%nodeWork) ! Find the non-alt density. - densityOuter=+state_(stateCount)%self%cosmologyFunctions_ %matterDensityEpochal ( state_(stateCount)%basic%time()) & - & *state_(stateCount)%self%virialDensityContrast_ %densityContrast (state_(stateCount)%basic%mass(),state_(stateCount)%basic%time()) + densityOuter=+state_(stateCount)%self%cosmologyFunctions_ %matterDensityEpochal( state_(stateCount)%basic%time()) & + & *state_(stateCount)%self%virialDensityContrast_%densityContrast (state_(stateCount)%basic%mass(),state_(stateCount)%basic%time()) + ! Get the current mass distribution. + massDistribution_ => state_(stateCount)%self%darkMatterProfileDMODefinition%get(state_(stateCount)%nodeWork) ! Solve for radius which encloses required non-alt density. - radiusOuter =+state_(stateCount)%self%darkMatterProfileDMODefinition%radiusEnclosingDensity(state_(stateCount)%nodeWork ,densityOuter) + radiusOuter=massDistribution_%radiusEnclosingDensity(densityOuter) ! Get the mass within this radius. - massOuter =+state_(stateCount)%self%darkMatterProfileDMODefinition%enclosedMass (state_(stateCount)%nodeWork , radiusOuter) + massOuter =massDistribution_%massEnclosedBySphere ( radiusOuter) + !![ + + !!] ! Return root function. concentrationMassRoot=massOuter-state_(stateCount)%mass return diff --git a/source/dark_matter_profiles.structure.scale.random_walk.F90 b/source/dark_matter_profiles.structure.scale.random_walk.F90 index d1df29c751..7e6c43cf16 100644 --- a/source/dark_matter_profiles.structure.scale.random_walk.F90 +++ b/source/dark_matter_profiles.structure.scale.random_walk.F90 @@ -70,11 +70,11 @@ end interface darkMatterProfileScaleRadiusRandomWalk ! Sub-module-scope variables used in root finding. - double precision :: energyTarget_ + double precision :: energyTarget_ , radiusVirial_ class (darkMatterProfileScaleRadiusRandomWalk), pointer :: self_ class (nodeComponentDarkMatterProfile ), pointer :: darkMatterProfile_ type (treeNode ), pointer :: node_ - !$omp threadprivate(energyTarget_,self_,node_,darkMatterProfile_) + !$omp threadprivate(energyTarget_,self_,node_,darkMatterProfile_,radiusVirial_) contains @@ -152,34 +152,47 @@ double precision function darkMatterProfileScaleRandomWalkRadius(self,node) resu use :: Galacticus_Nodes , only : nodeComponentBasic use :: Root_Finder , only : rootFinder , rangeExpandMultiplicative, rangeExpandSignExpectPositive, rangeExpandSignExpectNegative use :: Numerical_Constants_Astronomical, only : gravitationalConstantGalacticus + use :: Mass_Distributions , only : massDistributionClass implicit none class (darkMatterProfileScaleRadiusRandomWalk), intent(inout), target :: self type (treeNode ), intent(inout), target :: node class (nodeComponentDarkMatterProfile ) , pointer :: darkMatterProfileChild class (nodeComponentBasic ) , pointer :: basic , basicChild + class (massDistributionClass ) , pointer :: massDistribution_ , massDistributionChild_ type (rootFinder ) :: finder - double precision :: energyPerturbation , radiusScaleOriginal, & - & energyScaleChild , energyScale + double precision :: energyPerturbation , radiusScaleOriginal , & + & energyScaleChild , energyScale , & + & radiusVirialChild ! Set the scale radius to that given by the lower level scale radius class. radiusScale=self%darkMatterProfileScaleRadius_%radius(node) ! For nodes with a child, evaluate the perturbation to this radius. if (associated(node%firstChild)) then - basic => node %basic ( ) - basicChild => node %firstChild %basic ( ) - darkMatterProfileChild => node %firstChild %darkMatterProfile( ) - energyScale = +gravitationalConstantGalacticus & - & *basic %mass ( )**2 & - & /self %darkMatterHaloScale_ %radiusVirial (node ) - energyScaleChild = +gravitationalConstantGalacticus & - & *basicChild %mass ( )**2 & - & /self %darkMatterHaloScale_ %radiusVirial (node%firstChild) - energyPerturbation = +self %darkMatterProfileDMO_%energy (node%firstChild) - radiusScaleOriginal = +darkMatterProfileChild %scale ( ) - call darkMatterProfileChild%scaleSet(self%darkMatterProfileScaleRadius_%radius (node%firstChild)) - energyPerturbation = +energyPerturbation & - & -self %darkMatterProfileDMO_%energy (node%firstChild) - call darkMatterProfileChild%scaleSet( radiusScaleOriginal ) + basic => node %basic ( ) + basicChild => node %firstChild %basic ( ) + darkMatterProfileChild => node %firstChild %darkMatterProfile( ) + radiusVirial_ = self %darkMatterHaloScale_ %radiusVirial (node ) + radiusVirialChild = self %darkMatterHaloScale_ %radiusVirial (node%firstChild ) + energyScale = +gravitationalConstantGalacticus & + & *basic %mass ( )**2 & + & /self %darkMatterHaloScale_ %radiusVirial (node ) + energyScaleChild = +gravitationalConstantGalacticus & + & *basicChild %mass ( )**2 & + & /radiusVirialChild + massDistributionChild_ => self %darkMatterProfileDMO_%get (node%firstChild ) + energyPerturbation = +massDistributionChild_ %energy (radiusVirialChild,massDistributionChild_) + !![ + + !!] + radiusScaleOriginal = +darkMatterProfileChild %scale ( ) + call darkMatterProfileChild%scaleSet(self%darkMatterProfileScaleRadius_%radius (node%firstChild )) + massDistributionChild_ => self %darkMatterProfileDMO_%get (node%firstChild ) + energyPerturbation = +energyPerturbation & + & -massDistributionChild_ %energy (radiusVirialChild,massDistributionChild_) + !![ + + !!] + call darkMatterProfileChild%scaleSet( radiusScaleOriginal ) if (energyScale > energyScaleChild) then energyPerturbation =+energyPerturbation & & +sqrt( & @@ -204,13 +217,17 @@ double precision function darkMatterProfileScaleRandomWalkRadius(self,node) resu & ) self_ => self node_ => node - darkMatterProfile_ => node %darkMatterProfile (autoCreate=.true. ) - radiusScaleOriginal = darkMatterProfile_ %scale ( ) + darkMatterProfile_ => node %darkMatterProfile (autoCreate=.true. ) + radiusScaleOriginal = darkMatterProfile_ %scale ( ) call darkMatterProfile_%scaleSet(radiusScale ) - energyTarget_ = +self %darkMatterProfileDMO_%energy ( node ) & - & + energyPerturbation - radiusScale = finder %find (rootGuess =darkMatterProfile_%scale()) - call darkMatterProfile_%scaleSet(radiusScaleOriginal) + massDistribution_ => self %darkMatterProfileDMO_%get (node ) + energyTarget_ = +massDistribution_ %energy (radiusVirial_,massDistribution_ ) & + & + energyPerturbation + radiusScale = finder %find (rootGuess =darkMatterProfile_%scale()) + call darkMatterProfile_%scaleSet(radiusScaleOriginal) + !![ + + !!] end if return end function darkMatterProfileScaleRandomWalkRadius @@ -219,11 +236,17 @@ double precision function energyRoot(radiusScale) !!{ Root function used in finding the scale radius corresponding to a given halo energy. !!} + use :: Mass_Distributions, only : massDistributionClass implicit none - double precision, intent(in ) :: radiusScale + double precision , intent(in ) :: radiusScale + class (massDistributionClass), pointer :: massDistribution_ call darkMatterProfile_%scaleSet(radiusScale) - energyRoot=+self_%darkMatterProfileDMO_%energy (node_) & - & - energyTarget_ + massDistribution_ => self_ %darkMatterProfileDMO_%get (node_ ) + energyRoot = +massDistribution_ %energy (radiusVirial_,massDistribution_) & + & - energyTarget_ + !![ + + !!] return end function energyRoot diff --git a/source/dark_matter_profiles.structure_tasks.F90 b/source/dark_matter_profiles.structure_tasks.F90 deleted file mode 100644 index 4f8c78a8fc..0000000000 --- a/source/dark_matter_profiles.structure_tasks.F90 +++ /dev/null @@ -1,471 +0,0 @@ -!! Copyright 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, -!! 2019, 2020, 2021, 2022, 2023, 2024 -!! Andrew Benson -!! -!! This file is part of Galacticus. -!! -!! Galacticus is free software: you can redistribute it and/or modify -!! it under the terms of the GNU General Public License as published by -!! the Free Software Foundation, either version 3 of the License, or -!! (at your option) any later version. -!! -!! Galacticus is distributed in the hope that it will be useful, -!! but WITHOUT ANY WARRANTY; without even the implied warranty of -!! MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -!! GNU General Public License for more details. -!! -!! You should have received a copy of the GNU General Public License -!! along with Galacticus. If not, see . - -!!{ -Contains a module which implements structure tasks related to the dark matter halo density profile. -!!} - -module Dark_Matter_Profile_Structure_Tasks - !!{ - Implements structure tasks related to the dark matter halo density profile. - !!} - use :: Dark_Matter_Profiles, only : darkMatterProfileClass - private - public :: Dark_Matter_Profile_Enclosed_Mass_Task , Dark_Matter_Profile_Density_Task , Dark_Matter_Profile_Rotation_Curve_Task , Dark_Matter_Profile_Potential_Task , & - & Dark_Matter_Profile_Rotation_Curve_Gradient_Task , Dark_Matter_Profile_Acceleration_Task , Dark_Matter_Profile_Tidal_Tensor_Task , Dark_Matter_Profile_Chandrasekhar_Integral_Task , & - & Dark_Matter_Profile_Structure_Tasks_Thread_Initialize, Dark_Matter_Profile_Structure_Tasks_Thread_Uninitialize, Dark_Matter_Profile_Structure_Tasks_State_Store, Dark_Matter_Profile_Structure_Tasks_State_Restore, & - & Dark_Matter_Profile_Density_Spherical_Average_Task , Dark_Matter_Profile_Radius_Enclosing_Mass - - class(darkMatterProfileClass), pointer :: darkMatterProfile_ - !$omp threadprivate(darkMatterProfile_) - -contains - - !![ - - Dark_Matter_Profile_Structure_Tasks_Thread_Initialize - - !!] - subroutine Dark_Matter_Profile_Structure_Tasks_Thread_Initialize(parameters_) - !!{ - Initializes the dark matter profile structure tasks module. - !!} - use :: Input_Parameters, only : inputParameters - implicit none - type(inputParameters), intent(inout) :: parameters_ - - !![ - - !!] - return - end subroutine Dark_Matter_Profile_Structure_Tasks_Thread_Initialize - - !![ - - Dark_Matter_Profile_Structure_Tasks_Thread_Uninitialize - - !!] - subroutine Dark_Matter_Profile_Structure_Tasks_Thread_Uninitialize() - !!{ - Uninitializes the dark matter profile structure tasks module. - !!} - implicit none - - !![ - - !!] - return - end subroutine Dark_Matter_Profile_Structure_Tasks_Thread_Uninitialize - - !![ - - Dark_Matter_Profile_Enclosed_Mass_Task - - !!] - double precision function Dark_Matter_Profile_Enclosed_Mass_Task(node,radius,componentType,massType,weightBy,weightIndex) - !!{ - Computes the mass within a given radius for a dark matter profile. - !!} - use :: Galactic_Structure_Options, only : componentTypeAll , componentTypeDarkHalo, massTypeAll , massTypeDark , & - & radiusLarge , weightByMass , enumerationComponentTypeType, enumerationMassTypeType, & - & enumerationWeightByType - use :: Galacticus_Nodes , only : nodeComponentBasic , treeNode - implicit none - type (treeNode ), intent(inout) :: node - type (enumerationComponentTypeType), intent(in ) :: componentType - type (enumerationMassTypeType ), intent(in ) :: massType - type (enumerationWeightByType ), intent(in ) :: weightBy - integer , intent(in ) :: weightIndex - double precision , intent(in ) :: radius - class (nodeComponentBasic ) , pointer :: basic - !$GLC attributes unused :: weightIndex - - Dark_Matter_Profile_Enclosed_Mass_Task=0.0d0 - if (.not.(componentType == componentTypeAll .or. componentType == componentTypeDarkHalo)) return - if (.not.(massType == massTypeAll .or. massType == massTypeDark )) return - if (.not.(weightBy == weightByMass )) return - - ! Test radius. - if (radius >= radiusLarge) then - ! Return the total mass of the halo in this case. - basic => node%basic() - Dark_Matter_Profile_Enclosed_Mass_Task=basic%mass() - else if (radius <= 0.0d0) then - ! Zero radius. Return zero mass. - Dark_Matter_Profile_Enclosed_Mass_Task=0.0d0 - else - ! Return the mass within the radius. - Dark_Matter_Profile_Enclosed_Mass_Task=darkMatterProfile_%enclosedMass(node,radius) - end if - return - end function Dark_Matter_Profile_Enclosed_Mass_Task - - !![ - - Dark_Matter_Profile_Acceleration_Task - - !!] - function Dark_Matter_Profile_Acceleration_Task(node,positionCartesian,componentType,massType) - !!{ - Computes the acceleration due to a dark matter profile. - !!} - use :: Galactic_Structure_Options , only : weightByMass , weightIndexNull, enumerationComponentTypeType, enumerationMassTypeType - use :: Galacticus_Nodes , only : treeNode - use :: Numerical_Constants_Astronomical, only : gigaYear , megaParsec - use :: Numerical_Constants_Astronomical, only : gravitationalConstantGalacticus - use :: Numerical_Constants_Prefixes , only : kilo - implicit none - double precision , dimension(3) :: Dark_Matter_Profile_Acceleration_Task - type (treeNode ), intent(inout) :: node - type (enumerationComponentTypeType), intent(in ) :: componentType - type (enumerationMassTypeType ), intent(in ) :: massType - double precision , intent(in ), dimension(3) :: positionCartesian - double precision :: radius - - radius=sqrt(sum(positionCartesian**2)) - Dark_Matter_Profile_Acceleration_Task=-kilo & - & *gigaYear & - & /megaParsec & - & *gravitationalConstantGalacticus & - & *Dark_Matter_Profile_Enclosed_Mass_Task(node,radius,componentType,massType,weightByMass,weightIndexNull) & - & *positionCartesian & - & /radius**3 - return - end function Dark_Matter_Profile_Acceleration_Task - - !![ - - Dark_Matter_Profile_Chandrasekhar_Integral_Task - - !!] - function Dark_Matter_Profile_Chandrasekhar_Integral_Task(node,nodeSatellite,positionCartesian,velocityCartesian,componentType,massType) - !!{ - Computes the Chandrasekhar integral due to a dark matter profile. - !!} - use :: Galactic_Structure_Options, only : weightByMass, weightIndexNull, enumerationComponentTypeType, enumerationMassTypeType - use :: Galacticus_Nodes , only : treeNode - use :: Numerical_Constants_Math , only : Pi - implicit none - double precision , dimension(3) :: Dark_Matter_Profile_Chandrasekhar_Integral_Task - type (treeNode ), intent(inout) :: node , nodeSatellite - type (enumerationComponentTypeType), intent(in ) :: componentType - type (enumerationMassTypeType ), intent(in ) :: massType - double precision , intent(in ), dimension(3) :: positionCartesian , velocityCartesian - double precision , dimension(3) :: positionSpherical - double precision , parameter :: XvMaximum =10.0d0 - double precision :: radius , velocity , & - & density , xV , & - & velocityDispersion - !$GLC attributes unused :: nodeSatellite - - Dark_Matter_Profile_Chandrasekhar_Integral_Task=0.0d0 - radius =sqrt(sum(positionCartesian**2)) - velocity =sqrt(sum(velocityCartesian**2)) - if (velocity <= 0.0d0) return - positionSpherical =[radius,0.0d0,0.0d0] - density =Dark_Matter_Profile_Density_Task(node,positionSpherical,componentType,massType,weightByMass,weightIndexNull) - if (density <= 0.0d0) return - velocityDispersion=darkMatterProfile_%radialVelocityDispersion(node,radius) - if (velocityDispersion > 0.0d0) then - xV =+velocity & - & /velocityDispersion & - & /sqrt(2.0d0) - else - xV=huge(0.0d0) - end if - Dark_Matter_Profile_Chandrasekhar_Integral_Task = -density & - & *velocityCartesian & - & /velocity **3 - if (Xv <= XvMaximum) & - & Dark_Matter_Profile_Chandrasekhar_Integral_Task=+Dark_Matter_Profile_Chandrasekhar_Integral_Task & - & *( & - & +erf ( xV ) & - & -2.0d0 & - & * xV & - & *exp (-xV**2) & - & /sqrt( Pi ) & - & ) - return - end function Dark_Matter_Profile_Chandrasekhar_Integral_Task - - !![ - - Dark_Matter_Profile_Tidal_Tensor_Task - - !!] - function Dark_Matter_Profile_Tidal_Tensor_Task(node,positionCartesian,componentType,massType) - !!{ - Computes the tidalTensor due to a dark matter profile. - !!} - use :: Galactic_Structure_Options , only : weightByMass , weightIndexNull , enumerationComponentTypeType, enumerationMassTypeType - use :: Galacticus_Nodes , only : treeNode - use :: Numerical_Constants_Math , only : Pi - use :: Numerical_Constants_Astronomical, only : gravitationalConstantGalacticus - use :: Tensors , only : tensorRank2Dimension3Symmetric , tensorIdentityR2D3Sym, assignment(=) , operator(*) - use :: Vectors , only : Vector_Outer_Product - implicit none - type (tensorRank2Dimension3Symmetric) :: Dark_Matter_Profile_Tidal_Tensor_Task - type (treeNode ), intent(inout) :: node - type (enumerationComponentTypeType ), intent(in ) :: componentType - type (enumerationMassTypeType ), intent(in ) :: massType - double precision , intent(in ), dimension(3) :: positionCartesian - double precision , dimension(3) :: positionSpherical - double precision :: radius , massEnclosed, & - & density - type (tensorRank2Dimension3Symmetric) :: positionTensor - - radius =sqrt(sum(positionCartesian**2)) - positionSpherical=[radius,0.0d0,0.0d0] - massEnclosed =Dark_Matter_Profile_Enclosed_Mass_Task(node,radius ,componentType,massType,weightByMass,weightIndexNull) - density =Dark_Matter_Profile_Density_Task (node,positionSpherical,componentType,massType,weightByMass,weightIndexNull) - positionTensor =Vector_Outer_Product ( positionCartesian,symmetrize=.true. ) - Dark_Matter_Profile_Tidal_Tensor_Task=+gravitationalConstantGalacticus & - & *( & - & -(massEnclosed /radius**3)*tensorIdentityR2D3Sym & - & +(massEnclosed*3.0d0 /radius**5)*positionTensor & - & -(density *4.0d0*Pi/radius**2)*positionTensor & - & ) - return - end function Dark_Matter_Profile_Tidal_Tensor_Task - - !![ - - Dark_Matter_Profile_Rotation_Curve_Task - - !!] - double precision function Dark_Matter_Profile_Rotation_Curve_Task(node,radius,componentType,massType) - !!{ - Computes the rotation curve at a given radius for a dark matter profile. - !!} - use :: Galactic_Structure_Options , only : weightByMass , weightIndexNull, enumerationComponentTypeType, enumerationMassTypeType - use :: Galacticus_Nodes , only : treeNode - use :: Numerical_Constants_Astronomical, only : gravitationalConstantGalacticus - implicit none - type (treeNode ), intent(inout) :: node - type (enumerationComponentTypeType), intent(in ) :: componentType - type (enumerationMassTypeType ), intent(in ) :: massType - double precision , intent(in ) :: radius - double precision :: componentMass - - ! Set to zero by default. - Dark_Matter_Profile_Rotation_Curve_Task=0.0d0 - - ! Compute if a spheroid is present. - if (radius > 0.0d0) then - componentMass=Dark_Matter_Profile_Enclosed_Mass_Task(node,radius,componentType,massType,weightByMass,weightIndexNull) - if (componentMass > 0.0d0) Dark_Matter_Profile_Rotation_Curve_Task=sqrt(gravitationalConstantGalacticus*componentMass)& - &/sqrt(radius) - end if - return - end function Dark_Matter_Profile_Rotation_Curve_Task - - !![ - - Dark_Matter_Profile_Density_Task - - !!] - double precision function Dark_Matter_Profile_Density_Task(node,positionSpherical,componentType,massType,weightBy,weightIndex) - !!{ - Computes the density at a given position for a dark matter profile. - !!} - use :: Galactic_Structure_Options, only : componentTypeAll, componentTypeDarkHalo , massTypeAll , massTypeDark , & - & weightByMass , enumerationComponentTypeType, enumerationMassTypeType, enumerationWeightByType - use :: Galacticus_Nodes , only : treeNode - implicit none - type (treeNode ), intent(inout) :: node - type (enumerationComponentTypeType), intent(in ) :: componentType - type (enumerationMassTypeType ), intent(in ) :: massType - type (enumerationWeightByType ), intent(in ) :: weightBy - integer , intent(in ) :: weightIndex - double precision , intent(in ) :: positionSpherical(3) - !$GLC attributes unused :: weightIndex - - ! Return zero if the component and mass type is not matched. - Dark_Matter_Profile_Density_Task=0.0d0 - if (.not.(componentType == componentTypeAll .or. componentType == componentTypeDarkHalo)) return - if (.not.(massType == massTypeAll .or. massType == massTypeDark )) return - if (.not.(weightBy == weightByMass )) return - ! Compute the density - Dark_Matter_Profile_Density_Task=darkMatterProfile_%density(node,positionSpherical(1)) - return - end function Dark_Matter_Profile_Density_Task - - !![ - - Dark_Matter_Profile_Density_Spherical_Average_Task - - !!] - double precision function Dark_Matter_Profile_Density_Spherical_Average_Task(node,radius,componentType,massType,weightBy,weightIndex) - !!{ - Computes the density at a given position for a dark matter profile. - !!} - use :: Galactic_Structure_Options, only : componentTypeAll, componentTypeDarkHalo , massTypeAll , massTypeDark , & - & weightByMass , enumerationComponentTypeType, enumerationMassTypeType, enumerationWeightByType - use :: Galacticus_Nodes , only : treeNode - use :: Numerical_Constants_Math , only : Pi - implicit none - type (treeNode ), intent(inout) :: node - type (enumerationComponentTypeType), intent(in ) :: componentType - type (enumerationMassTypeType ), intent(in ) :: massType - type (enumerationWeightByType ), intent(in ) :: weightBy - integer , intent(in ) :: weightIndex - double precision , intent(in ) :: radius - !$GLC attributes unused :: weightIndex - - ! Return zero if the component and mass type is not matched. - Dark_Matter_Profile_Density_Spherical_Average_Task=0.0d0 - if (.not.(componentType == componentTypeAll .or. componentType == componentTypeDarkHalo)) return - if (.not.(massType == massTypeAll .or. massType == massTypeDark )) return - if (.not.(weightBy == weightByMass )) return - ! Compute the density - Dark_Matter_Profile_Density_Spherical_Average_Task=+3.0d0 & - & *darkMatterProfile_%enclosedMass(node,radius) & - & /4.0d0 & - & /Pi & - & /radius**3 - return - end function Dark_Matter_Profile_Density_Spherical_Average_Task - - !![ - - Dark_Matter_Profile_Rotation_Curve_Gradient_Task - - !!] - double precision function Dark_Matter_Profile_Rotation_Curve_Gradient_Task(node,radius,componentType,massType) - !!{ - Computes the rotation curve gradient for the dark matter. - !!} - use :: Galactic_Structure_Options , only : componentTypeAll , componentTypeDarkHalo, massTypeAll , massTypeDark , & - & weightByMass , weightIndexNull , enumerationComponentTypeType, enumerationMassTypeType - use :: Galacticus_Nodes , only : treeNode - use :: Numerical_Constants_Math , only : Pi - use :: Numerical_Constants_Astronomical, only : gravitationalConstantGalacticus - implicit none - type (treeNode ), intent(inout) :: node - type (enumerationComponentTypeType), intent(in ) :: componentType - type (enumerationMassTypeType ), intent(in ) :: massType - double precision , intent(in ) :: radius - double precision :: componentDensity, componentMass, positionSpherical(3) - - ! Set to zero by default. - Dark_Matter_Profile_Rotation_Curve_Gradient_Task=0.0d0 - if (.not.(componentType == componentTypeAll .or. componentType == componentTypeDarkHalo)) return - if (.not.( massType == massTypeAll .or. massType == massTypeDark )) return - if (radius <= 0.0d0) return - - positionSpherical=[radius,0.0d0,0.0d0] - componentMass =Dark_Matter_Profile_Enclosed_Mass_Task(node,radius ,componentType,massType,weightByMass,weightIndexNull) - componentDensity=Dark_Matter_Profile_Density_Task (node,positionSpherical,componentType,massType,weightByMass,weightIndexNull) - if (componentMass ==0.0d0 .or. componentDensity == 0.0d0) return - Dark_Matter_Profile_Rotation_Curve_Gradient_Task= & - & gravitationalConstantGalacticus & - & *(-componentMass/radius**2 & - & +4.0d0*Pi*radius*componentDensity & - & ) - return - end function Dark_Matter_Profile_Rotation_Curve_Gradient_Task - - !![ - - Dark_Matter_Profile_Potential_Task - - !!] - double precision function Dark_Matter_Profile_Potential_Task(node,radius,componentType,massType,status) - !!{ - Return the potential due to dark matter. - !!} - use :: Galactic_Structure_Options, only : componentTypeAll , componentTypeDarkHalo , massTypeAll , massTypeDark , & - & structureErrorCodeSuccess, enumerationComponentTypeType, enumerationMassTypeType, enumerationStructureErrorCodeType - use :: Galacticus_Nodes , only : treeNode - implicit none - type (treeNode ), intent(inout) :: node - type (enumerationComponentTypeType ), intent(in ) :: componentType - type (enumerationMassTypeType ), intent(in ) :: massType - double precision , intent(in ) :: radius - type (enumerationStructureErrorCodeType), intent(inout), optional :: status - type (enumerationStructureErrorCodeType) :: statusLocal - - Dark_Matter_Profile_Potential_Task=0.0d0 - if (.not.(componentType == componentTypeAll .or. componentType == componentTypeDarkHalo)) return - if (.not.(massType == massTypeAll .or. massType == massTypeDark )) return - Dark_Matter_Profile_Potential_Task=darkMatterProfile_%potential(node,radius,statusLocal) - if (present(status).and.statusLocal /= structureErrorCodeSuccess) status=structureErrorCodeSuccess - return - end function Dark_Matter_Profile_Potential_Task - - double precision function Dark_Matter_Profile_Radius_Enclosing_Mass(node,mass) - !!{ - Return the radius enclosing the given mass of dark matter. - !!} - use :: Galacticus_Nodes, only : treeNode - implicit none - type (treeNode), intent(inout) :: node - double precision , intent(in ) :: mass - - Dark_Matter_Profile_Radius_Enclosing_Mass=darkMatterProfile_%radiusEnclosingMass(node,mass) - return - end function Dark_Matter_Profile_Radius_Enclosing_Mass - - !![ - - Dark_Matter_Profile_Structure_Tasks_State_Store - - !!] - subroutine Dark_Matter_Profile_Structure_Tasks_State_Store(stateFile,gslStateFile,stateOperationID) - !!{ - Store object state, - !!} - use :: Display , only : displayMessage, verbosityLevelInfo - use, intrinsic :: ISO_C_Binding, only : c_ptr , c_size_t - implicit none - integer , intent(in ) :: stateFile - integer(c_size_t), intent(in ) :: stateOperationID - type (c_ptr ), intent(in ) :: gslStateFile - - call displayMessage('Storing state for: dark matter profile structure tasks',verbosity=verbosityLevelInfo) - !![ - - !!] - return - end subroutine Dark_Matter_Profile_Structure_Tasks_State_Store - - !![ - - Dark_Matter_Profile_Structure_Tasks_State_Restore - - !!] - subroutine Dark_Matter_Profile_Structure_Tasks_State_Restore(stateFile,gslStateFile,stateOperationID) - !!{ - Retrieve object state. - !!} - use :: Display , only : displayMessage, verbosityLevelInfo - use, intrinsic :: ISO_C_Binding, only : c_ptr , c_size_t - implicit none - integer , intent(in ) :: stateFile - integer(c_size_t), intent(in ) :: stateOperationID - type (c_ptr ), intent(in ) :: gslStateFile - - call displayMessage('Retrieving state for: dark matter profile structure tasks',verbosity=verbosityLevelInfo) - !![ - - !!] - return - end subroutine Dark_Matter_Profile_Structure_Tasks_State_Restore - -end module Dark_Matter_Profile_Structure_Tasks diff --git a/source/dark_matter_profiles_DMO.Burkert.F90 b/source/dark_matter_profiles_DMO.Burkert.F90 index d020e33227..dfc289348c 100644 --- a/source/dark_matter_profiles_DMO.Burkert.F90 +++ b/source/dark_matter_profiles_DMO.Burkert.F90 @@ -21,40 +21,13 @@ An implementation of \cite{burkert_structure_1995} dark matter halo profiles. !!} - use :: Kind_Numbers , only : kind_int8 - use :: Numerical_Constants_Math, only : Pi - use :: Tables , only : table1D , table1DLogarithmicLinear - + use :: Dark_Matter_Halo_Scales, only : darkMatterHaloScaleClass + !![ - A dark matter profile DMO class which implements the \citep{burkert_structure_1995} density profile is used - \begin{equation} - \rho_\mathrm{dark matter}(r) \propto \left(1+{r\over r_\mathrm{s}}\right)^{-1} \left(1+[{r\over - r_\mathrm{s}}]^2\right)^{-1}, - \end{equation} - normalized such that the total mass of the \gls{node} is enclosed with the virial radius and with the scale length - $r_\mathrm{s} = r_\mathrm{virial}/c$ where $c$ is the halo concentration (see - \refPhysics{darkMatterProfileConcentration}). The mass enclosed within radius $r$ is given by - \begin{equation} - M(<r) = M_\mathrm{virial} {2 \log(1 + R) + \log(1 + R^2) -2 \tan^{-1}(R) \over 2 \log(1 + c) + \log(1 + c^2) -2 \tan^{-1}(c)}, - \end{equation} - where $R=r/r_\mathrm{s}$. The associated gravitational potential is - \begin{equation} - \Phi(r) = -\mathrm{G} \left(1+{1 \over R}\right) { 2 \tan^{-1}(R) - 2 \log(1 + R) + \log(1 + R^2) \over -2 \tan^{-1}(c) + 2 - \log(1 + c) + \log(1 + c^2) }. - \end{equation} - The peak of the rotation curve occurs at $R=3.2446257246042642$ (found by numerical solution), and the Fourier transform of - the profile, $F(k) = \int_0^c 4 \pi r^2 \exp(-i k r) \rho(r) \mathrm{d} r / k r$ (needed in calculations of clustering - using the halo model) is given by - \begin{eqnarray} - F(k) &=& \left\{2 \exp(-i k) \mathrm{C}_\mathrm{i}(k) - 2 \exp(-i k) \mathrm{C}_\mathrm{i}(k[1 + c]) + (1 + i) - \left[-i \exp(k) \pi - \exp(k) \mathrm{E}_\mathrm{i}(-k) \right. \right. \nonumber \\ - & & +i \exp(-k) \mathrm{E}_\mathrm{i}(k) + \exp(k) \mathrm{E}_\mathrm{i}(i [i + c] k) - i \exp(-k) - \mathrm{E}_\mathrm{i}(k [1 + i c ]) + (1 + i) \exp(-i k) \mathrm{S}_\mathrm{i}(k) \nonumber \\ - & & \left. \left. - (1 + i) \exp(-i k) \mathrm{S}_\mathrm{i}(k[1 + c])\right]\right\}/\left[k \left\{-2 - \tan^{-1}(c) + 2 \log(1 + c) + \log(1 + c^2)\right\}\right]. - \end{eqnarray} + A dark matter only profile class which builds \refClass{massDistributionBurkert} objects to compute the + \citep{burkert_structure_1995} density profile. !!] @@ -63,89 +36,10 @@ A dark matter halo profile class implementing \cite{burkert_structure_1995} dark matter halos. !!} private - ! Minimum and maximum concentrations to tabulate. - double precision :: concentrationMinimum , concentrationMaximum - ! Minimum and maximum radii to tabulate. - double precision :: freefallRadiusMinimum , radiusMinimum , & - & densityRadiusMinimum , radialVelocityDispersionRadiusMinimum - double precision :: freefallRadiusMaximum , radiusMaximum , & - & densityRadiusMaximum , radialVelocityDispersionRadiusMaximum - double precision :: freefallTimeMinimum , specificAngularMomentumMinimum , & - & densityMinimum - double precision :: freefallTimeMaximum , specificAngularMomentumMaximum , & - & densityMaximum - ! Tables of Burkert properties. - logical :: burkertFreefallTableInitialized =.false., burkertInverseTableInitialized =.false., & - & burkertTableInitialized =.false., burkertDensityTableInitialized =.false., & - & burkertRadialVelocityDispersionTableInitialized =.false. - integer :: burkertFreefallTableNumberPoints , burkertInverseTableNumberPoints , & - & burkertTableNumberPoints , burkertDensityTableNumberPoints , & - & burkertRadialVelocityDispersionTableNumberPoints - type (table1DLogarithmicLinear) :: burkertConcentrationTable - ! Tables. - type (table1DLogarithmicLinear) :: burkertFreeFall , burkertSpecificAngularMomentum , & - & burkertDensityTable , burkertRadialVelocityDispersionTable - class (table1D ), allocatable :: burkertFreefallInverse , burkertSpecificAngularMomentumInverse , & - & burkertDensityTableInverse - ! Module variables used in integrations. - double precision :: concentrationParameter , radiusStart - ! Record of unique ID of node which we last computed results for. - integer (kind=kind_int8 ) :: lastUniqueID - ! Record of whether or not quantities have been computed. - logical :: specificAngularMomentumScalingsComputed , maximumVelocityComputed - ! Stored values of computed quantities. - double precision :: specificAngularMomentumLengthScale , specificAngularMomentumScale , & - & concentrationPrevious , burkertNormalizationFactorPrevious , & - & maximumVelocityPrevious + class(darkMatterHaloScaleClass), pointer :: darkMatterHaloScale_ => null() contains - !![ - - - - - - - - - - - - - - - - !!] - final :: burkertDestructor - procedure :: autoHook => burkertAutoHook - procedure :: calculationReset => burkertCalculationReset - procedure :: density => burkertDensity - procedure :: densityLogSlope => burkertDensityLogSlope - procedure :: enclosedMass => burkertEnclosedMass - procedure :: radiusEnclosingDensity => burkertRadiusEnclosingDensity - procedure :: radiusEnclosingDensityTabulate => burkertRadiusEnclosingDensityTabulate - procedure :: potential => burkertPotential - procedure :: circularVelocity => burkertCircularVelocity - procedure :: circularVelocityMaximum => burkertCircularVelocityMaximum - procedure :: radiusCircularVelocityMaximum => burkertRadiusCircularVelocityMaximum - procedure :: radialVelocityDispersion => burkertRadialVelocityDispersion - procedure :: radiusFromSpecificAngularMomentum => burkertRadiusFromSpecificAngularMomentum - procedure :: rotationNormalization => burkertRotationNormalization - procedure :: energy => burkertEnergy - procedure :: kSpace => burkertKSpace - procedure :: freefallRadius => burkertFreefallRadius - procedure :: freefallRadiusIncreaseRate => burkertFreefallRadiusIncreaseRate - procedure :: profileEnergy => burkertProfileEnergy - procedure :: specificAngularMomentumScaleFree => burkertSpecificAngularMomentumScaleFree - procedure :: angularMomentumScaleFree => burkertAngularMomentumScaleFree - procedure :: enclosedMassScaleFree => burkertEnclosedMassScaleFree - procedure :: densityScaleFree => burkertDensityScaleFree - procedure :: radialVelocityDispersionScaleFree => burkertRadialVelocityDispersionScaleFree - procedure :: radialVelocityDispersionTabulate => burkertRadialVelocityDispersionTabulate - procedure :: tabulate => burkertTabulate - procedure :: inverseAngularMomentum => burkertInverseAngularMomentum - procedure :: freefallTabulate => burkertFreefallTabulate - procedure :: freefallTimeScaleFree => burkertFreefallTimeScaleFree - procedure :: radialMoment => burkertRadialMoment + final :: burkertDestructor + procedure :: get => burkertGet end type darkMatterProfileDMOBurkert interface darkMatterProfileDMOBurkert @@ -156,19 +50,6 @@ module procedure burkertConstructorInternal end interface darkMatterProfileDMOBurkert - ! Number of points per decade of concentration in Burkert tabulations. - integer , parameter :: tablePointsPerDecade =100 - integer , parameter :: densityTablePointsPerDecade =100 - integer , parameter :: inverseTablePointsPerDecade =100 - integer , parameter :: freefallTablePointsPerDecade =100 - integer , parameter :: radialVelocityDispersionTablePointsPerDecade=100 - - ! Indices for tabulated quantities. - integer , parameter :: concentrationEnergyIndex = 1 , concetrationRotationNormalizationIndex=2 - - ! Minimum (scale-free) freefall time in the Burkert profile. - double precision, parameter :: freefallTimeScaleFreeMinimum =sqrt(3.0d0)*Pi/4.0d0 - contains function burkertConstructorParameters(parameters) result(self) @@ -205,23 +86,6 @@ function burkertConstructorInternal(darkMatterHaloScale_) result(self) !!] - self%concentrationPrevious = -1.0d+0 - self%concentrationMinimum = 1.0d+0 - self%concentrationMaximum = 20.0d+0 - self%densityRadiusMinimum = 1.0d-3 - self%freefallRadiusMinimum = 1.0d-3 - self%radiusMinimum = 1.0d-3 - self%radialVelocityDispersionRadiusMinimum = 1.0d-3 - self%densityRadiusMaximum = 1.0d+2 - self%freefallRadiusMaximum = 1.0d+2 - self%radiusMaximum = 1.0d+2 - self%radialVelocityDispersionRadiusMaximum = 1.0d+2 - self%burkertDensityTableInitialized = .false. - self%burkertFreefallTableInitialized = .false. - self%burkertInverseTableInitialized = .false. - self%burkertTableInitialized = .false. - self%burkertRadialVelocityDispersionTableInitialized= .false. - self%lastUniqueID = -1 ! Ensure that the dark matter profile component supports a "scale" property. if (.not.defaultDarkMatterProfileComponent%scaleIsGettable()) & & call Error_Report & @@ -233,1252 +97,78 @@ function burkertConstructorInternal(darkMatterHaloScale_) result(self) & ) // & & {introspection:location} & & ) - ! Initialize the tabulations. - call self%tabulate () - call self%inverseAngularMomentum () - call self%radialVelocityDispersionTabulate() return end function burkertConstructorInternal - subroutine burkertAutoHook(self) - !!{ - Attach to the calculation reset event. - !!} - use :: Events_Hooks, only : calculationResetEvent, openMPThreadBindingAllLevels - implicit none - class(darkMatterProfileDMOBurkert), intent(inout) :: self - - call calculationResetEvent%attach(self,burkertCalculationReset,openMPThreadBindingAllLevels,label='darkMatterProfileDMOBurkert') - return - end subroutine burkertAutoHook - subroutine burkertDestructor(self) !!{ Destructor for the {\normalfont \ttfamily burkert} dark matter halo profile class. !!} - use :: Events_Hooks, only : calculationResetEvent implicit none type(darkMatterProfileDMOBurkert), intent(inout) :: self - if (self%burkertFreefallTableInitialized ) then - call self%burkertFreeFall %destroy() - call self%burkertFreeFallInverse %destroy() - deallocate(self%burkertFreefallInverse ) - end if - if (self%burkertDensityTableInitialized ) then - call self%burkertDensityTable %destroy() - call self%burkertDensityTableInverse %destroy() - deallocate(self%burkertDensityTableInverse ) - end if - if (self%burkertInverseTableInitialized ) then - call self%burkertSpecificAngularMomentum %destroy() - call self%burkertSpecificAngularMomentumInverse%destroy() - deallocate(self%burkertSpecificAngularMomentumInverse) - end if - if (self%burkertTableInitialized ) then - call self%burkertConcentrationTable %destroy() - end if - if (self%burkertRadialVelocityDispersionTableInitialized) then - call self%burkertRadialVelocityDispersionTable %destroy() - end if !![ !!] - if (calculationResetEvent%isAttached(self,burkertCalculationReset)) call calculationResetEvent%detach(self,burkertCalculationReset) return end subroutine burkertDestructor - subroutine burkertCalculationReset(self,node,uniqueID) + function burkertGet(self,node,weightBy,weightIndex) result(massDistribution_) !!{ - Reset the dark matter profile calculation. + Return the dark matter mass distribution for the given {\normalfont \ttfamily node}. !!} - use :: Kind_Numbers, only : kind_int8 + use :: Galacticus_Nodes , only : nodeComponentBasic , nodeComponentDarkMatterProfile + use :: Galactic_Structure_Options, only : componentTypeDarkHalo , massTypeDark , weightByMass + use :: Mass_Distributions , only : massDistributionBurkert, kinematicsDistributionBurkert implicit none - class (darkMatterProfileDMOBurkert), intent(inout) :: self - type (treeNode ), intent(inout) :: node - integer(kind_int8 ), intent(in ) :: uniqueID - !$GLC attributes unused :: node - - self%specificAngularMomentumScalingsComputed=.false. - self%maximumVelocityComputed =.false. - self%lastUniqueID =uniqueID - return - end subroutine burkertCalculationReset - - subroutine burkertTabulate(self,concentration) - !!{ - Tabulate properties of the Burkert halo profile which must be computed numerically. - !!} - implicit none - class (darkMatterProfileDMOBurkert), intent(inout) :: self - double precision , intent(in ), optional :: concentration - integer :: iConcentration - logical :: retabulate - double precision :: tableConcentration - - retabulate=.not.self%burkertTableInitialized - if (present(concentration)) then - if (concentration < self%concentrationMinimum) then - self%concentrationMinimum=0.5d0*concentration - retabulate=.true. - end if - if (concentration > self%concentrationMaximum) then - self%concentrationMaximum=2.0d0*concentration - retabulate=.true. - end if - end if - if (retabulate) then - ! Decide how many points to tabulate and allocate table arrays. - self%burkertTableNumberPoints=int(log10(self%concentrationMaximum/self%concentrationMinimum)*dble(tablePointsPerDecade))+1 - call self%burkertConcentrationTable%destroy() - call self%burkertConcentrationTable%create(self%concentrationMinimum,self%concentrationMaximum,self%burkertTableNumberPoints,2) - ! Loop over concentrations and populate tables. - do iConcentration=1,self%burkertTableNumberPoints - tableConcentration=self%burkertConcentrationTable%x(iConcentration) - call self%burkertConcentrationTable%populate( self%profileEnergy (tableConcentration),iConcentration,table=concentrationEnergyIndex ) - call self%burkertConcentrationTable%populate(tableConcentration/self%angularMomentumScaleFree(tableConcentration),iConcentration,table=concetrationRotationNormalizationIndex) - end do - ! Specify that tabulation has been made. - self%burkertTableInitialized=.true. - end if - return - end subroutine burkertTabulate - - subroutine burkertInverseAngularMomentum(self,specificAngularMomentum) - !!{ - Tabulates the specific angular momentum vs. radius in an Burkert profile for rapid inversion. - !!} - implicit none - class (darkMatterProfileDMOBurkert), intent(inout) :: self - double precision , intent(in ), optional :: specificAngularMomentum - integer :: iRadius - logical :: retabulate - - retabulate=.not.self%burkertInverseTableInitialized - ! If the table has not yet been made, compute and store the specific angular momenta corresponding to the minimum and maximum - ! radii that will be tabulated by default. - if (retabulate) then - self%specificAngularMomentumMinimum=self%specificAngularMomentumScaleFree(self%radiusMinimum) - self%specificAngularMomentumMaximum=self%specificAngularMomentumScaleFree(self%radiusMaximum) - end if - if (present(specificAngularMomentum)) then - do while (specificAngularMomentum < self%specificAngularMomentumMinimum) - self%radiusMinimum =0.5d0*self%radiusMinimum - self%specificAngularMomentumMinimum=self%specificAngularMomentumScaleFree(self%radiusMinimum) - retabulate=.true. - end do - do while (specificAngularMomentum > self%specificAngularMomentumMaximum) - self%radiusMaximum =2.0d0*self%radiusMaximum - self%specificAngularMomentumMaximum=self%specificAngularMomentumScaleFree(self%radiusMaximum) - retabulate=.true. - end do - end if - if (retabulate) then - ! Decide how many points to tabulate and allocate table arrays. - self%burkertInverseTableNumberPoints=int(log10(self%radiusMaximum/self%radiusMinimum)*dble(inverseTablePointsPerDecade))+1 - ! Create a range of radii. - call self%burkertSpecificAngularMomentum%destroy( ) - call self%burkertSpecificAngularMomentum%create (self%radiusMinimum,self%radiusMaximum,self%burkertInverseTableNumberPoints) - ! Loop over radii and populate tables. - do iRadius=1,self%burkertInverseTableNumberPoints - call self%burkertSpecificAngularMomentum%populate( & - & self%specificAngularMomentumScaleFree(self%burkertSpecificAngularMomentum%x(iRadius)), & - & iRadius & - & ) - end do - call self%burkertSpecificAngularMomentum%reverse(self%burkertSpecificAngularMomentumInverse) - ! Specify that tabulation has been made. - self%burkertInverseTableInitialized=.true. - end if - return - end subroutine burkertInverseAngularMomentum - - double precision function burkertDensity(self,node,radius) - !!{ - Returns the density (in $M_\odot$ Mpc$^{-3}$) in the dark matter profile of {\normalfont \ttfamily node} at the given - {\normalfont \ttfamily radius} (given in units of Mpc). - !!} - use :: Galacticus_Nodes, only : nodeComponentBasic, nodeComponentDarkMatterProfile, treeNode - implicit none - class (darkMatterProfileDMOBurkert ), intent(inout) :: self - type (treeNode ), intent(inout) :: node - double precision , intent(in ) :: radius - class (nodeComponentBasic ), pointer :: basic - class (nodeComponentDarkMatterProfile), pointer :: darkMatterProfile - double precision :: radiusOverScaleRadius , scaleRadius, & - & virialRadiusOverScaleRadius - - basic => node %basic ( ) - darkMatterProfile => node %darkMatterProfile(autoCreate=.true.) - scaleRadius = darkMatterProfile%scale ( ) - radiusOverScaleRadius = radius /scaleRadius - virialRadiusOverScaleRadius = self%darkMatterHaloScale_%radiusVirial(node)/scaleRadius - burkertDensity = self%densityScaleFree(radiusOverScaleRadius,virialRadiusOverScaleRadius)*basic%mass()/scaleRadius**3 - return - end function burkertDensity - - double precision function burkertDensityLogSlope(self,node,radius) - !!{ - Returns the logarithmic slope of the density profile of {\normalfont \ttfamily node} at the given {\normalfont \ttfamily - radius} (given in units of Mpc). - !!} - use :: Galacticus_Nodes, only : nodeComponentBasic, nodeComponentDarkMatterProfile, treeNode - implicit none - class (darkMatterProfileDMOBurkert ), intent(inout) :: self - type (treeNode ), intent(inout) :: node - double precision , intent(in ) :: radius - class (nodeComponentDarkMatterProfile), pointer :: darkMatterProfile - double precision :: radiusOverScaleRadius, scaleRadius - !$GLC attributes unused :: self - - darkMatterProfile => node %darkMatterProfile(autoCreate=.true.) - scaleRadius = darkMatterProfile%scale ( ) - radiusOverScaleRadius = + radius & - & /scaleRadius - burkertDensityLogSlope = - radiusOverScaleRadius /(1.0d0+radiusOverScaleRadius ) & - & -2.0d0*radiusOverScaleRadius**2/(1.0d0+radiusOverScaleRadius**2) - return - end function burkertDensityLogSlope - - double precision function burkertEnclosedMass(self,node,radius) - !!{ - Returns the enclosed mass (in $M_\odot$) in the dark matter profile of {\normalfont \ttfamily node} at the given {\normalfont \ttfamily radius} (given in - units of Mpc). - !!} - use :: Galacticus_Nodes, only : nodeComponentBasic, nodeComponentDarkMatterProfile, treeNode - implicit none - class (darkMatterProfileDMOBurkert ), intent(inout) :: self - type (treeNode ), intent(inout) :: node - double precision , intent(in ) :: radius - class (nodeComponentBasic ), pointer :: basic - class (nodeComponentDarkMatterProfile), pointer :: darkMatterProfile - double precision :: radiusOverScaleRadius , scaleRadius, & - & virialRadiusOverScaleRadius - - basic => node%basic ( ) - darkMatterProfile => node%darkMatterProfile(autoCreate=.true.) - scaleRadius = darkMatterProfile%scale() - radiusOverScaleRadius = radius /scaleRadius - virialRadiusOverScaleRadius = self%darkMatterHaloScale_%radiusVirial(node)/scaleRadius - burkertEnclosedMass = self%enclosedMassScaleFree(radiusOverScaleRadius,virialRadiusOverScaleRadius)*basic%mass() - return - end function burkertEnclosedMass - - double precision function burkertPotential(self,node,radius,status) - !!{ - Returns the potential (in (km/s)$^2$) in the dark matter profile of {\normalfont \ttfamily node} at the given {\normalfont \ttfamily radius} (given in - units of Mpc). - !!} - use :: Galactic_Structure_Options, only : enumerationStructureErrorCodeType, structureErrorCodeSuccess - use :: Galacticus_Nodes , only : nodeComponentDarkMatterProfile - use :: Numerical_Constants_Math , only : Pi - implicit none - class (darkMatterProfileDMOBurkert ), intent(inout) :: self - type (treeNode ), intent(inout), target :: node - double precision , intent(in ) :: radius - type (enumerationStructureErrorCodeType), intent( out), optional :: status - class (nodeComponentDarkMatterProfile ) , pointer :: darkMatterProfile - double precision , parameter :: radiusSmall =1.0d-10 - double precision :: radiusOverScaleRadius , virialRadiusOverScaleRadius - - if (present(status)) status=structureErrorCodeSuccess - darkMatterProfile => node%darkMatterProfile(autoCreate=.true.) - radiusOverScaleRadius =radius /darkMatterProfile%scale() - virialRadiusOverScaleRadius =self%darkMatterHaloScale_%radiusVirial(node)/darkMatterProfile%scale() - if (radiusOverScaleRadius < radiusSmall) then - burkertPotential= & - & +( & - & -Pi & - & +2.0d0 & - & /3.0d0 & - & *radiusOverScaleRadius**2 & - & ) & - & /( & - & -2.0d0 *atan( virialRadiusOverScaleRadius ) & - & +2.0d0 *log (1.0d0+virialRadiusOverScaleRadius ) & - & + log (1.0d0+virialRadiusOverScaleRadius**2) & - & ) & - & *virialRadiusOverScaleRadius & - & *self%darkMatterHaloScale_%velocityVirial(node)**2 - else - burkertPotential= & - & +( & - & -Pi & - & *radiusOverScaleRadius & - & +( & - & +2.0d0*(1.0d0+radiusOverScaleRadius)*atan( radiusOverScaleRadius ) & - & -2.0d0*(1.0d0+radiusOverScaleRadius)*log (1.0d0+radiusOverScaleRadius ) & - & - (1.0d0-radiusOverScaleRadius)*log (1.0d0+radiusOverScaleRadius **2) & - & ) & - & ) & - & /radiusOverScaleRadius & - & /( & - & -2.0d0 *atan( virialRadiusOverScaleRadius ) & - & +2.0d0 *log (1.0d0+virialRadiusOverScaleRadius ) & - & + log (1.0d0+virialRadiusOverScaleRadius**2) & - & ) & - & *virialRadiusOverScaleRadius & - & *self%darkMatterHaloScale_%velocityVirial(node)**2 - end if - return - end function burkertPotential - - double precision function burkertCircularVelocity(self,node,radius) - !!{ - Returns the circular velocity (in km/s) in the dark matter profile of {\normalfont \ttfamily node} at the given {\normalfont \ttfamily radius} (given in - units of Mpc). - !!} - use :: Numerical_Constants_Astronomical, only : gravitationalConstantGalacticus - implicit none - class (darkMatterProfileDMOBurkert), intent(inout) :: self - type (treeNode ), intent(inout) :: node - double precision , intent(in ) :: radius - - if (radius > 0.0d0) then - burkertCircularVelocity=sqrt(gravitationalConstantGalacticus*self%enclosedMass(node,radius)/radius) - else - burkertCircularVelocity=0.0d0 - end if - return - end function burkertCircularVelocity - - double precision function burkertCircularVelocityMaximum(self,node) - !!{ - Returns the maximum circular velocity (in km/s) in the dark matter profile of {\normalfont \ttfamily node}. - !!} - use :: Galacticus_Nodes, only : nodeComponentDarkMatterProfile, treeNode - implicit none - class (darkMatterProfileDMOBurkert ), intent(inout) :: self - type (treeNode ), intent(inout) :: node - ! The radius (in units of the scale radius) at which the rotation speed peaks in a Burkert halo. - double precision , parameter :: radiusMaximum =3.2446257246042642d0 - class (nodeComponentDarkMatterProfile), pointer :: darkMatterProfile - double precision :: scaleRadius - - ! Check if node differs from previous one for which we performed calculations. - if (node%uniqueID() /= self%lastUniqueID) call self%calculationReset(node,node%uniqueID()) - ! Check if maximum velocity is already computed. Compute and store if not. - if (.not.self%maximumVelocityComputed) then - darkMatterProfile => node %darkMatterProfile(autoCreate=.true. ) - scaleRadius = darkMatterProfile%scale ( ) - self%maximumVelocityPrevious = self %circularVelocity (node ,radiusMaximum*scaleRadius) - self%maximumVelocityComputed = .true. - end if - burkertCircularVelocityMaximum=self%maximumVelocityPrevious - return - end function burkertCircularVelocityMaximum - - double precision function burkertRadiusCircularVelocityMaximum(self,node) - !!{ - Returns the radius (in Mpc) at which the maximum circular velocity is achieved in the dark matter profile of {\normalfont \ttfamily node}. - !!} - use :: Galacticus_Nodes, only : nodeComponentDarkMatterProfile, treeNode - implicit none - class (darkMatterProfileDMOBurkert ), intent(inout) :: self - type (treeNode ), intent(inout) :: node - ! The radius (in units of the scale radius) at which the rotation speed peaks in a Burkert halo. - double precision , parameter :: radiusMaximum =3.2446257246042642d0 - class (nodeComponentDarkMatterProfile), pointer :: darkMatterProfile - - darkMatterProfile => node %darkMatterProfile(autoCreate=.true.) - burkertRadiusCircularVelocityMaximum = + radiusMaximum & - & *darkMatterProfile%scale ( ) - return - end function burkertRadiusCircularVelocityMaximum - - double precision function burkertRadialVelocityDispersion(self,node,radius) - !!{ - Returns the radial velocity dispersion (in km/s) in the dark matter profile of {\normalfont \ttfamily node} at the given - {\normalfont \ttfamily radius} (given in units of Mpc). - !!} - use :: Galacticus_Nodes, only : nodeComponentDarkMatterProfile, treeNode - implicit none - class (darkMatterProfileDMOBurkert ), intent(inout) :: self - type (treeNode ), intent(inout) :: node - double precision , intent(in ) :: radius - class (nodeComponentDarkMatterProfile) , pointer :: darkMatterProfile - double precision :: radiusOverScaleRadius , scaleRadius, & - & virialRadiusOverScaleRadius - - darkMatterProfile => node%darkMatterProfile(autoCreate=.true.) - scaleRadius = darkMatterProfile%scale() - radiusOverScaleRadius = radius /scaleRadius - virialRadiusOverScaleRadius = self%darkMatterHaloScale_%radiusVirial(node)/scaleRadius - if (radius > 0.0d0) then - call self%radialVelocityDispersionTabulate(radiusOverScaleRadius) - burkertRadialVelocityDispersion=self%burkertRadialVelocityDispersionTable%interpolate(radiusOverScaleRadius) - else - burkertRadialVelocityDispersion=0.0d0 - end if - ! Compute the normalization factor. - call burkertMassNormalizationFactor(self,virialRadiusOverScaleRadius) - ! Evaluate the radial velocity dispersion. - burkertRadialVelocityDispersion=+burkertRadialVelocityDispersion & - & *sqrt( & - & +self%burkertNormalizationFactorPrevious & - & *virialRadiusOverScaleRadius & - & ) & - & *self%darkMatterHaloScale_%velocityVirial(node) - return - end function burkertRadialVelocityDispersion - - double precision function burkertRadiusFromSpecificAngularMomentum(self,node,specificAngularMomentum) - !!{ - Returns the radius (in Mpc) in {\normalfont \ttfamily node} at which a circular orbit has the given {\normalfont \ttfamily - specificAngularMomentum} (given in units of km s$^{-1}$ Mpc) - !!} - use :: Galacticus_Nodes, only : nodeComponentDarkMatterProfile, treeNode - implicit none - class (darkMatterProfileDMOBurkert ), intent(inout) :: self - type (treeNode ), intent(inout) :: node - double precision , intent(in ) :: specificAngularMomentum - class (nodeComponentDarkMatterProfile), pointer :: darkMatterProfile - double precision :: specificAngularMomentumScaleFree - - ! Return immediately with zero radius for non-positive specific angular momenta. - if (specificAngularMomentum <= 0.0d0) then - burkertRadiusFromSpecificAngularMomentum=0.0d0 - return - end if - ! Check if node differs from previous one for which we performed calculations. - if (node%uniqueID() /= self%lastUniqueID) call self%calculationReset(node,node%uniqueID()) - ! Check if scalings are already computed. Compute and store if not. - if (.not.self%specificAngularMomentumScalingsComputed) then - ! Flag that scale quantities are now computed. - self%specificAngularMomentumScalingsComputed=.true. - - ! Get the dark matter profile. - darkMatterProfile => node%darkMatterProfile(autoCreate=.true.) - - ! Get the scale radius. - self%specificAngularMomentumLengthScale=darkMatterProfile%scale() - - ! Get the specific angular momentum scale. - self%specificAngularMomentumScale=self%specificAngularMomentumLengthScale & - & *self%circularVelocity(node,self%specificAngularMomentumLengthScale) - end if - - ! Compute the specific angular momentum in scale free units (using the scale length for distances and sqrt(G M(r_scale) / - ! r_scale) for velocities). - specificAngularMomentumScaleFree=specificAngularMomentum/self%specificAngularMomentumScale - - ! Ensure that the interpolations exist and extend sufficiently far. - call self%inverseAngularMomentum(specificAngularMomentumScaleFree) - - ! Interpolate to get the dimensionless radius at which this specific angular momentum is found. - burkertRadiusFromSpecificAngularMomentum=self%burkertSpecificAngularMomentumInverse%interpolate(specificAngularMomentumScaleFree) - - ! Convert to a physical radius. - burkertRadiusFromSpecificAngularMomentum=burkertRadiusFromSpecificAngularMomentum*self%specificAngularMomentumLengthScale - return - end function burkertRadiusFromSpecificAngularMomentum - - double precision function burkertRotationNormalization(self,node) - !!{ - Return the normalization of the rotation velocity vs. specific angular momentum relation. - !!} - use :: Galacticus_Nodes, only : nodeComponentDarkMatterProfile, treeNode - implicit none - class (darkMatterProfileDMOBurkert ), intent(inout) :: self - type (treeNode ), intent(inout) :: node - class (nodeComponentDarkMatterProfile), pointer :: darkMatterProfile - double precision :: concentration - - ! Get components. - darkMatterProfile => node%darkMatterProfile(autoCreate=.true.) - - ! Find the concentration parameter of this halo. - concentration=self%darkMatterHaloScale_%radiusVirial(node)/darkMatterProfile%scale() - - ! Ensure that the interpolations exist and extend sufficiently far. - call self%tabulate(concentration) - - ! Find the rotation normalization by interpolation. - burkertRotationNormalization=+self%burkertConcentrationTable%interpolate(concentration,table=concetrationRotationNormalizationIndex) & - & /self%darkMatterHaloScale_%radiusVirial(node) - return - end function burkertRotationNormalization - - double precision function burkertEnergy(self,node) - !!{ - Return the energy of an Burkert halo density profile. - !!} - use :: Galacticus_Nodes, only : nodeComponentBasic, nodeComponentDarkMatterProfile, treeNode - implicit none - class (darkMatterProfileDMOBurkert ), intent(inout) :: self - type (treeNode ), intent(inout) :: node - class (nodeComponentDarkMatterProfile), pointer :: darkMatterProfile - class (nodeComponentBasic ), pointer :: basic - double precision :: concentration - - ! Get components. - basic => node%basic ( ) - darkMatterProfile => node%darkMatterProfile(autoCreate=.true.) - - ! Find the concentration parameter of this halo. - concentration=self%darkMatterHaloScale_%radiusVirial(node)/darkMatterProfile%scale() - - ! Ensure that the interpolations exist and extend sufficiently far. - call self%tabulate(concentration) - - ! Find the energy by interpolation. - burkertEnergy=self%burkertConcentrationTable%interpolate(concentration,table=concentrationEnergyIndex) & - & *basic%mass()*self%darkMatterHaloScale_%velocityVirial(node)**2 - return - end function burkertEnergy - - double precision function burkertAngularMomentumScaleFree(self,concentration) - !!{ - Returns the total angular momentum (in units of the virial mass times scale radius times [assumed constant] rotation speed) - in an Burkert dark matter profile with given {\normalfont \ttfamily concentration}. This is given by: - \begin{equation} - J = \left. \int_0^c 4 \pi x^3 \rho(x) \d x \right/ \int_0^c 4 \pi x^2 \rho(x) \d x, - \end{equation} - where $x$ is radius in units of the scale radius and $c$ is concentration. This can be evaluated to give - \begin{equation} - J = \left. \left[ 2 \tan^{-1} c + 2 \log(1+c) + \log(1+c^2) - 4c \right] \right/ \left[ 2 \tan^{-1} c - 2 \log(1+c) - \log(1+c^2) \right]. - \end{equation} - !!} - implicit none - class (darkMatterProfileDMOBurkert), intent(inout) :: self - double precision , intent(in ) :: concentration - !$GLC attributes unused :: self - - burkertAngularMomentumScaleFree=+( & - & +2.0d0*atan( concentration ) & - & +2.0d0*log (1.0d0+concentration ) & - & + log (1.0d0+concentration**2) & - & -4.0d0* concentration & - & ) & - & /( & - & +2.0d0*atan( concentration ) & - & -2.0d0*log (1.0d0+concentration ) & - & - log (1.0d0+concentration**2) & - & ) - return - end function burkertAngularMomentumScaleFree - - double precision function burkertSpecificAngularMomentumScaleFree(self,radius) - !!{ - Returns the specific angular momentum, normalized to unit scale length and unit velocity at the scale radius, at position - {\normalfont \ttfamily radius} (in units of the scale radius) in an Burkert profile. - !!} - implicit none - class (darkMatterProfileDMOBurkert), intent(inout) :: self - double precision , intent(in ) :: radius - - burkertSpecificAngularMomentumScaleFree=sqrt(radius*self%enclosedMassScaleFree(radius,1.0d0)) - return - end function burkertSpecificAngularMomentumScaleFree - - double precision function burkertEnclosedMassScaleFree(self,radius,concentration) - !!{ - Returns the enclosed mass (in units of the virial mass) in an Burkert dark matter profile with given {\normalfont \ttfamily - concentration} at the given {\normalfont \ttfamily radius} (given in units of the scale radius). - !!} - implicit none - class (darkMatterProfileDMOBurkert), intent(inout) :: self - double precision , intent(in ) :: concentration , radius - double precision , parameter :: minimumRadiusForExactSolution=1.0d-4 - - if (radius < minimumRadiusForExactSolution) then - ! Use a series solution for small radii. - burkertEnclosedMassScaleFree=(4.0d0/3.0d0)*radius**3*(1.0d0-radius) - else - ! Use the exact solution. - burkertEnclosedMassScaleFree=( & - & +2.0d0*log (1.0d0+radius ) & - & + log (1.0d0+radius**2) & - & -2.0d0*atan( radius ) & - & ) - end if - ! Compute the mass profile normalization factor. - call burkertMassNormalizationFactor(self,concentration) - ! Evaluate the scale-free enclosed mass. - burkertEnclosedMassScaleFree=burkertEnclosedMassScaleFree*self%burkertNormalizationFactorPrevious - return - end function burkertEnclosedMassScaleFree - - subroutine burkertMassNormalizationFactor(self,concentration) - !!{ - Compute the normalization factor for the burkert mass profile. - !!} - implicit none - class (darkMatterProfileDMOBurkert), intent(inout) :: self - double precision , intent(in ) :: concentration - ! Precomputed Burkert normalization factor for unit concentration. - double precision , parameter :: burkertNormalizationFactorUnitConcentration=1.0d0/(3.0d0*log(2.0d0)-2.0d0*atan(1.0d0)) - - ! Check if we were called with a different concentration compared to the previous call. - if (concentration /= self%concentrationPrevious) then - ! We were, so recompute the normalization factor. - if (concentration == 1.0d0) then - self%burkertNormalizationFactorPrevious=burkertNormalizationFactorUnitConcentration - else - self%burkertNormalizationFactorPrevious=+1.0d0 & - & /( & - & +2.0d0*log (1.0d0+concentration ) & - & + log (1.0d0+concentration**2) & - & -2.0d0*atan( concentration ) & - & ) - end if - self%concentrationPrevious=concentration - end if - return - end subroutine burkertMassNormalizationFactor - - double precision function burkertDensityScaleFree(self,radius,concentration) - !!{ - Returns the density (in units such that the virial mass and scale length are unity) in an Burkert dark matter profile with - given {\normalfont \ttfamily concentration} at the given {\normalfont \ttfamily radius} (given in units of the scale radius). - !!} - use :: Numerical_Constants_Math, only : Pi - implicit none - class (darkMatterProfileDMOBurkert), intent(inout) :: self - double precision , intent(in ) :: concentration, radius - !$GLC attributes unused :: self - - burkertDensityScaleFree=+1.0d0 & - & /(1.0d0+radius ) & - & /(1.0d0+radius**2) & - & /Pi & - & /( & - & +2.0d0*log (1.0d0+concentration ) & - & + log (1.0d0+concentration**2) & - & -2.0d0*atan( concentration ) & - & ) - return - end function burkertDensityScaleFree - - double precision function burkertProfileEnergy(self,concentration) - !!{ - Computes the total energy of an Burkert profile halo of given {\normalfont \ttfamily concentration} using the methods of - \citeauthor{cole_hierarchical_2000}~(\citeyear{cole_hierarchical_2000}; their Appendix~A). - !!} - use :: Numerical_Constants_Math, only : Pi - use :: Numerical_Integration , only : integrator - implicit none - class (darkMatterProfileDMOBurkert), intent(inout) :: self - double precision , intent(in ) :: concentration - type (integrator ) :: integratorPotential , integratorJeans , & - & integratorKinetic - double precision :: jeansEquationIntegral , kineticEnergy , & - & kineticEnergyIntegral , potentialEnergy , & - & potentialEnergyIntegral, radiusMaximum , & - & radiusMinimum , concentrationParameter - - ! Compute the potential energy. - radiusMinimum =0.0d0 - radiusMaximum =concentration - concentrationParameter =concentration - integratorPotential =integrator(burkertPotentialEnergyIntegrand,toleranceRelative=1.0d-3) - potentialEnergyIntegral=integratorPotential%integrate(radiusMinimum,radiusMaximum) - potentialEnergy =-0.5d0*(1.0d0/concentration+potentialEnergyIntegral) - ! Compute the velocity dispersion at the virial radius. - radiusMinimum =concentration - radiusMaximum =100.0d0*concentration - concentrationParameter=concentration - integratorJeans =integrator(burkertJeansEquationIntegrand,toleranceRelative=1.0d-3) - jeansEquationIntegral =integratorJeans%integrate(radiusMinimum,radiusMaximum) - ! Compute the kinetic energy. - radiusMinimum =0.0d0 - radiusMaximum =concentration - concentrationParameter=concentration - integratorKinetic =integrator(burkertKineticEnergyIntegrand,toleranceRelative=1.0d-3) - kineticEnergyIntegral =integratorKinetic%integrate(radiusMinimum,radiusMaximum) - kineticEnergy =2.0d0*Pi*(jeansEquationIntegral*concentration**3+kineticEnergyIntegral) - ! Compute the total energy. - burkertProfileEnergy =(potentialEnergy+kineticEnergy)*concentration - return - - contains - - double precision function burkertPotentialEnergyIntegrand(radius) - !!{ - Integrand for Burkert profile potential energy. - !!} - implicit none - double precision, intent(in ) :: radius - - burkertPotentialEnergyIntegrand=(self%enclosedMassScaleFree(radius,concentrationParameter)/radius)**2 - return - end function burkertPotentialEnergyIntegrand - - double precision function burkertKineticEnergyIntegrand(radius) - !!{ - Integrand for Burkert profile kinetic energy. - !!} - implicit none - double precision, intent(in ) :: radius - - burkertKineticEnergyIntegrand=+self%EnclosedMassScaleFree(radius,concentrationParameter) & - & *self%densityScaleFree (radius,concentrationParameter) & - & *radius - return - end function burkertKineticEnergyIntegrand - - double precision function burkertJeansEquationIntegrand(radius) - !!{ - Integrand for Burkert profile Jeans equation. - !!} - implicit none - double precision, intent(in ) :: radius - - burkertJeansEquationIntegrand=+self%enclosedMassScaleFree(radius,concentrationParameter) & - & *self%densityScaleFree (radius,concentrationParameter) & - & /radius**2 - return - end function burkertJeansEquationIntegrand - - end function burkertProfileEnergy - - double precision function burkertKSpace(self,node,waveNumber) - !!{ - Returns the Fourier transform of the Burkert density profile at the specified {\normalfont \ttfamily waveNumber} (given in Mpc$^{-1}$), using the - expression given in \citeauthor{cooray_halo_2002}~(\citeyear{cooray_halo_2002}; eqn.~81). - !!} - use :: Exponential_Integrals , only : Cosine_Integral , Exponential_Integral, Sine_Integral - use :: Galacticus_Nodes , only : nodeComponentDarkMatterProfile, treeNode - use :: Numerical_Constants_Math, only : Pi - implicit none - class (darkMatterProfileDMOBurkert ), intent(inout) :: self - type (treeNode ), intent(inout), target :: node - double precision , intent(in ) :: waveNumber - class (nodeComponentDarkMatterProfile) , pointer :: darkMatterProfile - double precision :: concentration , radiusScale, & - & waveNumberScaleFree - - ! Get components. - darkMatterProfile => node%darkMatterProfile(autoCreate=.true.) - - ! Get the scale radius. - radiusScale=darkMatterProfile%scale() - - ! Compute the concentration parameter. - concentration=self%darkMatterHaloScale_%radiusVirial(node)/radiusScale - - ! Get the dimensionless wavenumber. - waveNumberScaleFree=waveNumber*radiusScale - - ! Compute the Fourier transformed profile. - burkertKSpace=dimag( & - & +( & - & + 2.0d0 *exp(-dcmplx(0.0d0,1.0d0)*waveNumberScaleFree)*Cosine_Integral ( waveNumberScaleFree) & - & - 2.0d0 *exp(-dcmplx(0.0d0,1.0d0)*waveNumberScaleFree)*Cosine_Integral ( (+1.0d0+concentration)*waveNumberScaleFree) & - & +dcmplx(1.0d0,1.0d0) & - & *( & - & -dcmplx(0.0d0,1.0d0)*exp(+ waveNumberScaleFree)*Pi & - & - exp(+ waveNumberScaleFree)*Exponential_Integral( -1.0d0 *waveNumberScaleFree) & - & +dcmplx(0.0d0,1.0d0)*exp(- waveNumberScaleFree)*Exponential_Integral( +1.0d0 *waveNumberScaleFree) & - & + exp(+ waveNumberScaleFree)*Exponential_Integral(dcmplx(-1.0d0,concentration)*waveNumberScaleFree) & - & -dcmplx(0.0d0,1.0d0)*exp(- waveNumberScaleFree)*Exponential_Integral(dcmplx(+1.0d0,concentration)*waveNumberScaleFree) & - & +dcmplx(1.0d0,1.0d0)*exp(-dcmplx(0.0d0,1.0d0)*waveNumberScaleFree)*Sine_Integral ( waveNumberScaleFree) & - & -dcmplx(1.0d0,1.0d0)*exp(-dcmplx(0.0d0,1.0d0)*waveNumberScaleFree)*Sine_Integral ( (+1.0d0+concentration)*waveNumberScaleFree) & - & ) & - & ) & - & /( & - & +waveNumberScaleFree & - & *( & - & -2.0d0*atan( concentration ) & - & +2.0d0*log (1.0d0+concentration ) & - & + log (1.0d0+concentration**2) & - & ) & - & ) & - & ) - return - end function burkertKSpace - - double precision function burkertFreefallRadius(self,node,time) - !!{ - Returns the freefall radius in the Burkert density profile at the specified {\normalfont \ttfamily time} (given in Gyr). - !!} - use :: Galacticus_Nodes , only : nodeComponentDarkMatterProfile, treeNode - use :: Numerical_Constants_Astronomical, only : Mpc_per_km_per_s_To_Gyr - implicit none - class (darkMatterProfileDMOBurkert ), intent(inout), target :: self - type (treeNode ), intent(inout), target :: node - double precision , intent(in ) :: time - class (nodeComponentDarkMatterProfile), pointer :: darkMatterProfile - double precision :: concentration , freefallTimeScaleFree, & - & radiusScale , timeScale , & - & velocityScale - - ! For non-positive freefall times, return a zero freefall radius immediately. - if (time <= 0.0d0) then - burkertFreefallRadius=0.0d0 - return - end if - ! Get components. - darkMatterProfile => node%darkMatterProfile(autoCreate=.true.) - ! Get the scale radius. - radiusScale=darkMatterProfile%scale() - ! Get the concentration. - concentration=+self%darkMatterHaloScale_%radiusVirial (node) & - & /radiusScale - ! Get the virial velocity. - velocityScale=+self%darkMatterHaloScale_%velocityVirial(node) - ! Compute time scale. - timeScale=+Mpc_per_km_per_s_To_Gyr & - & *radiusScale & - & /velocityScale & - & /sqrt( & - & + concentration & - & ) & - & *sqrt( & - & -2.0d0*atan( concentration ) & - & +2.0d0*log (1.0d0+concentration ) & - & + log (1.0d0+concentration**2) & - & ) - ! Compute dimensionless time. - freefallTimeScaleFree=time/timeScale - ! Ensure table is sufficiently extensive. - call self%freefallTabulate(freefallTimeScaleFree) - ! The freefall time is finite at zero radius in this profile. If the requested time is less than this, return zero radius. - if (freefallTimeScaleFree < freefallTimeScaleFreeMinimum) then - burkertFreefallRadius=0.0d0 - else - burkertFreefallRadius=self%burkertFreefallInverse%interpolate(freefallTimeScaleFree)*radiusScale - end if - return - end function burkertFreefallRadius - - double precision function burkertFreefallRadiusIncreaseRate(self,node,time) - !!{ - Returns the rate of increase of the freefall radius in the Burkert density profile at the specified {\normalfont \ttfamily time} (given in - Gyr). - !!} - use :: Galacticus_Nodes , only : nodeComponentDarkMatterProfile, treeNode - use :: Numerical_Constants_Astronomical, only : Mpc_per_km_per_s_To_Gyr - implicit none - class (darkMatterProfileDMOBurkert ), intent(inout), target :: self - type (treeNode ), intent(inout), target :: node - double precision , intent(in ) :: time - class (nodeComponentDarkMatterProfile), pointer :: darkMatterProfile - double precision :: concentration , freefallTimeScaleFree, & - & radiusScale , timeScale , & - & velocityScale - - ! For non-positive freefall times, return the limiting value for small radii. - if (time <= 0.0d0) then - burkertFreefallRadiusIncreaseRate=0.0d0 - return - end if - ! Get components. - darkMatterProfile => node%darkMatterProfile(autoCreate=.true.) - ! Get the scale radius. - radiusScale=darkMatterProfile%scale() - ! Get the concentration. - concentration=+self%darkMatterHaloScale_%radiusVirial (node) & - & /radiusScale - ! Get the virial velocity. - velocityScale=+self%darkMatterHaloScale_%velocityVirial(node) - ! Compute time scale. - timeScale=+Mpc_per_km_per_s_To_Gyr & - & *radiusScale & - & /velocityScale & - & /sqrt( & - & + concentration & - & ) & - & *sqrt( & - & -2.0d0*atan( concentration ) & - & +2.0d0*log (1.0d0+concentration ) & - & + log (1.0d0+concentration**2) & - & ) - ! Compute dimensionless time. - freefallTimeScaleFree=time/timeScale - ! Ensure table is sufficiently extensive. - call self%freefallTabulate(freefallTimeScaleFree) - ! The freefall time is finite at zero radius in this profile. If the requested time is less than this, return zero radius. - if (freefallTimeScaleFree < freefallTimeScaleFreeMinimum) then - burkertFreefallRadiusIncreaseRate=0.0d0 - else - burkertFreefallRadiusIncreaseRate=self%burkertFreefallInverse%interpolateGradient(freefallTimeScaleFree)*radiusScale/timeScale - end if - return - end function burkertFreefallRadiusIncreaseRate - - subroutine burkertFreefallTabulate(self,freefallTimeScaleFree) - !!{ - Tabulates the freefall time vs. freefall radius for Burkert halos. - !!} - implicit none - class (darkMatterProfileDMOBurkert), intent(inout) :: self - double precision , intent(in ) :: freefallTimeScaleFree - logical :: retabulate - integer :: iRadius - double precision :: freefallTime - - retabulate=.not.self%burkertFreefallTableInitialized - ! If the table has not yet been made, compute and store the freefall corresponding to the minimum and maximum - ! radii that will be tabulated by default. - if (retabulate) then - self%freefallTimeMinimum=self%freefallTimeScaleFree(self%freefallRadiusMinimum) - self%freefallTimeMaximum=self%freefallTimeScaleFree(self%freefallRadiusMaximum) - end if - do while (freefallTimeScaleFree > self%freefallTimeMaximum) - self%freefallRadiusMaximum=2.0d0*self%freefallRadiusMaximum - self%freefallTimeMaximum=self%freefallTimeScaleFree(self%freefallRadiusMaximum) - retabulate=.true. - end do - if (retabulate) then - ! Decide how many points to tabulate and allocate table arrays. - self%burkertFreefallTableNumberPoints=int(log10(self%freefallRadiusMaximum/self%freefallRadiusMinimum)*dble(freefallTablePointsPerDecade))+1 - ! Create the table. - call self%burkertFreefall%destroy( ) - call self%burkertFreefall%create (self%freefallRadiusMinimum,self%freefallRadiusMaximum,self%burkertFreefallTableNumberPoints) - ! Loop over radii and populate tables. - do iRadius=1,self%burkertFreefallTableNumberPoints - freefallTime=self%freefallTimeScaleFree(self%burkertFreefall%x(iRadius)) - if (iRadius > 1) freefallTime=max(freefallTime,self%burkertFreefall%y(iRadius-1)) - call self%burkertFreefall%populate(freefallTime,iRadius) - end do - call self%burkertFreefall%reverse(self%burkertFreefallInverse) - ! Specify that tabulation has been made. - self%burkertFreefallTableInitialized=.true. - end if - return - end subroutine burkertFreefallTabulate - - double precision function burkertFreefallTimeScaleFree(self,radius) - !!{ - Compute the freefall time in a scale-free Burkert halo. - !!} - use :: Numerical_Integration, only : integrator - implicit none - class (darkMatterProfileDMOBurkert), intent(inout) :: self - double precision , intent(in ) :: radius - type (integrator ) :: integrator_ - double precision :: radiusEnd , radiusStart - !$GLC attributes unused :: self - - radiusStart =radius - radiusEnd =0.0d0 - integrator_ =integrator (burkertFreefallTimeScaleFreeIntegrand,toleranceRelative=1.0d-5) - burkertFreefallTimeScaleFree=integrator_%integrate(radiusEnd ,radiusStart ) - return - - contains - - double precision function burkertFreefallTimeScaleFreeIntegrand(radius) - !!{ - Integrand function used for finding the free-fall time in Burkert halos. - !!} - use :: Numerical_Constants_Math, only : Pi - implicit none - double precision, intent(in ) :: radius - double precision, parameter :: radiusSmall =1.0d-2 - double precision :: potential , potentialStart, & - & potentialDifference - - if (radius < radiusSmall) then - potential =+Pi & - & -2.0d0*radius **2/3.0d0 & - & + radius **3/3.0d0 - else - potential =-( & - & -Pi * radius & - & +2.0d0*(1.0d0+radius )*atan( radius ) & - & -2.0d0*(1.0d0+radius )*log (1.0d0+radius ) & - & - (1.0d0-radius )*log (1.0d0+radius **2) & - & ) & - & / radius - end if - if (radiusStart < radiusSmall) then - potentialStart=+Pi & - & -2.0d0*radiusStart**2/3.0d0 & - & + radiusStart**3/3.0d0 - else - potentialStart=-( & - & -Pi * radiusStart & - & +2.0d0*(1.0d0+radiusStart)*atan( radiusStart ) & - & -2.0d0*(1.0d0+radiusStart)*log (1.0d0+radiusStart ) & - & - (1.0d0-radiusStart)*log (1.0d0+radiusStart**2) & - & ) & - & / radiusStart - end if - potentialDifference=+potential-potentialStart - if (potentialDifference > 0.0d0) then - burkertFreefallTimeScaleFreeIntegrand=1.0d0/sqrt(2.0d0*potentialDifference) - else - burkertFreefallTimeScaleFreeIntegrand=0.0d0 - end if - return - end function burkertFreefallTimeScaleFreeIntegrand - - end function burkertFreefallTimeScaleFree - - double precision function burkertRadialMoment(self,node,moment,radiusMinimum,radiusMaximum) - !!{ - Returns the density (in $M_\odot$ Mpc$^{-3}$) in the dark matter profile of {\normalfont \ttfamily node} at the given {\normalfont \ttfamily radius} (given - in units of Mpc). - !!} - use :: Galacticus_Nodes, only : nodeComponentBasic, nodeComponentDarkMatterProfile, treeNode - implicit none - class (darkMatterProfileDMOBurkert ), intent(inout) :: self - type (treeNode ), intent(inout) :: node - double precision , intent(in ) :: moment - double precision , intent(in ), optional :: radiusMinimum , radiusMaximum - class (nodeComponentBasic ) , pointer :: basic - class (nodeComponentDarkMatterProfile) , pointer :: darkMatterProfile - double precision :: radiusMinimumActual, radiusMaximumActual, & - & radiusScale , concentration - - basic => node %basic ( ) - darkMatterProfile => node %darkMatterProfile(autoCreate=.true.) - radiusScale = darkMatterProfile %scale ( ) - concentration = +self %darkMatterHaloScale_%radiusVirial ( node ) & - & /radiusScale - radiusMinimumActual=0.0d0 - radiusMaximumActual=concentration - if (present(radiusMinimum)) radiusMinimumActual=radiusMinimum/radiusScale - if (present(radiusMaximum)) radiusMaximumActual=radiusMaximum/radiusScale - burkertRadialMoment=+basic%mass() & - & *radiusScale**(moment-2.0d0) & - & *( & - & +radialMoment(radiusMaximumActual) & - & -radialMoment(radiusMinimumActual) & - & ) - return - - contains - - double precision function radialMoment(radius) - !!{ - Evaluate the radial moment in the Burkert profile. - !!} - use :: Hypergeometric_Functions, only : Hypergeometric_2F1 - use :: Numerical_Comparison , only : Values_Agree - use :: Numerical_Constants_Math, only : Pi - implicit none - double precision, intent(in ) :: radius - - if (Values_Agree(moment,1.0d0,absTol=1.0d-6)) then - radialMoment=+( & - & -2.0d0* log(1.0d0+radius ) & - & + log(1.0d0+radius **2) & - & +2.0d0*atan( radius ) & - & ) & - & /4.0d0 & - & /Pi & - & /( & - & +2.0d0* log(1.0d0+concentration ) & - & + log(1.0d0+concentration**2) & - & -2.0d0*atan( concentration ) & - & ) - else if (Values_Agree(moment,2.0d0,absTol=1.0d-6)) then - radialMoment=+( & - & +2.0d0* log(1.0d0+radius ) & - & + log(1.0d0+radius **2) & - & -2.0d0*atan( radius ) & - & ) & - & /4.0d0 & - & /Pi & - & /( & - & +2.0d0* log(1.0d0+concentration ) & - & + log(1.0d0+concentration**2) & - & -2.0d0*atan( concentration ) & - & ) - else if (Values_Agree(moment,3.0d0,absTol=1.0d-6)) then - radialMoment=+( & - & +4.0d0* radius & - & -2.0d0* log(1.0d0+radius ) & - & - log(1.0d0+radius **2) & - & -2.0d0*atan( radius ) & - & ) & - & /4.0d0 & - & /Pi & - & /( & - & +2.0d0* log(1.0d0+concentration ) & - & + log(1.0d0+concentration**2) & - & -2.0d0*atan( concentration ) & - & ) - else - radialMoment=+ radius** (1.0d0+moment) & - & *( & - & - radius & - & *Hypergeometric_2F1([1.0d0,0.5d0*(2.0d0+moment)],[0.5d0*(4.0d0+moment)],-radius**2)/(2.0d0+moment) & - & +Hypergeometric_2F1([1.0d0,0.5d0*(1.0d0+moment)],[0.5d0*(3.0d0+moment)],-radius**2)/(1.0d0+moment) & - & +Hypergeometric_2F1([1.0d0, (1.0d0+moment)],[ (2.0d0+moment)],-radius )/(1.0d0+moment) & - & ) & - & /2.0d0 & - & /Pi & - & /( & - & +2.0d0* log(1.0d0+concentration ) & - & + log(1.0d0+concentration**2) & - & -2.0d0*atan( concentration ) & - & ) - end if - return - end function radialMoment - - end function burkertRadialMoment - - double precision function burkertRadiusEnclosingDensity(self,node,density) - !!{ - Null implementation of function to compute the radius enclosing a given density for Burkert dark matter halo profiles. - !!} - use :: Galacticus_Nodes, only : nodeComponentBasic, nodeComponentDarkMatterProfile, treeNode - implicit none - class (darkMatterProfileDMOBurkert ), intent(inout), target :: self - type (treeNode ), intent(inout), target :: node - double precision , intent(in ) :: density - class (nodeComponentDarkMatterProfile), pointer :: darkMatterProfile - class (nodeComponentBasic ), pointer :: basic - double precision :: densityScaleFree , radiusScale, & - & concentration - - basic => node %basic ( ) - darkMatterProfile => node %darkMatterProfile (autoCreate=.true.) - radiusScale = darkMatterProfile %scale ( ) - concentration = +self %darkMatterHaloScale_%radiusVirial ( node ) & - & /radiusScale - densityScaleFree = +density & - & *radiusScale **3 & - & /basic %mass ( ) & - & /self %enclosedMassScaleFree(1.0d0,concentration) - call self%radiusEnclosingDensityTabulate(densityScaleFree) - burkertRadiusEnclosingDensity=+self%burkertDensityTableInverse%interpolate(-densityScaleFree) & - & *radiusScale - return - end function burkertRadiusEnclosingDensity - - subroutine burkertRadiusEnclosingDensityTabulate(self,densityScaleFree) - !!{ - Tabulates the radius vs. enclosed density for Burkert halos. - !!} - use :: Numerical_Constants_Math, only : Pi - implicit none - class (darkMatterProfileDMOBurkert), intent(inout) :: self - double precision , intent(in ) :: densityScaleFree - logical :: retabulate - integer :: iRadius - - retabulate=.not.self%burkertDensityTableInitialized - ! If the table has not yet been made, compute and store the enclosed density corresponding to the minimum and maximum radii - ! that will be tabulated by default. - if (retabulate) then - self%densityMaximum=3.0d0*self%enclosedMassScaleFree(self%densityRadiusMinimum,1.0d0)/self%densityRadiusMinimum**3/4.0d0/Pi - self%densityMinimum=3.0d0*self%enclosedMassScaleFree(self%densityRadiusMaximum,1.0d0)/self%densityRadiusMaximum**3/4.0d0/Pi - end if - do while (densityScaleFree < self%densityMinimum) - self%densityRadiusMaximum=2.0d0*self%densityRadiusMaximum - self%densityMinimum =3.0d0*self%enclosedMassScaleFree(self%densityRadiusMaximum,1.0d0)/self%densityRadiusMaximum**3/4.0d0/Pi - retabulate =.true. - end do - do while (densityScaleFree > self%densityMaximum) - self%densityRadiusMinimum=0.5d0*self%densityRadiusMinimum - self%densityMaximum =3.0d0*self%enclosedMassScaleFree(self%densityRadiusMinimum,1.0d0)/self%densityRadiusMinimum**3/4.0d0/Pi - retabulate =.true. - end do - if (retabulate) then - ! Decide how many points to tabulate and allocate table arrays. - self%burkertDensityTableNumberPoints=int(log10(self%densityRadiusMaximum/self%densityRadiusMinimum)*dble(densityTablePointsPerDecade))+1 - ! Create the table. - call self%burkertDensityTable%destroy( ) - call self%burkertDensityTable%create (self%densityRadiusMinimum,self%densityRadiusMaximum,self%burkertDensityTableNumberPoints) - ! Loop over radii and populate tables. - do iRadius=1,self%burkertDensityTableNumberPoints - call self%burkertDensityTable%populate( & - & -3.0d0 & - & /4.0d0 & - & /Pi & - & *self%enclosedMassScaleFree(self%burkertDensityTable%x(iRadius),1.0d0) & - & / self%burkertDensityTable%x(iRadius) **3, & - & iRadius & - & ) - end do - call self%burkertDensityTable%reverse(self%burkertDensityTableInverse) - ! Specify that tabulation has been made. - self%burkertDensityTableInitialized=.true. - end if - return - end subroutine burkertRadiusEnclosingDensityTabulate - - double precision function burkertRadialVelocityDispersionScaleFree(self,radius) - !!{ - Compute the radial velocity dispersion in a scale-free Burkert halo. - !!} - use :: Numerical_Integration, only : integrator - implicit none - class (darkMatterProfileDMOBurkert), intent(inout) :: self - double precision , intent(in ) :: radius - double precision , parameter :: radiusTiny =1.0d-9, radiusLarge =5.0d3 - double precision :: radiusMinimum , radiusMaximum - type (integrator ) :: integrator_ - !$GLC attributes unused :: self - - radiusMinimum =max( radius,radiusTiny ) - radiusMaximum =max(10.0d0*radius,radiusLarge) - integrator_ =integrator(burkertJeansEquationIntegrand,toleranceRelative=1.0d-6) - burkertRadialVelocityDispersionScaleFree=sqrt( & - & +integrator_%integrate(radiusMinimum,radiusMaximum) & - & *(1.0d0+radius ) & - & *(1.0d0+radius**2) & - & ) - return - - contains - - double precision function burkertJeansEquationIntegrand(radius) - !!{ - Integrand for Burkert drak matter profile Jeans equation. - !!} - implicit none - double precision, intent(in ) :: radius - - if (radius > 0.0d0) then - burkertJeansEquationIntegrand=+( & - & +2.0d0*log (1.0d0+radius ) & - & + log (1.0d0+radius**2) & - & -2.0d0*atan( radius ) & - & ) & - & /(1.0d0+radius ) & - & /(1.0d0+radius**2) & - & / radius**2 - else - burkertJeansEquationIntegrand=0.0d0 - end if - return - end function burkertJeansEquationIntegrand - - end function burkertRadialVelocityDispersionScaleFree - - subroutine burkertRadialVelocityDispersionTabulate(self,radius) - !!{ - Tabulates the radial velocity dispersion vs. radius for Burkert halos. - !!} - implicit none - class (darkMatterProfileDMOBurkert), intent(inout) :: self - double precision , intent(in ), optional :: radius - logical :: retabulate - integer :: iRadius + class (massDistributionClass ), pointer :: massDistribution_ + type (kinematicsDistributionBurkert ), pointer :: kinematicsDistribution_ + class (darkMatterProfileDMOBurkert ), intent(inout) :: self + type (treeNode ), intent(inout) :: node + type (enumerationWeightByType ), intent(in ), optional :: weightBy + integer , intent(in ), optional :: weightIndex + class (nodeComponentBasic ), pointer :: basic + class (nodeComponentDarkMatterProfile), pointer :: darkMatterProfile + !![ + + !!] - retabulate=.not.self%burkertRadialVelocityDispersionTableInitialized - if (present(radius)) then - do while (radius < self%radialVelocityDispersionRadiusMinimum) - self%radialVelocityDispersionRadiusMinimum=0.5d0*self%radialVelocityDispersionRadiusMinimum - retabulate=.true. - end do - do while (radius > self%radialVelocityDispersionRadiusMaximum) - self%radialVelocityDispersionRadiusMaximum=2.0d0*self%radialVelocityDispersionRadiusMaximum - retabulate=.true. - end do - end if - if (retabulate) then - ! Decide how many points to tabulate and allocate table arrays. - self%burkertRadialVelocityDispersionTableNumberPoints=int(log10(self%radialVelocityDispersionRadiusMaximum/self%radialVelocityDispersionRadiusMinimum) & - & *dble(radialVelocityDispersionTablePointsPerDecade ) & - & )+1 - ! Create the table. - call self%burkertRadialVelocityDispersionTable%destroy( ) - call self%burkertRadialVelocityDispersionTable%create (self%radialVelocityDispersionRadiusMinimum ,self%radialVelocityDispersionRadiusMaximum, & - & self%burkertRadialVelocityDispersionTableNumberPoints ) - ! Loop over radii and populate tables. - do iRadius=1,self%burkertRadialVelocityDispersionTableNumberPoints - call self%burkertRadialVelocityDispersionTable%populate(self%radialVelocityDispersionScaleFree(self%burkertRadialVelocityDispersionTable%x(iRadius)),iRadius) - end do - ! Specify that tabulation has been made. - self%burkertRadialVelocityDispersionTableInitialized=.true. - end if + ! Assume a null distribution by default. + massDistribution_ => null() + ! If weighting is not by mass, return a null profile. + if (weightBy_ /= weightByMass) return + ! Create the mass distribution. + allocate(massDistributionBurkert :: massDistribution_) + select type(massDistribution_) + type is (massDistributionBurkert) + basic => node%basic () + darkMatterProfile => node%darkMatterProfile() + !![ + + + massDistributionBurkert( & + & mass =basic %mass ( ), & + & radiusOuter =self %darkMatterHaloScale_%radiusVirial (node), & + & scaleLength =darkMatterProfile%scale ( ), & + & componentType= componentTypeDarkHalo , & + & massType = massTypeDark & + & ) + + + !!] + end select + allocate(kinematicsDistribution_) + !![ + + + kinematicsDistributionBurkert( & + & ) + + + !!] + call massDistribution_%setKinematicsDistribution(kinematicsDistribution_) + !![ + + !!] return - end subroutine burkertRadialVelocityDispersionTabulate + end function burkertGet diff --git a/source/dark_matter_profiles_DMO.Einasto.F90 b/source/dark_matter_profiles_DMO.Einasto.F90 index 4196c60098..8d26fed2de 100644 --- a/source/dark_matter_profiles_DMO.Einasto.F90 +++ b/source/dark_matter_profiles_DMO.Einasto.F90 @@ -21,20 +21,16 @@ An implementation of ``Einasto'' dark matter halo profiles. !!} - use :: Numerical_Interpolation, only : interpolator - use :: Root_Finder , only : rootFinder - + use :: Dark_Matter_Halo_Scales, only : darkMatterHaloScaleClass + !![ - A dark matter profile DMO class which implements the Einasto density profile (e.g. \citealt{cardone_spherical_2005}): - \begin{equation} - \rho_\mathrm{dark matter}(r) = \rho_{-2} \exp \left( - {2 \over \alpha} \left[ \left( {r \over r_{-2}} \right)^\alpha - 1 - \right] \right), - \end{equation} - normalized such that the total mass of the \gls{node} is enclosed with the virial radius and with the characteristic length - $r_{-2} = r_\mathrm{virial}/c$ where $c$ is the halo concentration (see \refPhysics{darkMatterProfileConcentration}). The - shape parameter, $\alpha$, is set using the density profile shape method (see \refPhysics{darkMatterProfileShape}). + A dark matter profile DMO class which builds \refClass{massDistributionEinasto} objects to compute the Einasto density profile + (e.g. \citealt{cardone_spherical_2005}), normalized such that the total mass of the \gls{node} is enclosed with the virial + radius and with the characteristic length $r_{-2} = r_\mathrm{virial}/c$ where $c$ is the halo concentration (see + \refPhysics{darkMatterProfileConcentration}). The shape parameter, $\alpha$, is set using the density profile shape method + (see \refPhysics{darkMatterProfileShape}). !!] @@ -42,112 +38,10 @@ !!{ A dark matter halo profile class implementing ``Einasto'' dark matter halos. !!} - private - ! Tables for specific angular momentum vs. radius table - double precision :: angularMomentumTableRadiusMinimum - double precision :: angularMomentumTableRadiusMaximum - double precision :: angularMomentumTableAlphaMinimum - double precision :: angularMomentumTableAlphaMaximum - logical :: angularMomentumTableInitialized - integer :: angularMomentumTableAlphaCount , angularMomentumTableRadiusCount - double precision , allocatable, dimension(: ) :: angularMomentumTableAlpha , angularMomentumTableRadius - double precision , allocatable, dimension(:,:) :: angularMomentumTable - type (interpolator), allocatable :: angularMomentumTableAlphaInterpolator - type (interpolator), allocatable, dimension(: ) :: angularMomentumTableRadiusInterpolator - ! Tables for freefall time vs. radius table - double precision :: freefallRadiusTableRadiusMinimum - double precision :: freefallRadiusTableRadiusMaximum - double precision :: freefallRadiusTableAlphaMinimum - double precision :: freefallRadiusTableAlphaMaximum - logical :: freefallRadiusTableInitialized - integer :: freefallRadiusTableAlphaCount , freefallRadiusTableRadiusCount - double precision , allocatable, dimension(: ) :: freefallRadiusTableAlpha , freefallRadiusTableRadius - double precision , allocatable, dimension(:,:) :: freefallRadiusTable - double precision :: freefallTimeMaximum , freefallTimeMinimum - type (interpolator), allocatable :: freefallRadiusTableAlphaInterpolator - type (interpolator), allocatable, dimension(: ) :: freefallRadiusTableRadiusInterpolator - ! Tables for radial velocity dispersion vs. radius table - double precision :: radialVelocityDispersionRadiusMinimum - double precision :: radialVelocityDispersionRadiusMaximum - double precision :: radialVelocityDispersionAlphaMinimum - double precision :: radialVelocityDispersionAlphaMaximum - logical :: radialVelocityDispersionTableInitialized - integer :: radialVelocityDispersionTableAlphaCount , radialVelocityDispersionTableRadiusCount - double precision , allocatable, dimension(: ) :: radialVelocityDispersionTableAlpha , radialVelocityDispersionTableRadius - double precision , allocatable, dimension(:,:) :: radialVelocityDispersionTable - type (interpolator), allocatable :: radialVelocityDispersionTableAlphaInterpolator, radialVelocityDispersionTableRadiusInterpolator - ! Tables for energy as a function of concentration and α. - double precision :: energyTableConcentrationMinimum - double precision :: energyTableConcentrationMaximum - double precision :: energyTableAlphaMinimum - double precision :: energyTableAlphaMaximum - logical :: energyTableInitialized - integer :: energyTableAlphaCount , energyTableConcentrationCount - double precision , allocatable, dimension(: ) :: energyTableAlpha , energyTableConcentration - double precision , allocatable, dimension(:,:) :: energyTable - type (interpolator), allocatable :: energyTableAlphaInterpolator , energyTableConcentrationInterpolator - ! Tables for specific Fourier transform of density profile as a function of α and radius. - double precision :: fourierProfileTableConcentrationMinimum - - double precision :: fourierProfileTableConcentrationMaximum - double precision :: fourierProfileTableWavenumberMinimum - double precision :: fourierProfileTableWavenumberMaximum - double precision :: fourierProfileTableAlphaMinimum - double precision :: fourierProfileTableAlphaMaximum - logical :: fourierProfileTableInitialized - integer :: fourierProfileTableAlphaCount , fourierProfileTableConcentrationCount , & - & fourierProfileTableWavenumberCount - double precision , allocatable, dimension(: ) :: fourierProfileTableAlpha , fourierProfileTableConcentration , & - & fourierProfileTableWavenumber - double precision , allocatable, dimension(:,:,:) :: fourierProfileTable - type (interpolator), allocatable :: fourierProfileTableAlphaInterpolator , fourierProfileTableConcentrationInterpolator , & - & fourierProfileTableWavenumberInterpolator - ! Root finders. - type (rootFinder ) :: finderEnclosedDensity , finderVelocityPeak + class(darkMatterHaloScaleClass), pointer :: darkMatterHaloScale_ => null() contains - !![ - - - - - - - - - - - - - - !!] - final :: einastoDestructor - procedure :: density => einastoDensity - procedure :: densityLogSlope => einastoDensityLogSlope - procedure :: radialMoment => einastoRadialMoment - procedure :: enclosedMass => einastoEnclosedMass - procedure :: radiusEnclosingDensity => einastoRadiusEnclosingDensity - procedure :: potential => einastoPotential - procedure :: circularVelocity => einastoCircularVelocity - procedure :: circularVelocityMaximum => einastoCircularVelocityMaximum - procedure :: radiusCircularVelocityMaximum => einastoRadiusCircularVelocityMaximum - procedure :: radialVelocityDispersion => einastoRadialVelocityDispersion - procedure :: radiusFromSpecificAngularMomentum => einastoRadiusFromSpecificAngularMomentum - procedure :: rotationNormalization => einastoRotationNormalization - procedure :: energy => einastoEnergy - procedure :: kSpace => einastoKSpace - procedure :: freefallRadius => einastoFreefallRadius - procedure :: freefallRadiusIncreaseRate => einastoFreefallRadiusIncreaseRate - procedure :: densityScaleFree => einastoDensityScaleFree - procedure :: enclosedMassScaleFree => einastoEnclosedMassScaleFree - procedure :: radialVelocityDispersionScaleFree => einastoRadialVelocityDispersionScaleFree - procedure :: radialVelocityDispersionTabulate => einastoRadialVelocityDispersionTabulate - procedure :: freefallTimeScaleFree => einastoFreefallTimeScaleFree - procedure :: potentialScaleFree => einastoPotentialScaleFree - procedure :: radiusFromSpecificAngularMomentumScaleFree => einastoRadiusFromSpecificAngularMomentumScaleFree - procedure :: radiusFromSpecificAngularMomentumTableMake => einastoRadiusFromSpecificAngularMomentumTableMake - procedure :: freefallTabulate => einastoFreefallTabulate - procedure :: energyTableMake => energyTableMake - procedure :: fourierProfileTableMake => fourierProfileTableMake + final :: einastoDestructor + procedure :: get => einastoGet end type darkMatterProfileDMOEinasto interface darkMatterProfileDMOEinasto @@ -158,25 +52,6 @@ module procedure einastoConstructorInternal end interface darkMatterProfileDMOEinasto - ! Granularity parameters for tabulations. - integer, parameter :: angularMomentumTableRadiusPointsPerDecade =100 - integer, parameter :: angularMomentumTableAlphaPointsPerUnit =100 - integer, parameter :: freefallRadiusTableRadiusPointsPerDecade = 30 - integer, parameter :: freefallRadiusTableAlphaPointsPerUnit = 30 - integer, parameter :: energyTableConcentrationPointsPerDecade =100 - integer, parameter :: energyTableAlphaPointsPerUnit =100 - integer, parameter :: fourierProfileTableConcentrationPointsPerDecade =100 - integer, parameter :: fourierProfileTableWavenumberPointsPerDecade =100 - integer, parameter :: fourierProfileTableAlphaPointsPerUnit =100 - integer, parameter :: radialVelocityDispersionTableRadiusPointsPerDecade=100 - integer, parameter :: radialVelocityDispersionTableAlphaPointsPerUnit =100 - - ! Module-scope variables used in root finding. - class (darkMatterProfileDMOEinasto), pointer :: self_ - type (treeNode ), pointer :: node_ - double precision :: densityEnclosed_, alpha_ - !$omp threadprivate(self_,node_,densityEnclosed_,alpha_) - contains function einastoConstructorParameters(parameters) result(self) @@ -207,61 +82,13 @@ function einastoConstructorInternal(darkMatterHaloScale_) result(self) use :: Array_Utilities , only : operator(.intersection.) use :: Error , only : Component_List , Error_Report use :: Galacticus_Nodes, only : defaultDarkMatterProfileComponent - use :: Root_Finder , only : rangeExpandMultiplicative , rangeExpandSignExpectNegative, rangeExpandSignExpectPositive - implicit none - type (darkMatterProfileDMOEinasto) :: self - class (darkMatterHaloScaleClass ), intent(in ), target :: darkMatterHaloScale_ - double precision , parameter :: toleranceAbsolute =0.0d0, toleranceRelative=1.0d-3 + implicit none + type (darkMatterProfileDMOEinasto) :: self + class(darkMatterHaloScaleClass ), intent(in ), target :: darkMatterHaloScale_ !![ !!] - - ! Initialize table states. - self%angularMomentumTableRadiusMinimum = 1.0d-3 - self%angularMomentumTableRadiusMaximum =20.0d+0 - self%angularMomentumTableAlphaMinimum = 0.1d+0 - self%angularMomentumTableAlphaMaximum = 0.3d+0 - self%angularMomentumTableInitialized =.false. - self%freefallRadiusTableRadiusMinimum = 1.0d-3 - self%freefallRadiusTableRadiusMaximum =20.0d+0 - self%freefallRadiusTableAlphaMinimum = 0.1d+0 - self%freefallRadiusTableAlphaMaximum = 0.3d+0 - self%freefallRadiusTableInitialized =.false. - self%energyTableConcentrationMinimum = 2.0d0 - self%energyTableConcentrationMaximum =20.0d0 - self%energyTableAlphaMinimum = 0.1d0 - self%energyTableAlphaMaximum = 0.3d0 - self%energyTableInitialized =.false. - self%fourierProfileTableConcentrationMinimum = 2.0d0 - self%fourierProfileTableConcentrationMaximum =20.0d0 - self%fourierProfileTableWavenumberMinimum = 1.0d-3 - self%fourierProfileTableWavenumberMaximum = 1.0d+3 - self%fourierProfileTableAlphaMinimum = 0.1d+0 - self%fourierProfileTableAlphaMaximum = 0.3d+0 - self%fourierProfileTableInitialized =.false. - self%radialVelocityDispersionRadiusMinimum = 1.0d-3 - self%radialVelocityDispersionRadiusMaximum =20.0d+0 - self%radialVelocityDispersionAlphaMinimum = 0.1d+0 - self%radialVelocityDispersionAlphaMaximum = 0.3d+0 - self%radialVelocityDispersionTableInitialized=.false. - ! Initialize root finders. - self%finderEnclosedDensity=rootFinder( & - & rootFunction =einastoRadiusEnclosingDensityRoot, & - & toleranceAbsolute =toleranceAbsolute , & - & toleranceRelative =toleranceRelative , & - & rangeExpandUpward =2.0d0 , & - & rangeExpandDownward =0.5d0 , & - & rangeExpandType =rangeExpandMultiplicative , & - & rangeExpandDownwardSignExpect=rangeExpandSignExpectNegative , & - & rangeExpandUpwardSignExpect =rangeExpandSignExpectPositive & - & ) - self%finderVelocityPeak =rootFinder( & - & rootFunction =einastoCircularVelocityPeakRadius, & - & toleranceRelative =toleranceRelative , & - & rangeExpandUpward =2.0d0 , & - & rangeExpandDownward =0.5d0 , & - & rangeExpandType =rangeExpandMultiplicative & - & ) + ! Ensure that the dark matter profile component supports both "scale" and "shape" properties. Since we've been called with ! a treeNode to process, it should have been initialized by now. if ( & @@ -284,8 +111,6 @@ function einastoConstructorInternal(darkMatterHaloScale_) result(self) & {introspection:location} & & ) end if - ! Initialize the tabulations. - call self%radialVelocityDispersionTabulate() return end function einastoConstructorInternal @@ -302,1402 +127,62 @@ subroutine einastoDestructor(self) return end subroutine einastoDestructor - double precision function einastoDensity(self,node,radius) - !!{ - Returns the density (in $M_\odot$ Mpc$^{-3}$) in the dark matter profile of {\normalfont \ttfamily node} at the given {\normalfont \ttfamily radius} (given - in units of Mpc). - !!} - use :: Galacticus_Nodes, only : nodeComponentBasic, nodeComponentDarkMatterProfile, treeNode - implicit none - class (darkMatterProfileDMOEinasto ), intent(inout) :: self - type (treeNode ), intent(inout) :: node - double precision , intent(in ) :: radius - class (nodeComponentBasic ), pointer :: basic - class (nodeComponentDarkMatterProfile), pointer :: darkMatterProfile - double precision :: alpha , radiusOverScaleRadius , & - & scaleRadius , virialRadiusOverScaleRadius - - ! Get components. - basic => node%basic ( ) - darkMatterProfile => node%darkMatterProfile(autoCreate=.true.) - scaleRadius =darkMatterProfile%scale() - alpha =darkMatterProfile%shape() - radiusOverScaleRadius =radius /scaleRadius - virialRadiusOverScaleRadius=self%darkMatterHaloScale_%radiusVirial(node)/scaleRadius - einastoDensity =self%densityScaleFree(radiusOverScaleRadius,virialRadiusOverScaleRadius,alpha) & - & *basic%mass()/scaleRadius**3 - return - end function einastoDensity - - double precision function einastoDensityLogSlope(self,node,radius) - !!{ - Returns the logarithmic slope of the density in the dark matter profile of {\normalfont \ttfamily node} at the given - {\normalfont \ttfamily radius} (given in units of Mpc). - !!} - use :: Galacticus_Nodes, only : nodeComponentBasic, nodeComponentDarkMatterProfile, treeNode - implicit none - class (darkMatterProfileDMOEinasto ), intent(inout) :: self - type (treeNode ), intent(inout) :: node - double precision , intent(in ) :: radius - class (nodeComponentBasic ), pointer :: basic - class (nodeComponentDarkMatterProfile), pointer :: darkMatterProfile - double precision :: alpha , radiusOverScaleRadius , & - & scaleRadius - !$GLC attributes unused :: self - - ! Get components. - basic => node%basic ( ) - darkMatterProfile => node%darkMatterProfile(autoCreate=.true.) - scaleRadius = darkMatterProfile%scale() - alpha = darkMatterProfile%shape() - radiusOverScaleRadius = radius/scaleRadius - einastoDensityLogSlope = -2.0d0 & - & *radiusOverScaleRadius**alpha - return - end function einastoDensityLogSlope - - double precision function einastoRadialMoment(self,node,moment,radiusMinimum,radiusMaximum) + function einastoGet(self,node,weightBy,weightIndex) result(massDistribution_) !!{ - Returns the density (in $M_\odot$ Mpc$^{-3}$) in the dark matter profile of {\normalfont \ttfamily node} at the given {\normalfont \ttfamily radius} (given - in units of Mpc). + Return the dark matter mass distribution for the given {\normalfont \ttfamily node}. !!} - use :: Galacticus_Nodes , only : nodeComponentBasic, nodeComponentDarkMatterProfile , treeNode - use :: Gamma_Functions , only : Gamma_Function , Gamma_Function_Incomplete_Complementary - use :: Numerical_Constants_Math, only : Pi + use :: Galacticus_Nodes , only : nodeComponentBasic , nodeComponentDarkMatterProfile + use :: Galactic_Structure_Options, only : componentTypeDarkHalo , massTypeDark , weightByMass + use :: Mass_Distributions , only : massDistributionEinasto, kinematicsDistributionCollisionless implicit none - class (darkMatterProfileDMOEinasto ), intent(inout) :: self - type (treeNode ), intent(inout) :: node - double precision , intent(in ) :: moment - double precision , intent(in ), optional :: radiusMinimum , radiusMaximum - class (nodeComponentBasic ) , pointer :: basic - class (nodeComponentDarkMatterProfile) , pointer :: darkMatterProfile - double precision :: scaleRadius , virialRadiusOverScaleRadius, & - & radiusMinimumActual, radiusMaximumActual , & - & alpha , densityNormalization - - radiusMinimumActual=0.0d0 - radiusMaximumActual=self%darkMatterHaloScale_%radiusVirial(node) - if (present(radiusMinimum)) radiusMinimumActual=radiusMinimum - if (present(radiusMaximum)) radiusMaximumActual=radiusMaximum - ! Get components. - basic => node%basic ( ) - darkMatterProfile => node%darkMatterProfile(autoCreate=.true.) - scaleRadius =darkMatterProfile%scale() - alpha =darkMatterProfile%shape() - virialRadiusOverScaleRadius=self%darkMatterHaloScale_%radiusVirial(node)/scaleRadius - densityNormalization= (alpha/4.0d0/Pi) & - & * ((2.0d0/alpha) **(3.0d0/alpha) ) & - & *exp(-2.0d0/alpha ) & - & /Gamma_Function (3.0d0/alpha ) & - & /Gamma_Function_Incomplete_Complementary(3.0d0/alpha,2.0d0*virialRadiusOverScaleRadius**alpha/alpha) - einastoRadialMoment=+densityNormalization & - & *basic%mass() & - & *scaleRadius**(moment-2.0d0) & - & *( & - & +einastoRadialMomentScaleFree(radiusMaximumActual/scaleRadius) & - & -einastoRadialMomentScaleFree(radiusMinimumActual/scaleRadius) & - & ) - return - - contains - - double precision function einastoRadialMomentScaleFree(radius) - !!{ - Provides the scale-free part of the radial moment of the Einasto density profile. - !!} - use :: Gamma_Functions, only : Gamma_Function_Incomplete - implicit none - double precision, intent(in ) :: radius - - einastoRadialMomentScaleFree=-exp(2.0d0/alpha) & - & *0.5d0 **((1.0d0+moment)/alpha) & - & *alpha **((1.0d0+moment)/alpha) & - & *Gamma_Function ((1.0d0+moment)/alpha ) & - & *Gamma_Function_Incomplete((1.0d0+moment)/alpha,2.0d0*radius**alpha/alpha) & - & /alpha - return - end function einastoRadialMomentScaleFree - - end function einastoRadialMoment - - double precision function einastoEnclosedMass(self,node,radius) - !!{ - Returns the enclosed mass (in $M_\odot$) in the dark matter profile of {\normalfont \ttfamily node} at the given {\normalfont \ttfamily radius} (given in - units of Mpc). - !!} - use :: Galacticus_Nodes, only : nodeComponentBasic, nodeComponentDarkMatterProfile, treeNode - implicit none - class (darkMatterProfileDMOEinasto ), intent(inout) :: self - type (treeNode ), intent(inout) :: node - double precision , intent(in ) :: radius - class (nodeComponentBasic ), pointer :: basic - class (nodeComponentDarkMatterProfile), pointer :: darkMatterProfile - double precision :: alpha , radiusOverScaleRadius , & - & scaleRadius , virialRadiusOverScaleRadius - - ! Get components. - basic => node%basic ( ) - darkMatterProfile => node%darkMatterProfile(autoCreate=.true.) - scaleRadius =darkMatterProfile%scale() - alpha =darkMatterProfile%shape() - radiusOverScaleRadius =radius /scaleRadius - virialRadiusOverScaleRadius=self%darkMatterHaloScale_%radiusVirial(node)/scaleRadius - einastoEnclosedMass =self%enclosedMassScaleFree(radiusOverScaleRadius,virialRadiusOverScaleRadius,alpha) & - & *basic%mass() - return - end function einastoEnclosedMass - - double precision function einastoCircularVelocity(self,node,radius) - !!{ - Returns the circular velocity (in km/s) in the dark matter profile of {\normalfont \ttfamily node} at the given {\normalfont \ttfamily radius} (given in - units of Mpc). - !!} - use :: Numerical_Constants_Astronomical, only : gravitationalConstantGalacticus - implicit none - class (darkMatterProfileDMOEinasto), intent(inout) :: self - type (treeNode ), intent(inout) :: node - double precision , intent(in ) :: radius - - if (radius > 0.0d0) then - einastoCircularVelocity=sqrt(gravitationalConstantGalacticus*self%enclosedMass(node,radius)/radius) - else - einastoCircularVelocity=0.0d0 - end if - return - end function einastoCircularVelocity - - double precision function einastoCircularVelocityMaximum(self,node) - !!{ - Returns the maximum circular velocity (in km/s) in the dark matter profile of {\normalfont \ttfamily node}. - !!} - implicit none - class(darkMatterProfileDMOEinasto), intent(inout) :: self - type (treeNode ), intent(inout) :: node - - ! Find the peak velocity. - einastoCircularVelocityMaximum=self%circularVelocity(node,self%radiusCircularVelocityMaximum(node)) - return - end function einastoCircularVelocityMaximum - - double precision function einastoRadiusCircularVelocityMaximum(self,node) - !!{ - Returns the radius (in Mpc) at which the maximum circular velocity is achieved in the dark matter profile of {\normalfont \ttfamily node}. - !!} - use :: Galacticus_Nodes, only : nodeComponentBasic, nodeComponentDarkMatterProfile, treeNode - implicit none - class (darkMatterProfileDMOEinasto ), intent(inout) :: self - type (treeNode ), intent(inout) :: node - class (nodeComponentDarkMatterProfile), pointer :: darkMatterProfile - double precision :: alpha , radiusScale, & - & radiusPeak - - ! Get the shape parameter for this halo. - darkMatterProfile => node %darkMatterProfile(autoCreate=.true.) - alpha = darkMatterProfile%shape ( ) - radiusScale = darkMatterProfile%scale ( ) - ! Solve for the radius (in units of the scale radius) at which the rotation curve peaks. - alpha_ = alpha - radiusPeak = self %finderVelocityPeak%find(rootGuess=radiusScale) - ! Convert to a physical radius. - einastoRadiusCircularVelocityMaximum=radiusPeak*radiusScale - return - end function einastoRadiusCircularVelocityMaximum - - double precision function einastoCircularVelocityPeakRadius(radius) - !!{ - Computes the derivative of the square of circular velocity for an Einasto density profile. - !!} - use :: Gamma_Functions, only : Gamma_Function, Gamma_Function_Incomplete_Complementary - implicit none - double precision, intent(in ) :: radius - - einastoCircularVelocityPeakRadius=+ 2.0d0 **( +3.0d0/alpha_) & - & * radius **(-2.0d0+ alpha_) & - & *( radius**alpha_/alpha_)**(-1.0d0+3.0d0/alpha_) & - & * exp( & - & -( & - & +2.0d0 & - & *radius**alpha_ & - & ) & - & /alpha_ & - & ) & - & /Gamma_Function ( & - & 3.0d0 /alpha_ & - & ) & - & -Gamma_Function_Incomplete_Complementary( & - & 3.0d0 /alpha_, & - & 2.0d0*radius**alpha_/alpha_ & - & ) & - & / radius ** 2 - return - end function einastoCircularVelocityPeakRadius - - double precision function einastoRadialVelocityDispersion(self,node,radius) - !!{ - Returns the radial velocity dispersion (in km/s) in the dark matter profile of {\normalfont \ttfamily node} at the given - {\normalfont \ttfamily radius} (given in units of Mpc). - !!} - use :: Galacticus_Nodes, only : nodeComponentDarkMatterProfile , treeNode - use :: Gamma_Functions , only : Gamma_Function_Incomplete_Complementary - use, intrinsic :: ISO_C_Binding , only : c_size_t - implicit none - class (darkMatterProfileDMOEinasto ), intent(inout) :: self - type (treeNode ), intent(inout) :: node - double precision , intent(in ) :: radius - class (nodeComponentDarkMatterProfile), pointer :: darkMatterProfile - integer (c_size_t ), dimension(0:1) :: jAlpha - double precision , dimension(0:1) :: hAlpha - integer :: iAlpha - double precision :: alpha , scaleRadius , & - & radiusOverScaleRadius, virialRadiusOverScaleRadius - - darkMatterProfile => node%darkMatterProfile(autoCreate=.true.) - ! Get the scale radius. - scaleRadius = darkMatterProfile%scale() - radiusOverScaleRadius = radius /scaleRadius - virialRadiusOverScaleRadius = self%darkMatterHaloScale_%radiusVirial(node)/scaleRadius - ! Get the shape parameter. - alpha = darkMatterProfile%shape() - if (radius > 0.0d0) then - ! Ensure table is sufficiently extensive. - call self%radialVelocityDispersionTabulate(radiusOverScaleRadius,alpha) - ! Interpolate to get the radial velocity dispersion. - ! Get interpolating factors in α. - call self%radialVelocityDispersionTableAlphaInterpolator%linearFactors(alpha,jAlpha(0),hAlpha) - jAlpha(1)=jAlpha(0)+1 - einastoRadialVelocityDispersion=0.0d0 - do iAlpha=0,1 - einastoRadialVelocityDispersion=+einastoRadialVelocityDispersion & - & +self%radialVelocityDispersionTableRadiusInterpolator%interpolate(radiusOverScaleRadius,self%radialVelocityDispersionTable(:,jAlpha(iAlpha))) & - & * hAlpha(iAlpha) - end do - einastoRadialVelocityDispersion=+einastoRadialVelocityDispersion & - & *self%darkMatterHaloScale_%velocityVirial(node) & - & *sqrt( & - & +virialRadiusOverScaleRadius & - & /Gamma_Function_Incomplete_Complementary(3.0d0/alpha,2.0d0*virialRadiusOverScaleRadius**alpha/alpha) & - & ) - else - einastoRadialVelocityDispersion=0.0d0 - end if - return - end function einastoRadialVelocityDispersion - - double precision function einastoPotential(self,node,radius,status) - !!{ - Returns the potential (in (km/s)$^2$) in the dark matter profile of {\normalfont \ttfamily node} at the given {\normalfont \ttfamily radius} (given in - units of Mpc). - !!} - use :: Galactic_Structure_Options , only : enumerationStructureErrorCodeType, structureErrorCodeSuccess - use :: Galacticus_Nodes , only : nodeComponentBasic , nodeComponentDarkMatterProfile - use :: Numerical_Constants_Astronomical, only : gravitationalConstantGalacticus - implicit none - class (darkMatterProfileDMOEinasto ), intent(inout) :: self - type (treeNode ), intent(inout), target :: node - double precision , intent(in ) :: radius - type (enumerationStructureErrorCodeType), intent( out), optional :: status - class (nodeComponentBasic ) , pointer :: basic - class (nodeComponentDarkMatterProfile ) , pointer :: darkMatterProfile - double precision :: alpha , radiusOverScaleRadius , & - & scaleRadius , virialRadiusOverScaleRadius - - ! Assume success. - if (present(status)) status=structureErrorCodeSuccess - ! Get components. - basic => node%basic ( ) - darkMatterProfile => node%darkMatterProfile(autoCreate=.true.) - scaleRadius =darkMatterProfile%scale() - alpha =darkMatterProfile%shape() - radiusOverScaleRadius =radius /scaleRadius - virialRadiusOverScaleRadius=self%darkMatterHaloScale_%radiusVirial(node)/scaleRadius - einastoPotential=+self%potentialScaleFree(radiusOverScaleRadius,virialRadiusOverScaleRadius,alpha) & - & *gravitationalConstantGalacticus & - & *basic%mass() & - & /scaleRadius - return - end function einastoPotential - - double precision function einastoRadiusFromSpecificAngularMomentum(self,node,specificAngularMomentum) - !!{ - Returns the radius (in Mpc) in {\normalfont \ttfamily node} at which a circular orbit has the given {\normalfont \ttfamily specificAngularMomentum} (given - in units of km s$^{-1}$ Mpc). - !!} - use :: Galacticus_Nodes , only : nodeComponentDarkMatterProfile , treeNode - use :: Numerical_Constants_Astronomical, only : gravitationalConstantGalacticus - implicit none - class (darkMatterProfileDMOEinasto ), intent(inout) :: self - type (treeNode ), intent(inout) :: node - double precision , intent(in ) :: specificAngularMomentum - class (nodeComponentDarkMatterProfile), pointer :: darkMatterProfile - double precision :: alpha , scaleRadius, & - & specificAngularMomentumScaleFree - - ! Get components. - darkMatterProfile => node%darkMatterProfile(autoCreate=.true.) - ! Get the scale radius of the halo. - scaleRadius=darkMatterProfile%scale() - ! Get the shape parameter of the halo. - alpha =darkMatterProfile%shape() - ! Compute the specific angular momentum in scale free units. - specificAngularMomentumScaleFree=specificAngularMomentum & - & /sqrt( & - & +gravitationalConstantGalacticus & - & *scaleRadius & - & *self%enclosedMass(node,scaleRadius) & - & ) - ! Compute the corresponding radius. - einastoRadiusFromSpecificAngularMomentum=scaleRadius & - & *self%radiusFromSpecificAngularMomentumScaleFree(alpha,specificAngularMomentumScaleFree) - return - end function einastoRadiusFromSpecificAngularMomentum - - double precision function einastoRadiusFromSpecificAngularMomentumScaleFree(self,alpha,specificAngularMomentumScaleFree) - !!{ - Compute the radius at which a circular orbit has the given {\normalfont \ttfamily specificAngularMomentumScaleFree} in a scale free Einasto - profile. - !!} - use, intrinsic :: ISO_C_Binding, only : c_size_t - implicit none - class (darkMatterProfileDMOEinasto), intent(inout) :: self - double precision , intent(in ) :: alpha , specificAngularMomentumScaleFree - integer (c_size_t ), dimension(0:1) :: jAlpha - double precision , dimension(0:1) :: hAlpha - integer :: iAlpha - - ! Return immediately for zero angular momentum. - if (specificAngularMomentumScaleFree <= 0.0d0) then - einastoRadiusFromSpecificAngularMomentumScaleFree=0.0d0 - return - end if - - ! Ensure the table exists and is sufficiently tabulated. - call self%radiusFromSpecificAngularMomentumTableMake(alpha,specificAngularMomentumScaleFree) - - ! Get interpolating factors in α. - call self%angularMomentumTableAlphaInterpolator%linearFactors(alpha,jAlpha(0),hAlpha) - jAlpha(1)=jAlpha(0)+1 - - ! Interpolate in specific angular momentum to get radius. - einastoRadiusFromSpecificAngularMomentumScaleFree=0.0d0 - do iAlpha=0,1 - einastoRadiusFromSpecificAngularMomentumScaleFree= & - & einastoRadiusFromSpecificAngularMomentumScaleFree & - & +self%angularMomentumTableRadiusInterpolator(jAlpha(iAlpha))%interpolate(specificAngularMomentumScaleFree) & - & * hAlpha(iAlpha) - end do - return - end function einastoRadiusFromSpecificAngularMomentumScaleFree - - subroutine einastoRadiusFromSpecificAngularMomentumTableMake(self,alphaRequired,specificAngularMomentumRequired) - !!{ - Create a tabulation of the relation between specific angular momentum and radius in an Einasto profile. - !!} - use :: Gamma_Functions , only : Gamma_Function_Incomplete_Complementary - use :: Numerical_Ranges , only : Make_Range , rangeTypeLinear, rangeTypeLogarithmic - implicit none - class (darkMatterProfileDMOEinasto), intent(inout) :: self - double precision , intent(in ) :: alphaRequired, specificAngularMomentumRequired - integer :: iAlpha , iRadius - logical :: makeTable - double precision :: alpha , enclosedMass , & - & radius - - ! Always check if we need to make the table. - makeTable=.true. - do while (makeTable) - ! Assume table does not need remaking. - makeTable=.false. - ! Check for uninitialized table. - if (.not.self%angularMomentumTableInitialized) then - makeTable=.true. - ! Check for α out of range. - else if (alphaRequired < self%angularMomentumTableAlpha(1) .or. alphaRequired >& - & self%angularMomentumTableAlpha(self%angularMomentumTableAlphaCount)) then - makeTable=.true. - ! Compute the range of tabulation and number of points to use. - self%angularMomentumTableAlphaMinimum=min(self%angularMomentumTableAlphaMinimum,0.9d0*alphaRequired) - self%angularMomentumTableAlphaMaximum=max(self%angularMomentumTableAlphaMaximum,1.1d0*alphaRequired) - ! Check for angular momentum below minimum tabulated value. - else if (any(specificAngularMomentumRequired < self%angularMomentumTable(1,:))) then - makeTable=.true. - self%angularMomentumTableRadiusMinimum=0.5d0*self%angularMomentumTableRadiusMinimum - ! Check for angular momentum above maximum tabulated value. - else if (any(specificAngularMomentumRequired > self%angularMomentumTable(self%angularMomentumTableRadiusCount,:))) then - makeTable=.true. - self%angularMomentumTableRadiusMaximum=2.0d0*self%angularMomentumTableRadiusMaximum - end if - ! Remake the table if necessary. - if (makeTable) then - ! Allocate arrays to the appropriate sizes. - self%angularMomentumTableAlphaCount =int( (self%angularMomentumTableAlphaMaximum -self%angularMomentumTableAlphaMinimum ) & - & *dble(angularMomentumTableAlphaPointsPerUnit ) & - & )+1 - self%angularMomentumTableRadiusCount=int(log10(self%angularMomentumTableRadiusMaximum/self%angularMomentumTableRadiusMinimum) & - & *dble(angularMomentumTableRadiusPointsPerDecade) & - & )+1 - if (allocated(self%angularMomentumTableAlpha )) deallocate(self%angularMomentumTableAlpha ) - if (allocated(self%angularMomentumTableRadius)) deallocate(self%angularMomentumTableRadius) - if (allocated(self%angularMomentumTable )) deallocate(self%angularMomentumTable ) - allocate(self%angularMomentumTableAlpha (self%angularMomentumTableAlphaCount)) - allocate(self%angularMomentumTableRadius(self%angularMomentumTableRadiusCount )) - allocate(self%angularMomentumTable (self%angularMomentumTableRadiusCount,self%angularMomentumTableAlphaCount)) - ! Create ranges of α and radius. - self%angularMomentumTableAlpha =Make_Range(self%angularMomentumTableAlphaMinimum ,self%angularMomentumTableAlphaMaximum, & - & self%angularMomentumTableAlphaCount ,rangeType=rangeTypeLinear ) - self%angularMomentumTableRadius=Make_Range(self%angularMomentumTableRadiusMinimum,self%angularMomentumTableRadiusMaximum, & - & self%angularMomentumTableRadiusCount ,rangeType=rangeTypeLogarithmic) - ! Tabulate the radius vs. specific angular momentum relation. - do iAlpha=1,self%angularMomentumTableAlphaCount - alpha=self%angularMomentumTableAlpha(iAlpha) - do iRadius=1,self%angularMomentumTableRadiusCount - radius=self%angularMomentumTableRadius(iRadius) - enclosedMass= Gamma_Function_Incomplete_Complementary(3.0d0/alpha,2.0d0*radius**alpha/alpha) & - & /Gamma_Function_Incomplete_Complementary(3.0d0/alpha,2.0d0 /alpha) - self%angularMomentumTable(iRadius,iAlpha)=sqrt(enclosedMass*radius) - end do - end do - ! Build interpolators. - if (allocated(self%angularMomentumTableRadiusInterpolator)) deallocate(self%angularMomentumTableRadiusInterpolator) - if (allocated(self%angularMomentumTableAlphaInterpolator )) deallocate(self%angularMomentumTableAlphaInterpolator ) - allocate(self%angularMomentumTableRadiusInterpolator(self%angularMomentumTableAlphaCount)) - allocate(self%angularMomentumTableAlphaInterpolator ) - do iAlpha=1,self%angularMomentumTableAlphaCount - self%angularMomentumTableRadiusInterpolator(iAlpha)=interpolator(self%angularMomentumTable (:,iAlpha),self%angularMomentumTableRadius) - end do - self%angularMomentumTableAlphaInterpolator =interpolator(self%angularMomentumTableAlpha ) - ! Flag that the table is now initialized. - self%angularMomentumTableInitialized=.true. - end if - end do - return - end subroutine einastoRadiusFromSpecificAngularMomentumTableMake - - double precision function einastoRotationNormalization(self,node) - !!{ - Return the rotation normalization of an Einasto halo density profile. - !!} - use :: Galacticus_Nodes, only : nodeComponentDarkMatterProfile, treeNode - use :: Gamma_Functions , only : Gamma_Function , Gamma_Function_Incomplete_Complementary - implicit none - class (darkMatterProfileDMOEinasto ), intent(inout) :: self - type (treeNode ), intent(inout) :: node - class (nodeComponentDarkMatterProfile), pointer :: darkMatterProfile - double precision :: alpha , scaleRadius, & - & virialRadiusOverScaleRadius - - ! Get components. - darkMatterProfile => node%darkMatterProfile(autoCreate=.true.) - ! Get scale radius, shape and concentration. - scaleRadius =+darkMatterProfile%scale() - alpha =+darkMatterProfile%shape() - virialRadiusOverScaleRadius =+self %darkMatterHaloScale_%radiusVirial(node) & - & / scaleRadius - einastoRotationNormalization=+(2.0d0/alpha)**(1.0d0/alpha) & - & *Gamma_Function (3.0d0/alpha ) & - & /Gamma_Function (4.0d0/alpha ) & - & *Gamma_Function_Incomplete_Complementary(3.0d0/alpha,2.0d0*virialRadiusOverScaleRadius**alpha/alpha) & - & /Gamma_Function_Incomplete_Complementary(4.0d0/alpha,2.0d0*virialRadiusOverScaleRadius**alpha/alpha) & - & /scaleRadius - return - end function einastoRotationNormalization - - double precision function einastoEnergy(self,node) - !!{ - Return the energy of an Einasto halo density profile. - !!} - use :: Galacticus_Nodes, only : nodeComponentBasic, nodeComponentDarkMatterProfile, treeNode - use, intrinsic :: ISO_C_Binding , only : c_size_t - implicit none - class (darkMatterProfileDMOEinasto ), intent(inout) :: self - type (treeNode ), intent(inout) :: node - class (nodeComponentBasic ), pointer :: basic - class (nodeComponentDarkMatterProfile), pointer :: darkMatterProfile - integer (c_size_t ), dimension(0:1) :: jAlpha - double precision , dimension(0:1) :: hAlpha - integer :: iAlpha - double precision :: alpha , scaleRadius, & - & virialRadiusOverScaleRadius - - ! Get components. - basic => node%basic ( ) - darkMatterProfile => node%darkMatterProfile(autoCreate=.true.) - - ! Get scale radius, shape parameter and concentration. - scaleRadius =darkMatterProfile%scale() - alpha =darkMatterProfile%shape() - virialRadiusOverScaleRadius=self%darkMatterHaloScale_%radiusVirial(node)/scaleRadius - - ! Ensure the table exists and is sufficiently tabulated. - call self%energyTableMake(virialRadiusOverScaleRadius,alpha) - - ! Get interpolating factors in α. - call self%energyTableAlphaInterpolator%linearFactors(alpha,jAlpha(0),hAlpha) - jAlpha(1)=jAlpha(0)+1 - - ! Find the energy by interpolation. - einastoEnergy=0.0d0 - do iAlpha=0,1 - einastoEnergy=+einastoEnergy & - & +self%energyTableConcentrationInterpolator%interpolate(virialRadiusOverScaleRadius,self%energyTable(:,jAlpha(iAlpha))) & - & * hAlpha(iAlpha) - end do - - ! Scale to dimensionful units. - einastoEnergy=einastoEnergy*basic%mass() & - & *self%darkMatterHaloScale_%velocityVirial(node)**2 - return - end function einastoEnergy - - subroutine energyTableMake(self,concentrationRequired,alphaRequired) - !!{ - Create a tabulation of the energy of Einasto profiles as a function of their concentration of $\alpha$ parameter. - !!} - use :: Numerical_Constants_Math, only : Pi - use :: Numerical_Integration , only : integrator - use :: Numerical_Ranges , only : Make_Range , rangeTypeLinear, rangeTypeLogarithmic - implicit none - class (darkMatterProfileDMOEinasto), intent(inout) :: self - double precision , intent(in ) :: alphaRequired , concentrationRequired - integer :: iAlpha , iConcentration - logical :: makeTable - double precision :: alpha , concentration , & - & jeansEquationIntegral , kineticEnergy , & - & kineticEnergyIntegral , potentialEnergy , & - & potentialEnergyIntegral, radiusMaximum , & - & radiusMinimum , concentrationParameter, & - & alphaParameter - type (integrator ) :: integratorPotential , integratorKinetic , & - & integratorJeans - - ! Assume table does not need remaking. - makeTable=.false. - ! Check for uninitialized table. - if (.not.self%energyTableInitialized) makeTable=.true. - ! Check for α out of range. - if (alphaRequired < self%energyTableAlphaMinimum .or. alphaRequired > self%energyTableAlphaMaximum) then - makeTable=.true. - ! Compute the range of tabulation and number of points to use. - self%energyTableAlphaMinimum =min(self%energyTableAlphaMinimum,0.9d0*alphaRequired) - self%energyTableAlphaMaximum =max(self%energyTableAlphaMaximum,1.1d0*alphaRequired) - end if - ! Check for concentration below minimum tabulated value. - if (concentrationRequired < self%energyTableConcentrationMinimum .or. concentrationRequired > self%energyTableConcentrationMaximum) then - makeTable=.true. - self%energyTableConcentrationMinimum=min(self%energyTableConcentrationMinimum,0.5d0*self%energyTableConcentrationMinimum) - self%energyTableConcentrationMaximum=max(self%energyTableConcentrationMaximum,2.0d0*self%energyTableConcentrationMaximum) - end if - ! Remake the table if necessary. - if (makeTable) then - ! Allocate arrays to the appropriate sizes. - self%energyTableAlphaCount =int( (self%energyTableAlphaMaximum -self%energyTableAlphaMinimum ) & - & *dble(energyTableAlphaPointsPerUnit ) & - & )+1 - self%energyTableConcentrationCount=int(log10(self%energyTableConcentrationMaximum/self%energyTableConcentrationMinimum) & - & *dble(energyTableConcentrationPointsPerDecade ) & - & )+1 - if (allocated(self%energyTableAlpha )) deallocate(self%energyTableAlpha ) - if (allocated(self%energyTableConcentration)) deallocate(self%energyTableConcentration) - if (allocated(self%energyTable )) deallocate(self%energyTable ) - allocate(self%energyTableAlpha (self%energyTableAlphaCount)) - allocate(self%energyTableConcentration(self%energyTableConcentrationCount )) - allocate(self%energyTable (self%energyTableConcentrationCount,self%energyTableAlphaCount)) - ! Create ranges of α and concentration. - self%energyTableAlpha =Make_Range(self%energyTableAlphaMinimum ,self%energyTableAlphaMaximum , & - & self%energyTableAlphaCount ,rangeType=rangeTypeLinear ) - self%energyTableConcentration=Make_Range(self%energyTableConcentrationMinimum,self%energyTableConcentrationMaximum, & - & self%energyTableConcentrationCount ,rangeType=rangeTypeLogarithmic) - ! Tabulate the radius vs. specific angular momentum relation. - integratorPotential=integrator(einastoPotentialEnergyIntegrand,toleranceRelative=1.0d-3) - integratorKinetic =integrator(einastoKineticEnergyIntegrand ,toleranceRelative=1.0d-3) - integratorJeans =integrator(einastoJeansEquationIntegrand ,toleranceRelative=1.0d-3) - do iAlpha=1,self%energyTableAlphaCount - alpha=self%energyTableAlpha(iAlpha) - do iConcentration=1,self%energyTableConcentrationCount - concentration=self%energyTableConcentration(iConcentration) - - ! Compute the potential energy. - radiusMinimum =0.0d0 - radiusMaximum =concentration - concentrationParameter=concentration - alphaParameter =alpha - potentialEnergyIntegral=integratorPotential%integrate(radiusMinimum,radiusMaximum) - potentialEnergy=-0.5d0*(1.0d0/concentration+potentialEnergyIntegral) - - ! Compute the velocity dispersion at the virial radius. - radiusMinimum = concentration - radiusMaximum =100.0d0*concentration - concentrationParameter= concentration - alphaParameter =alpha - jeansEquationIntegral=integratorJeans%integrate(radiusMinimum,radiusMaximum) - - ! Compute the kinetic energy. - radiusMinimum =0.0d0 - radiusMaximum =concentration - concentrationParameter=concentration - alphaParameter =alpha - kineticEnergyIntegral=integratorKinetic%integrate(radiusMinimum,radiusMaximum) - kineticEnergy=2.0d0*Pi*(jeansEquationIntegral*concentration**3+kineticEnergyIntegral) - - ! Compute the total energy. - self%energyTable(iConcentration,iAlpha)=(potentialEnergy+kineticEnergy)*concentration - - end do - end do - ! Build interpolators. - if (allocated(self%energyTableConcentrationInterpolator)) deallocate(self%energyTableConcentrationInterpolator) - if (allocated(self%energyTableAlphaInterpolator )) deallocate(self%energyTableAlphaInterpolator ) - allocate(self%energyTableConcentrationInterpolator) - allocate(self%energyTableAlphaInterpolator ) - self%energyTableConcentrationInterpolator=interpolator(self%energyTableConcentration) - self%energyTableAlphaInterpolator =interpolator(self%energyTableAlpha ) - ! Flag that the table is now initialized. - self%energyTableInitialized=.true. - end if - return - - contains - - double precision function einastoPotentialEnergyIntegrand(radius) - !!{ - Integrand for Einasto profile potential energy. - !!} - implicit none - double precision, intent(in ) :: radius - - einastoPotentialEnergyIntegrand=(self%enclosedMassScaleFree(radius,concentrationParameter,alphaParameter)/radius)**2 - return - end function einastoPotentialEnergyIntegrand - - double precision function einastoKineticEnergyIntegrand(radius) - !!{ - Integrand for Einasto profile kinetic energy. - !!} - implicit none - double precision, intent(in ) :: radius - - einastoKineticEnergyIntegrand=self%enclosedMassScaleFree(radius,concentrationParameter,alphaParameter) & - & *self%densityScaleFree (radius,concentrationParameter,alphaParameter) & - & *radius - return - end function einastoKineticEnergyIntegrand - - double precision function einastoJeansEquationIntegrand(radius) - !!{ - Integrand for Einasto profile Jeans equation. - !!} - implicit none - double precision, intent(in ) :: radius - - einastoJeansEquationIntegrand=self%enclosedMassScaleFree(radius,concentrationParameter,alphaParameter) & - & *self%densityScaleFree (radius,concentrationParameter,alphaParameter) & - & /radius**2 - return - end function einastoJeansEquationIntegrand - - end subroutine energyTableMake - - double precision function einastoEnclosedMassScaleFree(self,radius,concentration,alpha) - !!{ - Returns the enclosed mass (in units of the virial mass) in an Einasto dark matter profile with given {\normalfont \ttfamily concentration} at the - given {\normalfont \ttfamily radius} (given in units of the scale radius). - !!} - use :: Gamma_Functions, only : Gamma_Function_Incomplete_Complementary - implicit none - class (darkMatterProfileDMOEinasto), intent(inout) :: self - double precision , intent(in ) :: alpha, concentration, radius - !$GLC attributes unused :: self - - einastoEnclosedMassScaleFree=+Gamma_Function_Incomplete_Complementary(3.0d0/alpha,2.0d0*radius **alpha/alpha) & - & /Gamma_Function_Incomplete_Complementary(3.0d0/alpha,2.0d0*concentration**alpha/alpha) - return - end function einastoEnclosedMassScaleFree - - double precision function einastoDensityScaleFree(self,radius,concentration,alpha) - !!{ - Returns the density (in units such that the virial mass and scale length are unity) in an Einasto dark matter profile with - given {\normalfont \ttfamily concentration} and {\normalfont \ttfamily alpha} at the given {\normalfont \ttfamily radius} (given in units of the scale radius). - !!} - use :: Gamma_Functions , only : Gamma_Function, Gamma_Function_Incomplete_Complementary - use :: Numerical_Constants_Math, only : Pi - implicit none - class (darkMatterProfileDMOEinasto), intent(inout) :: self - double precision , intent(in ) :: alpha , concentration, radius - double precision :: densityNormalization - !$GLC attributes unused :: self - - densityNormalization= (alpha/4.0d0/Pi) & - & * ((2.0d0/alpha) **(3.0d0/alpha) ) & - & *exp(-2.0d0/alpha ) & - & /Gamma_Function (3.0d0/alpha ) & - & /Gamma_Function_Incomplete_Complementary(3.0d0/alpha,2.0d0*concentration**alpha/alpha) - einastoDensityScaleFree=densityNormalization*exp(-(2.0d0/alpha)*(radius**alpha-1.0d0)) - return - end function einastoDensityScaleFree - - double precision function einastoPotentialScaleFree(self,radius,concentration,alpha) - !!{ - Returns the gravitational potential (in units where the virial mass and scale radius are unity) in an Einasto dark matter - profile with given {\normalfont \ttfamily concentration} and {\normalfont \ttfamily alpha} at the given {\normalfont - \ttfamily radius} (given in units of the scale radius). Uses the results from \cite{retana-montenegro_analytical_2012}, - their equations (19) and (20). - !!} - use :: Gamma_Functions, only : Gamma_Function, Gamma_Function_Incomplete, Gamma_Function_Incomplete_Complementary - implicit none - class (darkMatterProfileDMOEinasto), intent(inout) :: self - double precision , intent(in ) :: alpha , concentration, & - & radius - !$GLC attributes unused :: self - - if (radius <= 0.0d0) then - einastoPotentialScaleFree=-((2.0d0/alpha)**(1.0d0/alpha)) & - & * Gamma_Function (2.0d0/alpha ) & - & / Gamma_Function (3.0d0/alpha ) & - & / Gamma_Function_Incomplete_Complementary(3.0d0/alpha,2.0d0*concentration**alpha/alpha) - - else - einastoPotentialScaleFree=-1.0d0 & - & /radius & - & *( & - & +Gamma_Function_Incomplete_Complementary(3.0d0/alpha,2.0d0*radius **alpha/alpha) & - & +( & - & +radius & - & *(2.0d0/alpha)**(1.0d0/alpha) & - & ) & - & *Gamma_Function_Incomplete (2.0d0/alpha,2.0d0*radius **alpha/alpha) & - & *Gamma_Function (2.0d0/alpha ) & - & /Gamma_Function (3.0d0/alpha ) & - & ) & - & / Gamma_Function_Incomplete_Complementary(3.0d0/alpha,2.0d0*concentration**alpha/alpha) - end if - return - end function einastoPotentialScaleFree - - double precision function einastoKSpace(self,node,wavenumber) - !!{ - Returns the Fourier transform of the Einasto density profile at the specified {\normalfont \ttfamily waveNumber} (given in Mpc$^{-1}$)). - !!} - use :: Galacticus_Nodes, only : nodeComponentDarkMatterProfile, treeNode - use, intrinsic :: ISO_C_Binding , only : c_size_t - implicit none - class (darkMatterProfileDMOEinasto ) , intent(inout) :: self - type (treeNode ) , intent(inout), target :: node - double precision , intent(in ) :: wavenumber - class (nodeComponentDarkMatterProfile) , pointer :: darkMatterProfile - integer (c_size_t ), dimension(0:1) :: jAlpha , jConcentration - double precision , dimension(0:1) :: hAlpha , hConcentration - integer :: iAlpha , iConcentration - double precision :: alpha , scaleRadius , & - & virialRadiusOverScaleRadius, wavenumberScaleFree - - ! Get components. - darkMatterProfile => node%darkMatterProfile(autoCreate=.true.) - - ! Get scale radius, shape parameter and concentration. - scaleRadius =darkMatterProfile%scale() - alpha =darkMatterProfile%shape() - virialRadiusOverScaleRadius=self%darkMatterHaloScale_%radiusVirial(node)/scaleRadius - wavenumberScaleFree =wavenumber*scaleRadius - - ! Ensure the table exists and is sufficiently tabulated. - call self%fourierProfileTableMake(wavenumberScaleFree,virialRadiusOverScaleRadius,alpha) - - ! Get interpolating factors in α. - call self%fourierProfileTableAlphaInterpolator%linearFactors(alpha,jAlpha(0),hAlpha) - jAlpha(1)=jAlpha(0)+1 - - ! Get interpolating factors in concentration. - call self%fourierProfileTableConcentrationInterpolator%linearFactors(virialRadiusOverScaleRadius,jConcentration(0),hConcentration) - jConcentration(1)=jConcentration(0)+1 - - ! Find the Fourier profile by interpolation. - einastoKSpace=0.0d0 - do iAlpha=0,1 - do iConcentration=0,1 - einastoKSpace=+einastoKSpace & - & +self%fourierProfileTableWavenumberInterpolator%interpolate(wavenumberScaleFree,self%fourierProfileTable(:,jConcentration(iConcentration),jAlpha(iAlpha))) & - & * hAlpha(iAlpha) & - & * hConcentration(iConcentration) - end do - end do - return - end function einastoKSpace - - subroutine fourierProfileTableMake(self,wavenumberRequired,concentrationRequired,alphaRequired) - !!{ - Create a tabulation of the Fourier transform of Einasto profiles as a function of their $\alpha$ parameter and - dimensionless wavenumber. - !!} - use :: Display , only : displayCounter , displayCounterClear , displayIndent , displayUnindent, & - & verbosityLevelInfo, verbosityLevelWorking - use :: Error , only : Error_Report , errorStatusSuccess - use, intrinsic :: ISO_C_Binding , only : c_size_t - use :: Numerical_Integration, only : integrator - use :: Numerical_Ranges , only : Make_Range , rangeTypeLinear , rangeTypeLogarithmic - implicit none - class (darkMatterProfileDMOEinasto), intent(inout) :: self - double precision , intent(in ) :: alphaRequired , concentrationRequired, wavenumberRequired - double precision , parameter :: profileTruncateLevel =6.0d-6 - integer :: iAlpha , iConcentration , iWavenumber , & - & percentage , errorStatus - logical :: makeTable - double precision :: alpha , concentration , radiusMaximum , & - & radiusMinimum , wavenumber , wavenumberParameter, & - & concentrationParameter , alphaParameter - type (integrator ) :: integrator_ - character (len=12 ) :: label - type (varying_string ) :: message - - ! Assume table does not need remaking. - makeTable=.false. - ! Check for uninitialized table. - if (.not.self%fourierProfileTableInitialized) makeTable=.true. - ! Check for α out of range. - if (alphaRequired < self%fourierProfileTableAlphaMinimum .or. alphaRequired > self%fourierProfileTableAlphaMaximum ) then - makeTable=.true. - ! Compute the range of tabulation and number of points to use. - self%fourierProfileTableAlphaMinimum =min(self%fourierProfileTableAlphaMinimum ,0.9d0*alphaRequired ) - self%fourierProfileTableAlphaMaximum =max(self%fourierProfileTableAlphaMaximum ,1.1d0*alphaRequired ) - end if - ! Check for concentration out of range. - if (concentrationRequired < self%fourierProfileTableConcentrationMinimum .or. concentrationRequired > self%fourierProfileTableConcentrationMaximum ) then - makeTable=.true. - ! Compute the range of tabulation and number of points to use. - self%fourierProfileTableConcentrationMinimum=min(self%fourierProfileTableConcentrationMinimum,0.5d0*concentrationRequired) - self%fourierProfileTableConcentrationMaximum=max(self%fourierProfileTableConcentrationMaximum,2.0d0*concentrationRequired) - end if - ! Check for wavenumber below minimum tabulated value. - if (wavenumberRequired < self%fourierProfileTableWavenumberMinimum .or. wavenumberRequired > self%fourierProfileTableWavenumberMaximum ) then - makeTable=.true. - self%fourierProfileTableWavenumberMinimum =min(self%fourierProfileTableWavenumberMinimum ,0.5d0*wavenumberRequired ) - self%fourierProfileTableWavenumberMaximum =max(self%fourierProfileTableWavenumberMaximum ,2.0d0*wavenumberRequired ) - end if - ! Remake the table if necessary. - if (makeTable) then - ! Display a message. - call displayIndent('Constructing Einasto profile Fourier transform lookup table...',verbosityLevelInfo) - ! Allocate arrays to the appropriate sizes. - self%fourierProfileTableAlphaCount =int( (self%fourierProfileTableAlphaMaximum -self%fourierProfileTableAlphaMinimum ) & - &*dble(fourierProfileTableAlphaPointsPerUnit ))+1 - self%fourierProfileTableConcentrationCount=int(log10(self%fourierProfileTableConcentrationMaximum/self%fourierProfileTableConcentrationMinimum) & - &*dble(fourierProfileTableConcentrationPointsPerDecade))+1 - self%fourierProfileTableWavenumberCount =int(log10(self%fourierProfileTableWavenumberMaximum /self%fourierProfileTableWavenumberMinimum ) & - &*dble(fourierProfileTableWavenumberPointsPerDecade ))+1 - if (allocated(self%fourierProfileTableAlpha )) deallocate(self%fourierProfileTableAlpha ) - if (allocated(self%fourierProfileTableConcentration)) deallocate(self%fourierProfileTableConcentration) - if (allocated(self%fourierProfileTableWavenumber )) deallocate(self%fourierProfileTableWavenumber ) - if (allocated(self%fourierProfileTable )) deallocate(self%fourierProfileTable ) - allocate(self%fourierProfileTableAlpha (self%fourierProfileTableAlphaCount)) - allocate(self%fourierProfileTableConcentration(self%fourierProfileTableConcentrationCount )) - allocate(self%fourierProfileTableWavenumber (self%fourierProfileTableWavenumberCount )) - allocate(self%fourierProfileTable (self%fourierProfileTableWavenumberCount,self%fourierProfileTableConcentrationCount,self%fourierProfileTableAlphaCount)) - ! Create ranges of α and wavenumber. - self%fourierProfileTableAlpha =Make_Range(self%fourierProfileTableAlphaMinimum ,self%fourierProfileTableAlphaMaximum , & - & self%fourierProfileTableAlphaCount ,rangeType=rangeTypeLinear ) - self%fourierProfileTableConcentration=Make_Range(self%fourierProfileTableConcentrationMinimum,self%fourierProfileTableConcentrationMaximum, & - & self%fourierProfileTableConcentrationCount ,rangeType=rangeTypeLogarithmic) - self%fourierProfileTableWavenumber =Make_Range(self%fourierProfileTableWavenumberMinimum ,self%fourierProfileTableWavenumberMaximum , & - & self%fourierProfileTableWavenumberCount ,rangeType=rangeTypeLogarithmic) - ! Tabulate the Fourier profile. - integrator_=integrator(einastoFourierProfileIntegrand,toleranceRelative=1.0d-3,intervalsMaximum=1000_c_size_t) - do iAlpha=1,self%fourierProfileTableAlphaCount - alpha=self%fourierProfileTableAlpha(iAlpha) - do iConcentration=1,self%fourierProfileTableConcentrationCount - concentration=self%fourierProfileTableConcentration(iConcentration) - - ! Show progress. - percentage=int(100.0d0*dble((iAlpha-1)*self%fourierProfileTableConcentrationCount+iConcentration-1 ) & - & /dble(self%fourierProfileTableAlphaCount*self%fourierProfileTableConcentrationCount) & - & ) - call displayCounter(percentage,iAlpha == 1 .and. iConcentration == 1,verbosityLevelWorking) - - do iWavenumber=1,self%fourierProfileTableWavenumberCount - ! If the Fourier profile has fallen below some minimal level, simply truncate to zero to avoid numerical - ! integration problems. - if (iWavenumber > 1 .and. self%fourierProfileTable(iWavenumber-1,iConcentration,iAlpha) <= profileTruncateLevel) then - self%fourierProfileTable(iWavenumber,iConcentration,iAlpha)=0.0d0 - else - wavenumber=self%fourierProfileTableWavenumber(iWavenumber) - ! Compute the Fourier profile. - radiusMinimum =0.0d0 - radiusMaximum =concentration - wavenumberParameter =wavenumber - alphaParameter =alpha - concentrationParameter=concentration - self%fourierProfileTable(iWavenumber,iConcentration,iAlpha)=integrator_%integrate(radiusMinimum,radiusMaximum,status=errorStatus) - if (errorStatus /= errorStatusSuccess) then - message="Integration of Einasto profile Fourier transform failed at:"//char(10) - write (label,'(e12.6)') wavenumber - message=message//" wavenumber: k="//trim(adjustl(label))//"Mpc"//char(10) - if (iWavenumber == 1) then - message=message//" no previous tabulated point" - else - write (label,'(e12.6)') self%fourierProfileTable(iWavenumber-1,iConcentration,iAlpha) - message=message//" value at previous tabulated point was "//trim(adjustl(label)) - end if - call Error_Report(message//{introspection:location}) - end if - end if - end do - end do - end do - call displayCounterClear(verbosityLevelWorking) - ! Build interpolators. - if (allocated(self%fourierProfileTableWavenumberInterpolator )) deallocate(self%fourierProfileTableWavenumberInterpolator ) - if (allocated(self%fourierProfileTableAlphaInterpolator )) deallocate(self%fourierProfileTableAlphaInterpolator ) - if (allocated(self%fourierProfileTableConcentrationInterpolator)) deallocate(self%fourierProfileTableConcentrationInterpolator) - allocate(self%fourierProfileTableWavenumberInterpolator ) - allocate(self%fourierProfileTableAlphaInterpolator ) - allocate(self%fourierProfileTableConcentrationInterpolator) - self%fourierProfileTableWavenumberInterpolator =interpolator(self%fourierProfileTableWavenumber) - self%fourierProfileTableAlphaInterpolator =interpolator(self%fourierProfileTableAlpha) - self%fourierProfileTableConcentrationInterpolator=interpolator(self%fourierProfileTableConcentration) - ! Flag that the table is now initialized. - self%fourierProfileTableInitialized=.true. - ! Display a message. - call displayUnindent('done',verbosityLevelInfo) - end if - return - - contains - - double precision function einastoFourierProfileIntegrand(radius) - !!{ - Integrand for Einasto Fourier profile. - !!} - use :: Numerical_Constants_Math, only : Pi - implicit none - double precision, intent(in ) :: radius - - einastoFourierProfileIntegrand=4.0d0*Pi*radius*sin(wavenumberParameter*radius)*self%densityScaleFree(radius& - &,concentrationParameter,alphaParameter)/wavenumberParameter - return - end function einastoFourierProfileIntegrand - - end subroutine fourierProfileTableMake - - double precision function einastoFreefallRadius(self,node,time) - !!{ - Returns the freefall radius in the Einasto density profile at the specified {\normalfont \ttfamily time} (given in Gyr). - !!} - use :: Galacticus_Nodes , only : nodeComponentBasic , nodeComponentDarkMatterProfile , treeNode - use :: Gamma_Functions , only : Gamma_Function_Incomplete_Complementary - use, intrinsic :: ISO_C_Binding , only : c_size_t - use :: Numerical_Constants_Astronomical, only : Mpc_per_km_per_s_To_Gyr , gravitationalConstantGalacticus - implicit none - class (darkMatterProfileDMOEinasto ), intent(inout) , target :: self - type (treeNode ), intent(inout) , target :: node - double precision , intent(in ) :: time - class (nodeComponentBasic ), pointer :: basic - class (nodeComponentDarkMatterProfile), pointer :: darkMatterProfile - integer (c_size_t ), dimension(0:1) :: jAlpha - double precision , dimension(0:1) :: hAlpha - integer :: iAlpha - double precision :: alpha , freefallTimeScaleFree , & - & radiusScale , timeScale , & - & velocityScale , virialRadiusOverScaleRadius - - ! For non-positive freefall times, return a zero freefall radius immediately. - if (time <= 0.0d0) then - einastoFreefallRadius=0.0d0 - return - end if - - ! Get components. - basic => node%basic ( ) - darkMatterProfile => node%darkMatterProfile(autoCreate=.true.) - - ! Get the shape parameter. - alpha =darkMatterProfile%shape() - - ! Get the scale radius. - radiusScale =darkMatterProfile%scale() - virialRadiusOverScaleRadius=self%darkMatterHaloScale_%radiusVirial(node)/radiusScale - - ! Get the velocity scale. - velocityScale=sqrt(gravitationalConstantGalacticus*basic%mass()/radiusScale) - - ! Compute time scale. - timeScale=+Mpc_per_km_per_s_To_Gyr & - & *radiusScale & - & /velocityScale & - & *sqrt(Gamma_Function_Incomplete_Complementary(3.0d0/alpha,2.0d0*virialRadiusOverScaleRadius**alpha/alpha)) & - & /sqrt(Gamma_Function_Incomplete_Complementary(3.0d0/alpha,2.0d0 /alpha)) - - ! Compute dimensionless time. - freefallTimeScaleFree=time/timeScale - - ! Ensure table is sufficiently extensive. - call self%freefallTabulate(freefallTimeScaleFree,alpha) - - ! Interpolate to get the freefall radius. - ! Get interpolating factors in α. - call self%freefallRadiusTableAlphaInterpolator%linearFactors(alpha,jAlpha(0),hAlpha) - jAlpha(1)=jAlpha(0)+1 - - einastoFreefallRadius=0.0d0 - do iAlpha=0,1 - einastoFreefallRadius=+einastoFreefallRadius & - & +self%freefallRadiusTableRadiusInterpolator(jAlpha(iAlpha))%interpolate(freefallTimeScaleFree,self%freefallRadiusTableRadius) & - & * hAlpha(iAlpha) - end do - einastoFreefallRadius=einastoFreefallRadius*radiusScale - return - end function einastoFreefallRadius - - double precision function einastoFreefallRadiusIncreaseRate(self,node,time) - !!{ - Returns the rate of increase of the freefall radius in the Einasto density profile at the specified {\normalfont \ttfamily time} (given in - Gyr). - !!} - use :: Galacticus_Nodes , only : nodeComponentBasic , nodeComponentDarkMatterProfile , treeNode - use :: Gamma_Functions , only : Gamma_Function_Incomplete_Complementary - use, intrinsic :: ISO_C_Binding , only : c_size_t - use :: Numerical_Constants_Astronomical, only : Mpc_per_km_per_s_To_Gyr , gravitationalConstantGalacticus - implicit none - class (darkMatterProfileDMOEinasto ), intent(inout) , target :: self - type (treeNode ), intent(inout) , target :: node - double precision , intent(in ) :: time - class (nodeComponentBasic ), pointer :: basic - class (nodeComponentDarkMatterProfile), pointer :: darkMatterProfile - integer (c_size_t ), dimension(0:1) :: jAlpha - double precision , dimension(0:1) :: hAlpha - integer :: iAlpha - double precision :: alpha , freefallTimeScaleFree , & - & radiusScale , timeScale , & - & velocityScale , virialRadiusOverScaleRadius - - ! For non-positive freefall times, return a zero freefall radius immediately. - if (time <= 0.0d0) then - einastoFreefallRadiusIncreaseRate=0.0d0 - return - end if - - ! Get components. - basic => node%basic ( ) - darkMatterProfile => node%darkMatterProfile(autoCreate=.true.) - - ! Get the shape parameter. - alpha =darkMatterProfile%shape() - - ! Get the scale radius. - radiusScale =darkMatterProfile%scale() - virialRadiusOverScaleRadius=self%darkMatterHaloScale_%radiusVirial(node)/radiusScale - - ! Get the velocity scale. - velocityScale=sqrt(gravitationalConstantGalacticus*basic%mass()/radiusScale) - - ! Compute time scale. - timeScale=+Mpc_per_km_per_s_To_Gyr & - & *radiusScale & - & /velocityScale & - & *sqrt(Gamma_Function_Incomplete_Complementary(3.0d0/alpha,2.0d0*virialRadiusOverScaleRadius**alpha/alpha)) & - & /sqrt(Gamma_Function_Incomplete_Complementary(3.0d0/alpha,2.0d0 /alpha)) - - ! Compute dimensionless time. - freefallTimeScaleFree=time/timeScale - - ! Ensure table is sufficiently extensive. - call self%freefallTabulate(freefallTimeScaleFree,alpha) - - ! Interpolate to get the freefall radius. - ! Get interpolating factors in α. - call self%freefallRadiusTableAlphaInterpolator%linearFactors(alpha,jAlpha(0),hAlpha) - jAlpha(1)=jAlpha(0)+1 - - einastoFreefallRadiusIncreaseRate=0.0d0 - do iAlpha=0,1 - einastoFreefallRadiusIncreaseRate=+einastoFreefallRadiusIncreaseRate & - & +self%freefallRadiusTableRadiusInterpolator(jAlpha(iAlpha))%derivative(freefallTimeScaleFree,self%freefallRadiusTableRadius) & - & * hAlpha(iAlpha) - end do - einastoFreefallRadiusIncreaseRate=einastoFreefallRadiusIncreaseRate*radiusScale/timeScale - return - end function einastoFreefallRadiusIncreaseRate - - subroutine einastoFreefallTabulate(self,freefallTimeScaleFree,alphaRequired) - !!{ - Tabulates the freefall time vs. freefall radius for Einasto halos. - !!} - use :: Display , only : displayCounter, displayIndent , displayUnindent , verbosityLevelWorking - use :: Numerical_Ranges , only : Make_Range , rangeTypeLinear, rangeTypeLogarithmic - implicit none - class (darkMatterProfileDMOEinasto), intent(inout) :: self - double precision , intent(in ) :: alphaRequired, freefallTimeScaleFree - logical :: retabulate - integer :: iAlpha , iRadius , percentage - double precision :: alpha - - retabulate=.not.self%freefallRadiusTableInitialized - ! If the table has not yet been made, compute and store the freefall times corresponding to the minimum and maximum - ! radii that will be tabulated by default. - if (retabulate) then - self%freefallTimeMinimum=self%freefallTimeScaleFree(self%freefallRadiusTableRadiusMinimum,alphaRequired) - self%freefallTimeMaximum=self%freefallTimeScaleFree(self%freefallRadiusTableRadiusMaximum,alphaRequired) - end if - do while (freefallTimeScaleFree < self%freefallTimeMinimum) - self%freefallRadiusTableRadiusMinimum=0.5d0*self%freefallRadiusTableRadiusMinimum - self%freefallTimeMinimum=self%freefallTimeScaleFree(self%freefallRadiusTableRadiusMinimum,alphaRequired) - retabulate=.true. - end do - do while (freefallTimeScaleFree > self%freefallTimeMaximum) - self%freefallRadiusTableRadiusMaximum=2.0d0*self%freefallRadiusTableRadiusMaximum - self%freefallTimeMaximum=self%freefallTimeScaleFree(self%freefallRadiusTableRadiusMaximum,alphaRequired) - retabulate=.true. - end do - ! Check for α out of range. - if (alphaRequired < self%freefallRadiusTableAlphaMinimum .or. alphaRequired > self%freefallRadiusTableAlphaMaximum) then - retabulate=.true. - ! Compute the range of tabulation. - self%freefallRadiusTableAlphaMinimum=min(self%freefallRadiusTableAlphaMinimum,0.9d0*alphaRequired) - self%freefallRadiusTableAlphaMaximum=max(self%freefallRadiusTableAlphaMaximum,1.1d0*alphaRequired) - end if - - if (retabulate) then - ! Display a message. - call displayIndent('Constructing Einasto profile freefall radius lookup table...',verbosityLevelWorking) - ! Decide how many points to tabulate and allocate table arrays. - self%freefallRadiusTableRadiusCount=int(log10(self%freefallRadiusTableRadiusMaximum/self%freefallRadiusTableRadiusMinimum)*dble(freefallRadiusTableRadiusPointsPerDecade))+1 - self%freefallRadiusTableAlphaCount =int( (self%freefallRadiusTableAlphaMaximum -self%freefallRadiusTableAlphaMinimum )*dble(freefallRadiusTableAlphaPointsPerUnit ))+1 - if (allocated(self%freefallRadiusTableRadius)) then - deallocate(self%freefallRadiusTableAlpha ) - deallocate(self%freefallRadiusTableRadius) - deallocate(self%freefallRadiusTable ) - end if - allocate(self%freefallRadiusTableAlpha (self%freefallRadiusTableAlphaCount)) - allocate(self%freefallRadiusTableRadius(self%freefallRadiusTableRadiusCount )) - allocate(self%freefallRadiusTable (self%freefallRadiusTableRadiusCount,self%freefallRadiusTableAlphaCount)) - ! Create a range of radii and α. - self%freefallRadiusTableAlpha =Make_Range(self%freefallRadiusTableAlphaMinimum ,self%freefallRadiusTableAlphaMaximum ,self%freefallRadiusTableAlphaCount ,rangeType=rangeTypeLinear ) - self%freefallRadiusTableRadius=Make_Range(self%freefallRadiusTableRadiusMinimum,self%freefallRadiusTableRadiusMaximum,self%freefallRadiusTableRadiusCount,rangeType=rangeTypeLogarithmic) - ! Loop over radii and α and populate tables. - do iAlpha=1,self%freefallRadiusTableAlphaCount - alpha=self%freefallRadiusTableAlpha(iAlpha) - do iRadius=1,self%freefallRadiusTableRadiusCount - ! Show progress. - percentage=int(100.0d0*dble((iAlpha-1)*self%freefallRadiusTableRadiusCount+iRadius-1 ) & - & /dble(self%freefallRadiusTableAlphaCount*self%freefallRadiusTableRadiusCount) & - & ) - call displayCounter(percentage,iAlpha == 1 .and. iRadius == 1,verbosityLevelWorking) - ! Compute the freefall radius. - self%freefallRadiusTable(iRadius,iAlpha)=self%freefallTimeScaleFree(self%freefallRadiusTableRadius(iRadius),alpha) - end do - end do - ! Build interpolators. - if (allocated(self%freefallRadiusTableAlphaInterpolator )) deallocate(self%freefallRadiusTableAlphaInterpolator ) - if (allocated(self%freefallRadiusTableRadiusInterpolator)) deallocate(self%freefallRadiusTableRadiusInterpolator) - allocate(self%freefallRadiusTableAlphaInterpolator ) - allocate(self%freefallRadiusTableRadiusInterpolator(self%freefallRadiusTableAlphaCount)) - self %freefallRadiusTableAlphaInterpolator =interpolator(self%freefallRadiusTableAlpha ) - do iAlpha=1,self%freefallRadiusTableAlphaCount - self%freefallRadiusTableRadiusInterpolator(iAlpha)=interpolator(self%freefallRadiusTable (:,iAlpha)) - end do - ! Store the minimum and maximum tabulated freefall times across all α values. - self%freefallTimeMinimum=maxval(self%freefallRadiusTable( 1,:)) - self%freefallTimeMaximum=minval(self%freefallRadiusTable(self%freefallRadiusTableRadiusCount,:)) - ! Display a message. - call displayUnindent('...done',verbosityLevelWorking) - ! Specify that tabulation has been made. - self%freefallRadiusTableInitialized=.true. - end if - return - end subroutine einastoFreefallTabulate - - double precision function einastoFreefallTimeScaleFree(self,radius,alpha) - !!{ - Compute the freefall time in a scale-free Einasto halo. - !!} - use :: Numerical_Integration, only : integrator - implicit none - class (darkMatterProfileDMOEinasto), intent(inout) :: self - double precision , intent(in ) :: alpha , radius - type (integrator ) :: integrator_ - double precision :: radiusStart , radiusEnd, & - & alphaParameter - - radiusStart =radius - radiusEnd =0.0d0 - alphaParameter =alpha - integrator_ =integrator (einastoFreefallTimeScaleFreeIntegrand,toleranceRelative=1.0d-3) - einastoFreefallTimeScaleFree=integrator_%integrate(radiusEnd ,radiusStart ) - return - - contains - - double precision function einastoFreefallTimeScaleFreeIntegrand(radius) - !!{ - Integrand function used for finding the free-fall time in Einasto halos. - !!} - implicit none - double precision, intent(in ) :: radius - - einastoFreefallTimeScaleFreeIntegrand=+1.0d0 & - & /sqrt(+2.0d0 & - & *( & - & +self%potentialScaleFree(radiusStart,1.0d0,alphaParameter) & - & -self%potentialScaleFree(radius ,1.0d0,alphaParameter) & - & ) & - & ) - return - end function einastoFreefallTimeScaleFreeIntegrand - - end function einastoFreefallTimeScaleFree - - double precision function einastoRadiusEnclosingDensity(self,node,density) - !!{ - Implementation of function to compute the radius enclosing a given density for Einasto dark matter halo profiles. This - function uses a numerical root finder to find the enclosing radius---this is likely not the most efficient solution\ldots - !!} - implicit none - class (darkMatterProfileDMOEinasto), intent(inout), target :: self - type (treeNode ), intent(inout), target :: node - double precision , intent(in ) :: density - - densityEnclosed_ = density - node_ => node - self_ => self - einastoRadiusEnclosingDensity = self%finderEnclosedDensity%find(rootGuess=self%darkMatterHaloScale_%radiusVirial(node)) - return - end function einastoRadiusEnclosingDensity - - double precision function einastoRadiusEnclosingDensityRoot(radius) - !!{ - Root function used in finding the radius enclosing a given density in Einasto profiles. - !!} - use :: Numerical_Constants_Math, only : Pi - implicit none - double precision, intent(in ) :: radius - - einastoRadiusEnclosingDensityRoot=+densityEnclosed_ & - & -self_%enclosedMass(node_,radius) & - & *3.0d0 & - & /4.0d0 & - & /Pi & - & /radius**3 - return - end function einastoRadiusEnclosingDensityRoot - - double precision function einastoRadialVelocityDispersionScaleFree(self,radius,alpha) - !!{ - Compute the radial velocity dispersion in a scale-free Einasto halo. - !!} - use :: Numerical_Integration, only : integrator - implicit none - class (darkMatterProfileDMOEinasto), intent(inout) :: self - double precision , intent(in ) :: radius , alpha - double precision , parameter :: radiusTiny =1.0d-9, radiusLarge =5.0d3 - double precision :: radiusMinimum , radiusMaximum - type (integrator ) :: integrator_ - !$GLC attributes unused :: self - - radiusMinimum=max( radius,radiusTiny ) - radiusMaximum=max(10.0d0*radius,radiusLarge) - integrator_=integrator(einastoJeansEquationIntegrand,toleranceRelative=1.0d-6) - einastoRadialVelocityDispersionScaleFree=sqrt( & - & +integrator_%integrate(radiusMinimum,radiusMaximum) & - & /exp(-(2.0d0/alpha)*(radius**alpha-1.0d0)) & - & ) - return - - contains - - double precision function einastoJeansEquationIntegrand(radius) - !!{ - Integrand for Einasto dark matter profile Jeans equation. - !!} - use :: Gamma_Functions, only : Gamma_Function_Incomplete_Complementary - implicit none - double precision, intent(in ) :: radius - - if (radius > 0.0d0) then - einastoJeansEquationIntegrand=+Gamma_Function_Incomplete_Complementary(3.0d0/alpha,2.0d0*radius**alpha/alpha) & - & *exp(-(2.0d0/alpha)*(radius**alpha-1.0d0)) & - & /radius**2 - else - einastoJeansEquationIntegrand=0.0d0 - end if - return - end function einastoJeansEquationIntegrand - - end function einastoRadialVelocityDispersionScaleFree - - subroutine einastoRadialVelocityDispersionTabulate(self,radius,alphaRequired) - !!{ - Tabulates the radial velocity dispersion vs. radius for Einasto halos. - !!} - use :: Display , only : displayCounter, displayIndent , displayUnindent , verbosityLevelWorking - use :: Numerical_Ranges , only : Make_Range , rangeTypeLinear, rangeTypeLogarithmic - implicit none - class (darkMatterProfileDMOEinasto), intent(inout) :: self - double precision , intent(in ), optional :: radius , alphaRequired - logical :: retabulate - integer :: iAlpha , iRadius , percentage - double precision :: alpha + class (massDistributionClass ), pointer :: massDistribution_ + type (kinematicsDistributionCollisionless), pointer :: kinematicsDistribution_ + class (darkMatterProfileDMOEinasto ), intent(inout) :: self + type (treeNode ), intent(inout) :: node + type (enumerationWeightByType ), intent(in ), optional :: weightBy + integer , intent(in ), optional :: weightIndex + class (nodeComponentBasic ), pointer :: basic + class (nodeComponentDarkMatterProfile ), pointer :: darkMatterProfile + !![ + + !!] - retabulate=.not.self%radialVelocityDispersionTableInitialized - if (present(radius)) then - do while (radius < self%radialVelocityDispersionRadiusMinimum) - self%radialVelocityDispersionRadiusMinimum=0.5d0*self%radialVelocityDispersionRadiusMinimum - retabulate=.true. - end do - do while (radius > self%radialVelocityDispersionRadiusMaximum) - self%radialVelocityDispersionRadiusMaximum=2.0d0*self%radialVelocityDispersionRadiusMaximum - retabulate=.true. - end do - end if - if (present(alphaRequired)) then - ! Check for α out of range. - if (alphaRequired < self%radialVelocityDispersionAlphaMinimum .or. alphaRequired > self%radialVelocityDispersionAlphaMaximum) then - retabulate=.true. - ! Compute the range of tabulation. - self%radialVelocityDispersionAlphaMinimum=min(self%radialVelocityDispersionAlphaMinimum,0.9d0*alphaRequired) - self%radialVelocityDispersionAlphaMaximum=max(self%radialVelocityDispersionAlphaMaximum,1.1d0*alphaRequired) - end if - end if - if (retabulate) then - ! Display a message. - call displayIndent('Constructing Einasto profile radial velocity dispersion lookup table...',verbosityLevelWorking) - ! Decide how many points to tabulate and allocate table arrays. - self%radialVelocityDispersionTableRadiusCount=int(log10(self%radialVelocityDispersionRadiusMaximum/self%radialVelocityDispersionRadiusMinimum) & - & *dble(radialVelocityDispersionTableRadiusPointsPerDecade ) & - & )+1 - self%radialVelocityDispersionTableAlphaCount =int( (self%radialVelocityDispersionAlphaMaximum -self%radialVelocityDispersionAlphaMinimum ) & - & *dble(radialVelocityDispersionTableAlphaPointsPerUnit ) & - & )+1 - if (allocated(self%radialVelocityDispersionTableRadius)) then - deallocate(self%radialVelocityDispersionTableAlpha ) - deallocate(self%radialVelocityDispersionTableRadius) - deallocate(self%radialVelocityDispersionTable ) - end if - allocate(self%radialVelocityDispersionTableAlpha (self%radialVelocityDispersionTableAlphaCount)) - allocate(self%radialVelocityDispersionTableRadius(self%radialVelocityDispersionTableRadiusCount )) - allocate(self%radialVelocityDispersionTable (self%radialVelocityDispersionTableRadiusCount,self%radialVelocityDispersionTableAlphaCount)) - ! Create a range of radii and α. - self%radialVelocityDispersionTableAlpha =Make_Range(self%radialVelocityDispersionAlphaMinimum ,self%radialVelocityDispersionAlphaMaximum ,self%radialVelocityDispersionTableAlphaCount ,rangeType=rangeTypeLinear ) - self%radialVelocityDispersionTableRadius=Make_Range(self%radialVelocityDispersionRadiusMinimum,self%radialVelocityDispersionRadiusMaximum,self%radialVelocityDispersionTableRadiusCount,rangeType=rangeTypeLogarithmic) - ! Loop over radii and α and populate tables. - do iAlpha=1,self%radialVelocityDispersionTableAlphaCount - alpha=self%radialVelocityDispersionTableAlpha(iAlpha) - do iRadius=1,self%radialVelocityDispersionTableRadiusCount - ! Show progress. - percentage=int(100.0d0*dble((iAlpha-1)*self%radialVelocityDispersionTableRadiusCount+iRadius-1 ) & - & /dble(self%radialVelocityDispersionTableAlphaCount*self%radialVelocityDispersionTableRadiusCount) & - & ) - call displayCounter(percentage,iAlpha == 1 .and. iRadius == 1,verbosityLevelWorking) - ! Compute the radial velocity dispersion. - self%radialVelocityDispersionTable(iRadius,iAlpha)=self%radialVelocityDispersionScaleFree(self%radialVelocityDispersionTableRadius(iRadius),alpha) - end do - end do - ! Build interpolators. - if (allocated(self%radialVelocityDispersionTableAlphaInterpolator )) deallocate(self%radialVelocityDispersionTableAlphaInterpolator ) - if (allocated(self%radialVelocityDispersionTableRadiusInterpolator)) deallocate(self%radialVelocityDispersionTableRadiusInterpolator) - allocate(self%radialVelocityDispersionTableAlphaInterpolator ) - allocate(self%radialVelocityDispersionTableRadiusInterpolator) - self%radialVelocityDispersionTableAlphaInterpolator =interpolator(self%radialVelocityDispersionTableAlpha ) - self%radialVelocityDispersionTableRadiusInterpolator=interpolator(self%radialVelocityDispersionTableRadius) - ! Display a message. - call displayUnindent('...done',verbosityLevelWorking) - ! Specify that tabulation has been made. - self%radialVelocityDispersionTableInitialized=.true. - end if + ! Assume a null distribution by default. + massDistribution_ => null() + ! If weighting is not by mass, return a null profile. + if (weightBy_ /= weightByMass) return + ! Create the mass distribution. + allocate(massDistributionEinasto :: massDistribution_) + select type(massDistribution_) + type is (massDistributionEinasto) + basic => node%basic () + darkMatterProfile => node%darkMatterProfile() + !![ + + + massDistributionEinasto( & + & mass =basic %mass ( ), & + & virialRadius =self %darkMatterHaloScale_%radiusVirial (node), & + & scaleLength =darkMatterProfile%scale ( ), & + & shapeParameter=darkMatterProfile%shape ( ), & + & componentType= componentTypeDarkHalo , & + & massType = massTypeDark & + & ) + + + !!] + end select + allocate(kinematicsDistribution_) + !![ + + + kinematicsDistributionCollisionless() + + + !!] + call massDistribution_%setKinematicsDistribution(kinematicsDistribution_) + !![ + + !!] return - end subroutine einastoRadialVelocityDispersionTabulate + end function einastoGet diff --git a/source/dark_matter_profiles_DMO.F90 b/source/dark_matter_profiles_DMO.F90 index 4888192a28..319ffee4b8 100644 --- a/source/dark_matter_profiles_DMO.F90 +++ b/source/dark_matter_profiles_DMO.F90 @@ -25,180 +25,27 @@ module Dark_Matter_Profiles_DMO !!{ Provides an object that implements dark matter halo profiles. !!} - use :: Dark_Matter_Halo_Scales , only : darkMatterHaloScale , darkMatterHaloScaleClass - use :: Dark_Matter_Profiles_Generic, only : darkMatterProfileGeneric - use :: Galacticus_Nodes , only : treeNode - use :: Galactic_Structure_Options , only : enumerationStructureErrorCodeType + use :: Dark_Matter_Halo_Scales , only : darkMatterHaloScale , darkMatterHaloScaleClass + use :: Galacticus_Nodes , only : treeNode + use :: Mass_Distributions , only : massDistributionClass , massDistributionHeatingClass + use :: Galactic_Structure_Options, only : enumerationStructureErrorCodeType, enumerationWeightByType private !![ darkMatterProfileDMO - darkMatterProfileGeneric Dark Matter Only Halo Profiles Class providing dark matter-only halo profiles. NFW - - Returns the density (in $M_\odot$ Mpc$^{-3}$) in the dark matter profile of {\normalfont \ttfamily node} at the given {\normalfont \ttfamily radius} (given in units of Mpc). - double precision + + Return the mass distribution of the dark matter-only profile. + class(massDistributionClass) yes - type (treeNode), intent(inout) :: node - double precision , intent(in ) :: radius - - - Returns the logarithmic slope of the density profile in the dark matter profile of {\normalfont \ttfamily node} at the given {\normalfont \ttfamily radius} (given in units of Mpc). - double precision - yes - type (treeNode), intent(inout) :: node - double precision , intent(in ) :: radius - - - Returns the {\normalfont \ttfamily m}$^\mathrm{th}$ radial moment of the dark matter profile of {\normalfont \ttfamily node} optionally between the given {\normalfont \ttfamily radiusMinimum} and {\normalfont \ttfamily radiusMaximum} (given in units of Mpc). - double precision - yes - type (treeNode), intent(inout) :: node - double precision , intent(in ) :: moment - double precision , intent(in ), optional :: radiusMinimum, radiusMaximum - - - Return the total energy for the given {\normalfont \ttfamily node} in units of $M_\odot$ km$^2$ s$^{-2}$. - double precision - yes - type(treeNode), intent(inout) :: node - - - Returns the relation between specific angular momentum and rotation velocity (assuming a rotation velocity that is constant in radius) for the given {\normalfont \ttfamily node}. Specifically, the normalization, $A$, returned is such that $V_\mathrm{rot} = A J/M$ - double precision - yes - type (treeNode), intent(inout) :: node - - - Returns the radius (in Mpc) in the dark matter profile of {\normalfont \ttfamily node} at which the specific angular momentum of a circular orbit equals {\normalfont \ttfamily specificAngularMomentum} (specified in units of km s$^{-1}$ Mpc. - double precision - yes - type (treeNode), intent(inout) :: node - double precision , intent(in ) :: specificAngularMomentum - - - Returns the circular velocity (in km/s) in the dark matter profile of {\normalfont \ttfamily node} at the given {\normalfont \ttfamily radius} (given in units of Mpc). - double precision - yes - type (treeNode), intent(inout) :: node - double precision , intent(in ) :: radius - - - Returns the radius (in Mpc) at which the maximum circular velocity is achieved in the dark matter profile of {\normalfont \ttfamily node}. - double precision - yes - type (treeNode), intent(inout) :: node - - - Returns the maximum circular velocity (in km/s) in the dark matter profile of {\normalfont \ttfamily node}. - double precision - yes - type (treeNode), intent(inout) :: node - - - Returns the radial velocity dispersion (in km/s) in the dark matter profile of {\normalfont \ttfamily node} at the given {\normalfont \ttfamily radius} (given in units of Mpc). - double precision - yes - type (treeNode), intent(inout) :: node - double precision , intent(in ) :: radius - - - Returns the gravitational potential (in (km/s)$^2$) in the dark matter profile of {\normalfont \ttfamily node} at the given {\normalfont \ttfamily radius} (given in units of Mpc). - double precision - yes - type (treeNode ), intent(inout), target :: node - double precision , intent(in ) :: radius - type (enumerationStructureErrorCodeType), intent( out), optional :: status - - - Returns the enclosed mass (in $M_\odot$) in the dark matter profile of {\normalfont \ttfamily node} at the given {\normalfont \ttfamily radius} (given in units of Mpc). - double precision - yes - type (treeNode), intent(inout) :: node - double precision , intent(in ) :: radius - - - Returns the radius (in Mpc) enclosing a given density threshold (in $M_\odot \hbox{Mpc}^{-3}$) in the dark matter profile of {\normalfont \ttfamily node}. - double precision - yes - yes - type (treeNode), intent(inout), target :: node - double precision , intent(in ) :: density - - - Returns the normalized Fourier space density profile of the dark matter profile of {\normalfont \ttfamily node} at the given {\normalfont \ttfamily waveNumber} (given in units of Mpc$^{-1}$). - double precision - yes - type (treeNode), intent(inout), target :: node - double precision , intent(in ) :: wavenumber - - - Returns the freefall radius (in Mpc) corresponding to the given {\normalfont \ttfamily time} (in Gyr) in {\normalfont \ttfamily node}. - double precision - yes - yes - type (treeNode), intent(inout), target :: node - double precision , intent(in ) :: time - - - Returns the rate of increase of the freefall radius (in Mpc/Gyr) corresponding to the given {\normalfont \ttfamily time} (in Gyr) in {\normalfont \ttfamily node}. - double precision - yes - yes - type (treeNode), intent(inout), target :: node - double precision , intent(in ) :: time - - - Returns the radius (in Mpc) enclosing a given mass (in $M_\odot$) in the dark matter profile of {\normalfont \ttfamily node}. - double precision - yes - yes - type (treeNode), intent(inout), target :: node - double precision , intent(in ) :: mass - Root_Finder Kind_Numbers - - double precision :: radiusGuess - type (rootFinder), save :: finder - logical , save :: finderConstructed=.false. - double precision , save :: radiusPrevious =-huge(0.0d0) - integer (kind_int8 ), save :: uniqueIDPrevious =-1_kind_int8 - !$omp threadprivate(finder,finderConstructed,radiusPrevious,uniqueIDPrevious) - if(mass <= 0.0d0) then - darkMatterProfileDMORadiusEnclosingMass=0.0d0 - return - end if - ! Initialize the root finder. - if (.not.finderConstructed) then - finder=rootFinder( & - & rootFunction =darkMatterProfileDMOEnclosedMassRoot, & - & rangeExpandDownward =0.5d0 , & - & rangeExpandUpward =2.0d0 , & - & rangeExpandDownwardSignExpect=rangeExpandSignExpectNegative , & - & rangeExpandUpwardSignExpect =rangeExpandSignExpectPositive , & - & rangeExpandType =rangeExpandMultiplicative , & - & toleranceAbsolute =0.0d0 , & - & toleranceRelative =1.0d-6 & - & ) - finderConstructed=.true. - end if - if (node%uniqueID() == uniqueIDPrevious) then - radiusGuess =radiusPrevious - else - radiusGuess =self%darkMatterHaloScale_%radiusVirial(node) - uniqueIDPrevious=node %uniqueID ( ) - end if - darkMatterProfileDMOSelf => self - darkMatterProfileDMONode => node - darkMatterProfileDMOMass_ = mass - radiusPrevious=finder%find(rootGuess=radiusGuess) - darkMatterProfileDMORadiusEnclosingMass=radiusPrevious - return - + type (treeNode ), intent(inout) :: node + type (enumerationWeightByType), intent(in ), optional :: weightBy + integer , intent(in ), optional :: weightIndex !!] @@ -211,49 +58,13 @@ module Dark_Matter_Profiles_DMO Class providing models of heating of dark matter profiles. null - - The specific energy of heating at the given {\normalfont \ttfamily radius} in the given {\normalfont \ttfamily node}. - double precision - yes - type (treeNode ), intent(inout) :: node - double precision , intent(in ) :: radius - class (darkMatterProfileDMOClass), intent(inout) :: darkMatterProfileDMO_ - - - The gradient of the specific energy of heating at the given {\normalfont \ttfamily radius} in the given {\normalfont \ttfamily node}. - double precision - yes - type (treeNode ), intent(inout) :: node - double precision , intent(in ) :: radius - class (darkMatterProfileDMOClass), intent(inout) :: darkMatterProfileDMO_ - - - Returns true if the specific energy is zero everywhere in the given {\normalfont \ttfamily node}. - logical + + Return the dark matter profile heating in the dark matter-only profile. + class(massDistributionHeatingClass) yes - type (treeNode ), intent(inout) :: node - class(darkMatterProfileDMOClass), intent(inout) :: darkMatterProfileDMO_ + type(treeNode), intent(inout) :: node !!] - ! Module-scope variables used in root finding. - class (darkMatterProfileDMOClass), pointer :: darkMatterProfileDMOSelf => null() - type (treeNode ), pointer :: darkMatterProfileDMONode => null() - double precision :: darkMatterProfileDMOMass_ - !$omp threadprivate(darkMatterProfileDMOSelf,darkMatterProfileDMONode,darkMatterProfileDMOMass_) - -contains - - double precision function darkMatterProfileDMOEnclosedMassRoot(radius) - !!{ - Root function used in solving for the radius that encloses a given mass. - !!} - implicit none - double precision,intent(in) :: radius - - darkMatterProfileDMOEnclosedMassRoot=darkMatterProfileDMOSelf%enclosedMass(darkMatterProfileDMONode,radius)-darkMatterProfileDMOMass_ - return - end function darkMatterProfileDMOEnclosedMassRoot - end module Dark_Matter_Profiles_DMO diff --git a/source/dark_matter_profiles_DMO.NFW.F90 b/source/dark_matter_profiles_DMO.NFW.F90 index a076fc6699..5bd95a7062 100644 --- a/source/dark_matter_profiles_DMO.NFW.F90 +++ b/source/dark_matter_profiles_DMO.NFW.F90 @@ -21,19 +21,15 @@ An implementation of \cite{navarro_universal_1997} dark matter halo profiles. !!} - use :: Kind_Numbers, only : kind_int8 - use :: Tables , only : table1D , table1DLogarithmicLinear + use :: Dark_Matter_Halo_Scales, only : darkMatterHaloScaleClass !![ - A dark matter profile DMO class which implements the \gls{nfw} density profile \citep{navarro_universal_1997}: - \begin{equation} - \rho_\mathrm{dark matter}(r) \propto \left({r\over r_\mathrm{s}}\right)^{-1} \left[1 + \left({r\over r_\mathrm{s}}\right) - \right]^{-2}, - \end{equation} - normalized such that the total mass of the \gls{node} is enclosed with the virial radius and with the scale length - $r_\mathrm{s} = r_\mathrm{virial}/c$ where $c$ is the halo concentration (see \refPhysics{darkMatterProfileConcentration}). + A dark matter profile DMO class which builds \refClass{} objects to implement the \gls{nfw} density profile + \citep{navarro_universal_1997}, normalized such that the total mass of the \gls{node} is enclosed with the virial radius and + with the scale length $r_\mathrm{s} = r_\mathrm{virial}/c$ where $c$ is the halo concentration (see + \refPhysics{darkMatterProfileConcentration}). !!] @@ -42,95 +38,11 @@ A dark matter halo profile class implementing \cite{navarro_universal_1997} dark matter halos. !!} private - ! Minimum and maximum concentrations to tabulate. - double precision :: concentrationMinimum , concentrationMaximum - ! Minimum and maximum radii to tabulate. - double precision :: freefallRadiusMinimum , radiusMinimum - double precision :: freefallRadiusMaximum , radiusMaximum - double precision :: freefallTimeMinimum , specificAngularMomentumMinimum - double precision :: freefallTimeMaximum , specificAngularMomentumMaximum - double precision :: enclosedDensityRadiusMinimum , enclosedDensityRadiusMaximum - double precision :: enclosedDensityMinimum , enclosedDensityMaximum - ! Tables of NFW properties. - logical :: nfwFreefallTableInitialized =.false., nfwInverseTableInitialized =.false., & - & nfwTableInitialized =.false., nfwEnclosedDensityTableInitialized =.false. - integer :: nfwFreefallTableNumberPoints , nfwInverseTableNumberPoints , & - & nfwTableNumberPoints , nfwEnclosedDensityTableNumberPoints - type (table1DLogarithmicLinear) :: nfwConcentrationTable - ! Tables. - type (table1DLogarithmicLinear) :: nfwFreeFall , nfwSpecificAngularMomentum , & - & nfwEnclosedDensity - class (table1D ), allocatable :: nfwFreefallInverse , nfwSpecificAngularMomentumInverse , & - & nfwEnclosedDensityInverse - ! Module variables used in integrations. - double precision :: concentrationParameter , radiusStart - ! Record of unique ID of node which we last computed results for. - integer (kind=kind_int8 ) :: lastUniqueID - ! Record of whether or not quantities have been computed. - logical :: specificAngularMomentumScalingsComputed , maximumVelocityComputed - ! Stored values of computed quantities. - double precision :: specificAngularMomentumLengthScale , specificAngularMomentumScale , & - & concentrationPrevious , nfwNormalizationFactorPrevious , & - & maximumVelocityPrevious , enclosedDensityPrevious , & - & enclosingDensityRadiusPrevious , densityScalePrevious , & - & enclosedMassPrevious , enclosingMassRadiusPrevious , & - & massScalePrevious , circularVelocityPrevious , & - & circularVelocityRadiusPrevious , radialVelocityDispersionPrevious , & - & radialVelocityDispersionRadiusPrevious , enclosedMassScaleFreePrevious , & - & enclosedMassScaleFreeRadiusPrevious - logical :: velocityDispersionUseSeriesExpansion + class (darkMatterHaloScaleClass), pointer :: darkMatterHaloScale_ => null() + logical :: velocityDispersionUseSeriesExpansion contains - !![ - - - - - - - - - - - - - - - - - !!] - final :: nfwDestructor - procedure :: autoHook => nfwAutoHook - procedure :: calculationReset => nfwCalculationReset - procedure :: density => nfwDensity - procedure :: densityLogSlope => nfwDensityLogSlope - procedure :: radialMoment => nfwRadialMoment - procedure :: enclosedMass => nfwEnclosedMass - procedure :: radiusEnclosingDensity => nfwRadiusEnclosingDensity - procedure :: radiusEnclosingMass => nfwRadiusEnclosingMass - procedure :: potential => nfwPotential - procedure :: circularVelocity => nfwCircularVelocity - procedure :: radiusCircularVelocityMaximum => nfwRadiusCircularVelocityMaximum - procedure :: circularVelocityMaximum => nfwCircularVelocityMaximum - procedure :: radialVelocityDispersion => nfwRadialVelocityDispersion - procedure :: radiusFromSpecificAngularMomentum => nfwRadiusFromSpecificAngularMomentum - procedure :: rotationNormalization => nfwRotationNormalization - procedure :: energy => nfwEnergy - procedure :: kSpace => nfwKSpace - procedure :: freefallRadius => nfwFreefallRadius - procedure :: freefallRadiusIncreaseRate => nfwFreefallRadiusIncreaseRate - procedure :: profileEnergy => nfwProfileEnergy - procedure :: specificAngularMomentumScaleFree => nfwSpecificAngularMomentumScaleFree - procedure :: angularMomentumScaleFree => nfwAngularMomentumScaleFree - procedure :: enclosedMassScaleFree => nfwEnclosedMassScaleFree - procedure :: densityEnclosedByRadiusScaleFree => nfwDensityEnclosedByRadiusScaleFree - procedure :: densityScaleFree => nfwDensityScaleFree - procedure :: radialVelocityDispersionScaleFree => nfwRadialVelocityDispersionScaleFree - procedure :: radialVelocityDispersionScaleFreeSeriesExpansion => nfwRadialVelocityDispersionScaleFreeSeriesExpansion - procedure :: tabulate => nfwTabulate - procedure :: inverseAngularMomentum => nfwInverseAngularMomentum - procedure :: freefallTabulate => nfwFreefallTabulate - procedure :: freefallTimeScaleFree => nfwFreefallTimeScaleFree - procedure :: enclosedDensityTabulate => nfwEnclosedDensityTabulate + final :: nfwDestructor + procedure :: get => nfwGet end type darkMatterProfileDMONFW interface darkMatterProfileDMONFW @@ -141,14 +53,6 @@ module procedure nfwConstructorInternal end interface darkMatterProfileDMONFW - ! Number of points per decade of concentration in NFW tabulations. - integer, parameter :: tablePointsPerDecade =100 - integer, parameter :: inverseTablePointsPerDecade =100 - integer, parameter :: freefallTablePointsPerDecade =300 - integer, parameter :: enclosedDensityTablePointsPerDecade=100 - ! Indices for tabulated quantities. - integer, parameter :: concentrationEnergyIndex = 1, concentrationRotationNormalizationIndex=2 - contains function nfwConstructorParameters(parameters) result(self) @@ -193,22 +97,6 @@ function nfwConstructorInternal(velocityDispersionUseSeriesExpansion,darkMatterH !!] - self%enclosedMassScaleFreePrevious =-1.0d+0 - self%enclosedMassScaleFreeRadiusPrevious=-1.0d+0 - self%concentrationPrevious =-1.0d+0 - self%concentrationMinimum = 1.0d+0 - self%concentrationMaximum =20.0d+0 - self%freefallRadiusMinimum = 1.0d-3 - self%freefallRadiusMaximum = 1.0d+2 - self%radiusMinimum = 1.0d-3 - self%radiusMaximum = 1.0d+2 - self%enclosedDensityRadiusMinimum = 1.0d-3 - self%enclosedDensityRadiusMaximum = 1.0d+2 - self%nfwEnclosedDensityTableInitialized =.false. - self%nfwFreefallTableInitialized =.false. - self%nfwInverseTableInitialized =.false. - self%nfwTableInitialized =.false. - self%lastUniqueID =-1 ! Ensure that the dark matter profile component supports a "scale" property. if (.not.defaultDarkMatterProfileComponent%scaleIsGettable()) & & call Error_Report & @@ -220,1400 +108,79 @@ function nfwConstructorInternal(velocityDispersionUseSeriesExpansion,darkMatterH & ) // & & {introspection:location} & & ) - ! Initialize the tabulations. - call self%tabulate () - call self%inverseAngularMomentum() return end function nfwConstructorInternal - subroutine nfwAutoHook(self) - !!{ - Attach to the calculation reset event. - !!} - use :: Events_Hooks, only : calculationResetEvent, openMPThreadBindingAllLevels - implicit none - class(darkMatterProfileDMONFW), intent(inout) :: self - - call calculationResetEvent%attach(self,nfwCalculationReset,openMPThreadBindingAllLevels,label='darkMatterProfileDMONFW') - return - end subroutine nfwAutoHook - subroutine nfwDestructor(self) !!{ Destructor for the {\normalfont \ttfamily nfw} dark matter halo profile class. !!} - use :: Events_Hooks, only : calculationResetEvent implicit none type(darkMatterProfileDMONFW), intent(inout) :: self - if (self%nfwFreefallTableInitialized) then - call self%nfwFreeFall %destroy() - call self%nfwFreeFallInverse %destroy() - deallocate(self%nfwFreefallInverse ) - end if - if (self%nfwEnclosedDensityTableInitialized) then - call self%nfwEnclosedDensity %destroy() - call self%nfwEnclosedDensityInverse %destroy() - deallocate(self%nfwEnclosedDensityInverse ) - end if - if (self%nfwInverseTableInitialized ) then - call self%nfwSpecificAngularMomentum %destroy() - call self%nfwSpecificAngularMomentumInverse%destroy() - deallocate(self%nfwSpecificAngularMomentumInverse) - end if - if (self%nfwTableInitialized ) then - call self%nfwConcentrationTable %destroy() - end if !![ !!] - if (calculationResetEvent%isAttached(self,nfwCalculationReset)) call calculationResetEvent%detach(self,nfwCalculationReset) return end subroutine nfwDestructor - subroutine nfwCalculationReset(self,node,uniqueID) - !!{ - Reset the dark matter profile calculation. - !!} - use :: Kind_Numbers, only : kind_int8 - implicit none - class (darkMatterProfileDMONFW), intent(inout) :: self - type (treeNode ), intent(inout) :: node - integer(kind_int8 ), intent(in ) :: uniqueID - !$GLC attributes unused :: node - - self%specificAngularMomentumScalingsComputed=.false. - self%maximumVelocityComputed =.false. - self%enclosedDensityPrevious =-1.0d0 - self%densityScalePrevious =-1.0d0 - self%enclosedMassPrevious =-1.0d0 - self%massScalePrevious =-1.0d0 - self%circularVelocityRadiusPrevious =-1.0d0 - self%radialVelocityDispersionRadiusPrevious =-1.0d0 - self%lastUniqueID =uniqueID - return - end subroutine nfwCalculationReset - - subroutine nfwTabulate(self,concentration) - !!{ - Tabulate properties of the NFW halo profile which must be computed numerically. - !!} - implicit none - class (darkMatterProfileDMONFW), intent(inout) :: self - double precision , intent(in ), optional :: concentration - integer :: iConcentration - logical :: retabulate - double precision :: tableConcentration - - retabulate=.not.self%nfwTableInitialized - if (present(concentration)) then - if (concentration < self%concentrationMinimum) then - self%concentrationMinimum=0.5d0*concentration - retabulate=.true. - end if - if (concentration > self%concentrationMaximum) then - self%concentrationMaximum=2.0d0*concentration - retabulate=.true. - end if - end if - if (retabulate) then - ! Decide how many points to tabulate and allocate table arrays. - self%nfwTableNumberPoints=int(log10(self%concentrationMaximum/self%concentrationMinimum)*dble(tablePointsPerDecade))+1 - call self%nfwConcentrationTable%destroy() - call self%nfwConcentrationTable%create(self%concentrationMinimum,self%concentrationMaximum,self%nfwTableNumberPoints,2) - ! Loop over concentrations and populate tables. - do iConcentration=1,self%nfwTableNumberPoints - tableConcentration=self%nfwConcentrationTable%x(iConcentration) - call self%nfwConcentrationTable%populate( self%profileEnergy (tableConcentration),iConcentration,table=concentrationEnergyIndex ) - call self%nfwConcentrationTable%populate(tableConcentration/self%angularMomentumScaleFree(tableConcentration),iConcentration,table=concentrationRotationNormalizationIndex) - end do - ! Specify that tabulation has been made. - self%nfwTableInitialized=.true. - end if - return - end subroutine nfwTabulate - - subroutine nfwInverseAngularMomentum(self,specificAngularMomentum) - !!{ - Tabulates the specific angular momentum vs. radius in an NFW profile for rapid inversion. - !!} - implicit none - class (darkMatterProfileDMONFW), intent(inout) :: self - double precision , intent(in ), optional :: specificAngularMomentum - integer :: iRadius - logical :: retabulate - - retabulate=.not.self%nfwInverseTableInitialized - ! If the table has not yet been made, compute and store the specific angular momenta corresponding to the minimum and maximum - ! radii that will be tabulated by default. - if (retabulate) then - self%specificAngularMomentumMinimum=self%specificAngularMomentumScaleFree(self%radiusMinimum) - self%specificAngularMomentumMaximum=self%specificAngularMomentumScaleFree(self%radiusMaximum) - end if - if (present(specificAngularMomentum)) then - do while (specificAngularMomentum < self%specificAngularMomentumMinimum) - self%radiusMinimum =0.5d0*self%radiusMinimum - self%specificAngularMomentumMinimum=self%specificAngularMomentumScaleFree(self%radiusMinimum) - retabulate=.true. - end do - do while (specificAngularMomentum > self%specificAngularMomentumMaximum) - self%radiusMaximum =2.0d0*self%radiusMaximum - self%specificAngularMomentumMaximum=self%specificAngularMomentumScaleFree(self%radiusMaximum) - retabulate=.true. - end do - end if - if (retabulate) then - ! Decide how many points to tabulate and allocate table arrays. - self%nfwInverseTableNumberPoints=int(log10(self%radiusMaximum/self%radiusMinimum)*dble(inverseTablePointsPerDecade))+1 - ! Create a range of radii. - call self%nfwSpecificAngularMomentum%destroy( ) - call self%nfwSpecificAngularMomentum%create (self%radiusMinimum,self%radiusMaximum,self%nfwInverseTableNumberPoints) - ! Loop over radii and populate tables. - do iRadius=1,self%nfwInverseTableNumberPoints - call self%nfwSpecificAngularMomentum%populate( & - & self%specificAngularMomentumScaleFree(self%nfwSpecificAngularMomentum%x(iRadius)), & - & iRadius & - & ) - end do - call self%nfwSpecificAngularMomentum%reverse(self%nfwSpecificAngularMomentumInverse) - ! Specify that tabulation has been made. - self%nfwInverseTableInitialized=.true. - end if - return - end subroutine nfwInverseAngularMomentum - - double precision function nfwDensity(self,node,radius) - !!{ - Returns the density (in $M_\odot$ Mpc$^{-3}$) in the dark matter profile of {\normalfont \ttfamily node} at the given {\normalfont \ttfamily radius} (given - in units of Mpc). - !!} - use :: Galacticus_Nodes, only : nodeComponentBasic, nodeComponentDarkMatterProfile, treeNode - implicit none - class (darkMatterProfileDMONFW ), intent(inout) :: self - type (treeNode ), intent(inout) :: node - double precision , intent(in ) :: radius - class (nodeComponentBasic ), pointer :: basic - class (nodeComponentDarkMatterProfile), pointer :: darkMatterProfile - double precision :: radiusOverScaleRadius , scaleRadius, & - & virialRadiusOverScaleRadius - - basic => node %basic ( ) - darkMatterProfile => node %darkMatterProfile(autoCreate=.true.) - scaleRadius = darkMatterProfile%scale ( ) - radiusOverScaleRadius = radius /scaleRadius - virialRadiusOverScaleRadius = self %darkMatterHaloScale_%radiusVirial(node)/scaleRadius - nfwDensity = +self %densityScaleFree(radiusOverScaleRadius,virialRadiusOverScaleRadius) & - & *basic%mass ( ) & - & / scaleRadius**3 - return - end function nfwDensity - - double precision function nfwDensityLogSlope(self,node,radius) + function nfwGet(self,node,weightBy,weightIndex) result(massDistribution_) !!{ - Returns the logarithmic slope of the density in the dark matter profile of {\normalfont \ttfamily node} at the given - {\normalfont \ttfamily radius} (given in units of Mpc). + Return the dark matter mass distribution for the given {\normalfont \ttfamily node}. !!} - use :: Galacticus_Nodes, only : nodeComponentDarkMatterProfile, treeNode + use :: Galacticus_Nodes , only : nodeComponentBasic , nodeComponentDarkMatterProfile + use :: Galactic_Structure_Options, only : componentTypeDarkHalo, massTypeDark , weightByMass + use :: Mass_Distributions , only : massDistributionNFW , kinematicsDistributionNFW implicit none - class (darkMatterProfileDMONFW ), intent(inout) :: self - type (treeNode ), intent(inout) :: node - double precision , intent(in ) :: radius - class (nodeComponentDarkMatterProfile), pointer :: darkMatterProfile - double precision :: radiusOverScaleRadius, scaleRadius - !$GLC attributes unused :: self - - darkMatterProfile => node%darkMatterProfile(autoCreate=.true.) - scaleRadius = darkMatterProfile%scale() - radiusOverScaleRadius = radius/scaleRadius - nfwDensityLogSlope = -(1.0d0+3.0d0*radiusOverScaleRadius) & - & /(1.0d0+ radiusOverScaleRadius) - return - end function nfwDensityLogSlope - - double precision function nfwRadialMoment(self,node,moment,radiusMinimum,radiusMaximum) - !!{ - Returns the density (in $M_\odot$ Mpc$^{-3}$) in the dark matter profile of {\normalfont \ttfamily node} at the given {\normalfont \ttfamily radius} (given - in units of Mpc). - !!} - use :: Galacticus_Nodes , only : nodeComponentBasic, nodeComponentDarkMatterProfile, treeNode - use :: Numerical_Constants_Math, only : Pi - implicit none - class (darkMatterProfileDMONFW ), intent(inout) :: self - type (treeNode ), intent(inout) :: node - double precision , intent(in ) :: moment - double precision , intent(in ), optional :: radiusMinimum , radiusMaximum - class (nodeComponentBasic ) , pointer :: basic - class (nodeComponentDarkMatterProfile) , pointer :: darkMatterProfile - double precision :: scaleRadius , virialRadiusOverScaleRadius, & - & radiusMinimumActual , radiusMaximumActual - - radiusMinimumActual=0.0d0 - radiusMaximumActual=self%darkMatterHaloScale_%radiusVirial(node) - if (present(radiusMinimum)) radiusMinimumActual=radiusMinimum - if (present(radiusMaximum)) radiusMaximumActual=radiusMaximum - basic => node%basic ( ) - darkMatterProfile => node%darkMatterProfile(autoCreate=.true.) - scaleRadius =darkMatterProfile%scale() - virialRadiusOverScaleRadius =self%darkMatterHaloScale_%radiusVirial(node)/scaleRadius - nfwRadialMoment =+basic%mass() & - & *scaleRadius**(moment-2.0d0) & - & /( & - & +log(1.0d0+virialRadiusOverScaleRadius) & - & - virialRadiusOverScaleRadius & - & / (1.0d0+virialRadiusOverScaleRadius) & - & ) & - & /4.0d0 & - & /Pi & - & *( & - & +nfwRadialMomentScaleFree(radiusMaximumActual/scaleRadius) & - & -nfwRadialMomentScaleFree(radiusMinimumActual/scaleRadius) & - & ) - return - - contains - - double precision function nfwRadialMomentScaleFree(radius) - !!{ - Provides the scale-free part of the radial moment of the NFW density profile. - !!} - use :: Hypergeometric_Functions, only : Hypergeometric_2F1 - use :: Numerical_Comparison , only : Values_Agree - implicit none - double precision, intent(in ) :: radius - - if (Values_Agree(moment,0.0d0,absTol=1.0d-6)) then - ! Take the real part of this improper integral. The imaginary parts must cancel when taking differences to compute a - ! proper integral. - nfwRadialMomentScaleFree=+1.0d0/ (1.0d0+ radius ) & - & -2.0d0*real(atanh(dcmplx(1.0d0+2.0d0*radius,0.0d0))) - else if (Values_Agree(moment,1.0d0,absTol=1.0d-6)) then - nfwRadialMomentScaleFree=-1.0d0/ (1.0d0 +radius ) - else if (Values_Agree(moment,2.0d0,absTol=1.0d-6)) then - nfwRadialMomentScaleFree=+1.0d0/ (1.0d0 +radius ) & - & + log (1.0d0 +radius ) - else if (Values_Agree(moment,3.0d0,absTol=1.0d-6)) then - nfwRadialMomentScaleFree=+ radius & - & -1.0d0/ (1.0d0 +radius ) & - & -2.0d0*log (1.0d0 +radius ) - else - nfwRadialMomentScaleFree=+(1.0d0+radius)**(moment-1.0d0) & - & /moment & - & / (moment-1.0d0) & - & *( & - & - moment & - & * Hypergeometric_2F1([1.0d0-moment,-moment],[2.0d0-moment],1.0d0/(1.0d0+radius)) & - & +(1.0d0+radius) & - & *(moment-1.0d0) & - & *( & - & +(radius/(1.0d0+radius))**moment & - & -Hypergeometric_2F1([ -moment,-moment],[1.0d0-moment],1.0d0/(1.0d0+radius)) & - & ) & - & ) - end if - return - end function nfwRadialMomentScaleFree - - end function nfwRadialMoment - - double precision function nfwEnclosedMass(self,node,radius) - !!{ - Returns the enclosed mass (in $M_\odot$) in the dark matter profile of {\normalfont \ttfamily node} at the given {\normalfont \ttfamily radius} (given in - units of Mpc). - !!} - use :: Galacticus_Nodes, only : nodeComponentBasic, nodeComponentDarkMatterProfile, treeNode - implicit none - class (darkMatterProfileDMONFW ), intent(inout) :: self - type (treeNode ), intent(inout) :: node - double precision , intent(in ) :: radius - class (nodeComponentBasic ), pointer :: basic - class (nodeComponentDarkMatterProfile), pointer :: darkMatterProfile - double precision :: radiusOverScaleRadius , scaleRadius, & - & virialRadiusOverScaleRadius - - basic => node%basic ( ) - darkMatterProfile => node%darkMatterProfile(autoCreate=.true.) - scaleRadius = darkMatterProfile%scale() - radiusOverScaleRadius = radius /scaleRadius - virialRadiusOverScaleRadius = self%darkMatterHaloScale_%radiusVirial(node)/scaleRadius - nfwEnclosedMass = self%enclosedMassScaleFree(radiusOverScaleRadius,virialRadiusOverScaleRadius) & - & *basic%mass() - return - end function nfwEnclosedMass - - double precision function nfwPotential(self,node,radius,status) - !!{ - Returns the potential (in (km/s)$^2$) in the dark matter profile of {\normalfont \ttfamily node} at the given {\normalfont \ttfamily radius} (given in - units of Mpc). - !!} - use :: Galactic_Structure_Options, only : enumerationStructureErrorCodeType, structureErrorCodeSuccess - use :: Galacticus_Nodes , only : nodeComponentDarkMatterProfile - implicit none - class (darkMatterProfileDMONFW ), intent(inout) :: self - type (treeNode ), intent(inout), target :: node - double precision , intent(in ) :: radius - type (enumerationStructureErrorCodeType), intent( out), optional :: status - class (nodeComponentDarkMatterProfile ) , pointer :: darkMatterProfile - double precision , parameter :: radiusSmall =1.0d-10 - double precision :: radiusOverScaleRadius , radiusTerm, & - & virialRadiusOverScaleRadius - - if (present(status)) status=structureErrorCodeSuccess - darkMatterProfile => node%darkMatterProfile(autoCreate=.true.) - radiusOverScaleRadius = radius /darkMatterProfile%scale() - virialRadiusOverScaleRadius = self%darkMatterHaloScale_%radiusVirial(node)/darkMatterProfile%scale() - if (radiusOverScaleRadius < radiusSmall) then - ! Use a series solution for very small radii. - radiusTerm=1.0d0-0.5d0*radiusOverScaleRadius - else - ! Use the full expression for larger radii. - radiusTerm=log(1.0d0+radiusOverScaleRadius)/radiusOverScaleRadius - end if - nfwPotential=-virialRadiusOverScaleRadius & - & *radiusTerm & - & /( & - & +log(1.0d0+virialRadiusOverScaleRadius) & - & - virialRadiusOverScaleRadius & - & / (1.0d0+virialRadiusOverScaleRadius) & - & ) & - & *self%darkMatterHaloScale_%velocityVirial(node)**2 - return - end function nfwPotential - - double precision function nfwCircularVelocity(self,node,radius) - !!{ - Returns the circular velocity (in km/s) in the dark matter profile of {\normalfont \ttfamily node} at the given {\normalfont \ttfamily radius} (given in - units of Mpc). - !!} - use :: Numerical_Constants_Astronomical, only : gravitationalConstantGalacticus - implicit none - class (darkMatterProfileDMONFW), intent(inout) :: self - type (treeNode ), intent(inout) :: node - double precision , intent(in ) :: radius - - if (radius > 0.0d0) then - ! Check if node differs from previous one for which we performed calculations. - if (node%uniqueID() /= self%lastUniqueID) call self%calculationReset(node,node%uniqueID()) - ! Compute the circular velocity if the radius has changed. - if (radius /= self%circularVelocityRadiusPrevious) then - self%circularVelocityPrevious =sqrt(gravitationalConstantGalacticus*self%enclosedMass(node,radius)/radius) - self%circularVelocityRadiusPrevious=radius - end if - nfwCircularVelocity=self%circularVelocityPrevious - else - nfwCircularVelocity=0.0d0 - end if - return - end function nfwCircularVelocity - - double precision function nfwRadiusCircularVelocityMaximum(self,node) - !!{ - Returns the radius (in Mpc) at which the maximum circular velocity is achieved in the dark matter profile of {\normalfont \ttfamily node}. - !!} - use :: Galacticus_Nodes , only : nodeComponentDarkMatterProfile , treeNode - use :: Numerical_Constants_Astronomical, only : gravitationalConstantGalacticus - implicit none - class (darkMatterProfileDMONFW ), intent(inout) :: self - type (treeNode ), intent(inout) :: node - class (nodeComponentDarkMatterProfile), pointer :: darkMatterProfile - ! The radius (in scale-free units) at the peak of the NFW rotation curve. Numerical value found using Mathematica. - double precision , parameter :: radiusCircularVelocityMaximumScaleFree=2.162581587064612d0 - double precision :: scaleRadius - - darkMatterProfile => node %darkMatterProfile(autoCreate=.true.) - scaleRadius = darkMatterProfile%scale ( ) - nfwRadiusCircularVelocityMaximum = +radiusCircularVelocityMaximumScaleFree & - & *scaleRadius - return - end function nfwRadiusCircularVelocityMaximum - - double precision function nfwCircularVelocityMaximum(self,node) - !!{ - Returns the maximum circular velocity (in km/s) in the dark matter profile of {\normalfont \ttfamily node}. - !!} - use :: Galacticus_Nodes , only : nodeComponentBasic , nodeComponentDarkMatterProfile, treeNode - use :: Numerical_Constants_Astronomical, only : gravitationalConstantGalacticus - implicit none - class (darkMatterProfileDMONFW ), intent(inout) :: self - type (treeNode ), intent(inout) :: node - class (nodeComponentDarkMatterProfile), pointer :: darkMatterProfile - class (nodeComponentBasic ), pointer :: basic - ! The circular velocity (in scale-free units) at the peak of the NFW rotation curve. Numerical value found using Mathematica. - double precision , parameter :: circularVelocityMaximumScaleFree=0.4649909628174221d0 - double precision :: scaleRadius - - ! Check if node differs from previous one for which we performed calculations. - if (node%uniqueID() /= self%lastUniqueID) call self%calculationReset(node,node%uniqueID()) - ! Check if maximum velocity is already computed. Compute and store if not. - if (.not.self%maximumVelocityComputed) then - basic => node %basic ( ) - darkMatterProfile => node %darkMatterProfile(autoCreate=.true.) - scaleRadius = darkMatterProfile%scale ( ) - ! Ensure mass profile normalization factor has been computed. - call nfwMassNormalizationFactor(self,self%darkMatterHaloScale_%radiusVirial(node)/scaleRadius) - ! Evaluate the circular velocity at the peak of the rotation curve. - self%maximumVelocityPrevious=+circularVelocityMaximumScaleFree & - & *sqrt( & - & +gravitationalConstantGalacticus & - & *basic %mass () & - & *self %nfwNormalizationFactorPrevious & - & /scaleRadius & - & ) - self%maximumVelocityComputed= .true. - end if - nfwCircularVelocityMaximum=self%maximumVelocityPrevious - return - end function nfwCircularVelocityMaximum - - double precision function nfwRadialVelocityDispersion(self,node,radius) - !!{ - Returns the radial velocity dispersion (in km/s) in the dark matter profile of {\normalfont \ttfamily node} at the given - {\normalfont \ttfamily radius} (given in units of Mpc). - !!} - use :: Galacticus_Nodes, only : nodeComponentDarkMatterProfile, treeNode - implicit none - class (darkMatterProfileDMONFW ), intent(inout) :: self - type (treeNode ), intent(inout) :: node - double precision , intent(in ) :: radius - class (nodeComponentDarkMatterProfile) , pointer :: darkMatterProfile - double precision :: radiusOverScaleRadius , scaleRadius, & - & virialRadiusOverScaleRadius - - if (radius > 0.0d0) then - ! Check if node differs from previous one for which we performed calculations. - if (node%uniqueID() /= self%lastUniqueID) call self%calculationReset(node,node%uniqueID()) - ! Compute the radial velocity dispersion if the radius has changed. - if (radius /= self%radialVelocityDispersionRadiusPrevious) then - darkMatterProfile => node%darkMatterProfile(autoCreate=.true.) - scaleRadius = darkMatterProfile%scale() - radiusOverScaleRadius = radius /scaleRadius - virialRadiusOverScaleRadius = self%darkMatterHaloScale_%radiusVirial(node)/scaleRadius - if (self%velocityDispersionUseSeriesExpansion) then - self%radialVelocityDispersionPrevious=+self%radialVelocityDispersionScaleFreeSeriesExpansion(radiusOverScaleRadius,virialRadiusOverScaleRadius) & - & *self%darkMatterHaloScale_%velocityVirial(node) - else - self%radialVelocityDispersionPrevious=+self%radialVelocityDispersionScaleFree (radiusOverScaleRadius,virialRadiusOverScaleRadius) & - & *self%darkMatterHaloScale_%velocityVirial(node) - end if - end if - nfwRadialVelocityDispersion=self%radialVelocityDispersionPrevious - else - nfwRadialVelocityDispersion=0.0d0 - end if - return - end function nfwRadialVelocityDispersion - - double precision function nfwRadiusFromSpecificAngularMomentum(self,node,specificAngularMomentum) - !!{ - Returns the radius (in Mpc) in {\normalfont \ttfamily node} at which a circular orbit has the given {\normalfont \ttfamily specificAngularMomentum} (given - in units of km s$^{-1}$ Mpc). - !!} - use :: Galacticus_Nodes, only : nodeComponentDarkMatterProfile, treeNode - implicit none - class (darkMatterProfileDMONFW ), intent(inout) :: self - type (treeNode ), intent(inout) :: node - double precision , intent(in ) :: specificAngularMomentum - class (nodeComponentDarkMatterProfile), pointer :: darkMatterProfile - double precision :: specificAngularMomentumScaleFree - - ! Return immediately with zero radius for non-positive specific angular momenta. - if (specificAngularMomentum <= 0.0d0) then - nfwRadiusFromSpecificAngularMomentum=0.0d0 - return - end if - ! Check if node differs from previous one for which we performed calculations. - if (node%uniqueID() /= self%lastUniqueID) call self%calculationReset(node,node%uniqueID()) - ! Check if scalings are already computed. Compute and store if not. - if (.not.self%specificAngularMomentumScalingsComputed) then - ! Flag that scale quantities are now computed. - self%specificAngularMomentumScalingsComputed=.true. - - ! Get the dark matter profile. - darkMatterProfile => node%darkMatterProfile(autoCreate=.true.) - - ! Get the scale radius. - self%specificAngularMomentumLengthScale=darkMatterProfile%scale() - - ! Get the specific angular momentum scale. - self%specificAngularMomentumScale=self%specificAngularMomentumLengthScale*self%circularVelocity(node& - &,self%specificAngularMomentumLengthScale) - end if - - ! Compute the specific angular momentum in scale free units (using the scale length for distances the sqrt(G M(r_scale) / - ! r_scale) for velocities). - specificAngularMomentumScaleFree=specificAngularMomentum/self%specificAngularMomentumScale - ! Ensure that the interpolations exist and extend sufficiently far. - call self%inverseAngularMomentum(specificAngularMomentumScaleFree) - - ! Interpolate to get the dimensionless radius at which this specific angular momentum is found. - nfwRadiusFromSpecificAngularMomentum=self%nfwSpecificAngularMomentumInverse%interpolate(specificAngularMomentumScaleFree) - - ! Convert to a physical radius. - nfwRadiusFromSpecificAngularMomentum=nfwRadiusFromSpecificAngularMomentum*self%specificAngularMomentumLengthScale - return - end function nfwRadiusFromSpecificAngularMomentum - - double precision function nfwRotationNormalization(self,node) - !!{ - Return the normalization of the rotation velocity vs. specific angular momentum relation. - !!} - use :: Galacticus_Nodes, only : nodeComponentDarkMatterProfile, treeNode - implicit none - class (darkMatterProfileDMONFW ), intent(inout) :: self - type (treeNode ), intent(inout) :: node - class (nodeComponentDarkMatterProfile), pointer :: darkMatterProfile - double precision :: concentration - - ! Get components. - darkMatterProfile => node%darkMatterProfile(autoCreate=.true.) - - ! Find the concentration parameter of this halo. - concentration=self%darkMatterHaloScale_%radiusVirial(node)/darkMatterProfile%scale() - - ! Ensure that the interpolations exist and extend sufficiently far. - call self%tabulate(concentration) - - ! Find the rotation normalization by interpolation. - nfwRotationNormalization=self%nfwConcentrationTable%interpolate(concentration,table& - &=concentrationRotationNormalizationIndex)/self%darkMatterHaloScale_%radiusVirial(node) - return - end function nfwRotationNormalization - - double precision function nfwEnergy(self,node) - !!{ - Return the energy of an NFW halo density profile. - !!} - use :: Galacticus_Nodes, only : nodeComponentBasic, nodeComponentDarkMatterProfile, treeNode - implicit none - class (darkMatterProfileDMONFW ), intent(inout) :: self - type (treeNode ), intent(inout) :: node - class (nodeComponentDarkMatterProfile), pointer :: darkMatterProfile - class (nodeComponentBasic ), pointer :: basic - double precision :: concentration - - ! Get components. - basic => node%basic ( ) - darkMatterProfile => node%darkMatterProfile(autoCreate=.true.) - - ! Find the concentration parameter of this halo. - concentration=self%darkMatterHaloScale_%radiusVirial(node)/darkMatterProfile%scale() - - ! Ensure that the interpolations exist and extend sufficiently far. - call self%tabulate(concentration) - - ! Find the energy by interpolation. - nfwEnergy=+self %nfwConcentrationTable%interpolate (concentration,table=concentrationEnergyIndex) & - & *self %darkMatterHaloScale_ %velocityVirial(node )**2 & - & *basic %mass ( ) - return - end function nfwEnergy - - double precision function nfwAngularMomentumScaleFree(self,concentration) - !!{ - Returns the total angular momentum (in units of the virial mass times scale radius times [assumed constant] rotation speed) - in an NFW dark matter profile with given {\normalfont \ttfamily concentration}. This is given by: - \begin{equation} - J = \left. \int_0^c 4 \pi x^3 \rho(x) \d x \right/ \int_0^c 4 \pi x^2 \rho(x) \d x, - \end{equation} - where $x$ is radius in units of the scale radius and $c$ is concentration. This can be evaluated to give - \begin{equation} - J = \left. \left[ 1 + c - 2 \ln (1+c) - {1 \over 1+c} \right] \right/ \left[ \ln(1+c)-{c\over 1+c} \right]. - \end{equation} - !!} - implicit none - class (darkMatterProfileDMONFW), intent(inout) :: self - double precision , intent(in ) :: concentration - !$GLC attributes unused :: self - - nfwAngularMomentumScaleFree=(1.0d0+concentration-2.0d0*log(1.0d0+concentration)-1.0d0/(1.0d0+concentration)) & - &/(log(1.0d0+concentration)-concentration/(1.0d0+concentration)) - return - end function nfwAngularMomentumScaleFree - - double precision function nfwSpecificAngularMomentumScaleFree(self,radius) - !!{ - Returns the specific angular momentum, normalized to unit scale length and unit velocity at the scale radius, at position - {\normalfont \ttfamily radius} (in units of the scale radius) in an NFW profile. - !!} - implicit none - class (darkMatterProfileDMONFW), intent(inout) :: self - double precision , intent(in ) :: radius - double precision , parameter :: radiusSmall=1.0d-9 - !$GLC attributes unused :: self - - if (radius < radiusSmall) then - ! Use a series expansion solution for accuracy. - nfwSpecificAngularMomentumScaleFree=+radius**1.5d0 & - & /sqrt( & - & + 2.0d0 & - & *log(2.0d0) & - & - 1.0d0 & - & ) & - & *( & - & + 1.0d0 & - & - radius & - & *( & - & - 2.0d0/ 3.0d0 & - & + radius & - & *( & - & + 19.0d0/ 36.0d0 & - & + radius & - & *( & - & - 121.0d0/ 270.0d0 & - & +radius & - & *( & - & +5123.0d0/12960.0d0 & - & ) & - & ) & - & ) & - & ) & - & ) - else - ! Use the full solution. - nfwSpecificAngularMomentumScaleFree=sqrt(radius*self%enclosedMassScaleFree(radius,1.0d0)) - end if - return - end function nfwSpecificAngularMomentumScaleFree - - double precision function nfwEnclosedMassScaleFree(self,radius,concentration) - !!{ - Returns the enclosed mass (in units of the virial mass) in an NFW dark matter profile with given {\normalfont \ttfamily concentration} at the - given {\normalfont \ttfamily radius} (given in units of the scale radius). - !!} - implicit none - class (darkMatterProfileDMONFW), intent(inout) :: self - double precision , intent(in ) :: concentration , radius - double precision , parameter :: minimumRadiusForExactSolution =1.0d-6 - ! Precomputed NFW normalization factor for unit radius. - double precision , parameter :: nfwNormalizationFactorUnitRadius=log(2.0d0)-0.5d0 - - if (radius /= self%enclosedMassScaleFreeRadiusPrevious) then - self%enclosedMassScaleFreeRadiusPrevious=radius - if (radius == 1.0d0 ) then - self%enclosedMassScaleFreePrevious=nfwNormalizationFactorUnitRadius - else if (radius >= minimumRadiusForExactSolution) then - self%enclosedMassScaleFreePrevious=(log(1.0d0+radius)-radius/(1.0d0+radius)) - else - self%enclosedMassScaleFreePrevious=(radius**2)*(0.5d0+radius*(-2.0d0/3.0d0+radius*(0.75d0+radius*(-0.8d0)))) - end if - end if - nfwEnclosedMassScaleFree=self%enclosedMassScaleFreePrevious - ! Compute the mass profile normalization factor. - call nfwMassNormalizationFactor(self,concentration) - ! Evaluate the scale-free enclosed mass. - nfwEnclosedMassScaleFree=nfwEnclosedMassScaleFree*self%nfwNormalizationFactorPrevious - return - end function nfwEnclosedMassScaleFree - - subroutine nfwMassNormalizationFactor(self,concentration) - !!{ - Compute the normalization factor for the NFW mass profile. - !!} - implicit none - class (darkMatterProfileDMONFW), intent(inout) :: self - double precision , intent(in ) :: concentration - ! Precomputed NFW normalization factor for unit concentration. - double precision , parameter :: nfwNormalizationFactorUnitConcentration=1.0d0/(log(2.0d0)-0.5d0) - - ! Check if we were called with a different concentration compared to the previous call. - if (concentration /= self%concentrationPrevious) then - ! We were, so recompute the normalization factor. - if (concentration == 1.0d0) then - self%nfwNormalizationFactorPrevious=nfwNormalizationFactorUnitConcentration - else - self%nfwNormalizationFactorPrevious=1.0d0/(log(1.0d0+concentration)-concentration/(1.0d0+concentration)) - end if - self%concentrationPrevious=concentration - end if - return - end subroutine nfwMassNormalizationFactor - - double precision function nfwRadiusEnclosingDensity(self,node,density) - !!{ - Returns the radius (in units of the scale radius) in an NFW dark matter profile with given {\normalfont \ttfamily - concentration} which encloses a given density (in units of the virial mass per cubic scale radius). - !!} - use :: Galacticus_Nodes, only : nodeComponentBasic, nodeComponentDarkMatterProfile, treeNode - implicit none - class (darkMatterProfileDMONFW ), intent(inout), target :: self - type (treeNode ), intent(inout), target :: node - double precision , intent(in ) :: density - class (nodeComponentBasic ), pointer :: basic - class (nodeComponentDarkMatterProfile), pointer :: darkMatterProfile - double precision :: scaleRadius , densityScaleFree, & - & virialRadiusOverScaleRadius - - ! Check if node differs from previous one for which we performed calculations. - if (node%uniqueID() /= self%lastUniqueID) call self%calculationReset(node,node%uniqueID()) - ! Get scale radius if required. - if (self%densityScalePrevious < 0.0d0 .or. density /= self%enclosedDensityPrevious) then - darkMatterProfile => node %darkMatterProfile(autoCreate=.true.) - scaleRadius = darkMatterProfile%scale ( ) - ! Compute the density scale if necessary. - if (self%densityScalePrevious < 0.0d0) then - ! Extract profile parameters. - basic => node %basic ( ) - virialRadiusOverScaleRadius = self%darkMatterHaloScale_%radiusVirial(node)/scaleRadius - ! Compute density normalization scale. - self%densityScalePrevious=+scaleRadius **3 & - & /basic %mass ( ) & - & /self %enclosedMassScaleFree(1.0d0,virialRadiusOverScaleRadius) - end if - ! Compute radius enclosing density if necessary. - if (density /= self%enclosedDensityPrevious) then - self%enclosedDensityPrevious=density - ! Compute scaled density. - densityScaleFree=+ density & - & *self%densityScalePrevious - ! Ensure density table spans required range. - call self%enclosedDensityTabulate(densityScaleFree) - ! Interpolate in density table to find the required radius. - self%enclosingDensityRadiusPrevious=self%nfwEnclosedDensityInverse%interpolate(-densityScaleFree)*scaleRadius - end if - end if - nfwRadiusEnclosingDensity=self%enclosingDensityRadiusPrevious - return - end function nfwRadiusEnclosingDensity - - double precision function nfwRadiusEnclosingMass(self,node,mass) - !!{ - Returns the radius (in Mpc) in an NFW dark matter profile with given {\normalfont \ttfamily - concentration} which encloses a given mass (in $M_\odot$). - !!} - use :: Galacticus_Nodes, only : nodeComponentBasic, nodeComponentDarkMatterProfile, treeNode - use :: Lambert_Ws , only : Lambert_W0 - implicit none - class (darkMatterProfileDMONFW ), intent(inout), target :: self - type (treeNode ), intent(inout), target :: node - double precision , intent(in ) :: mass - class (nodeComponentBasic ), pointer :: basic - class (nodeComponentDarkMatterProfile), pointer :: darkMatterProfile - double precision , parameter :: massScaleFreeSmall =3.0d-4 - double precision :: scaleRadius , massScaleFree, & - & virialRadiusOverScaleRadius - - ! Check if node differs from previous one for which we performed calculations. - if (node%uniqueID() /= self%lastUniqueID) call self%calculationReset(node,node%uniqueID()) - ! Get scale radius if required. - if (self%massScalePrevious < 0.0d0 .or. mass /= self%enclosedMassPrevious) then - darkMatterProfile => node %darkMatterProfile(autoCreate=.true.) - scaleRadius = darkMatterProfile%scale ( ) - ! Compute the mass scale if necessary. - if (self%massScalePrevious < 0.0d0) then - ! Extract profile parameters. - basic => node%basic() - virialRadiusOverScaleRadius = self%darkMatterHaloScale_%radiusVirial(node)/scaleRadius - ! Compute the mass profile normalization factor. - call nfwMassNormalizationFactor(self,virialRadiusOverScaleRadius) - ! Compute mass normalization scale. - self%massScalePrevious = 1.0d0/basic%mass()/self%nfwNormalizationFactorPrevious - end if - ! Compute radius enclosing mass if necessary. - if (mass /= self%enclosedMassPrevious) then - self%enclosedMassPrevious = mass - ! Compute scaled mass. - massScaleFree =+mass & - & *self%massScalePrevious - ! Compute radius. - if (massScaleFree < massScaleFreeSmall) then - ! Use a series solution for very small radii. - self%enclosingMassRadiusPrevious=+ sqrt(2.0d0)*massScaleFree**0.5d0 & - & + 4.0d0/ 3.0d0 *massScaleFree & - & + 13.0d0/ 9.0d0/sqrt(2.0d0)*massScaleFree**1.5d0 & - & + 92.0d0/ 135.0d0 *massScaleFree**2 & - & + 313.0d0/ 540.0d0/sqrt(2.0d0)*massScaleFree**2.5d0 & - & + 1928.0d0/ 8505.0d0 *massScaleFree**3 & - & +56201.0d0/340200.0d0/sqrt(2.0d0)*massScaleFree**3.5d0 & - & + 358.0d0/ 1701.0d0 *massScaleFree**4 - else - self%enclosingMassRadiusPrevious=-( & - & +1.0d0/Lambert_W0(-exp(-1.0d0-massScaleFree)) & - & +1.0d0 & - & ) - end if - self%enclosingMassRadiusPrevious = self%enclosingMassRadiusPrevious * scaleRadius - end if - end if - nfwRadiusEnclosingMass=self%enclosingMassRadiusPrevious - return - end function nfwRadiusEnclosingMass - - double precision function nfwDensityEnclosedByRadiusScaleFree(self,radius) - !!{ - Returns the density (in units of the virial mass per cubic scale radius) in an NFW dark matter profile with given {\normalfont \ttfamily - concentration} which is enclosed a given radius (in units of the scale radius). - !!} - use :: Numerical_Constants_Math, only : Pi - implicit none - class (darkMatterProfileDMONFW), intent(inout) :: self - double precision , intent(in ) :: radius - - nfwDensityEnclosedByRadiusScaleFree=+3.0d0 & - & *self%enclosedMassScaleFree(radius,1.0d0) & - & /4.0d0 & - & /Pi & - & / radius **3 - return - end function nfwDensityEnclosedByRadiusScaleFree - - subroutine nfwEnclosedDensityTabulate(self,enclosedDensityScaleFree) - !!{ - Tabulates the enclosed density vs. radius for NFW halos. - !!} - implicit none - class (darkMatterProfileDMONFW), intent(inout) :: self - double precision , intent(in ) :: enclosedDensityScaleFree - logical :: retabulate - integer :: iRadius - - retabulate=.not.self%nfwEnclosedDensityTableInitialized - ! If the table has not yet been made, compute and store the enclosed density corresponding to the minimum and maximum radii - ! that will be tabulated by default. - if (retabulate) then - self%enclosedDensityMinimum=self%densityEnclosedByRadiusScaleFree(self%enclosedDensityRadiusMaximum) - self%enclosedDensityMaximum=self%densityEnclosedByRadiusScaleFree(self%enclosedDensityRadiusMinimum) - end if - do while (enclosedDensityScaleFree < self%enclosedDensityMinimum) - self%enclosedDensityRadiusMaximum=2.0d0*self%enclosedDensityRadiusMaximum - self%enclosedDensityMinimum=self%densityEnclosedByRadiusScaleFree(self%enclosedDensityRadiusMaximum) - retabulate=.true. - end do - do while (enclosedDensityScaleFree > self%enclosedDensityMaximum) - self%enclosedDensityRadiusMinimum=0.5d0*self%enclosedDensityRadiusMinimum - self%enclosedDensityMaximum=self%densityEnclosedByRadiusScaleFree(self%enclosedDensityRadiusMinimum) - retabulate=.true. - end do - if (retabulate) then - ! Decide how many points to tabulate and allocate table arrays. - self%nfwEnclosedDensityTableNumberPoints=int(log10(self%enclosedDensityRadiusMaximum/self%enclosedDensityRadiusMinimum)*dble(enclosedDensityTablePointsPerDecade))+1 - ! Create the table. - call self%nfwEnclosedDensity%destroy( ) - call self%nfwEnclosedDensity%create (self%enclosedDensityRadiusMinimum,self%enclosedDensityRadiusMaximum,self%nfwEnclosedDensityTableNumberPoints) - ! Loop over radii and populate tables. - do iRadius=1,self%nfwEnclosedDensityTableNumberPoints - call self%nfwEnclosedDensity%populate( & - & -self%densityEnclosedByRadiusScaleFree(self%nfwEnclosedDensity%x(iRadius)), & - & iRadius & - & ) - end do - call self%nfwEnclosedDensity%reverse(self%nfwEnclosedDensityInverse) - ! Specify that tabulation has been made. - self%nfwEnclosedDensityTableInitialized=.true. - end if - return - end subroutine nfwEnclosedDensityTabulate - - double precision function nfwDensityScaleFree(self,radius,concentration) - !!{ - Returns the density (in units such that the virial mass and scale length are unity) in an NFW dark matter profile with - given {\normalfont \ttfamily concentration} at the given {\normalfont \ttfamily radius} (given in units of the scale radius). - !!} - use :: Numerical_Constants_Math, only : Pi - implicit none - class (darkMatterProfileDMONFW), intent(inout) :: self - double precision , intent(in ) :: concentration, radius - !$GLC attributes unused :: self - - nfwDensityScaleFree=1.0d0/(log(1.0d0+concentration)-concentration/(1.0d0+concentration))/radius/(1.0d0+radius)**2/4.0d0/Pi - return - end function nfwDensityScaleFree - - double precision function nfwRadialVelocityDispersionScaleFree(self,radius,concentration) - !!{ - Returns the radial velocity dispersion (in units of the virial velocity) in an NFW dark matter profile with given - {\normalfont \ttfamily concentration} at the given {\normalfont \ttfamily radius} (given in units of the scale radius) - using the result derived by \citeauthor{lokas_properties_2001}~(\citeyear{lokas_properties_2001}; eqn.~14). Note that - approximate solutions are used at small and large radii. - !!} - use :: Dilogarithms , only : Dilogarithm - use :: Numerical_Constants_Math, only : Pi - implicit none - class (darkMatterProfileDMONFW), intent(inout) :: self - double precision , intent(in ) :: concentration, radius - double precision , parameter :: minimumRadiusForExactSolution =1.0d-2 - double precision , parameter :: maximumRadiusForExactSolution =1.0d2 - ! Precomputed NFW normalization factor for unit radius. - double precision , parameter :: nfwNormalizationFactorUnitRadius=-8.5d0+Pi**2-6.0d0*log(2.0d0)+6.0d0*log(2.0d0)**2 - double precision :: radialVelocityDispersionSquare - double precision :: logRadius, onePlusRadius, logOnePlusRadius - - if (radius == 1.0d0) then - radialVelocityDispersionSquare=nfwNormalizationFactorUnitRadius - else if (radius >= maximumRadiusForExactSolution) then - logRadius = log(radius) - radialVelocityDispersionSquare=+(- 3.0d0+ 4.0d0*logRadius)/( 16.0d0*radius ) & - & +( 69.0d0+ 20.0d0*logRadius)/( 200.0d0*radius**2) & - & +(- 97.0d0- 60.0d0*logRadius)/( 1200.0d0*radius**3) & - & +( 71.0d0+ 105.0d0*logRadius)/( 3675.0d0*radius**4) & - & +(- 1.0d0- 56.0d0*logradius)/( 3136.0d0*radius**5) & - & +(-1271.0d0+2520.0d0*logRadius)/(211680.0d0*radius**6) - else if (radius >= minimumRadiusForExactSolution) then - onePlusRadius = 1.0d0+radius - logRadius = log( radius) - logOnePlusRadius = log(onePlusRadius) - radialVelocityDispersionSquare=+0.5d0 & - & * radius & - & *onePlusRadius**2 & - & *( & - & +Pi**2 & - & -logRadius & - & -1.0d0/ radius & - & -1.0d0/onePlusRadius**2 & - & -6.0d0/onePlusRadius & - & +( & - & +1.0d0+ 1.0d0/radius**2 & - & - 4.0d0/radius & - & -2.0d0/onePlusRadius & - & ) & - & *logOnePlusRadius & - & +3.0d0 & - & *logOnePlusRadius**2 & - & +6.0d0 & - & *Dilogarithm(-radius) & - & ) - else if (radius > 0.0d0) then - logRadius = log(radius) - radialVelocityDispersionSquare=+ 1.0d0/ 4.0d0*(-23.0d0 + 2.0d0*Pi**2- 2.0d0*logRadius)*radius & - & + (-59.0d0/6.0d0 + Pi**2- logRadius)*radius**2 & - & + 1.0d0/ 24.0d0*(-101.0d0 +12.0d0*Pi**2-12.0d0*logRadius)*radius**3 & - & +11.0d0/ 60.0d0 *radius**4 & - & -13.0d0/ 240.0d0 *radius**5 & - & +37.0d0/1400.0d0 *radius**6 - else - radialVelocityDispersionSquare=0.0d0 - end if - nfwRadialVelocityDispersionScaleFree=sqrt(radialVelocityDispersionSquare) - ! Compute the normalization factor. - call nfwMassNormalizationFactor(self,concentration) - ! Evaluate the scale-free radial velocity dispersion. - nfwRadialVelocityDispersionScaleFree=+nfwRadialVelocityDispersionScaleFree & - & *sqrt( & - & +self%nfwNormalizationFactorPrevious & - & *concentration & - & ) - return - end function nfwRadialVelocityDispersionScaleFree - - double precision function nfwRadialVelocityDispersionScaleFreeSeriesExpansion(self,radius,concentration) - !!{ - Returns the radial velocity dispersion (in units of the virial velocity) in an NFW dark matter profile with given - {\normalfont \ttfamily concentration} at the given {\normalfont \ttfamily radius} (given in units of the scale radius) - using the result derived by \citeauthor{lokas_properties_2001}~(\citeyear{lokas_properties_2001}; eqn.~14). The - analytic solution is expanded around 0, 1/2, 1, 2, and infinity. The relative error of the approximate series is less - than $10^{-5}$. - !!} - use :: Numerical_Constants_Math, only : Pi - implicit none - class (darkMatterProfileDMONFW) , intent(inout) :: self - double precision , intent(in ) :: concentration , radius - double precision :: radialVelocityDispersionSquare - integer , parameter :: maximumExpansionOrder =7 - double precision , dimension(maximumExpansionOrder+1) :: coefficient , radiusPower - double precision :: logRadius - integer :: i - - if (radius == 0.0d0) then - radialVelocityDispersionSquare=0.0d0 - else - if (radius < 0.33d0) then - ! Expand around 0. - radiusPower(1)= 1.0d0 - radiusPower(2)= radius - logRadius = log(radius) - coefficient(1)= 0.0d0 - coefficient(2)= 1.0d0/ 4.0d0*(-23.0d0 + 2.0d0*Pi**2- 2.0d0*logRadius) - coefficient(3)= (-59.0d0/6.0d0 + Pi**2- logRadius) - coefficient(4)= 1.0d0/ 24.0d0*(-101.0d0 +12.0d0*Pi**2-12.0d0*logRadius) - coefficient(5)= 11.0d0/ 60.0d0 - coefficient(6)=-13.0d0/ 240.0d0 - coefficient(7)= 37.0d0/1400.0d0 - coefficient(8)=-17.0d0/1050.0d0 - else if (radius < 0.68d0) then - ! Expand around 1/2. - radiusPower(1)= 1.0d0 - radiusPower(2)= radius-0.5d0 - coefficient(1)= 9.2256912491493508d-2 - coefficient(2)= 1.8995942538987498d-2 - coefficient(3)=-6.1247239215578800d-2 - coefficient(4)= 9.7544538830827322d-2 - coefficient(5)=-1.4457663797045428d-1 - coefficient(6)= 2.1545129876370470d-1 - coefficient(7)=-3.2824371986452579d-1 - coefficient(8)= 5.1242111712986012d-1 - else if (radius < 1.35d0) then - ! Expand around 1. - radiusPower(1)= 1.0d0 - radiusPower(2)= radius-1.0d0 - coefficient(1)= 9.3439401238895310d-2 - coefficient(2)=-6.2683780821546887d-3 - coefficient(3)=-8.2007484513808621d-3 - coefficient(4)= 1.0119593363084506d-2 - coefficient(5)=-9.2481085050239271d-3 - coefficient(6)= 7.8754354146912774d-3 - coefficient(7)=-6.5855139302751235d-3 - coefficient(8)= 5.5035102596088475d-3 - else if (radius < 2.66d0) then - ! Expand around 2. - radiusPower(1)= 1.0d0 - radiusPower(2)= radius-2.0d0 - coefficient(1)= 8.4126434467263518d-2 - coefficient(2)=-9.8388986218866523d-3 - coefficient(3)= 6.1288152708705594d-4 - coefficient(4)= 4.3464937545102683d-4 - coefficient(5)=-3.4479664620159904d-4 - coefficient(6)= 1.8815165134120623d-4 - coefficient(7)=-9.2066324234421410d-5 - coefficient(8)= 4.3068151103206337d-5 - else - ! Expand around infinity. - radiusPower(1)= 1.0d0 - radiusPower(2)= 1.0d0/radius - logRadius = log(radius) - coefficient(1)= 0.0d0 - coefficient(2)=(- 3.0d0+ 4.0d0*logRadius)/ 16.0d0 - coefficient(3)=( 69.0d0+ 20.0d0*logRadius)/ 200.0d0 - coefficient(4)=(- 97.0d0- 60.0d0*logRadius)/ 1200.0d0 - coefficient(5)=( 71.0d0+ 105.0d0*logRadius)/ 3675.0d0 - coefficient(6)=(- 1.0d0- 56.0d0*logRadius)/ 3136.0d0 - coefficient(7)=(-1271.0d0+2520.0d0*logRadius)/211680.0d0 - coefficient(8)=( 341.0d0- 360.0d0*logRadius)/ 43200.0d0 - end if - do i=3, maximumExpansionOrder+1 - radiusPower(i)=radiusPower(i-1)*radiusPower(2) - end do - radialVelocityDispersionSquare=sum(coefficient*radiusPower) - end if - nfwRadialVelocityDispersionScaleFreeSeriesExpansion=sqrt(radialVelocityDispersionSquare) - ! Compute the normalization factor. - call nfwMassNormalizationFactor(self,concentration) - ! Evaluate the scale-free radial velocity dispersion. - nfwRadialVelocityDispersionScaleFreeSeriesExpansion=+nfwRadialVelocityDispersionScaleFreeSeriesExpansion & - & *sqrt( & - & +self%nfwNormalizationFactorPrevious & - & *concentration & - & ) - return - end function nfwRadialVelocityDispersionScaleFreeSeriesExpansion - - double precision function nfwProfileEnergy(self,concentration) - !!{ - Computes the total energy of an NFW profile halo of given {\normalfont \ttfamily concentration} using the methods of - \citeauthor{cole_hierarchical_2000}~(\citeyear{cole_hierarchical_2000}; their Appendix~A), except for potential energy - which is computed using the result derived by \citeauthor{mo_formation_1998}~(\citeyear{mo_formation_1998}; eqn.~23). - !!} - use :: Numerical_Constants_Math, only : Pi - use :: Numerical_Integration , only : integrator - implicit none - class (darkMatterProfileDMONFW), intent(inout) :: self - double precision , intent(in ) :: concentration - type (integrator ) :: integratorJeans , integratorKinetic - double precision :: jeansEquationIntegral , kineticEnergy , & - & kineticEnergyIntegral , potentialEnergy , & - & radiusMinimum , radiusMaximum , & - & concentrationParameter - - ! Compute the potential energy. - potentialEnergy=-0.5d0 & - & *( & - & +1.0d0 & - & -1.0d0 & - & / (1.0d0+concentration)**2 & - & -2.0d0*log(1.0d0+concentration) & - & / (1.0d0+concentration) & - & ) & - & /( & - & + concentration & - & / (1.0d0+concentration) & - & -log(1.0d0+concentration) & - & ) **2 - ! Compute the velocity dispersion at the virial radius. - radiusMinimum = concentration - radiusMaximum =100.0d0*concentration - concentrationParameter= concentration - integratorJeans =integrator (nfwJeansEquationIntegrand,toleranceRelative=1.0d-3) - jeansEquationIntegral =integratorJeans%integrate(radiusMinimum ,radiusMaximum ) - ! Compute the kinetic energy. - radiusMinimum =0.0d0 - radiusMaximum =concentration - concentrationParameter=concentration - integratorKinetic =integrator (nfwKineticEnergyIntegrand,toleranceRelative=1.0d-3) - kineticEnergyIntegral =integratorKinetic%integrate(radiusMinimum ,radiusMaximum ) - kineticEnergy =2.0d0*Pi*(jeansEquationIntegral*concentration**3+kineticEnergyIntegral) - ! Compute the total energy. - nfwProfileEnergy=(potentialEnergy+kineticEnergy)*concentration - return - - contains - - double precision function nfwKineticEnergyIntegrand(radius) - !!{ - Integrand for NFW profile kinetic energy. - !!} - implicit none - double precision, intent(in ) :: radius - - nfwKineticEnergyIntegrand=self%EnclosedMassScaleFree(radius,concentrationParameter) & - & *self%densityScaleFree (radius,concentrationParameter) & - & *radius - return - end function nfwKineticEnergyIntegrand - - double precision function nfwJeansEquationIntegrand(radius) - !!{ - Integrand for NFW profile Jeans equation. - !!} - implicit none - double precision, intent(in ) :: radius - - nfwJeansEquationIntegrand=self%enclosedMassScaleFree(radius,concentrationParameter) & - & *self%densityScaleFree (radius,concentrationParameter) & - & /radius**2 - return - end function nfwJeansEquationIntegrand - - end function nfwProfileEnergy - - double precision function nfwKSpace(self,node,waveNumber) - !!{ - Returns the Fourier transform of the NFW density profile at the specified {\normalfont \ttfamily waveNumber} (given in Mpc$^{-1}$), using the - expression given in \citeauthor{cooray_halo_2002}~(\citeyear{cooray_halo_2002}; eqn.~81). - !!} - use :: Exponential_Integrals, only : Cosine_Integral , Sine_Integral - use :: Galacticus_Nodes , only : nodeComponentDarkMatterProfile, treeNode - implicit none - class (darkMatterProfileDMONFW ), intent(inout) :: self - type (treeNode ), intent(inout), target :: node - double precision , intent(in ) :: waveNumber - class (nodeComponentDarkMatterProfile) , pointer :: darkMatterProfile - double precision :: concentration , radiusScale, & - & waveNumberScaleFree - - ! Get components. - darkMatterProfile => node%darkMatterProfile(autoCreate=.true.) - - ! Get the scale radius. - radiusScale=darkMatterProfile%scale() - - ! Compute the concentration parameter. - concentration=self%darkMatterHaloScale_%radiusVirial(node)/radiusScale - - ! Get the dimensionless wavenumber. - waveNumberScaleFree=waveNumber*radiusScale - - ! Compute the Fourier transformed profile. - nfwKSpace= ( & - & +sin( waveNumberScaleFree)*(Sine_Integral ((1.0d0+concentration)*waveNumberScaleFree)-Sine_Integral (waveNumberScaleFree)) & - & -sin(concentration*waveNumberScaleFree)/(1.0d0+concentration)/waveNumberScaleFree & - & +cos( waveNumberScaleFree)*(Cosine_Integral((1.0d0+concentration)*waveNumberScaleFree)-Cosine_Integral(waveNumberScaleFree)) & - & ) & - & /(log(1.0d0+concentration)-concentration/(1.0d0+concentration)) - return - end function nfwKSpace - - double precision function nfwFreefallRadius(self,node,time) - !!{ - Returns the freefall radius in the NFW density profile at the specified {\normalfont \ttfamily time} (given in Gyr). - !!} - use :: Galacticus_Nodes , only : nodeComponentDarkMatterProfile, treeNode - use :: Numerical_Constants_Astronomical, only : Mpc_per_km_per_s_To_Gyr - implicit none - class (darkMatterProfileDMONFW ), intent(inout), target :: self - type (treeNode ), intent(inout), target :: node - double precision , intent(in ) :: time - class (nodeComponentDarkMatterProfile), pointer :: darkMatterProfile - double precision :: concentration , freefallTimeScaleFree, & - & radiusScale , timeScale , & - & velocityScale - - ! For non-positive freefall times, return a zero freefall radius immediately. - if (time <= 0.0d0) then - nfwFreefallRadius=0.0d0 - return - end if - - ! Get components. - darkMatterProfile => node%darkMatterProfile(autoCreate=.true.) - - ! Get the scale radius. - radiusScale=darkMatterProfile%scale() - - ! Get the concentration. - concentration=self%darkMatterHaloScale_%radiusVirial(node)/radiusScale - - ! Get the virial velocity. - velocityScale=self%darkMatterHaloScale_%velocityVirial(node) - - ! Compute time scale. - timeScale=+Mpc_per_km_per_s_To_Gyr & - & *radiusScale & - & /velocityScale & - & /sqrt(concentration/(log(1.0d0+concentration)-concentration/(1.0d0+concentration))) - - ! Compute dimensionless time. - freefallTimeScaleFree=time/timeScale - - ! Ensure table is sufficiently extensive. - call self%freefallTabulate(freefallTimeScaleFree) - - ! Interpolate to get the freefall radius. - nfwFreefallRadius=self%nfwFreefallInverse%interpolate(freefallTimeScaleFree)*radiusScale - return - end function nfwFreefallRadius - - double precision function nfwFreefallRadiusIncreaseRate(self,node,time) - !!{ - Returns the rate of increase of the freefall radius in the NFW density profile at the specified {\normalfont \ttfamily time} (given in - Gyr). - !!} - use :: Galacticus_Nodes , only : nodeComponentDarkMatterProfile, treeNode - use :: Numerical_Constants_Astronomical, only : Mpc_per_km_per_s_To_Gyr - implicit none - class (darkMatterProfileDMONFW ), intent(inout), target :: self - type (treeNode ), intent(inout), target :: node - double precision , intent(in ) :: time - class (nodeComponentDarkMatterProfile), pointer :: darkMatterProfile - double precision :: concentration , freefallTimeScaleFree, & - & radiusScale , timeScale , & - & velocityScale - - ! For non-positive freefall times, return the limiting value for small radii. - if (time <= 0.0d0) then - nfwFreefallRadiusIncreaseRate=0.0d0 - return - end if - - ! Get components. - darkMatterProfile => node%darkMatterProfile(autoCreate=.true.) - - ! Get the scale radius. - radiusScale=darkMatterProfile%scale() - - ! Get the concentration. - concentration=self%darkMatterHaloScale_%radiusVirial(node)/radiusScale - - ! Get the virial velocity. - velocityScale=self%darkMatterHaloScale_%velocityVirial(node) - - ! Compute time scale. - timeScale=+Mpc_per_km_per_s_To_Gyr & - & *radiusScale & - & /velocityScale & - & /sqrt(concentration/(log(1.0d0+concentration)-concentration/(1.0d0+concentration))) - - ! Compute dimensionless time. - freefallTimeScaleFree=time/timeScale - - ! Ensure table is sufficiently extensive. - call self%freefallTabulate(freefallTimeScaleFree) - - ! Interpolate to get the freefall radius growth rate. - nfwFreefallRadiusIncreaseRate=self%nfwFreefallInverse%interpolateGradient(freefallTimeScaleFree)*radiusScale/timeScale - return - end function nfwFreefallRadiusIncreaseRate - - subroutine nfwFreefallTabulate(self,freefallTimeScaleFree) - !!{ - Tabulates the freefall time vs. freefall radius for NFW halos. - !!} - implicit none - class (darkMatterProfileDMONFW), intent(inout) :: self - double precision , intent(in ) :: freefallTimeScaleFree - logical :: retabulate - integer :: iRadius - - retabulate=.not.self%nfwFreefallTableInitialized - ! If the table has not yet been made, compute and store the freefall corresponding to the minimum and maximum - ! radii that will be tabulated by default. - if (retabulate) then - self%freefallTimeMinimum=self%freefallTimeScaleFree(self%freefallRadiusMinimum) - self%freefallTimeMaximum=self%freefallTimeScaleFree(self%freefallRadiusMaximum) - end if - do while (freefallTimeScaleFree < self%freefallTimeMinimum) - self%freefallRadiusMinimum=0.5d0*self%freefallRadiusMinimum - self%freefallTimeMinimum=self%freefallTimeScaleFree(self%freefallRadiusMinimum) - retabulate=.true. - end do - do while (freefallTimeScaleFree > self%freefallTimeMaximum) - self%freefallRadiusMaximum=2.0d0*self%freefallRadiusMaximum - self%freefallTimeMaximum=self%freefallTimeScaleFree(self%freefallRadiusMaximum) - retabulate=.true. - end do - if (retabulate) then - ! Decide how many points to tabulate and allocate table arrays. - self%nfwFreefallTableNumberPoints=int(log10(self%freefallRadiusMaximum/self%freefallRadiusMinimum)*dble(freefallTablePointsPerDecade))+1 - ! Create the table. - call self%nfwFreefall%destroy( ) - call self%nfwFreefall%create (self%freefallRadiusMinimum,self%freefallRadiusMaximum,self%nfwFreefallTableNumberPoints) - ! Loop over radii and populate tables. - do iRadius=1,self%nfwFreefallTableNumberPoints - call self%nfwFreefall%populate( & - & self%freefallTimeScaleFree(self%nfwFreefall%x(iRadius)), & - & iRadius & - & ) - end do - call self%nfwFreefall%reverse(self%nfwFreefallInverse) - ! Specify that tabulation has been made. - self%nfwFreefallTableInitialized=.true. - end if - return - end subroutine nfwFreefallTabulate - - double precision function nfwFreefallTimeScaleFree(self,radius) - !!{ - Compute the freefall time in a scale-free NFW halo. - !!} - use :: Numerical_Integration, only : integrator - implicit none - class (darkMatterProfileDMONFW), intent(inout) :: self - double precision , intent(in ) :: radius - double precision , parameter :: radiusSmall=4.0d-6 - type (integrator ) :: integrator_ - double precision :: radiusEnd , radiusStart - !$GLC attributes unused :: self + class (massDistributionClass ), pointer :: massDistribution_ + type (kinematicsDistributionNFW ), pointer :: kinematicsDistribution_ + class (darkMatterProfileDMONFW ), intent(inout) :: self + type (treeNode ), intent(inout) :: node + type (enumerationWeightByType ), intent(in ), optional :: weightBy + integer , intent(in ), optional :: weightIndex + class (nodeComponentBasic ), pointer :: basic + class (nodeComponentDarkMatterProfile), pointer :: darkMatterProfile + !![ + + !!] - if (radius > radiusSmall) then - ! Use the full solution. - radiusStart =radius - radiusEnd =0.0d0 - integrator_ =integrator (nfwFreefallTimeScaleFreeIntegrand,toleranceRelative=1.0d-3) - nfwFreefallTimeScaleFree=integrator_%integrate(radiusEnd ,radiusStart ) - else - ! Use an approximation here, found by taking series expansions of the logarithms in the integrand and keeping only the - ! first order terms. - nfwFreefallTimeScaleFree=2.0d0*sqrt(radius) - end if + ! Assume a null distribution by default. + massDistribution_ => null() + ! If weighting is not by mass, return a null profile. + if (weightBy_ /= weightByMass) return + ! Create the mass distribution. + allocate(massDistributionNFW :: massDistribution_) + select type(massDistribution_) + type is (massDistributionNFW) + basic => node%basic () + darkMatterProfile => node%darkMatterProfile() + !![ + + + massDistributionNFW( & + & mass =basic %mass ( ), & + & virialRadius =self %darkMatterHaloScale_%radiusVirial (node), & + & scaleLength =darkMatterProfile%scale ( ), & + & componentType= componentTypeDarkHalo , & + & massType = massTypeDark & + & ) + + + !!] + end select + allocate(kinematicsDistribution_) + !![ + + + kinematicsDistributionNFW( & + & useSeriesApproximation=self%velocityDispersionUseSeriesExpansion & + & ) + + + !!] + call massDistribution_%setKinematicsDistribution(kinematicsDistribution_) + !![ + + !!] return - - contains - - double precision function nfwFreefallTimeScaleFreeIntegrand(radius) - !!{ - Integrand function used for finding the free-fall time in NFW halos. - !!} - implicit none - double precision, intent(in ) :: radius - double precision, parameter :: radiusSmall =1.0d-6 - double precision, parameter :: radiusSmallFraction=1.0d-3 - double precision :: x - - if (radius < radiusSmall) then - ! Use a series approximation for small radii. - nfwFreefallTimeScaleFreeIntegrand=log(1.0d0+radiusStart)/radiusStart-1.0d0+radius*(0.5d0-radius/3.0d0) - else if (radius > radiusStart*(1.0d0-radiusSmallFraction)) then - ! Use a series approximation for radii close to the initial radius. - x=1.0d0-radius/radiusStart - nfwFreefallTimeScaleFreeIntegrand=+(1.0d0/(1.0d0+radiusStart)-log(1.0d0+radiusStart)/radiusStart)*x & - & +( & - & +0.5d0*radiusStart/(1.0d0+radiusStart)**2 & - & +(radiusStart-(1.0d0+radiusStart)*log(1.0d0+radiusStart)) & - & /radiusStart & - & /(1.0d0+radiusStart) & - & )*x**2 - else - ! Use full expression for larger radii. - nfwFreefallTimeScaleFreeIntegrand=log(1.0d0+radiusStart)/radiusStart-log(1.0d0+radius)/radius - end if - nfwFreefallTimeScaleFreeIntegrand=1.0d0/sqrt(-2.0d0*nfwFreefallTimeScaleFreeIntegrand) - return - end function nfwFreefallTimeScaleFreeIntegrand - - end function nfwFreefallTimeScaleFree + end function nfwGet diff --git a/source/dark_matter_profiles_DMO.Penarrubia2010.F90 b/source/dark_matter_profiles_DMO.Penarrubia2010.F90 index 6ab3c16b1f..c9e1567b5d 100644 --- a/source/dark_matter_profiles_DMO.Penarrubia2010.F90 +++ b/source/dark_matter_profiles_DMO.Penarrubia2010.F90 @@ -24,13 +24,14 @@ !![ - A dark matter profile DMO class which implements the \cite{penarrubia_impact_2010} density profile. + A dark matter profile DMO class which builds \refClass{massDistributionZhao1996} to implement the + \cite{penarrubia_impact_2010} density profile. - + - + !!] @@ -39,26 +40,16 @@ A dark matter halo profile class implementing \cite{penarrubia_impact_2010} dark matter halos. !!} private - double precision :: betaStripped , muRadius , & - & etaRadius , muVelocity , & - & etaVelocity , ratioRadiusMaximumRadiusScaleStripped , & - & ratioRadiusMaximumRadiusScaleUnstripped , ratioVelocityMaximumVelocityScaleUnstripped , & - & ratioVelocityMaximumVelocityScaleStripped , scaleRadiusPrevious , & - & normalizationPrevious - type (darkMatterProfileDMOZhao1996), pointer :: darkMatterProfileStripped => null(), darkMatterProfileUnstripped => null() - integer (kind_int8 ) :: uniqueIDPrevious + double precision :: betaStripped , muRadius , & + & etaRadius , muVelocity , & + & etaVelocity , ratioRadiusMaximumRadiusScaleStripped , & + & ratioRadiusMaximumRadiusScaleUnstripped + type (massDistributionZhao1996), pointer :: massDistributionStripped => null(), massDistributionUnstripped => null() contains - !![ - - - - !!] - final :: penarrubia2010Destructor - procedure :: autoHook => penarrubia2010AutoHook - procedure :: calculationReset => penarrubia2010CalculationReset - procedure :: exponents => penarrubia2010Exponents - procedure :: scaleRadius => penarrubia2010ScaleRadius - procedure :: normalization => penarrubia2010Normalization + final :: penarrubia2010Destructor + procedure :: exponents => penarrubia2010Exponents + procedure :: scaleRadius => penarrubia2010ScaleRadius + procedure :: normalization => penarrubia2010Normalization end type darkMatterProfileDMOPenarrubia2010 interface darkMatterProfileDMOPenarrubia2010 @@ -130,6 +121,11 @@ function penarrubia2010ConstructorParameters(parameters) result(self) parameters The parameter $\eta$ of the \cite{penarrubia_impact_2010} tidal track for $V_\mathrm{max}$. + + etaVelocity + parameters + The parameter $\eta$ of the \cite{penarrubia_impact_2010} tidal track for $V_\mathrm{max}$. + !!] self=darkMatterProfileDMOPenarrubia2010(alpha,beta,gamma,betaStripped,muRadius,etaRadius,muVelocity,etaVelocity,darkMatterHaloScale_) @@ -144,8 +140,7 @@ function penarrubia2010ConstructorInternal(alpha,beta,gamma,betaStripped,muRadiu !!{ Generic constructor for the {\normalfont \ttfamily penarrubia2010} dark matter halo profile class. !!} - use :: Galacticus_Nodes, only : nodeComponentBasic, nodeComponentDarkMatterProfile - use :: Kind_Numbers , only : kind_int8 + use :: Mass_Distributions, only : massDistributionZhao1996 implicit none type (darkMatterProfileDMOPenarrubia2010) :: self class (darkMatterHaloScaleClass ), intent(in ), target :: darkMatterHaloScale_ @@ -153,86 +148,35 @@ function penarrubia2010ConstructorInternal(alpha,beta,gamma,betaStripped,muRadiu & gamma , betaStripped, & & muRadius , etaRadius , & & muVelocity , etaVelocity - type (treeNode ), pointer :: node - class (nodeComponentBasic ), pointer :: basic - class (nodeComponentDarkMatterProfile ), pointer :: darkMatterProfile !![ !!] - ! Compute the mapping between scale radius and radius of peak velocity in the scale-free stripped and unstripped profiles. - node => treeNode ( ) - basic => node %basic (autoCreate=.true.) - darkMatterProfile => node %darkMatterProfile(autoCreate=.true.) - call basic %timeSet (1.0d0) - call basic %timeLastIsolatedSet(1.0d0) - call basic %massSet (1.0d0) - call darkMatterProfile%scaleSet (1.0d0) - allocate(self%darkMatterProfileStripped ) - allocate(self%darkMatterProfileUnstripped) + allocate(self%massDistributionStripped ) + allocate(self%massDistributionUnstripped) !![ - - + + !!] - self%ratioRadiusMaximumRadiusScaleStripped =+self%darkMatterProfileStripped %radiusCircularVelocityMaximum(node ) - self%ratioRadiusMaximumRadiusScaleUnstripped =+self%darkMatterProfileUnstripped%radiusCircularVelocityMaximum(node ) - self%ratioVelocityMaximumVelocityScaleStripped =+self%darkMatterProfileStripped % circularVelocityMaximum(node ) & - & /self%darkMatterProfileStripped % circularVelocity (node,radius=1.0d0) - self%ratioVelocityMaximumVelocityScaleUnstripped=+self%darkMatterProfileUnstripped% circularVelocityMaximum(node ) & - & /self%darkMatterProfileUnstripped% circularVelocity (node,radius=1.0d0) - call node%destroy() - deallocate(node) - ! Initialize state. - self%specialCase =specialCaseGeneral - self%scaleRadiusPrevious =-1.0d0 - self%normalizationPrevious=-1.0d0 - self%uniqueIDPrevious =-1_kind_int8 + self%ratioRadiusMaximumRadiusScaleStripped =+self%massDistributionStripped %radiusRotationCurveMaximum() + self%ratioRadiusMaximumRadiusScaleUnstripped=+self%massDistributionUnstripped%radiusRotationCurveMaximum() return end function penarrubia2010ConstructorInternal - subroutine penarrubia2010AutoHook(self) - !!{ - Attach to the calculation reset event. - !!} - use :: Events_Hooks, only : calculationResetEvent, openMPThreadBindingAllLevels - implicit none - class(darkMatterProfileDMOPenarrubia2010), intent(inout) :: self - - call calculationResetEvent%attach(self,penarrubia2010CalculationReset,openMPThreadBindingAllLevels,label='darkMatterProfileDMOPenarrubia2010') - return - end subroutine penarrubia2010AutoHook - subroutine penarrubia2010Destructor(self) !!{ Destructor for the {\normalfont \ttfamily penarrubia2010} dark matter halo profile class. !!} implicit none type(darkMatterProfileDMOPenarrubia2010), intent(inout) :: self - + !![ - - + + !!] return end subroutine penarrubia2010Destructor - - subroutine penarrubia2010CalculationReset(self,node,uniqueID) - !!{ - Reset the dark matter profile calculation. - !!} - use :: Kind_Numbers, only : kind_int8 - implicit none - class (darkMatterProfileDMOPenarrubia2010), intent(inout) :: self - type (treeNode ), intent(inout) :: node - integer(kind_int8 ), intent(in ) :: uniqueID - !$GLC attributes unused :: node - - self%scaleRadiusPrevious =-1.0d0 - self%normalizationPrevious=-1.0d0 - self%uniqueIDPrevious =uniqueID - return - end subroutine penarrubia2010CalculationReset - + subroutine penarrubia2010Exponents(self,node,alpha,beta,gamma) !!{ Compute the exponents of the {\normalfont \ttfamily penarrubia2010} dark matter halo profile. @@ -260,7 +204,7 @@ subroutine penarrubia2010Exponents(self,node,alpha,beta,gamma) return end subroutine penarrubia2010Exponents - double precision function penarrubia2010ScaleRadius(self,node) + double precision function penarrubia2010ScaleRadius(self,node) result(radiusScale) !!{ Compute the scale radius of the {\normalfont \ttfamily penarrubia2010} dark matter halo profile. !!} @@ -273,79 +217,71 @@ double precision function penarrubia2010ScaleRadius(self,node) class (nodeComponentDarkMatterProfile ), pointer :: darkMatterProfile double precision :: fractionMassBound , fractionRadiusMaximum, & & ratioRadiusMaximumRadiusScale - - if (node%uniqueID() /= self%uniqueIDPrevious) call self%calculationReset(node,node%uniqueID()) - if (self%scaleRadiusPrevious < 0.0d0) then - basic => node %basic () - satellite => node %satellite () - darkMatterProfile => node %darkMatterProfile() - fractionMassBound = +satellite%boundMass () & - & /basic %mass () - fractionRadiusMaximum = +2.0d0 **self%muRadius & - & * fractionMassBound**self%etaRadius & - & /( & - & +1.0d0 & - & +fractionMassBound & - & ) **self%muRadius - if (fractionMassBound >= fractionMassTransition) then - ratioRadiusMaximumRadiusScale=self%ratioRadiusMaximumRadiusScaleUnstripped - else - ratioRadiusMaximumRadiusScale=self%ratioRadiusMaximumRadiusScaleStripped - end if - self%scaleRadiusPrevious=+ fractionRadiusMaximum & - & *self %ratioRadiusMaximumRadiusScaleUnstripped & - & / ratioRadiusMaximumRadiusScale & - & *darkMatterProfile%scale () + + basic => node %basic () + satellite => node %satellite () + darkMatterProfile => node %darkMatterProfile() + fractionMassBound = +satellite%boundMass () & + & /basic %mass () + fractionRadiusMaximum = +2.0d0 **self%muRadius & + & * fractionMassBound**self%etaRadius & + & /( & + & +1.0d0 & + & +fractionMassBound & + & ) **self%muRadius + if (fractionMassBound >= fractionMassTransition) then + ratioRadiusMaximumRadiusScale=self%ratioRadiusMaximumRadiusScaleUnstripped + else + ratioRadiusMaximumRadiusScale=self%ratioRadiusMaximumRadiusScaleStripped end if - penarrubia2010ScaleRadius=self%scaleRadiusPrevious + radiusScale=+ fractionRadiusMaximum & + & *self %ratioRadiusMaximumRadiusScaleUnstripped & + & / ratioRadiusMaximumRadiusScale & + & *darkMatterProfile%scale () return end function penarrubia2010ScaleRadius - + double precision function penarrubia2010Normalization(self,node) !!{ - Compute the normalization of the {\normalfont \ttfamily penarrubia2010} dark matter halo profile. + Compute the mass normalization of the {\normalfont \ttfamily penarrubia2010} dark matter halo profile. !!} use :: Galacticus_Nodes, only : nodeComponentSatellite, nodeComponentBasic, nodeComponentDarkMatterProfile implicit none - class (darkMatterProfileDMOPenarrubia2010), intent(inout) :: self - type (treeNode ), intent(inout) :: node - class (nodeComponentBasic ), pointer :: basic - class (nodeComponentSatellite ), pointer :: satellite - class (nodeComponentDarkMatterProfile ), pointer :: darkMatterProfile - double precision :: fractionMassBound , fractionVelocityMaximum, & - & ratioVelocityMaximumVelocityScale, massScale , & - & massScaleOriginal + class(darkMatterProfileDMOPenarrubia2010 ), intent(inout) :: self + type (treeNode ), intent(inout) :: node + class(nodeComponentBasic ), pointer :: basic + class (nodeComponentSatellite ), pointer :: satellite + class (nodeComponentDarkMatterProfile), pointer :: darkMatterProfile + double precision :: fractionMassBound , fractionVelocityMaximum, & + & radiusScale , radiusVirial , & + & velocityRotationFactor - if (node%uniqueID() /= self%uniqueIDPrevious) call self%calculationReset(node,node%uniqueID()) - if (self%normalizationPrevious < 0.0d0) then - basic => node %basic () - satellite => node %satellite () - darkMatterProfile => node %darkMatterProfile() - fractionMassBound = +satellite%boundMass () & - & /basic %mass () - fractionVelocityMaximum = +2.0d0 **self%muVelocity & - & * fractionMassBound**self%etaVelocity & - & /( & - & +1.0d0 & - & +fractionMassBound & - & ) **self%muVelocity - if (fractionMassBound >= fractionMassTransition) then - ratioVelocityMaximumVelocityScale=self%ratioVelocityMaximumVelocityScaleUnstripped - else - ratioVelocityMaximumVelocityScale=self%ratioVelocityMaximumVelocityScaleStripped - end if - massScaleOriginal=self%darkMatterProfileUnstripped%enclosedMass(node,darkMatterProfile%scale()) - massScale=+massScaleOriginal & - & *self %scaleRadius (node ) & - & /darkmatterProfile%scale ( ) & - & *self %ratioVelocityMaximumVelocityScaleUnstripped **2 & - & / ratioVelocityMaximumVelocityScale **2 & - & * fractionVelocityMaximum **2 - self%normalizationPrevious=+ massScale & - & /self%massUnnormalized(node,radiusScaleFree=1.0d0) & - & /self%scaleRadius (node )**3 + basic => node %basic () + satellite => node %satellite () + darkMatterProfile => node %darkMatterProfile() + fractionMassBound = +satellite%boundMass () & + & /basic %mass () + fractionVelocityMaximum = +2.0d0 **self%muVelocity & + & * fractionMassBound**self%etaVelocity & + & /( & + & +1.0d0 & + & +fractionMassBound & + & ) **self%muVelocity + radiusScale =darkMatterProfile %scale ( ) + radiusVirial =self %darkMatterHaloScale_%radiusVirial(node) + if (fractionMassBound >= fractionMassTransition) then + velocityRotationFactor=+self%massDistributionUnstripped% rotationCurve (radius=radiusVirial/self%scaleRadius(node)) & + & /self%massDistributionUnstripped%velocityRotationCurveMaximum( ) + else + velocityRotationFactor=+self%massDistributionStripped % rotationCurve (radius=radiusVirial/self%scaleRadius(node)) & + & /self%massDistributionStripped %velocityRotationCurveMaximum( ) end if - penarrubia2010Normalization=self%normalizationPrevious + penarrubia2010Normalization=+basic%mass() & + & *fractionVelocityMaximum**2 & + & *velocityRotationFactor **2 & + & /( & + & +self%massDistributionUnstripped%rotationCurve (radius=radiusVirial/radiusScale) & + & /self%massDistributionUnstripped%velocityRotationCurveMaximum( ) & + & )**2 return end function penarrubia2010Normalization - diff --git a/source/dark_matter_profiles_DMO.SIDM.F90 b/source/dark_matter_profiles_DMO.SIDM.F90 deleted file mode 100644 index 14ee425998..0000000000 --- a/source/dark_matter_profiles_DMO.SIDM.F90 +++ /dev/null @@ -1,145 +0,0 @@ -!! Copyright 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, -!! 2019, 2020, 2021, 2022, 2023, 2024 -!! Andrew Benson -!! -!! This file is part of Galacticus. -!! -!! Galacticus is free software: you can redistribute it and/or modify -!! it under the terms of the GNU General Public License as published by -!! the Free Software Foundation, either version 3 of the License, or -!! (at your option) any later version. -!! -!! Galacticus is distributed in the hope that it will be useful, -!! but WITHOUT ANY WARRANTY; without even the implied warranty of -!! MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -!! GNU General Public License for more details. -!! -!! You should have received a copy of the GNU General Public License -!! along with Galacticus. If not, see . - - !!{ - An abstract dark matter halo profile class for SIDM profiles. - !!} - - use :: Dark_Matter_Particles, only : darkMatterParticleClass - - !![ - - - Abstract dark matter halo profile for self-interacting dark matter particles. Provides a method to compute the - characteristic radius for interactions, $r_1$, defined as (e.g. Jiang et al. 2020): - \begin{equation} - \frac{4}{\sqrt{\pi}} \rho_\mathrm{dm}(r_1) v_\mathrm{rms}(r_1) \frac{\sigma}{m} = \frac{1}{t_\mathrm{age}}, - \end{equation} - where the left-hand side is the scattering rate per particle, with $\rho_\mathrm{dm}(r)$ being the dark matter density - profile, $v_\mathrm{rms}$ the average relative velocity between DM particles (which is approximated by the 1D velocity - dispersion), and $\sigma/m$ is the self-interaction cross-section per unit mass. - - - !!] - type, abstract, extends(darkMatterProfileDMOClass) :: darkMatterProfileDMOSIDM - !!{ - An abstract dark matter halo profile class for SIDM profiles. - !!} - private - class (darkMatterProfileDMOClass), pointer :: darkMatterProfileDMO_ => null() - class (darkMatterParticleClass ), pointer :: darkMatterParticle_ => null() - integer (kind=kind_int8 ) :: uniqueIDPreviousSIDM - double precision :: radiusInteractivePrevious - contains - !![ - - - - !!] - procedure :: radiusInteraction => sidmRadiusInteraction - end type darkMatterProfileDMOSIDM - - ! Submodule-scope variables used in root finding. - class (darkMatterProfileDMOSIDM), pointer :: self_ - type (treeNode ), pointer :: node_ - double precision :: timeAge_, crossSection_ - !$omp threadprivate(self_,node_,timeAge_,crossSection_) - -contains - - double precision function sidmRadiusInteraction(self,node,timeAge) - !!{ - Returns the characteristic interaction radius (in Mpc) of the self-interacting dark matter profile of {\normalfont \ttfamily node}. - !!} - use :: Dark_Matter_Particles , only : darkMatterParticleSelfInteractingDarkMatter - use :: Error , only : Error_Report - use :: Galacticus_Nodes , only : nodeComponentBasic - use :: Numerical_Constants_Prefixes , only : centi , kilo - use :: Numerical_Constants_Astronomical, only : megaParsec , massSolar - use :: Root_Finder , only : rootFinder , rangeExpandMultiplicative, rangeExpandSignExpectNegative, rangeExpandSignExpectPositive - implicit none - class (darkMatterProfileDMOSIDM), intent(inout), target :: self - type (treeNode ), intent(inout), target :: node - double precision , intent(in ), optional :: timeAge - class (nodeComponentBasic ) , pointer :: basic - type (rootFinder ), save :: finder - logical , save :: finderInitialized=.false. - double precision , parameter :: toleranceAbsolute=0.0d0 , toleranceRelative=1.0d-3 - !$omp threadprivate(finder,finderInitialized) - - if (node%uniqueID() /= self%uniqueIDPreviousSIDM .or. self%radiusInteractivePrevious < 0.0d0) then - self_ => self - node_ => node - if (present(timeAge)) then - timeAge_ = timeAge - else - basic => node %basic () - timeAge_ = basic%time () - end if - select type (darkMatterParticle_ => self%darkMatterParticle_) - class is (darkMatterParticleSelfInteractingDarkMatter) - crossSection_=+darkMatterParticle_%crossSectionSelfInteraction() & - & *centi **2 & - & /megaParsec**2 & - & *kilo & - & *massSolar - class default - call Error_Report('expected self-interacting dark matter particle'//{introspection:location}) - end select - if (.not.finderInitialized) then - finder=rootFinder( & - & rootFunction =sidmRadiusInteractionRoot, & - & toleranceAbsolute=toleranceAbsolute , & - & toleranceRelative=toleranceRelative & - & ) - call finder%rangeExpand( & - & rangeExpandUpward =2.0d0 , & - & rangeExpandDownward =0.5d0 , & - & rangeExpandDownwardSignExpect=rangeExpandSignExpectPositive, & - & rangeExpandUpwardSignExpect =rangeExpandSignExpectNegative, & - & rangeExpandType =rangeExpandMultiplicative & - & ) - finderInitialized=.true. - end if - self%radiusInteractivePrevious=finder%find (rootGuess=self%darkMatterHaloScale_%radiusVirial(node)) - self%uniqueIDPreviousSIDM =node %uniqueID( ) - end if - sidmRadiusInteraction=self%radiusInteractivePrevious - return - end function sidmRadiusInteraction - - double precision function sidmRadiusInteractionRoot(radius) - !!{ - Root function used in seeking the characteristic interaction radius in self-interacting dark matter profiles. - !!} - use :: Numerical_Constants_Astronomical, only : Mpc_per_km_per_s_To_Gyr - implicit none - double precision, intent(in ) :: radius - - sidmRadiusInteractionRoot=+4.0d0 & - & /sqrt(Pi) & - & /Mpc_per_km_per_s_To_Gyr & - & *self_%darkMatterProfileDMO_%density (node_,radius) & - & *self_%darkMatterProfileDMO_%radialVelocityDispersion(node_,radius) & - & *crossSection_ & - & -1.0d0 & - & /timeAge_ - return - end function sidmRadiusInteractionRoot - diff --git a/source/dark_matter_profiles_DMO.SIDM.coreNFW.F90 b/source/dark_matter_profiles_DMO.SIDM.coreNFW.F90 index 07f82dfd8a..beb70edc54 100644 --- a/source/dark_matter_profiles_DMO.SIDM.coreNFW.F90 +++ b/source/dark_matter_profiles_DMO.SIDM.coreNFW.F90 @@ -18,17 +18,21 @@ !! along with Galacticus. If not, see . !!{ - An implementation of a cored-NFW dark matter halo profile to approximate the effects of SIDM based on the model of Jiang et al. (2022). + An implementation of a cored-NFW dark matter halo profile to approximate the effects of SIDM based on the model of \cite{jiang_semi-analytic_2023}. !!} - use :: Dark_Matter_Particles, only : darkMatterParticleClass - + use :: Dark_Matter_Particles , only : darkMatterParticleClass + use :: Dark_Matter_Halo_Scales, only : darkmatterHaloScaleClass + !![ - Cored-NFW dark matter halo profile to approximate the effects of SIDM based on the model of Jiang et al. (2022). + + Cored-NFW dark matter halo profiles to approximate the effects of SIDM based on the model of \cite{jiang_semi-analytic_2023} + are built via \refClass{} objects. + !!] - type, extends(darkMatterProfileDMOSIDM) :: darkMatterProfileDMOSIDMCoreNFW + type, extends(darkMatterProfileDMOClass) :: darkMatterProfileDMOSIDMCoreNFW !!{ A dark matter halo profile class implementing a cored-NFW dark matter halo profile to approximate the effects of SIDM based on the model of Jiang et al. (2022). The profile is defined by the enclosed mass, with (Jiang et al. 2022): @@ -39,35 +43,13 @@ $\alpha = ${\normalfont \ttfamily [factorRadiusCore]}. !!} private - double precision :: factorRadiusCore + class (darkMatterParticleClass ), pointer :: darkMatterParticle_ => null() + class (darkMatterProfileDMOClass), pointer :: darkMatterProfileDMO_ => null() + class (darkMatterHaloScaleClass ), pointer :: darkMatterHaloScale_ => null() + double precision :: factorRadiusCore contains - !![ - - - - - !!] - final :: sidmCoreNFWDestructor - procedure :: autoHook => sidmCoreNFWAutoHook - procedure :: calculationReset => sidmCoreNFWCalculationReset - procedure :: radiusCore => sidmCoreNFWRadiusCore - procedure :: density => sidmCoreNFWDensity - procedure :: densityLogSlope => sidmCoreNFWDensityLogSlope - procedure :: radiusEnclosingDensity => sidmCoreNFWRadiusEnclosingDensity - procedure :: radiusEnclosingMass => sidmCoreNFWRadiusEnclosingMass - procedure :: radialMoment => sidmCoreNFWRadialMoment - procedure :: enclosedMass => sidmCoreNFWEnclosedMass - procedure :: potential => sidmCoreNFWPotential - procedure :: circularVelocity => sidmCoreNFWCircularVelocity - procedure :: circularVelocityMaximum => sidmCoreNFWCircularVelocityMaximum - procedure :: radiusCircularVelocityMaximum => sidmCoreNFWRadiusCircularVelocityMaximum - procedure :: radialVelocityDispersion => sidmCoreNFWRadialVelocityDispersion - procedure :: radiusFromSpecificAngularMomentum => sidmCoreNFWRadiusFromSpecificAngularMomentum - procedure :: rotationNormalization => sidmCoreNFWRotationNormalization - procedure :: energy => sidmCoreNFWEnergy - procedure :: kSpace => sidmCoreNFWKSpace - procedure :: freefallRadius => sidmCoreNFWFreefallRadius - procedure :: freefallRadiusIncreaseRate => sidmCoreNFWFreefallRadiusIncreaseRate + final :: sidmCoreNFWDestructor + procedure :: get => sidmCoreNFWGet end type darkMatterProfileDMOSIDMCoreNFW interface darkMatterProfileDMOSIDMCoreNFW @@ -88,8 +70,8 @@ function sidmCoreNFWConstructorParameters(parameters) result(self) implicit none type (darkMatterProfileDMOSIDMCoreNFW) :: self type (inputParameters ), intent(inout) :: parameters - class (darkMatterHaloScaleClass ), pointer :: darkMatterHaloScale_ class (darkMatterParticleClass ), pointer :: darkMatterParticle_ + class (darkMatterHaloScaleClass ), pointer :: darkMatterHaloScale_ double precision :: factorRadiusCore !![ @@ -100,14 +82,14 @@ function sidmCoreNFWConstructorParameters(parameters) result(self) parameters The factor $\alpha$ appearing in the definition of the core radius, $r_\mathrm{c}=\alpha r_1$ where $r_1$ is the radius at which an SIDM particle has had, on average, 1 interaction. - + !!] self=darkMatterProfileDMOSIDMCoreNFW(factorRadiusCore,darkMatterHaloScale_,darkMatterParticle_) !![ - + !!] return end function sidmCoreNFWConstructorParameters @@ -119,8 +101,8 @@ function sidmCoreNFWConstructorInternal(factorRadiusCore,darkMatterHaloScale_,da use :: Dark_Matter_Particles, only : darkMatterParticleSelfInteractingDarkMatter implicit none type (darkMatterProfileDMOSIDMCoreNFW) :: self - class (darkMatterHaloScaleClass ), intent(in ), target :: darkMatterHaloScale_ class (darkMatterParticleClass ), intent(in ), target :: darkMatterParticle_ + class (darkMatterHaloScaleClass ), intent(in ), target :: darkMatterHaloScale_ double precision , intent(in ) :: factorRadiusCore !![ @@ -148,403 +130,90 @@ function sidmCoreNFWConstructorInternal(factorRadiusCore,darkMatterHaloScale_,da !!] end select - self%genericLastUniqueID =-1_kind_int8 - self%uniqueIDPreviousSIDM=-1_kind_int8 return end function sidmCoreNFWConstructorInternal - subroutine sidmCoreNFWAutoHook(self) - !!{ - Attach to the calculation reset event. - !!} - use :: Events_Hooks, only : calculationResetEvent, openMPThreadBindingAllLevels - implicit none - class(darkMatterProfileDMOSIDMCoreNFW), intent(inout) :: self - - call calculationResetEvent%attach(self,sidmCoreNFWCalculationReset,openMPThreadBindingAllLevels,label='darkMatterProfileDMOSIDMCoreNFW') - return - end subroutine sidmCoreNFWAutoHook - subroutine sidmCoreNFWDestructor(self) !!{ Destructor for the {\normalfont \ttfamily sidmCoreNFW} dark matter halo profile class. !!} - use :: Events_Hooks, only : calculationResetEvent implicit none type(darkMatterProfileDMOSIDMCoreNFW), intent(inout) :: self !![ - + !!] - if (calculationResetEvent%isAttached(self,sidmCoreNFWCalculationReset)) call calculationResetEvent%detach(self,sidmCoreNFWCalculationReset) return end subroutine sidmCoreNFWDestructor - subroutine sidmCoreNFWCalculationReset(self,node,uniqueID) + function sidmCoreNFWGet(self,node,weightBy,weightIndex) result(massDistribution_) !!{ - Reset the dark matter profile calculation. + Return the dark matter mass distribution for the given {\normalfont \ttfamily node}. !!} - use :: Kind_Numbers, only : kind_int8 + use :: Galacticus_Nodes , only : nodeComponentBasic + use :: Galactic_Structure_Options, only : componentTypeDarkHalo , massTypeDark , weightByMass + use :: Mass_Distributions , only : massDistributionSphericalSIDMCoreNFW, kinematicsDistributionCollisionless, massDistributionNFW, nonAnalyticSolversNumerical implicit none - class (darkMatterProfileDMOSIDMCoreNFW), intent(inout) :: self - type (treeNode ), intent(inout) :: node - integer(kind_int8 ), intent(in ) :: uniqueID - !$GLC attributes unused :: node - - self%genericLastUniqueID =uniqueID - self%uniqueIDPreviousSIDM =uniqueID - self%radiusInteractivePrevious =-1.0d0 - self%genericEnclosedMassRadiusMinimum =+huge(0.0d0) - self%genericEnclosedMassRadiusMaximum =-huge(0.0d0) - self%genericVelocityDispersionRadialRadiusMinimum=+huge(0.0d0) - self%genericVelocityDispersionRadialRadiusMaximum=-huge(0.0d0) - if (allocated(self%genericVelocityDispersionRadialVelocity)) deallocate(self%genericVelocityDispersionRadialVelocity) - if (allocated(self%genericVelocityDispersionRadialRadius )) deallocate(self%genericVelocityDispersionRadialRadius ) - if (allocated(self%genericEnclosedMassMass )) deallocate(self%genericEnclosedMassMass ) - if (allocated(self%genericEnclosedMassRadius )) deallocate(self%genericEnclosedMassRadius ) - return - end subroutine sidmCoreNFWCalculationReset - - double precision function sidmCoreNFWRadiusCore(self,node) - !!{ - Returns the core radius (in Mpc) of the ``coreNFW'' approximation to the self-interacting dark matter profile of {\normalfont \ttfamily node}. - !!} - implicit none - class(darkMatterProfileDMOSIDMCoreNFW), intent(inout) :: self - type (treeNode ), intent(inout) :: node - - sidmCoreNFWRadiusCore=+self%factorRadiusCore & - & *self%radiusInteraction(node) - return - end function sidmCoreNFWRadiusCore - - double precision function sidmCoreNFWDensity(self,node,radius) - !!{ - Returns the density (in $M_\odot$ Mpc$^{-3}$) in the dark matter profile of {\normalfont \ttfamily node} at the given - {\normalfont \ttfamily radius} (given in units of Mpc). - !!} - use :: Numerical_Constants_Math, only : Pi - implicit none - class (darkMatterProfileDMOSIDMCoreNFW), intent(inout) :: self - type (treeNode ), intent(inout) :: node - double precision , intent(in ) :: radius - double precision , parameter :: radiusFractionalLarge=10.0d0 - double precision :: radiusFractional , radiusCore - - radiusCore =+self%radiusCore(node) - radiusFractional=+ radius & - & / radiusCore - if (radiusFractional < radiusFractionalLarge) then - ! Use the full solution for sufficiently small radii. - sidmCoreNFWDensity=+self%darkMatterProfileDMO_%density(node,radius) & - & *tanh( & - & +radiusFractional & - & ) & - & +self%darkMatterProfileDMO_%enclosedMass(node,radius) & - & /4.0d0 & - & /Pi & - & / radiusFractional**2 & - & / radiusCore **3 & - & /cosh( & - & +radiusFractional & - & )**2 - else - ! For large fractional radii avoid floating point overflow by approximating cosh(x) ~ 1/2/exp(-x). - sidmCoreNFWDensity=+self%darkMatterProfileDMO_%density(node,radius) & - & *tanh( & - & +radiusFractional & - & ) & - & +self%darkMatterProfileDMO_%enclosedMass(node,radius) & - & /4.0d0 & - & /Pi & - & / radiusFractional**2 & - & / radiusCore **3 & - & *4.0d0 & - & *exp( & - & -2.0d0 & - & * radiusFractional & - & ) - end if - return - end function sidmCoreNFWDensity - - double precision function sidmCoreNFWDensityLogSlope(self,node,radius) - !!{ - Returns the logarithmic slope of the density in the dark matter profile of {\normalfont \ttfamily node} at the given - {\normalfont \ttfamily radius} (given in units of Mpc). - !!} - implicit none - class (darkMatterProfileDMOSIDMCoreNFW), intent(inout) :: self - type (treeNode ), intent(inout) :: node - double precision , intent(in ) :: radius - double precision :: radiusCore, massEnclosedNFW , & - & densityNFW, densityLogSlopeNFW - - radiusCore =+self%radiusCore (node ) - massEnclosedNFW =+self%darkMatterProfileDMO_%enclosedMass (node,radius) - densityNFW =+self%darkMatterProfileDMO_%density (node,radius) - densityLogSlopeNFW =+self%darkMatterProfileDMO_%densityLogSlope(node,radius) - sidmCoreNFWDensityLogSlope=+( & - & -2.0d0 & - & *massEnclosedNFW & - & *( & - & +radiusCore & - & +radius & - & *tanh( & - & +radius & - & /radiusCore & - & ) & - & ) & - & +radiusCore*radius & - & *( & - & +4.0d0 & - & *Pi & - & *radius**2 & - & *densityNFW & - & +2.0d0 & - & *Pi & - & *radius**2 & - & *( & - & +2.0d0 & - & *densityNFW & - & +( & - & +radiusCore & - & *sinh( & - & +2.0d0 & - & *radius & - & /radiusCore & - & ) & - & *densityLogSlopeNFW*densityNFW & - & ) & - & /radius & - & ) & - & ) & - & ) & - & /( & - & +radiusCore & - & *( & - & +massEnclosedNFW & - & +2.0d0 & - & *Pi & - & *radiusCore & - & *radius **2 & - & *sinh( & - & +2.0d0 & - & *radius & - & /radiusCore & - & ) & - & *densityNFW & - & ) & - & ) - return - end function sidmCoreNFWDensityLogSlope - - double precision function sidmCoreNFWEnclosedMass(self,node,radius) - !!{ - Returns the enclosed mass (in $M_\odot$) in the dark matter profile of {\normalfont \ttfamily node} at the given {\normalfont \ttfamily radius} (given in - units of Mpc). - !!} - implicit none - class (darkMatterProfileDMOSIDMCoreNFW), intent(inout) :: self - type (treeNode ), intent(inout) :: node - double precision , intent(in ) :: radius - - sidmCoreNFWEnclosedMass=+ self%darkMatterProfileDMO_%enclosedMass(node,radius) & - & *tanh( & - & + radius & - & /self%radiusCore (node ) & - & ) - return - end function sidmCoreNFWEnclosedMass - - double precision function sidmCoreNFWRadiusEnclosingDensity(self,node,density) - !!{ - Returns the radius (in Mpc) in the dark matter profile of {\normalfont \ttfamily node} which encloses the given - {\normalfont \ttfamily density} (given in units of $M_\odot/$Mpc$^{-3}$). - !!} - implicit none - class (darkMatterProfileDMOSIDMCoreNFW), intent(inout), target :: self - type (treeNode ), intent(inout), target :: node - double precision , intent(in ) :: density - - sidmCoreNFWRadiusEnclosingDensity=self%radiusEnclosingDensityNumerical(node,density) - return - end function sidmCoreNFWRadiusEnclosingDensity - - double precision function sidmCoreNFWRadiusEnclosingMass(self,node,mass) - !!{ - Returns the radius (in Mpc) in the dark matter profile of {\normalfont \ttfamily node} which encloses the given - {\normalfont \ttfamily mass} (given in units of $M_\odot$). - !!} - implicit none - class (darkMatterProfileDMOSIDMCoreNFW), intent(inout), target :: self - type (treeNode ), intent(inout), target :: node - double precision , intent(in ) :: mass - - sidmCoreNFWRadiusEnclosingMass=self%radiusEnclosingMassNumerical(node,mass) - return - end function sidmCoreNFWRadiusEnclosingMass - - double precision function sidmCoreNFWRadialMoment(self,node,moment,radiusMinimum,radiusMaximum) - !!{ - Returns the density (in $M_\odot$ Mpc$^{-3}$) in the dark matter profile of {\normalfont \ttfamily node} at the given - {\normalfont \ttfamily radius} (given in units of Mpc). - !!} - implicit none - class (darkMatterProfileDMOSIDMCoreNFW), intent(inout) :: self - type (treeNode ), intent(inout) :: node - double precision , intent(in ) :: moment - double precision , intent(in ), optional :: radiusMinimum, radiusMaximum - - sidmCoreNFWRadialMoment=self%radialMomentNumerical(node,moment,radiusMinimum,radiusMaximum) - return - end function sidmCoreNFWRadialMoment - - double precision function sidmCoreNFWPotential(self,node,radius,status) - !!{ - Returns the potential (in (km/s)$^2$) in the dark matter profile of {\normalfont \ttfamily node} at the given {\normalfont - \ttfamily radius} (given in units of Mpc). - !!} - implicit none - class (darkMatterProfileDMOSIDMCoreNFW ), intent(inout) :: self - type (treeNode ), intent(inout), target :: node - double precision , intent(in ) :: radius - type (enumerationStructureErrorCodeType), intent( out), optional :: status - - sidmCoreNFWPotential=self%potentialNumerical(node,radius,status) - return - end function sidmCoreNFWPotential - - double precision function sidmCoreNFWCircularVelocity(self,node,radius) - !!{ - Returns the circular velocity (in km/s) in the dark matter profile of {\normalfont \ttfamily node} at the given - {\normalfont \ttfamily radius} (given in units of Mpc). - !!} - implicit none - class (darkMatterProfileDMOSIDMCoreNFW), intent(inout) :: self - type (treeNode ), intent(inout) :: node - double precision , intent(in ) :: radius - - sidmCoreNFWCircularVelocity=self%circularVelocityNumerical(node,radius) - return - end function sidmCoreNFWCircularVelocity - - double precision function sidmCoreNFWCircularVelocityMaximum(self,node) - !!{ - Returns the maximum circular velocity (in km/s) in the dark matter profile of {\normalfont \ttfamily node}. - !!} - implicit none - class(darkMatterProfileDMOSIDMCoreNFW), intent(inout) :: self - type (treeNode ), intent(inout) :: node - - sidmCoreNFWCircularVelocityMaximum=self%circularVelocityMaximumNumerical(node) - return - end function sidmCoreNFWCircularVelocityMaximum - - double precision function sidmCoreNFWRadiusCircularVelocityMaximum(self,node) - !!{ - Returns the radius (in Mpc) at which the maximum circular velocity is achieved in the dark matter profile of {\normalfont \ttfamily node}. - !!} - implicit none - class(darkMatterProfileDMOSIDMCoreNFW), intent(inout) :: self - type (treeNode ), intent(inout) :: node - - sidmCoreNFWRadiusCircularVelocityMaximum=self%radiusCircularVelocityMaximumNumerical(node) - return - end function sidmCoreNFWRadiusCircularVelocityMaximum - - double precision function sidmCoreNFWRadialVelocityDispersion(self,node,radius) - !!{ - Returns the radial velocity dispersion (in km/s) in the dark matter profile of {\normalfont \ttfamily node} at the given - {\normalfont \ttfamily radius} (given in units of Mpc). - !!} - implicit none - class (darkMatterProfileDMOSIDMCoreNFW), intent(inout) :: self - type (treeNode ), intent(inout) :: node - double precision , intent(in ) :: radius - - sidmCoreNFWRadialVelocityDispersion=self%radialVelocityDispersionNumerical(node,radius) - return - end function sidmCoreNFWRadialVelocityDispersion - - double precision function sidmCoreNFWRadiusFromSpecificAngularMomentum(self,node,specificAngularMomentum) - !!{ - Returns the radius (in Mpc) in {\normalfont \ttfamily node} at which a circular orbit has the given {\normalfont \ttfamily specificAngularMomentum} (given - in units of km s$^{-1}$ Mpc). - !!} - implicit none - class (darkMatterProfileDMOSIDMCoreNFW), intent(inout) :: self - type (treeNode ), intent(inout) :: node - double precision , intent(in ) :: specificAngularMomentum - - sidmCoreNFWRadiusFromSpecificAngularMomentum=self%radiusFromSpecificAngularMomentumNumerical(node,specificAngularMomentum) - return - end function sidmCoreNFWRadiusFromSpecificAngularMomentum - - double precision function sidmCoreNFWRotationNormalization(self,node) - !!{ - Return the normalization of the rotation velocity vs. specific angular momentum relation. - !!} - implicit none - class(darkMatterProfileDMOSIDMCoreNFW), intent(inout) :: self - type (treeNode ), intent(inout) :: node - - sidmCoreNFWRotationNormalization=self%rotationNormalizationNumerical(node) - return - end function sidmCoreNFWRotationNormalization - - double precision function sidmCoreNFWEnergy(self,node) - !!{ - Return the energy of a sidmCoreNFW halo density profile. - !!} - implicit none - class(darkMatterProfileDMOSIDMCoreNFW), intent(inout) :: self - type (treeNode ), intent(inout) :: node - - sidmCoreNFWEnergy=self%energyNumerical(node) - return - end function sidmCoreNFWEnergy - - double precision function sidmCoreNFWKSpace(self,node,waveNumber) - !!{ - Returns the Fourier transform of the sidmCoreNFW density profile at the specified {\normalfont \ttfamily waveNumber} - (given in Mpc$^{-1}$). - !!} - implicit none - class (darkMatterProfileDMOSIDMCoreNFW), intent(inout) :: self - type (treeNode ), intent(inout), target :: node - double precision , intent(in ) :: waveNumber - - sidmCoreNFWKSpace=self%kSpaceNumerical(node,waveNumber) - return - end function sidmCoreNFWKSpace - - double precision function sidmCoreNFWFreefallRadius(self,node,time) - !!{ - Returns the freefall radius in the sidmCoreNFW density profile at the specified {\normalfont \ttfamily time} (given in - Gyr). - !!} - implicit none - class (darkMatterProfileDMOSIDMCoreNFW), intent(inout), target :: self - type (treeNode ), intent(inout), target :: node - double precision , intent(in ) :: time - - sidmCoreNFWFreefallRadius=self%freefallRadiusNumerical(node,time) - return - end function sidmCoreNFWFreefallRadius - - double precision function sidmCoreNFWFreefallRadiusIncreaseRate(self,node,time) - !!{ - Returns the rate of increase of the freefall radius in the sidmCoreNFW density profile at the specified {\normalfont - \ttfamily time} (given in Gyr). - !!} - implicit none - class (darkMatterProfileDMOSIDMCoreNFW), intent(inout), target :: self - type (treeNode ), intent(inout), target :: node - double precision , intent(in ) :: time + class (massDistributionClass ), pointer :: massDistribution_ + type (kinematicsDistributionCollisionless), pointer :: kinematicsDistribution_ + class (darkMatterProfileDMOSIDMCoreNFW ), intent(inout) :: self + type (treeNode ), intent(inout) :: node + type (enumerationWeightByType ), intent(in ), optional :: weightBy + integer , intent(in ), optional :: weightIndex + class (massDistributionClass ), pointer :: massDistributionDecorated + class (nodeComponentBasic ), pointer :: basic + !![ + + !!] - sidmCoreNFWFreefallRadiusIncreaseRate=self%freefallRadiusIncreaseRateNumerical(node,time) + ! Assume a null distribution by default. + massDistribution_ => null() + ! If weighting is not by mass, return a null profile. + if (weightBy_ /= weightByMass) return + ! Create the mass distribution. + allocate(massDistributionSphericalSIDMCoreNFW :: massDistribution_) + select type(massDistribution_) + type is (massDistributionSphericalSIDMCoreNFW) + massDistributionDecorated => self%darkMatterProfileDMO_%get (node,weightBy,weightIndex) + basic => node %basic( ) + select type (massDistributionDecorated) + class is (massDistributionNFW) + !![ + + + massDistributionSphericalSIDMCoreNFW( & + & factorRadiusCore =self %factorRadiusCore , & + & timeAge =basic%time (), & + & nonAnalyticSolver = nonAnalyticSolversNumerical , & + & massDistribution_ = massDistributionDecorated , & + & darkMatterParticle_=self %darkMatterParticle_ , & + & componentType = componentTypeDarkHalo , & + & massType = massTypeDark & + & ) + + + !!] + class default + call Error_Report('expected a spherical mass distribution'//{introspection:location}) + end select + !![ + + !!] + end select + allocate(kinematicsDistribution_) + !![ + + + kinematicsDistributionCollisionless( & + & ) + + + !!] + call massDistribution_%setKinematicsDistribution(kinematicsDistribution_) + !![ + + !!] return - end function sidmCoreNFWFreefallRadiusIncreaseRate + end function sidmCoreNFWGet diff --git a/source/dark_matter_profiles_DMO.SIDM.isothermal.F90 b/source/dark_matter_profiles_DMO.SIDM.isothermal.F90 index e003844cee..f29de2c518 100644 --- a/source/dark_matter_profiles_DMO.SIDM.isothermal.F90 +++ b/source/dark_matter_profiles_DMO.SIDM.isothermal.F90 @@ -18,102 +18,29 @@ !! along with Galacticus. If not, see . !!{ - An implementation of dark matter halo profiles for self-interacting dark matter following the ``isothermal'' model of Jiang et al. (2022). + An implementation of dark matter halo profiles for self-interacting dark matter following the ``isothermal'' model of \cite{iang_semi-analytic_2023}. !!} - use, intrinsic :: ISO_C_Binding , only : c_size_t - use :: Numerical_Interpolation, only : interpolator - use :: Numerical_ODE_Solvers , only : odeSolver - + use :: Dark_Matter_Particles, only : darkMatterParticleClass + !![ - Dark matter halo profiles for self-interacting dark matter following the ``isothermal'' model of Jiang et al. (2022). This - model assumes that the dark matter within the interaction radius, $r_1$, has thermalized and can therefore be described by a - constant velocity dispersion, $\sigma_0$. Under this assumption the spherical Jeans equation has a solution of the form: - \begin{equation} - \rho(r) = \rho_0 \exp\left[-\frac{\phi(r)}{\sigma_0^2}\right], - \end{equation} - where $\rho(r)$ is the density $\rho_0$ is the density at $r=0$, and the gravitational potential satisfies (Jiang et al. 2022): - \begin{equation} - \nabla^2 \phi(r) = 4 \pi \mathrm{G} \rho_0 \exp \left( - \frac{\phi(r)}{\sigma_0^2} \right). - \end{equation} - This second-order differential equation is solved using the boundary conditions $\phi(r=0)=0$ and - $\mathrm{d}\phi/\mathrm{d}r(r=0)=0$. The values of $\rho_0$ and $\sigma_0$ are then found by minimizing a function - \begin{equation} - \delta^2(\rho_0,\sigma_0) = \left[ \frac{\rho(r_1)}{\rho^\prime(r_1)} - 1 \right]^2 + \left[ \frac{M(r_1)}{M^\prime(r_1)} - 1 \right]^2, - \end{equation} - where $M(r)$ is the mass contained within radius $r$, and primes indicate the profile prior to SIDM thermalization. - - This can be expressed in a convenient dimensionless form. We define $x=r/r_1$, $y=\rho/\rho_1$, $z=\sigma/\sigma_1$, where - \begin{equation} - \sigma_1^2 = \frac{4 \pi}{3} \mathrm{G} \rho_1 r_1^2 \xi, - \end{equation} - and we define $\xi$ through the relation: - \begin{equation} - M_1 = \xi \frac{4 \pi}{3} \rho_1 r_1^3. - \end{equation} - Using these definitions we can define a dimensionless potential, $\Phi(r) = \phi(r) / \sigma_1^2$. The above differential - equation can then be written as - \begin{equation} - \nabla^{\prime 2} \Phi = \frac{3}{\xi} y_0 \exp\left[ - \frac{\Phi}{z_0^2} \right] , - \end{equation} - where $\nabla^{\prime 2}$ indicates the Laplacian with respect to coordinate $x$. Written in this form it is straightforward - to see that this equation has three parameters, $\xi$, $y_0$, and $z_0$. The value of $\xi$ is determined from the initial - (pre-thermalization) density profile. We then have two constraints at $x=1$, namely $y=1$ and $m=M/M_1=1$. We can solve for - the values of $y_0$ and $z_0$ which satisfy these constraints for a given $\xi$. As a result, we can tabulate solutions - $y_0(\xi)$ and $z_0(\xi)$ which are applicable to any initial density profile and depend only on the effective slope of the - density profile inside $r_1$, since if $\rho \propto r^\alpha$ then $\xi = 1/(1+\alpha/3)$, such that $\alpha=0$ (the - largest physically-allowed value of $\alpha$) implies $\xi=1$. + Dark matter halo profiles for self-interacting dark matter following the ``isothermal'' model of + \cite{iang_semi-analytic_2023} are built via the \refClass{massDistributionSphericalSIDMIsothermal} class. !!] - type, extends(darkMatterProfileDMOSIDM) :: darkMatterProfileDMOSIDMIsothermal + type, extends(darkMatterProfileDMOClass) :: darkMatterProfileDMOSIDMIsothermal !!{ - A dark matter halo profile class implementing profiles for self-interacting dark matter following the ``isothermal'' model of Jiang et al. (2022). + A dark matter halo profile class implementing profiles for self-interacting dark matter following the ``isothermal'' model of \cite{iang_semi-analytic_2023}. !!} private - integer (kind=kind_int8) :: uniqueIDPrevious - double precision :: velocityDispersionCentral , radiusInteraction_ , & - & densityInteraction , massInteraction , & - & velocityDispersionInteraction - type (interpolator ), allocatable :: densityCentralDimensionless , velocityDispersionCentralDimensionless, & - & interpolatorRadiiDimensionless, interpolatorXi - double precision :: xiTabulatedMinimum , xiTabulatedMaximum - double precision , allocatable, dimension( : ,:) :: densityProfileDimensionless , massProfileDimensionless - double precision , allocatable, dimension( : ) :: radiiDimensionless - integer (c_size_t ) :: indexXi - double precision , dimension(0:1 ) :: factorsXi + class(darkMatterParticleClass ), pointer :: darkMatterParticle_ => null() + class(darkMatterProfileDMOClass), pointer :: darkMatterProfileDMO_ => null() contains - !![ - - - - - - !!] - final :: sidmIsothermalDestructor - procedure :: autoHook => sidmIsothermalAutoHook - procedure :: calculationReset => sidmIsothermalCalculationReset - procedure :: density => sidmIsothermalDensity - procedure :: densityLogSlope => sidmIsothermalDensityLogSlope - procedure :: radiusEnclosingDensity => sidmIsothermalRadiusEnclosingDensity - procedure :: radiusEnclosingMass => sidmIsothermalRadiusEnclosingMass - procedure :: radialMoment => sidmIsothermalRadialMoment - procedure :: enclosedMass => sidmIsothermalEnclosedMass - procedure :: potential => sidmIsothermalPotential - procedure :: circularVelocity => sidmIsothermalCircularVelocity - procedure :: circularVelocityMaximum => sidmIsothermalCircularVelocityMaximum - procedure :: radiusCircularVelocityMaximum => sidmIsothermalRadiusCircularVelocityMaximum - procedure :: radialVelocityDispersion => sidmIsothermalRadialVelocityDispersion - procedure :: radiusFromSpecificAngularMomentum => sidmIsothermalRadiusFromSpecificAngularMomentum - procedure :: rotationNormalization => sidmIsothermalRotationNormalization - procedure :: energy => sidmIsothermalEnergy - procedure :: kSpace => sidmIsothermalKSpace - procedure :: freefallRadius => sidmIsothermalFreefallRadius - procedure :: freefallRadiusIncreaseRate => sidmIsothermalFreefallRadiusIncreaseRate - procedure :: computeSolution => sidmIsothermalComputeSolution - procedure :: tabulateSolutions => sidmIsothermalTabulateSolutions + final :: sidmIsothermalDestructor + procedure :: get => sidmIsothermalGet end type darkMatterProfileDMOSIDMIsothermal interface darkMatterProfileDMOSIDMIsothermal @@ -124,15 +51,6 @@ module procedure sidmIsothermalConstructorInternal end interface darkMatterProfileDMOSIDMIsothermal - ! Number of properties in ODE. - integer (c_size_t ), parameter :: propertyCount=2 - - ! Submodule-scope variables. - double precision :: xi_ , y0_, & - & z0_ - type (odeSolver), allocatable :: odeSolver_ - !$omp threadprivate(xi_,y0_,z0_,odeSolver_) - contains function sidmIsothermalConstructorParameters(parameters) result(self) @@ -143,37 +61,33 @@ function sidmIsothermalConstructorParameters(parameters) result(self) implicit none type (darkMatterProfileDMOSIDMIsothermal) :: self type (inputParameters ), intent(inout) :: parameters - class(darkMatterHaloScaleClass ), pointer :: darkMatterHaloScale_ class(darkMatterParticleClass ), pointer :: darkMatterParticle_ class(darkMatterProfileDMOClass ), pointer :: darkMatterProfileDMO_ !![ - !!] - self=darkMatterProfileDMOSIDMIsothermal(darkMatterProfileDMO_,darkMatterHaloScale_,darkMatterParticle_) + self=darkMatterProfileDMOSIDMIsothermal(darkMatterProfileDMO_,darkMatterParticle_) !![ - !!] return end function sidmIsothermalConstructorParameters - function sidmIsothermalConstructorInternal(darkMatterProfileDMO_,darkMatterHaloScale_,darkMatterParticle_) result(self) + function sidmIsothermalConstructorInternal(darkMatterProfileDMO_,darkMatterParticle_) result(self) !!{ Internal constructor for the {\normalfont \ttfamily sidmIsothermal} dark matter profile class. !!} use :: Dark_Matter_Particles, only : darkMatterParticleSelfInteractingDarkMatter implicit none type (darkMatterProfileDMOSIDMIsothermal) :: self - class(darkMatterHaloScaleClass ), intent(in ), target :: darkMatterHaloScale_ class(darkMatterParticleClass ), intent(in ), target :: darkMatterParticle_ class(darkMatterProfileDMOClass ), intent(in ), target :: darkMatterProfileDMO_ !![ - + !!] ! Validate the dark matter particle type. @@ -182,667 +96,89 @@ function sidmIsothermalConstructorInternal(darkMatterProfileDMO_,darkMatterHaloS ! This is as expected. class default call Error_Report('SIDM isothermal dark matter profile expects a self-interacting dark matter particle'//{introspection:location}) - end select - self%xiTabulatedMinimum =+huge(0.0d0) - self%xiTabulatedMaximum =-huge(0.0d0) - self%uniqueIDPrevious =-1_kind_int8 - self%genericLastUniqueID =-1_kind_int8 - self%uniqueIDPreviousSIDM=-1_kind_int8 + end select return end function sidmIsothermalConstructorInternal - subroutine sidmIsothermalAutoHook(self) - !!{ - Attach to the calculation reset event. - !!} - use :: Events_Hooks, only : calculationResetEvent, openMPThreadBindingAllLevels - implicit none - class(darkMatterProfileDMOSIDMIsothermal), intent(inout) :: self - - call calculationResetEvent%attach(self,sidmIsothermalCalculationReset,openMPThreadBindingAllLevels,label='darkMatterProfileDMOSIDMIsothermal') - return - end subroutine sidmIsothermalAutoHook - subroutine sidmIsothermalDestructor(self) !!{ Destructor for the {\normalfont \ttfamily sidmIsothermal} dark matter halo profile class. !!} - use :: Events_Hooks, only : calculationResetEvent implicit none type(darkMatterProfileDMOSIDMIsothermal), intent(inout) :: self !![ - !!] - if (calculationResetEvent%isAttached(self,sidmIsothermalCalculationReset)) call calculationResetEvent%detach(self,sidmIsothermalCalculationReset) return end subroutine sidmIsothermalDestructor - subroutine sidmIsothermalCalculationReset(self,node,uniqueID) - !!{ - Reset the dark matter profile calculation. - !!} - use :: Kind_Numbers, only : kind_int8 - implicit none - class (darkMatterProfileDMOSIDMIsothermal), intent(inout) :: self - type (treeNode ), intent(inout) :: node - integer(kind_int8 ), intent(in ) :: uniqueID - !$GLC attributes unused :: node - - self%uniqueIDPrevious =uniqueID - self%genericLastUniqueID =uniqueID - self%uniqueIDPreviousSIDM =uniqueID - self%velocityDispersionCentral =-1.0d0 - self%radiusInteractivePrevious =-1.0d0 - self%radiusInteraction_ =-1.0d0 - self%densityInteraction =-1.0d0 - self%massInteraction =-1.0d0 - self%velocityDispersionInteraction =-1.0d0 - self%genericEnclosedMassRadiusMinimum =+huge(0.0d0) - self%genericEnclosedMassRadiusMaximum =-huge(0.0d0) - self%genericVelocityDispersionRadialRadiusMinimum=+huge(0.0d0) - self%genericVelocityDispersionRadialRadiusMaximum=-huge(0.0d0) - if (allocated(self%genericVelocityDispersionRadialVelocity)) deallocate(self%genericVelocityDispersionRadialVelocity) - if (allocated(self%genericVelocityDispersionRadialRadius )) deallocate(self%genericVelocityDispersionRadialRadius ) - if (allocated(self%genericEnclosedMassMass )) deallocate(self%genericEnclosedMassMass ) - if (allocated(self%genericEnclosedMassRadius )) deallocate(self%genericEnclosedMassRadius ) - return - end subroutine sidmIsothermalCalculationReset - - subroutine sidmIsothermalTabulateSolutions(self,xiRequired) - !!{ - Tabulate solutions for $y_0(\xi)$, $z_0(\xi)$. - !!} - use :: Display , only : displayIndent , displayUnindent , displayMessage, verbosityLevelWorking, & - & displayCounter , displayCounterClear - use :: File_Utilities , only : Directory_Make , File_Exists , File_Lock , File_Path , & - & File_Unlock , lockDescriptor - use :: Input_Paths , only : inputPath , pathTypeDataDynamic - use :: HDF5_Access , only : hdf5Access - use :: IO_HDF5 , only : hdf5Object - use :: Numerical_Ranges , only : Make_Range , rangeTypeLinear - use :: Multidimensional_Minimizer, only : multiDMinimizer - implicit none - class (darkMatterProfileDMOSIDMIsothermal), intent(inout) :: self - double precision , intent(in ) :: xiRequired - integer , parameter :: countXiPerUnit = 100 - integer , parameter :: countRadii =1000 - double precision , parameter :: Y0Minimum =1.0d+0, Y0Maximum =1.0d+6 - double precision , parameter :: Z0Minimum =0.1d+0, Z0Maximum =3.0d+0 - double precision , parameter :: xiMinimum =1.1d+0, xiMaximum =1.0d+1 - double precision , parameter :: x1 =1.0d+0 - double precision , parameter :: odeToleranceAbsolute=1.0d-9, odeToleranceRelative=1.0d-9 - double precision , dimension(propertyCount+1 ) :: properties , propertyScales - double precision , dimension(propertyCount ) :: locationMinimum - double precision , dimension( : ), allocatable :: xi , y0 , & - & z0 - double precision :: x - type (multiDMinimizer ) , allocatable :: minimizer_ - integer :: countXi , count - integer (c_size_t ) :: i , j , & - & iteration - logical :: converged , retabulate - type (varying_string ) :: fileName - type (hdf5Object ) :: file - type (lockDescriptor ) :: fileLock - character (len=16 ) :: labelXiMinimum , labelXiMaximum - - ! Return immediately if solutions have been tabulated with sufficient extent already. - if ( & - & xiRequired >= self%xiTabulatedMinimum & - & .and. & - & xiRequired <= self%xiTabulatedMaximum & - & ) return - ! Deallocate existing table if necessary. - if (allocated(self%radiiDimensionless )) deallocate(self%radiiDimensionless ) - if (allocated(self%densityProfileDimensionless )) deallocate(self%densityProfileDimensionless ) - if (allocated(self%massProfileDimensionless )) deallocate(self%massProfileDimensionless ) - if (allocated(self%interpolatorXi )) deallocate(self%interpolatorXi ) - if (allocated(self%interpolatorRadiiDimensionless )) deallocate(self%interpolatorRadiiDimensionless ) - if (allocated(self%densityCentralDimensionless )) deallocate(self%densityCentralDimensionless ) - if (allocated(self%velocityDispersionCentralDimensionless)) deallocate(self%velocityDispersionCentralDimensionless) - ! By default assume that we do need to retabulate. - retabulate=.true. - ! Construct a file name for the table. - fileName=inputPath(pathTypeDataDynamic)// & - & 'darkMatter/' // & - & self%objectType() // & - & '.hdf5' - call Directory_Make(char(File_Path(char(fileName)))) - if (File_Exists(fileName)) then - ! Always obtain the file lock before the hdf5Access lock to avoid deadlocks between OpenMP threads. - call File_Lock(char(fileName),fileLock,lockIsShared=.true.) - ! Restore tables from file. - !$ call hdf5Access%set() - call file%openFile (char(fileName) ) - call file%readDataset('xi' , xi ) - call file%readDataset('radii' ,self%radiiDimensionless ) - call file%readDataset('y0' , y0 ) - call file%readDataset('z0' , z0 ) - call file%readDataset('densityProfileDimensionless',self%densityProfileDimensionless) - call file%readDataset('massProfileDimensionless' ,self%massProfileDimensionless ) - call file%close ( ) - !$ call hdf5Access%unset() - self%xiTabulatedMinimum=xi( 1 ) - self%xiTabulatedMaximum=xi(size(xi)) - ! Check if the table is sufficient. - retabulate= xiRequired < self%xiTabulatedMinimum & - & .or. & - & xiRequired > self%xiTabulatedMaximum - call File_Unlock(fileLock) - end if - ! Retabulate now if necessary. - if (retabulate) then - if (allocated( xi )) deallocate( xi ) - if (allocated( y0 )) deallocate( y0 ) - if (allocated( z0 )) deallocate( z0 ) - if (allocated(self%radiiDimensionless )) deallocate(self%radiiDimensionless ) - if (allocated(self%densityProfileDimensionless)) deallocate(self%densityProfileDimensionless) - if (allocated(self%massProfileDimensionless )) deallocate(self%massProfileDimensionless ) - ! Set extent for tabulation. - self%xiTabulatedMinimum=min(1.0d0*xiRequired,xiMinimum) - self%xiTabulatedMaximum=max(1.1d0*xiRequired,xiMaximum) - write (labelXiMinimum,'(f5.2)') self%xiTabulatedMinimum - write (labelXiMaximum,'(f5.2)') self%xiTabulatedMaximum - call displayIndent ('tabulating isothermal SIDM density profile solutions' ,verbosityLevelWorking) - call displayMessage('range: '//trim(adjustl(labelXiMinimum))//' < ξ < '//trim(adjustl(labelXiMaximum))//'',verbosityLevelWorking) - ! Construct ranges of the parameter ξ to span. - countXi=int((self%xiTabulatedMaximum-self%xiTabulatedMinimum)*dble(countXiPerUnit))+1 - allocate( xi ( countXi)) - allocate( y0 ( countXi)) - allocate( z0 ( countXi)) - allocate(self%radiiDimensionless (countRadii )) - allocate(self%densityProfileDimensionless(countRadii,countXi)) - allocate(self%massProfileDimensionless (countRadii,countXi)) - xi =Make_Range(self%xiTabulatedMinimum,self%xiTabulatedMaximum,countXi ,rangeTypeLinear) - self%radiiDimensionless=Make_Range( 0.0d0 , 1.0d0 ,countRadii,rangeTypeLinear) - ! Set absolute property scales for ODE solving. - propertyScales=1.0d0 - ! Start parallel region to solve for halo structure at each value of ξ. - count=0 - call displayCounter(count,isNew=.true.,verbosity=verbosityLevelWorking) - !$omp parallel private(i,j,x,properties,locationMinimum,iteration,converged,minimizer_) - !! Allocate and construct objects needed by each thread. - allocate(odeSolver_) - allocate(minimizer_) - odeSolver_=odeSolver (propertyCount+1,sidmIsothermalDimensionlessODEs ,toleranceAbsolute=odeToleranceAbsolute,toleranceRelative=odeToleranceRelative,scale=propertyScales) - minimizer_=multiDMinimizer(propertyCount ,sidmIsothermalDimensionlessFitMetric ) - !$omp do schedule(dynamic) - do i=1,countXi - xi_=xi(i) - ! Seek the low-density solution. - call minimizer_%set(x=[0.0d0,1.0d0],stepSize=[0.01d0,0.01d0]) - iteration=0 - converged=.false. - do while (.not.converged .and. iteration < 100) - call minimizer_%iterate() - iteration=iteration+1 - converged=minimizer_%testSize(toleranceAbsolute=1.0d-12) - end do - locationMinimum=minimizer_%x() - y0(i)=exp(locationMinimum(1)) - z0(i)= locationMinimum(2) - ! Tabulate solutions for density and mass. - do j=1,countRadii - x =0.0d0 - properties=0.0d0 - call odeSolver_%solve(x,self%radiiDimensionless(j),properties) - self%densityProfileDimensionless(j,i)=+y0(i) & - & *exp( & - & -properties(1) & - & /z0(i) **2 & - & ) - self%massProfileDimensionless (j,i)=+ properties(3) - end do - !$omp atomic - count=count+1 - call displayCounter(int(100.0d0*dble(count)/dble(countXi)),isNew=.false.,verbosity=verbosityLevelWorking) - end do - !$omp end do - call displayCounterClear(verbosityLevelWorking) - deallocate(odeSolver_) - deallocate(minimizer_) - !$omp end parallel - ! Write the data to file. - call File_Lock(char(fileName),fileLock,lockIsShared=.false.) - !$ call hdf5Access%set() - call file%openFile (char( fileName ) ,overWrite=.true.,readOnly=.false.) - call file%writeDataset( xi ,'xi' ) - call file%writeDataset( self%radiiDimensionless ,'radii' ) - call file%writeDataset( y0 ,'y0' ) - call file%writeDataset( z0 ,'z0' ) - call file%writeDataset( self%densityProfileDimensionless ,'densityProfileDimensionless' ) - call file%writeDataset( self%massProfileDimensionless ,'massProfileDimensionless' ) - call file%close ( ) - !$ call hdf5Access%unset() - call File_Unlock(fileLock) - call displayUnindent('done',verbosityLevelWorking) - end if - ! Build the interpolators. - allocate(self%interpolatorXi ) - allocate(self%interpolatorRadiiDimensionless ) - allocate(self%densityCentralDimensionless ) - allocate(self%velocityDispersionCentralDimensionless) - self%densityCentralDimensionless =interpolator( xi ,y0) - self%velocityDispersionCentralDimensionless=interpolator( xi ,z0) - self%interpolatorXi =interpolator( xi ) - self%interpolatorRadiiDimensionless =interpolator(self%radiiDimensionless ) - return - end subroutine sidmIsothermalTabulateSolutions - - double precision function sidmIsothermalDimensionlessFitMetric(propertiesCentral) - !!{ - Evaluate the fit metric. - !!} - implicit none - double precision, intent(in ), dimension(:) :: propertiesCentral - double precision, parameter :: x1 =1.0d0 - double precision , dimension(propertyCount+1) :: properties - double precision :: x , y1, & - & m1 - - ! Extract current parameters to submodule-scope. - y0_=exp(propertiesCentral(1)) - z0_= propertiesCentral(2) - ! Solve the ODE to x₁. - x =0.0d0 - properties=0.0d0 - call odeSolver_%solve(x,x1,properties) - ! Extract density and mass at x₁. - y1 =+y0_ & - & *exp( & - & -properties(1) & - & /z0_ **2 & - & ) - m1 =+ properties(3) - ! Evaluate the fit metric. - sidmIsothermalDimensionlessFitMetric=+(y1-1.0d0)**2 & - & +(m1-1.0d0)**2 - return - end function sidmIsothermalDimensionlessFitMetric - - integer function sidmIsothermalDimensionlessODEs(x,properties,propertiesRateOfChange) - !!{ - Define the dimensionless ODE system to solve for isothermal self-interacting dark matter cores. - !!} - use :: Interface_GSL, only : GSL_Success - implicit none - double precision, intent(in ) :: x - double precision, intent(in ), dimension(:) :: properties - double precision, intent( out), dimension(:) :: propertiesRateOfChange - double precision :: y - - ! Compute the dimensionless density. - y =+y0_ & - & *exp( & - & -max(properties(1),0.0d0) & - & /z0_**2 & - & ) - ! Evaluate the ODE. - propertiesRateOfChange (1)=+properties(2) - propertiesRateOfChange (2)=+3.0d0 & - & /xi_ & - & *y - if (x > 0.0d0) & - & propertiesRateOfChange(2)=+propertiesRateOfChange(2) & - & -2.0d0 & - & *properties (2) & - & /x - propertiesRateOfChange (3)=+3.0d0 & - & /xi_ & - & *x**2 & - & *y - sidmIsothermalDimensionlessODEs=GSL_Success - return - end function sidmIsothermalDimensionlessODEs - - subroutine sidmIsothermalComputeSolution(self,node) - !!{ - Compute a solution for the isothermal core of an SIDM halo. - !!} - use :: Numerical_Constants_Math , only : Pi - use :: Numerical_Constants_Astronomical, only : gravitationalConstantGalacticus - implicit none - class (darkMatterProfileDMOSIDMIsothermal), intent(inout) :: self - type (treeNode ), intent(inout) :: node - integer , parameter :: countTable =1000 - double precision , parameter :: odeToleranceAbsolute =1.0d-3, odeToleranceRelative =1.0d-3 - double precision :: densityCentral , velocityDispersionCentral , & - & densityInteraction , massInteraction , & - & radiusInteraction , xi , & - & velocityDispersionInteraction - - ! Find the interaction radius. - radiusInteraction =self%radiusInteraction (node ) - ! Properties of the original density profile at the interaction radius. - densityInteraction =self%darkMatterProfileDMO_%density (node,radiusInteraction) - massInteraction =self%darkMatterProfileDMO_%enclosedMass (node,radiusInteraction) - ! Find the velocity dispersion scale to be applied to the dimensionless solutions. - velocityDispersionInteraction=sqrt(gravitationalConstantGalacticus*massInteraction/radiusInteraction) - ! Compute the ξ parameter. - xi =+massInteraction & - & *3.0d0 & - & /4.0d0 & - & /Pi & - & /densityInteraction & - & /radiusInteraction **3 - ! Ensure dimensionless solutions have been tabulated. - call self%tabulateSolutions(xi) - ! Find the properties at the halo center. - densityCentral =self%densityCentralDimensionless %interpolate(xi)*densityInteraction - velocityDispersionCentral =self%velocityDispersionCentralDimensionless%interpolate(xi)*velocityDispersionInteraction - ! Store properties of current profile. - self%radiusInteraction_ =radiusInteraction - self%densityInteraction =densityInteraction - self%massInteraction =massInteraction - self%velocityDispersionInteraction=velocityDispersionInteraction - self%velocityDispersionCentral =velocityDispersionCentral - ! Compute interpolating factors in ξ. - call self%interpolatorXi%linearFactors(xi,self%indexXi,self%factorsXi) - return - end subroutine sidmIsothermalComputeSolution - - double precision function sidmIsothermalDensity(self,node,radius) - !!{ - Returns the density (in $M_\odot$ Mpc$^{-3}$) in the dark matter profile of {\normalfont \ttfamily node} at the given - {\normalfont \ttfamily radius} (given in units of Mpc). - !!} - implicit none - class (darkMatterProfileDMOSIDMIsothermal), intent(inout) :: self - type (treeNode ), intent(inout) :: node - double precision , intent(in ) :: radius - integer (c_size_t ) :: i , j, & - & indexRadius - double precision , dimension(0:1) :: factorsRadius - - if (radius > self%radiusInteraction(node)) then - sidmIsothermalDensity=self%darkMatterProfileDMO_%density(node,radius) - else - if (node%uniqueID() /= self%uniqueIDPrevious) call self%calculationReset(node,node%uniqueID()) - if (self%velocityDispersionCentral <= 0.0d0 ) call self%computeSolution (node ) - call self%interpolatorRadiiDimensionless%linearFactors(radius/self%radiusInteraction_,indexRadius,factorsRadius) - sidmIsothermalDensity=0.0d0 - do i=0,1 - do j=0,1 - sidmIsothermalDensity=+ sidmIsothermalDensity & - & +self%densityProfileDimensionless(indexRadius+i,self%indexXi+j) & - & * factorsRadius ( i ) & - & *self%factorsXi ( j) - end do - end do - sidmIsothermalDensity=+ sidmIsothermalDensity & - & *self%densityInteraction - end if - return - end function sidmIsothermalDensity - - double precision function sidmIsothermalDensityLogSlope(self,node,radius) - !!{ - Returns the logarithmic slope of the density in the dark matter profile of {\normalfont \ttfamily node} at the given - {\normalfont \ttfamily radius} (given in units of Mpc). - !!} - implicit none - class (darkMatterProfileDMOSIDMIsothermal), intent(inout) :: self - type (treeNode ), intent(inout) :: node - double precision , intent(in ) :: radius - integer (c_size_t ) :: indexRadius - double precision , dimension(0:1) :: factorsRadius - - if (radius > self%radiusInteraction(node)) then - sidmIsothermalDensityLogSlope=self%darkMatterProfileDMO_%densityLogSlope(node,radius) - else - if (node%uniqueID() /= self%uniqueIDPrevious) call self%calculationReset(node,node%uniqueID()) - if (self%velocityDispersionCentral <= 0.0d0 ) call self%computeSolution (node ) - call self%interpolatorRadiiDimensionless%linearFactors(radius/self%radiusInteraction_,indexRadius,factorsRadius) - if (indexRadius > 1) then - sidmIsothermalDensityLogSlope=+log(self%densityProfileDimensionless(indexRadius+1,self%indexXi+0)/self%densityProfileDimensionless(indexRadius+0,self%indexXi+0)) & - & /log(self%radiiDimensionless (indexRadius+1 )/self%radiiDimensionless (indexRadius+0 )) - else - sidmIsothermalDensityLogSlope=+0.0d0 - end if - end if - return - end function sidmIsothermalDensityLogSlope - - double precision function sidmIsothermalEnclosedMass(self,node,radius) - !!{ - Returns the enclosed mass (in $M_\odot$) in the dark matter profile of {\normalfont \ttfamily node} at the given {\normalfont \ttfamily radius} (given in - units of Mpc). - !!} - implicit none - class (darkMatterProfileDMOSIDMIsothermal), intent(inout) :: self - type (treeNode ), intent(inout) :: node - double precision , intent(in ) :: radius - integer (c_size_t ) :: i , j, & - & indexRadius - double precision , dimension(0:1) :: factorsRadius - - if (radius > self%radiusInteraction(node)) then - sidmIsothermalEnclosedMass=self%darkMatterProfileDMO_%enclosedMass(node,radius) - else - if (node%uniqueID() /= self%uniqueIDPrevious) call self%calculationReset(node,node%uniqueID()) - if (self%velocityDispersionCentral <= 0.0d0 ) call self%computeSolution (node ) - call self%interpolatorRadiiDimensionless%linearFactors(radius/self%radiusInteraction_,indexRadius,factorsRadius) - sidmIsothermalEnclosedMass=0.0d0 - do i=0,1 - do j=0,1 - sidmIsothermalEnclosedMass=+ sidmIsothermalEnclosedMass & - & +self%massProfileDimensionless (indexRadius+i,self%indexXi+j) & - & * factorsRadius ( i ) & - & *self%factorsXi ( j) - end do - end do - sidmIsothermalEnclosedMass=+ sidmIsothermalEnclosedMass & - & *self%massInteraction - end if - return - end function sidmIsothermalEnclosedMass - - double precision function sidmIsothermalRadiusEnclosingDensity(self,node,density) - !!{ - Returns the radius (in Mpc) in the dark matter profile of {\normalfont \ttfamily node} which encloses the given - {\normalfont \ttfamily density} (given in units of $M_\odot/$Mpc$^{-3}$). - !!} - implicit none - class (darkMatterProfileDMOSIDMIsothermal), intent(inout), target :: self - type (treeNode ), intent(inout), target :: node - double precision , intent(in ) :: density - - sidmIsothermalRadiusEnclosingDensity=self%radiusEnclosingDensityNumerical(node,density) - return - end function sidmIsothermalRadiusEnclosingDensity - - double precision function sidmIsothermalRadiusEnclosingMass(self,node,mass) - !!{ - Returns the radius (in Mpc) in the dark matter profile of {\normalfont \ttfamily node} which encloses the given - {\normalfont \ttfamily mass} (given in units of $M_\odot$). - !!} - implicit none - class (darkMatterProfileDMOSIDMIsothermal), intent(inout), target :: self - type (treeNode ), intent(inout), target :: node - double precision , intent(in ) :: mass - - sidmIsothermalRadiusEnclosingMass=self%radiusEnclosingMassNumerical(node,mass) - return - end function sidmIsothermalRadiusEnclosingMass - - double precision function sidmIsothermalRadialMoment(self,node,moment,radiusMinimum,radiusMaximum) - !!{ - Returns the density (in $M_\odot$ Mpc$^{-3}$) in the dark matter profile of {\normalfont \ttfamily node} at the given - {\normalfont \ttfamily radius} (given in units of Mpc). - !!} - implicit none - class (darkMatterProfileDMOSIDMIsothermal), intent(inout) :: self - type (treeNode ), intent(inout) :: node - double precision , intent(in ) :: moment - double precision , intent(in ), optional :: radiusMinimum, radiusMaximum - - sidmIsothermalRadialMoment=self%radialMomentNumerical(node,moment,radiusMinimum,radiusMaximum) - return - end function sidmIsothermalRadialMoment - - double precision function sidmIsothermalPotential(self,node,radius,status) + function sidmIsothermalGet(self,node,weightBy,weightIndex) result(massDistribution_) !!{ - Returns the potential (in (km/s)$^2$) in the dark matter profile of {\normalfont \ttfamily node} at the given {\normalfont - \ttfamily radius} (given in units of Mpc). + Return the dark matter mass distribution for the given {\normalfont \ttfamily node}. !!} + use :: Galacticus_Nodes , only : nodeComponentBasic + use :: Galactic_Structure_Options, only : componentTypeDarkHalo , massTypeDark , weightByMass + use :: Mass_Distributions , only : massDistributionSphericalSIDMIsothermal, kinematicsDistributionSIDMIsothermal, nonAnalyticSolversNumerical, massDistributionSpherical implicit none - class (darkMatterProfileDMOSIDMIsothermal), intent(inout) :: self - type (treeNode ), intent(inout), target :: node - double precision , intent(in ) :: radius - type (enumerationStructureErrorCodeType ), intent( out), optional :: status - - if (radius > self%radiusInteraction(node)) then - sidmIsothermalPotential=self%darkMatterProfileDMO_%potential(node,radius) - else - if (node%uniqueID() /= self%uniqueIDPrevious) call self%calculationReset(node,node%uniqueID()) - if (self%velocityDispersionCentral <= 0.0d0 ) call self%computeSolution (node ) - sidmIsothermalPotential=+ self%darkMatterProfileDMO_%potential (node,self%radiusInteraction_) & - & - self %velocityDispersionCentral **2 & - & *log( & - & +self %density (node, radius ) & - & /self %density (node,self%radiusInteraction_) & - & ) - end if - return - end function sidmIsothermalPotential - - double precision function sidmIsothermalCircularVelocity(self,node,radius) - !!{ - Returns the circular velocity (in km/s) in the dark matter profile of {\normalfont \ttfamily node} at the given - {\normalfont \ttfamily radius} (given in units of Mpc). - !!} - implicit none - class (darkMatterProfileDMOSIDMIsothermal), intent(inout) :: self - type (treeNode ), intent(inout) :: node - double precision , intent(in ) :: radius - - sidmIsothermalCircularVelocity=self%circularVelocityNumerical(node,radius) - return - end function sidmIsothermalCircularVelocity - - double precision function sidmIsothermalCircularVelocityMaximum(self,node) - !!{ - Returns the maximum circular velocity (in km/s) in the dark matter profile of {\normalfont \ttfamily node}. - !!} - implicit none - class(darkMatterProfileDMOSIDMIsothermal), intent(inout) :: self - type (treeNode ), intent(inout) :: node - - sidmIsothermalCircularVelocityMaximum=self%circularVelocityMaximumNumerical(node) - return - end function sidmIsothermalCircularVelocityMaximum - - double precision function sidmIsothermalRadiusCircularVelocityMaximum(self,node) - !!{ - Returns the radius (in Mpc) at which the maximum circular velocity is acheived in the dark matter profile of {\normalfont \ttfamily node}. - !!} - implicit none - class(darkMatterProfileDMOSIDMIsothermal), intent(inout) :: self - type (treeNode ), intent(inout) :: node - - sidmIsothermalRadiusCircularVelocityMaximum=self%radiusCircularVelocityMaximumNumerical(node) - return - end function sidmIsothermalRadiusCircularVelocityMaximum - - double precision function sidmIsothermalRadialVelocityDispersion(self,node,radius) - !!{ - Returns the radial velocity dispersion (in km/s) in the dark matter profile of {\normalfont \ttfamily node} at the given - {\normalfont \ttfamily radius} (given in units of Mpc). - !!} - implicit none - class (darkMatterProfileDMOSIDMIsothermal), intent(inout) :: self - type (treeNode ), intent(inout) :: node - double precision , intent(in ) :: radius - - if (radius > self%radiusInteraction(node)) then - sidmIsothermalRadialVelocityDispersion=self%darkMatterProfileDMO_%radialVelocityDispersion(node,radius) - else - if (node%uniqueID() /= self%uniqueIDPrevious) call self%calculationReset(node,node%uniqueID()) - if (self%velocityDispersionCentral <= 0.0d0 ) call self%computeSolution (node ) - sidmIsothermalRadialVelocityDispersion=self%velocityDispersionCentral - end if - return - end function sidmIsothermalRadialVelocityDispersion - - double precision function sidmIsothermalRadiusFromSpecificAngularMomentum(self,node,specificAngularMomentum) - !!{ - Returns the radius (in Mpc) in {\normalfont \ttfamily node} at which a circular orbit has the given {\normalfont \ttfamily specificAngularMomentum} (given - in units of km s$^{-1}$ Mpc). - !!} - implicit none - class (darkMatterProfileDMOSIDMIsothermal), intent(inout) :: self - type (treeNode ), intent(inout) :: node - double precision , intent(in ) :: specificAngularMomentum - - sidmIsothermalRadiusFromSpecificAngularMomentum=self%radiusFromSpecificAngularMomentumNumerical(node,specificAngularMomentum) - return - end function sidmIsothermalRadiusFromSpecificAngularMomentum - - double precision function sidmIsothermalRotationNormalization(self,node) - !!{ - Return the normalization of the rotation velocity vs. specific angular momentum relation. - !!} - implicit none - class(darkMatterProfileDMOSIDMIsothermal), intent(inout) :: self - type (treeNode ), intent(inout) :: node - - sidmIsothermalRotationNormalization=self%rotationNormalizationNumerical(node) - return - end function sidmIsothermalRotationNormalization - - double precision function sidmIsothermalEnergy(self,node) - !!{ - Return the energy of a sidmIsothermal halo density profile. - !!} - implicit none - class(darkMatterProfileDMOSIDMIsothermal), intent(inout) :: self - type (treeNode ), intent(inout) :: node - - sidmIsothermalEnergy=self%energyNumerical(node) - return - end function sidmIsothermalEnergy - - double precision function sidmIsothermalKSpace(self,node,waveNumber) - !!{ - Returns the Fourier transform of the sidmIsothermal density profile at the specified {\normalfont \ttfamily waveNumber} - (given in Mpc$^{-1}$). - !!} - implicit none - class (darkMatterProfileDMOSIDMIsothermal), intent(inout) :: self - type (treeNode ), intent(inout), target :: node - double precision , intent(in ) :: waveNumber - - sidmIsothermalKSpace=self%kSpaceNumerical(node,waveNumber) - return - end function sidmIsothermalKSpace - - double precision function sidmIsothermalFreefallRadius(self,node,time) - !!{ - Returns the freefall radius in the sidmIsothermal density profile at the specified {\normalfont \ttfamily time} (given in - Gyr). - !!} - implicit none - class (darkMatterProfileDMOSIDMIsothermal), intent(inout), target :: self - type (treeNode ), intent(inout), target :: node - double precision , intent(in ) :: time - - sidmIsothermalFreefallRadius=self%freefallRadiusNumerical(node,time) - return - end function sidmIsothermalFreefallRadius - - double precision function sidmIsothermalFreefallRadiusIncreaseRate(self,node,time) - !!{ - Returns the rate of increase of the freefall radius in the sidmIsothermal density profile at the specified {\normalfont - \ttfamily time} (given in Gyr). - !!} - implicit none - class (darkMatterProfileDMOSIDMIsothermal), intent(inout), target :: self - type (treeNode ), intent(inout), target :: node - double precision , intent(in ) :: time + class (massDistributionClass ), pointer :: massDistribution_ + type (kinematicsDistributionSIDMIsothermal), pointer :: kinematicsDistribution_ + class (darkMatterProfileDMOSIDMIsothermal ), intent(inout) :: self + type (treeNode ), intent(inout) :: node + type (enumerationWeightByType ), intent(in ), optional :: weightBy + integer , intent(in ), optional :: weightIndex + class (massDistributionClass ), pointer :: massDistributionDecorated + class (nodeComponentBasic ), pointer :: basic + !![ + + !!] - sidmIsothermalFreefallRadiusIncreaseRate=self%freefallRadiusIncreaseRateNumerical(node,time) + ! Assume a null distribution by default. + massDistribution_ => null() + ! If weighting is not by mass, return a null profile. + if (weightBy_ /= weightByMass) return + ! Create the mass distribution. + allocate(massDistributionSphericalSIDMIsothermal :: massDistribution_) + select type(massDistribution_) + type is (massDistributionSphericalSIDMIsothermal) + massDistributionDecorated => self%darkMatterProfileDMO_%get (node,weightBy,weightIndex) + basic => node %basic( ) + select type (massDistributionDecorated) + class is (massDistributionSpherical) + !![ + + + massDistributionSphericalSIDMIsothermal( & + & timeAge =basic%time (), & + & nonAnalyticSolver = nonAnalyticSolversNumerical , & + & massDistribution_ = massDistributionDecorated , & + & darkMatterParticle_=self %darkMatterParticle_ , & + & componentType = componentTypeDarkHalo , & + & massType = massTypeDark & + & ) + + + !!] + class default + call Error_Report('expected a spherical mass distribution'//{introspection:location}) + end select + !![ + + !!] + end select + allocate(kinematicsDistribution_) + !![ + + + kinematicsDistributionSIDMIsothermal( & + & ) + + + !!] + call massDistribution_%setKinematicsDistribution(kinematicsDistribution_) + !![ + + !!] return - end function sidmIsothermalFreefallRadiusIncreaseRate + end function sidmIsothermalGet diff --git a/source/dark_matter_profiles_DMO.Zhao1996.F90 b/source/dark_matter_profiles_DMO.Zhao1996.F90 index e55a525244..a4cfba5c8b 100644 --- a/source/dark_matter_profiles_DMO.Zhao1996.F90 +++ b/source/dark_matter_profiles_DMO.Zhao1996.F90 @@ -21,28 +21,10 @@ An implementation of \cite{zhao_analytical_1996} dark matter halo profiles. !!} - !![ - - specialCase - Special cases for {\normalfont \ttfamily zhao1996} dark matter halo profile class. - - - - - - - !!] - !![ - A dark matter profile DMO class which implements the \cite{zhao_analytical_1996} density profile - \begin{equation} - \rho_\mathrm{dark matter}(r) \propto \left({r\over r_\mathrm{s}}\right)^{-\gamma} \left(1+\left[{r\over r_\mathrm{s}}\right]^\alpha \right)^{-(\beta-\gamma)/\alpha}, - \end{equation} - normalized such that the total mass of the \gls{node} is enclosed with the virial radius and with the scale length - $r_\mathrm{s}$. Numerical solutions are implemented for the case of general $(\alpha,\beta,\gamma)$, with some analytic - solution for certain special cases. + A dark matter profile DMO class which builds \refClass{massDistributionZhao1996} objects. !!] @@ -51,43 +33,22 @@ A dark matter halo profile class implementing \cite{zhao_analytical_1996} dark matter halos. !!} private - type (enumerationSpecialCaseType) :: specialCase - double precision :: alpha , beta, & - & gamma - contains + class (darkMatterHaloScaleClass ), pointer :: darkMatterHaloScale_ => null() + double precision :: alpha , beta, & + & gamma + contains !![ - - - - - + + + !!] - final :: zhao1996Destructor - procedure :: autoHook => zhao1996AutoHook - procedure :: calculationReset => zhao1996CalculationReset - procedure :: exponents => zhao1996Exponents - procedure :: scaleRadius => zhao1996ScaleRadius - procedure :: normalization => zhao1996Normalization - procedure :: massUnnormalized => zhao1996MassUnnormalized - procedure :: density => zhao1996Density - procedure :: densityLogSlope => zhao1996DensityLogSlope - procedure :: enclosedMass => zhao1996EnclosedMass - procedure :: radiusEnclosingMass => zhao1996RadiusEnclosingMass - procedure :: radiusEnclosingDensity => zhao1996RadiusEnclosingDensity - procedure :: potential => zhao1996Potential - procedure :: circularVelocity => zhao1996CircularVelocity - procedure :: circularVelocityMaximum => zhao1996CircularVelocityMaximum - procedure :: radiusCircularVelocityMaximum => zhao1996RadiusCircularVelocityMaximum - procedure :: radialVelocityDispersion => zhao1996RadialVelocityDispersion - procedure :: radiusFromSpecificAngularMomentum => zhao1996RadiusFromSpecificAngularMomentum - procedure :: rotationNormalization => zhao1996RotationNormalization - procedure :: energy => zhao1996Energy - procedure :: kSpace => zhao1996KSpace - procedure :: freefallRadius => zhao1996FreefallRadius - procedure :: freefallRadiusIncreaseRate => zhao1996FreefallRadiusIncreaseRate - procedure :: radialMoment => zhao1996RadialMoment + final :: zhao1996Destructor + procedure :: get => zhao1996Get + procedure :: exponents => zhao1996Exponents + procedure :: scaleRadius => zhao1996ScaleRadius + procedure :: normalization => zhao1996Normalization end type darkMatterProfileDMOZhao1996 interface darkMatterProfileDMOZhao1996 @@ -98,12 +59,6 @@ module procedure zhao1996ConstructorInternal end interface darkMatterProfileDMOZhao1996 - ! Sub-module scope variables used in numerical solutions. - class (darkMatterProfileDMOZhao1996), pointer :: self_ - type (treeNode ), pointer :: node_ - double precision :: time_, radiusFreefall_ - !$omp threadprivate(self_,node_,time_,radiusFreefall_) - contains function zhao1996ConstructorParameters(parameters) result(self) @@ -148,7 +103,6 @@ function zhao1996ConstructorInternal(alpha,beta,gamma,darkMatterHaloScale_) resu !!{ Generic constructor for the {\normalfont \ttfamily zhao1996} dark matter halo profile class. !!} - use :: Numerical_Comparison, only : Values_Agree implicit none type (darkMatterProfileDMOZhao1996) :: self class (darkMatterHaloScaleClass ), intent(in ), target :: darkMatterHaloScale_ @@ -158,100 +112,86 @@ function zhao1996ConstructorInternal(alpha,beta,gamma,darkMatterHaloScale_) resu !!] - ! Detect special cases. - if ( & - & Values_Agree(alpha,1.0d0,absTol=1.0d-6) & - & .and. & - & Values_Agree(beta ,3.0d0,absTol=1.0d-6) & - & .and. & - & Values_Agree(gamma,1.0d0,absTol=1.0d-6) & - & ) then - ! The "NFW" profile. - self%specialCase=specialCaseNFW - else if ( & - & Values_Agree(alpha,1.0d0,absTol=1.0d-6) & - & .and. & - & Values_Agree(beta ,3.0d0,absTol=1.0d-6) & - & .and. & - & Values_Agree(gamma,0.0d0,absTol=1.0d-6) & - & ) then - ! The "cored NFW" profile. - self%specialCase=specialCaseCoredNFW - else if ( & - & Values_Agree(alpha,1.0d0,absTol=1.0d-6) & - & .and. & - & Values_Agree(beta ,3.0d0,absTol=1.0d-6) & - & .and. & - & Values_Agree(gamma,0.5d0,absTol=1.0d-6) & - & ) then - ! The "γ=1/2 NFW" profile. - self%specialCase=specialCaseGamma0_5NFW - else if ( & - & Values_Agree(alpha,1.0d0,absTol=1.0d-6) & - & .and. & - & Values_Agree(beta ,3.0d0,absTol=1.0d-6) & - & .and. & - & Values_Agree(gamma,1.5d0,absTol=1.0d-6) & - & ) then - ! The "γ=3/2 NFW" profile. - self%specialCase=specialCaseGamma1_5NFW - else - ! Use general solutions. - self%specialCase=specialCaseGeneral - end if return end function zhao1996ConstructorInternal - subroutine zhao1996AutoHook(self) + function zhao1996Get(self,node,weightBy,weightIndex) result(massDistribution_) !!{ - Attach to the calculation reset event. + Return the dark matter mass distribution for the given {\normalfont \ttfamily node}. !!} - use :: Events_Hooks, only : calculationResetEvent, openMPThreadBindingAllLevels + use :: Galactic_Structure_Options, only : componentTypeDarkHalo , massTypeDark , weightByMass + use :: Mass_Distributions , only : massDistributionZhao1996, kinematicsDistributionZhao1996 implicit none - class(darkMatterProfileDMOZhao1996), intent(inout) :: self + class (massDistributionClass ), pointer :: massDistribution_ + type (kinematicsDistributionZhao1996 ), pointer :: kinematicsDistribution_ + class (darkMatterProfileDMOZhao1996 ), intent(inout) :: self + type (treeNode ), intent(inout) :: node + type (enumerationWeightByType ), intent(in ), optional :: weightBy + integer , intent(in ), optional :: weightIndex + double precision :: alpha , beta , & + & gamma , scaleRadius, & + & mass + !![ + + !!] - call calculationResetEvent%attach(self,zhao1996CalculationReset,openMPThreadBindingAllLevels,label='darkMatterProfileDMOZhao1996') + ! Assume a null distribution by default. + massDistribution_ => null() + ! If weighting is not by mass, return a null profile. + if (weightBy_ /= weightByMass) return + ! Create the mass distribution. + allocate(massDistributionZhao1996 :: massDistribution_) + select type(massDistribution_) + type is (massDistributionZhao1996) + mass =self%normalization(node) + scaleRadius=self%scaleRadius (node) + call self%exponents(node,alpha,beta,gamma) + !![ + + + massDistributionZhao1996( & + & mass = mass , & + & radiusOuter =self %darkMatterHaloScale_%radiusVirial (node), & + & scaleLength = scaleRadius , & + & alpha = alpha , & + & beta = beta , & + & gamma = gamma , & + & componentType= componentTypeDarkHalo , & + & massType = massTypeDark & + & ) + + + !!] + end select + allocate(kinematicsDistribution_) + !![ + + + kinematicsDistributionZhao1996( & + & ) + + + !!] + call massDistribution_%setKinematicsDistribution(kinematicsDistribution_) + !![ + + !!] return - end subroutine zhao1996AutoHook + end function zhao1996Get subroutine zhao1996Destructor(self) !!{ Destructor for the {\normalfont \ttfamily zhao1996} dark matter halo profile class. !!} - use :: Events_Hooks, only : calculationResetEvent implicit none type(darkMatterProfileDMOZhao1996), intent(inout) :: self !![ !!] - if (calculationResetEvent%isAttached(self,zhao1996CalculationReset)) call calculationResetEvent%detach(self,zhao1996CalculationReset) return end subroutine zhao1996Destructor - subroutine zhao1996CalculationReset(self,node,uniqueID) - !!{ - Reset the dark matter profile calculation. - !!} - use :: Kind_Numbers, only : kind_int8 - implicit none - class (darkMatterProfileDMOZhao1996), intent(inout) :: self - type (treeNode ), intent(inout) :: node - integer(kind_int8 ), intent(in ) :: uniqueID - !$GLC attributes unused :: node - - self%genericLastUniqueID =uniqueID - self%genericEnclosedMassRadiusMinimum =+huge(0.0d0) - self%genericEnclosedMassRadiusMaximum =-huge(0.0d0) - self%genericVelocityDispersionRadialRadiusMinimum=+huge(0.0d0) - self%genericVelocityDispersionRadialRadiusMaximum=-huge(0.0d0) - if (allocated(self%genericVelocityDispersionRadialVelocity)) deallocate(self%genericVelocityDispersionRadialVelocity) - if (allocated(self%genericVelocityDispersionRadialRadius )) deallocate(self%genericVelocityDispersionRadialRadius ) - if (allocated(self%genericEnclosedMassMass )) deallocate(self%genericEnclosedMassMass ) - if (allocated(self%genericEnclosedMassRadius )) deallocate(self%genericEnclosedMassRadius ) - return - end subroutine zhao1996CalculationReset - subroutine zhao1996Exponents(self,node,alpha,beta,gamma) !!{ Compute the exponents of the {\normalfont \ttfamily zhao1996} dark matter halo profile. @@ -282,913 +222,18 @@ double precision function zhao1996ScaleRadius(self,node) zhao1996ScaleRadius = darkMatterProfile%scale () return end function zhao1996ScaleRadius - + double precision function zhao1996Normalization(self,node) !!{ - Returns the normalization of the dark matter profile of {\normalfont \ttfamily node}. + Compute the mass normalization of the {\normalfont \ttfamily zhao1996} dark matter halo profile. !!} use :: Galacticus_Nodes, only : nodeComponentBasic implicit none - class (darkMatterProfileDMOZhao1996), intent(inout) :: self - type (treeNode ), intent(inout) :: node - class (nodeComponentBasic ), pointer :: basic - double precision :: radiusScale, radiusVirialScaleFree - - basic => node %basic ( ) - radiusScale = self %scaleRadius (node ) - radiusVirialScaleFree = +self %darkMatterHaloScale_%radiusVirial (node ) & - & / radiusScale - zhao1996Normalization = +basic %mass ( ) & - & /self %massUnnormalized(node,radiusVirialScaleFree) & - & / radiusScale **3 - return - end function zhao1996Normalization - - double precision function zhao1996MassUnnormalized(self,node,radiusScaleFree) - !!{ - Returns the unnormalized mass in the dark matter profile of {\normalfont \ttfamily node} at the given - {\normalfont \ttfamily radiusScaleFree}. - !!} - use :: Error , only : Error_Report - use :: Numerical_Constants_Math, only : Pi - use :: Hypergeometric_Functions, only : Hypergeometric_2F1 - implicit none - class (darkMatterProfileDMOZhao1996), intent(inout) :: self - type (treeNode ), intent(inout) :: node - double precision , intent(in ) :: radiusScaleFree - double precision , parameter :: radiusScaleFreeTiny=1.0d-3 - double precision :: alpha , beta, & - & gamma - - select case (self%specialCase%ID) - case (specialCaseGeneral%ID) - call self%exponents(node,alpha,beta,gamma) - zhao1996MassUnnormalized=+4.0d0 & - & *Pi & - & *radiusScaleFree**(3.0d0-gamma) & - & *Hypergeometric_2F1([(3.0d0-gamma)/alpha,(beta-gamma)/alpha],[1.0d0+(3.0d0-gamma)/alpha],-radiusScaleFree**alpha) & - & / (3.0d0-gamma) - case (specialCaseNFW%ID) - if (radiusScaleFree node%basic ( ) - radiusScale = self%scaleRadius (node) - radiusScaleFree = + radius & - & / radiusScale - select case (self%specialCase%ID) - case (specialCaseGeneral%ID) - zhao1996Potential = -4.0d0 & - & *Pi & - & *( & - & +1.0 & - & -radiusScaleFree**(2.0d0-gamma) & - & *( & - & +Hypergeometric_2F1([(2.0d0-gamma)/alpha,(beta-gamma)/alpha],[(2.0d0+alpha-gamma)/alpha],-radiusScaleFree**alpha)/(2.0d0-gamma) & - & -Hypergeometric_2F1([(3.0d0-gamma)/alpha,(beta-gamma)/alpha],[(3.0d0+alpha-gamma)/alpha],-radiusScaleFree**alpha)/(3.0d0-gamma) & - & ) & - & ) & - & *gravitationalConstantGalacticus & - & *self%normalization(node) & - & *self%scaleRadius (node)**2 - case (specialCaseNFW%ID) - zhao1996Potential = -4.0d0 & - & *Pi & - & *log(1.0d0+radiusScaleFree) & - & / radiusScaleFree & - & *gravitationalConstantGalacticus & - & *self%normalization(node) & - & *self%scaleRadius (node)**2 - case (specialCaseCoredNFW%ID) - zhao1996Potential = -4.0d0 & - & *Pi & - & *( & - & +0.5d0 & - & * radiusScaleFree & - & / (1.0d0+radiusScaleFree) & - & +log(1.0d0+radiusScaleFree) & - & / radiusScaleFree & - & ) & - & *gravitationalConstantGalacticus & - & *self%normalization(node) & - & *self%scaleRadius (node)**2 - case (specialCaseGamma0_5NFW%ID) - zhao1996Potential = -4.0d0 & - & *Pi & - & *( & - & +1.0d0 & - & -2.0d0 & - & /3.0d0 & - & *sqrt( & - & + radiusScaleFree & - & /(1.0d0+radiusScaleFree) & - & ) & - & -2.0d0 & - & /sqrt( & - & + radiusScaleFree & - & *(1.0d0+radiusScaleFree) & - & ) & - & +2.0d0 & - & *asinh(sqrt( radiusScaleFree)) & - & / radiusScaleFree & - & ) & - & *gravitationalConstantGalacticus & - & *self%normalization(node) & - & *self%scaleRadius (node)**2 - case (specialCaseGamma1_5NFW%ID) - zhao1996Potential = -4.0d0 & - & *Pi & - & *( & - & +1.0d0 & - & -2.0d0 & - & *sqrt( & - & + radiusScaleFree & - & /(1.0d0+radiusScaleFree) & - & ) & - & -2.0d0 & - & /sqrt( & - & + radiusScaleFree & - & *(1.0d0+radiusScaleFree) & - & ) & - & +2.0d0 & - & *asinh(sqrt( radiusScaleFree)) & - & / radiusScaleFree & - & ) & - & *gravitationalConstantGalacticus & - & *self%normalization(node) & - & *self%scaleRadius (node)**2 - case default - zhao1996Potential=+0.0d0 - call Error_Report('unknown special case'//{introspection:location}) - end select - return - end function zhao1996Potential - - double precision function zhao1996CircularVelocity(self,node,radius) - !!{ - Returns the circular velocity (in km/s) in the dark matter profile of {\normalfont \ttfamily node} at the given {\normalfont \ttfamily radius} (given in - units of Mpc). - !!} - use :: Numerical_Constants_Astronomical, only : gravitationalConstantGalacticus - implicit none - class (darkMatterProfileDMOZhao1996), intent(inout) :: self - type (treeNode ), intent(inout) :: node - double precision , intent(in ) :: radius - - if (radius > 0.0d0) then - zhao1996CircularVelocity=sqrt(gravitationalConstantGalacticus*self%enclosedMass(node,radius)/radius) - else - zhao1996CircularVelocity=0.0d0 - end if - return - end function zhao1996CircularVelocity - - double precision function zhao1996CircularVelocityMaximum(self,node) - !!{ - Returns the maximum circular velocity (in km/s) in the dark matter profile of {\normalfont \ttfamily node}. - !!} - implicit none - class(darkMatterProfileDMOZhao1996 ), intent(inout) :: self - type (treeNode ), intent(inout) :: node - - zhao1996CircularVelocityMaximum=self%circularVelocityMaximumNumerical(node) - return - end function zhao1996CircularVelocityMaximum - - double precision function zhao1996RadiusCircularVelocityMaximum(self,node) - !!{ - Returns the radius (in Mpc) at which the maximum circular velocity occurs in the dark matter profile of {\normalfont \ttfamily node}. - !!} - implicit none - class(darkMatterProfileDMOZhao1996 ), intent(inout) :: self - type (treeNode ), intent(inout) :: node - - zhao1996RadiusCircularVelocityMaximum=self%radiusCircularVelocityMaximumNumerical(node) - return - end function zhao1996RadiusCircularVelocityMaximum - - double precision function zhao1996RadialVelocityDispersion(self,node,radius) - !!{ - Returns the radial velocity dispersion (in km/s) in the dark matter profile of {\normalfont \ttfamily node} at the given - {\normalfont \ttfamily radius} (given in units of Mpc). - !!} - use :: Error , only : Error_Report - use :: Numerical_Constants_Math , only : Pi - use :: Numerical_Constants_Astronomical, only : gravitationalConstantGalacticus - use :: Polylogarithms , only : Polylogarithm_2 - implicit none - class (darkMatterProfileDMOZhao1996 ), intent(inout) :: self - type (treeNode ), intent(inout) :: node - double precision , intent(in ) :: radius - double precision , parameter :: radiusScaleFreeTiny =1.0d-3, radiusScaleFreeLarge=1.0d2 - double precision :: radiusScale , radiusScaleFree , & - & velocityDispersionSquaredScaleFree - - select case (self%specialCase%ID) - case (specialCaseGeneral%ID) - zhao1996RadialVelocityDispersion=self%radialVelocityDispersionNumerical(node,radius) - case (specialCaseNFW%ID) - radiusScale = self%scaleRadius(node) - radiusScaleFree=+ radius & - & / radiusScale - if (radiusScaleFree < radiusScaleFreeTiny ) then - ! Use series solution for small radii. - velocityDispersionSquaredScaleFree=+11.0d0*Pi/15.0d0*radiusScaleFree**4 & - & + Pi/ 6.0d0*radiusScaleFree**3*(-101.0d0+12.0d0*Pi**2-12.0d0*log(radiusScaleFree)) & - & + 2.0d0*Pi/ 3.0d0*radiusScaleFree**2*(- 59.0d0+ 6.0d0*Pi**2- 6.0d0*log(radiusScaleFree)) & - & + Pi *radiusScaleFree *(- 23.0d0+ 2.0d0*Pi**2- 2.0d0*log(radiusScaleFree)) - else if (radiusScaleFree > radiusScaleFreeLarge) then - ! Use series solution for large radii. - velocityDispersionSquaredScaleFree=+Pi*(- 3.0d0+ 4.0d0*log(radiusScaleFree))/( 4.0d0*radiusScaleFree ) & - & +Pi*(+ 69.0d0+ 20.0d0*log(radiusScaleFree))/( 50.0d0*radiusScaleFree**2) & - & +Pi*(- 97.0d0- 60.0d0*log(radiusScaleFree))/( 300.0d0*radiusScaleFree**3) & - & +Pi*(+284.0d0+420.0d0*log(radiusScaleFree))/(3675.0d0*radiusScaleFree**4) - else - ! Use full solution. - velocityDispersionSquaredScaleFree=+( & - & +2.0d0 & - & *Pi & - & *( & - & +radiusScaleFree & - & *( & - & -1.0d0 & - & +radiusScaleFree & - & *( & - & - 9.0d0 & - & - 7.0d0*radiusScaleFree & - & +Pi**2*(+1.0d0+radiusScaleFree)**2 & - & ) & - & ) & - & +radiusScaleFree**4*log(+1.0d0+1.0d0/radiusScaleFree) & - & + log(+1.0d0+ radiusScaleFree) & - & +radiusScaleFree & - & *( & - & -( & - & + radiusScaleFree*(+1.0d0+2.0d0*radiusScaleFree) *log( +radiusScaleFree) & - & ) & - & + log(+1.0d0+radiusScaleFree) & - & *( & - & -2.0d0-4.0d0*radiusScaleFree*(+2.0d0+ radiusScaleFree) & - & +3.0d0 *radiusScaleFree*(+1.0d0+ radiusScaleFree)**2*log(+1.0d0+radiusScaleFree) & - & ) & - & ) & - & + 6.0d0 & - & * radiusScaleFree **2 & - & * (+1.0d0+radiusScaleFree)**2 & - & *PolyLogarithm_2( -radiusScaleFree) & - & ) & - & ) & - & /radiusScaleFree - end if - zhao1996RadialVelocityDispersion=+sqrt( & - & + gravitationalConstantGalacticus & - & *self%normalization (node) & - & * radiusScale **2 & - & * velocityDispersionSquaredScaleFree & - & ) - case (specialCaseCoredNFW%ID) - radiusScale = self%scaleRadius(node) - radiusScaleFree=+ radius & - & / radiusScale - if (radiusScaleFree < radiusScaleFreeTiny ) then - ! Use series solution for small radii. - velocityDispersionSquaredScaleFree=+(119.0d0*Pi-12.0d0*Pi**3)/ 6.0d0 & - & +(119.0d0*Pi-12.0d0*Pi**3)/ 2.0d0*radiusScaleFree & - & +(353.0d0*Pi-36.0d0*Pi**3)/ 6.0d0*radiusScaleFree**2 & - & +(121.0d0*Pi-12.0d0*Pi**3)/ 6.0d0*radiusScaleFree**3 & - & - 9.0d0*Pi**4 /20.0d0*radiusScaleFree**4 - else if (radiusScaleFree > radiusScaleFreeLarge) then - ! Use series solution for large radii. - velocityDispersionSquaredScaleFree=+Pi*(- 5.0d0+ 4.0d0*log(radiusScaleFree))/( 4.0d0*radiusScaleFree ) & - & +Pi*(+ 177.0d0+ 60.0d0*log(radiusScaleFree))/( 100.0d0*radiusScaleFree**2) & - & +Pi*(- 157.0d0- 60.0d0*log(radiusScaleFree))/( 300.0d0*radiusScaleFree**3) & - & +Pi*(+5857.0d0+1260.0d0*log(radiusScaleFree))/(14700.0d0*radiusScaleFree**4) - else - ! Use full solution. - velocityDispersionSquaredScaleFree=+( & - & +( & - & +Pi & - & *( & - & +95.0d0 & - & -12.0d0*Pi**2 & - & *(1.0d0+ radiusScaleFree)**4 & - & + 2.0d0*radiusScaleFree & - & *( & - & + 130.0d0 & - & + 9.0d0*radiusScaleFree & - & *( & - & + 13.0d0 & - & + 4.0d0*radiusScaleFree & - & ) & - & ) & - & ) & - & ) & - & /6.0d0 & - & / (1.0d0+ radiusScaleFree )**4 & - & +2.0d0*Pi*log (1.0d0+ radiusScaleFree ) & - & * (2.0d0+9.0d0*radiusScaleFree+6.0d0*radiusScaleFree**2) & - & / radiusScaleFree & - & / (1.0d0+ radiusScaleFree )**2 & - & - 6.0d0*Pi*log (1.0d0+ radiusScaleFree )**2 & - & -12.0d0*Pi*Polylogarithm_2( - radiusScaleFree ) & - & ) & - & * (1.0d0+ radiusScaleFree )**3 - end if - zhao1996RadialVelocityDispersion=+sqrt( & - & + gravitationalConstantGalacticus & - & *self%normalization (node) & - & * radiusScale **2 & - & * velocityDispersionSquaredScaleFree & - & ) - case (specialCaseGamma0_5NFW%ID) - radiusScale = self%scaleRadius(node) - radiusScaleFree=+ radius & - & / radiusScale - if (radiusScaleFree < radiusScaleFreeTiny ) then - ! Use series solution for small radii. - velocityDispersionSquaredScaleFree=+16.0d0*Pi/ 3.0d0*sqrt(radiusScaleFree )*(- 11.0d0+ 16.0d0*log(2.0d0)) & - & +16.0d0*Pi/ 15.0d0* radiusScaleFree**1.5d0*(-139.0d0+ 200.0d0*log(2.0d0)) & - & + 2.0d0*Pi/ 7.0d0* radiusScaleFree**2.5d0*(-387.0d0+ 560.0d0*log(2.0d0)) & - & + 4.0d0*Pi/189.0d0* radiusScaleFree**3.5d0*(-887.0d0+1260.0d0*log(2.0d0)) - else if (radiusScaleFree > radiusScaleFreeLarge) then - ! Use series solution for large radii. - velocityDispersionSquaredScaleFree=+Pi*(- 29.0d0+ 24.0d0*log(2.0d0)+ 12.0d0*log(radiusScaleFree))/( 12.0d0*radiusScaleFree ) & - & +Pi*(+107.0d0+120.0d0*log(2.0d0)+ 60.0d0*log(radiusScaleFree))/( 120.0d0*radiusScaleFree**2) & - & +Pi*(- 11.0d0- 40.0d0*log(2.0d0)- 20.0d0*log(radiusScaleFree))/( 96.0d0*radiusScaleFree**3) & - & +Pi*(+ 57.0d0+280.0d0*log(2.0d0)+140.0d0*log(radiusScaleFree))/(1344.0d0*radiusScaleFree**4) - else - ! Use full solution. - velocityDispersionSquaredScaleFree=+( & - & +8.0d0 & - & *Pi & - & *( & - & - 6.0d0*sqrt(radiusScaleFree *(+1.0d0+radiusScaleFree)) & - & -38.0d0*sqrt(radiusScaleFree**3*(+1.0d0+radiusScaleFree)) & - & -57.0d0*sqrt(radiusScaleFree**5*(+1.0d0+radiusScaleFree)) & - & -24.0d0*sqrt(radiusScaleFree**7*(+1.0d0+radiusScaleFree)) & - & - 6.0d0 & - & *(+1.0d0+ radiusScaleFree )**2 & - & *(+1.0d0+2.0d0*radiusScaleFree ) & - & *(-1.0d0+8.0d0*radiusScaleFree*(1.0d0+radiusScaleFree)) & - & *asinh(sqrt(radiusScaleFree)) & - & -24.0d0 & - & * radiusScaleFree **1.5d0 & - & *(+1.0d0+radiusScaleFree)**3.5d0 & - & *log(radiusScaleFree/(16.0d0*(1.0d0+radiusScaleFree))) & - & +24.0d0 & - & *( & - & + sqrt(radiusScaleFree**3*(1.0d0+radiusScaleFree)) & - & +3.0d0*sqrt(radiusScaleFree**5*(1.0d0+radiusScaleFree)) & - & +3.0d0*sqrt(radiusScaleFree**7*(1.0d0+radiusScaleFree)) & - & + sqrt(radiusScaleFree**9*(1.0d0+radiusScaleFree)) & - & ) & - & *( & - & +log( radiusScaleFree) & - & +log(+1.0d0+radiusScaleFree) & - & ) & - & ) & - & ) & - & /( & - & +9.0d0 & - & * radiusScaleFree & - & *(+1.0d0+radiusScaleFree) & - & ) - end if - zhao1996RadialVelocityDispersion=+sqrt( & - & + gravitationalConstantGalacticus & - & *self%normalization (node) & - & * radiusScale **2 & - & * velocityDispersionSquaredScaleFree & - & ) - case (specialCaseGamma1_5NFW%ID) - radiusScale = self%scaleRadius(node) - radiusScaleFree=+ radius & - & / radiusScale - if (radiusScaleFree < radiusScaleFreeTiny ) then - ! Use series solution for small radii. - velocityDispersionSquaredScaleFree=+8.0d0*Pi/ 3.0d0*sqrt(radiusScaleFree ) & - & - Pi/3150.0d0* radiusScaleFree**3.5d0 *(-19861.0d0+60480.0d0*log(2.0d0)-7560.0d0*log(radiusScaleFree)) & - & - Pi/ 175.0d0* radiusScaleFree**2.5d0 *(- 8683.0d0+13440.0d0*log(2.0d0)-1680.0d0*log(radiusScaleFree)) & - & -4.0d0*Pi/ 75.0d0* radiusScaleFree**1.5d0 *(- 817.0d0+ 960.0d0*log(2.0d0)- 120.0d0*log(radiusScaleFree)) - else if (radiusScaleFree > radiusScaleFreeLarge) then - ! Use series solution for large radii. - velocityDispersionSquaredScaleFree=+Pi*(- 7.0d0+ 8.0d0*log(2.0d0)+ 4.0d0*log(radiusScaleFree))/( 4.0d0*radiusScaleFree ) & - & +Pi*(+ 147.0d0+ 120.0d0*log(2.0d0)+ 60.0d0*log(radiusScaleFree))/( 200.0d0*radiusScaleFree**2) & - & +Pi*(- 79.0d0- 840.0d0*log(2.0d0)- 420.0d0*log(radiusScaleFree))/( 2400.0d0*radiusScaleFree**3) & - & +Pi*(-3589.0d0+7560.0d0*log(2.0d0)+3780.0d0*log(radiusScaleFree))/(33600.0d0*radiusScaleFree**4) - else - ! Use full solution. - velocityDispersionSquaredScaleFree=+( & - & +8.0d0 & - & *Pi & - & * radiusScaleFree **1.5d0 & - & *(+1.0d0+radiusScaleFree)**1.5d0 & - & *( & - & +( & - & + 2.0d0 & - & *( & - & + 1.0d0 & - & + 2.0d0*radiusScaleFree & - & *(-1.0d0+4.0d0*radiusScaleFree+8.0d0*radiusScaleFree**2) & - & ) & - & *asinh(sqrt(radiusScaleFree)) & - & ) & - & /sqrt(radiusScaleFree**5*(1.0d0+radiusScaleFree)) & - & +( & - & -2.0d0 & - & +radiusScaleFree & - & *( & - & + 5.0d0 & - & +12.0d0*radiusScaleFree & - & -32.0d0*radiusScaleFree *(+1.0d0+radiusScaleFree)*log(2.0d0) & - & ) & - & + 4.0d0*radiusScaleFree**2*(+1.0d0+radiusScaleFree) & - & *( & - & + log( radiusScaleFree) & - & -5.0d0*log(+1.0d0+radiusScaleFree) & - & ) & - & ) & - & /( & - & + radiusScaleFree**2*(+1.0d0+radiusScaleFree) & - & ) & - & ) & - & ) & - & /5.0d0 - end if - zhao1996RadialVelocityDispersion=+sqrt( & - & + gravitationalConstantGalacticus & - & *self%normalization (node) & - & * radiusScale **2 & - & * velocityDispersionSquaredScaleFree & - & ) - case default - zhao1996RadialVelocityDispersion=+0.0d0 - call Error_Report('unknown special case'//{introspection:location}) - end select - return - end function zhao1996RadialVelocityDispersion - - double precision function zhao1996RadiusFromSpecificAngularMomentum(self,node,specificAngularMomentum) - !!{ - Returns the radius (in Mpc) in {\normalfont \ttfamily node} at which a circular orbit has the given {\normalfont \ttfamily - specificAngularMomentum} (given in units of km s$^{-1}$ Mpc) - !!} - implicit none - class (darkMatterProfileDMOZhao1996), intent(inout) :: self - type (treeNode ), intent(inout) :: node - double precision , intent(in ) :: specificAngularMomentum - - zhao1996RadiusFromSpecificAngularMomentum=self%radiusFromSpecificAngularMomentumNumerical(node,specificAngularMomentum) - return - end function zhao1996RadiusFromSpecificAngularMomentum - - double precision function zhao1996RotationNormalization(self,node) - !!{ - Return the normalization of the rotation velocity vs. specific angular momentum relation. - !!} - implicit none - class (darkMatterProfileDMOZhao1996), intent(inout) :: self - type (treeNode ), intent(inout) :: node - double precision :: radiusVirial - - radiusVirial =+self%darkMatterHaloScale_%radiusVirial(node ) - zhao1996RotationNormalization=+self %radialMoment(node,moment=2.0d0,radiusMaximum=radiusVirial) & - & /self %radialMoment(node,moment=3.0d0,radiusMaximum=radiusVirial) - return - end function zhao1996RotationNormalization - - double precision function zhao1996Energy(self,node) - !!{ - Return the energy of an Zhao1996 halo density profile. - !!} - implicit none class(darkMatterProfileDMOZhao1996), intent(inout) :: self type (treeNode ), intent(inout) :: node + class(nodeComponentBasic ), pointer :: basic - zhao1996Energy=self%energyNumerical(node) - return - end function zhao1996Energy - - double precision function zhao1996KSpace(self,node,waveNumber) - !!{ - Returns the Fourier transform of the Zhao1996 density profile at the specified {\normalfont \ttfamily waveNumber} (given in Mpc$^{-1}$), using the - expression given in \citeauthor{cooray_halo_2002}~(\citeyear{cooray_halo_2002}; eqn.~81). - !!} - implicit none - class (darkMatterProfileDMOZhao1996), intent(inout) :: self - type (treeNode ), intent(inout), target :: node - double precision , intent(in ) :: waveNumber - - zhao1996KSpace=self%kSpaceNumerical(node,wavenumber) - return - end function zhao1996KSpace - - double precision function zhao1996FreefallRadius(self,node,time) - !!{ - Returns the freefall radius in the Zhao1996 density profile at the specified {\normalfont \ttfamily time} (given in Gyr). - !!} - use :: Root_Finder, only : rangeExpandMultiplicative, rangeExpandSignExpectNegative, rangeExpandSignExpectPositive, rootFinder - implicit none - class (darkMatterProfileDMOZhao1996), intent(inout), target :: self - type (treeNode ), intent(inout), target :: node - double precision , intent(in ) :: time - double precision , parameter :: toleranceAbsolute=0.0d0, toleranceRelative=1.0d-3 - type (rootFinder ) :: finder - - self_ => self - node_ => node - time_ = time - finder = rootFinder( & - & rootFunction =rootRadiusFreefall , & - & toleranceAbsolute =toleranceAbsolute , & - & toleranceRelative =toleranceRelative , & - & rangeExpandDownward =0.5d0 , & - & rangeExpandUpward =2.0d0 , & - & rangeExpandType =rangeExpandMultiplicative , & - & rangeExpandUpwardSignExpect =rangeExpandSignExpectPositive, & - & rangeExpandDownwardSignExpect=rangeExpandSignExpectNegative & - & ) - zhao1996FreefallRadius=finder%find(rootGuess=self%darkMatterHaloScale_%radiusVirial(node)) - return - end function zhao1996FreefallRadius - - double precision function rootRadiusFreefall(radiusFreefall) - !!{ - Root function used in finding the radius corresponding to a given freefall time. - !!} - use :: Numerical_Integration, only : integrator - implicit none - double precision , intent(in ) :: radiusFreefall - type (integrator) :: integrator_ - - radiusFreefall_ =+radiusFreefall - integrator_ = integrator (integrandTimeFreefall,toleranceRelative=1.0d-3) - rootRadiusFreefall=+integrator_ %integrate(0.0d0 ,radiusFreefall ) & - & -time_ - return - end function rootRadiusFreefall - - double precision function integrandTimeFreefall(radius) - !!{ - Integrand for freefall time in the halo. - !!} - use :: Numerical_Constants_Astronomical, only : Mpc_per_km_per_s_To_Gyr - implicit none - double precision, intent(in ) :: radius - double precision :: potentialDifference - - potentialDifference=-self_%potential(node_,radiusFreefall_) & - & +self_%potential(node_,radius ) - if (potentialDifference < 0.0d0) then - integrandTimeFreefall=+Mpc_per_km_per_s_To_Gyr & - & /sqrt( & - & -2.0d0 & - & *potentialDifference & - & ) - else - ! Avoid floating point errors arising from rounding errors. - integrandTimeFreefall=0.0d0 - end if - return - end function integrandTimeFreefall - - double precision function zhao1996FreefallRadiusIncreaseRate(self,node,time) - !!{ - Returns the rate of increase of the freefall radius in the Zhao1996 density profile at the specified {\normalfont \ttfamily time} (given in - Gyr). - !!} - use :: Numerical_Differentiation, only : differentiator - implicit none - class (darkMatterProfileDMOZhao1996), intent(inout), target :: self - type (treeNode ), intent(inout), target :: node - double precision , intent(in ) :: time - double precision , parameter :: timeLogarithmicStep=0.1d0 - type (differentiator ) :: differentiator_ - - self_ => self - node_ => node - differentiator_ = differentiator (freefallRadiusEvaluate ) - zhao1996FreefallRadiusIncreaseRate = +differentiator_%derivative(log(time) ,timeLogarithmicStep) & - & / time - return - end function zhao1996FreefallRadiusIncreaseRate - - double precision function freefallRadiusEvaluate(timeLogarithmic) - !!{ - GSL-callable function to evaluate the freefall radius of the dark matter profile. - !!} - implicit none - double precision, intent(in ), value :: timeLogarithmic - - freefallRadiusEvaluate=self_%freefallRadiusNumerical(node_,exp(timeLogarithmic)) - return - end function freefallRadiusEvaluate - - double precision function zhao1996RadialMoment(self,node,moment,radiusMinimum,radiusMaximum) - !!{ - Returns the density (in $M_\odot$ Mpc$^{-3}$) in the dark matter profile of {\normalfont \ttfamily node} at the given {\normalfont \ttfamily radius} (given - in units of Mpc). - !!} - use :: Error, only : Error_Report - implicit none - class (darkMatterProfileDMOZhao1996), intent(inout) :: self - type (treeNode ), intent(inout) :: node - double precision , intent(in ) :: moment - double precision , intent(in ), optional :: radiusMinimum , radiusMaximum - double precision :: radiusScale , radiusScaleFree , & - & radialMomentUpper, radialMomentLower, & - & alpha , beta , & - & gamma - - call self%exponents(node,alpha,beta,gamma) - radiusScale = self %scaleRadius (node) - if (present(radiusMinimum)) then - radiusScaleFree =+radiusMinimum & - & /radiusScale - radialMomentLower= radialMomentIndefinite(radiusScaleFree) - else - radialMomentLower=0.0d0 - if (alpha <= 0.0d0 .or. 1.0d0+moment <= gamma) call Error_Report('radial moment is undefined'//{introspection:location}) - end if - if (present(radiusMaximum)) then - radiusScaleFree =+radiusMaximum & - & /radiusScale - radialMomentUpper= radialMomentIndefinite(radiusScaleFree) - else - radialMomentUpper=0.0d0 - call Error_Report('radial moment is not implemented'//{introspection:location}) - end if - zhao1996RadialMoment=+( & - & +radialMomentUpper & - & -radialMomentLower & - & ) & - & *self%normalization(node) & - & *radiusScale**(moment+1.0d0) + basic => node %basic() + zhao1996Normalization = basic%mass () return - - contains - - double precision function radialMomentIndefinite(radiusScaleFree) - !!{ - Compute the indefinite radial moment. - !!} - use :: Hypergeometric_Functions, only : Hypergeometric_2F1 - implicit none - double precision, intent(in ) :: radiusScaleFree - - radialMomentIndefinite=+radiusScaleFree** (1.0d0+moment-gamma) & - & / (1.0d0+moment-gamma) & - & *Hypergeometric_2F1([(1.0d0+moment-gamma)/alpha,(beta-gamma)/alpha],[1.0d0+(1.0d0+moment-gamma)/alpha],-radiusScaleFree**alpha) - return - end function radialMomentIndefinite - - end function zhao1996RadialMoment - - double precision function zhao1996RadiusEnclosingDensity(self,node,density) - !!{ - Compute the radius enclosing a given density for {\normalfont \ttfamily Zhao1996} dark matter halo profiles. - !!} - implicit none - class (darkMatterProfileDMOZhao1996), intent(inout), target :: self - type (treeNode ), intent(inout), target :: node - double precision , intent(in ) :: density - - zhao1996RadiusEnclosingDensity=self%radiusEnclosingDensityNumerical(node,density) - return - end function zhao1996RadiusEnclosingDensity - - double precision function zhao1996RadiusEnclosingMass(self,node,mass) - !!{ - Compute the radius enclosing a given mass for {\normalfont \ttfamily Zhao1996} dark matter halo profiles. - !!} - implicit none - class (darkMatterProfileDMOZhao1996), intent(inout), target :: self - type (treeNode ), intent(inout), target :: node - double precision , intent(in ) :: mass - - zhao1996RadiusEnclosingMass=self%radiusEnclosingMassNumerical(node,mass) - return - end function zhao1996RadiusEnclosingMass + end function zhao1996Normalization diff --git a/source/dark_matter_profiles_DMO.accelerator.F90 b/source/dark_matter_profiles_DMO.accelerator.F90 index b2f350f613..45118438c0 100644 --- a/source/dark_matter_profiles_DMO.accelerator.F90 +++ b/source/dark_matter_profiles_DMO.accelerator.F90 @@ -21,9 +21,6 @@ An accelerator class for dark matter halo profiles. !!} - use :: Binary_Search_Trees, only : binaryTree - use :: Kind_Numbers , only : kind_int8 - !![ @@ -36,38 +33,11 @@ An accelerator class for the dark matter halo profile class. !!} private - class (darkMatterProfileDMOClass), pointer :: darkMatterProfileDMO_ => null() - integer (kind_int8 ), dimension(2) :: uniqueIDPrevious - type (binaryTree ), dimension(2) :: treeMassEnclosed - integer :: treePrevious - double precision :: toleranceRelative , factorRadiusMaximum, & - & factorRadiusLogarithmicMaximum + class (darkMatterProfileDMOClass), pointer :: darkMatterProfileDMO_ => null() + double precision :: toleranceRelative , factorRadiusMaximum contains - !![ - - - - !!] - final :: acceleratorDestructor - procedure :: autoHook => acceleratorAutoHook - procedure :: calculationReset => acceleratorCalculationReset - procedure :: density => acceleratorDensity - procedure :: densityLogSlope => acceleratorDensityLogSlope - procedure :: radiusEnclosingDensity => acceleratorRadiusEnclosingDensity - procedure :: radiusEnclosingMass => acceleratorRadiusEnclosingMass - procedure :: radialMoment => acceleratorRadialMoment - procedure :: enclosedMass => acceleratorEnclosedMass - procedure :: potential => acceleratorPotential - procedure :: circularVelocity => acceleratorCircularVelocity - procedure :: radiusCircularVelocityMaximum => acceleratorRadiusCircularVelocityMaximum - procedure :: circularVelocityMaximum => acceleratorCircularVelocityMaximum - procedure :: radialVelocityDispersion => acceleratorRadialVelocityDispersion - procedure :: radiusFromSpecificAngularMomentum => acceleratorRadiusFromSpecificAngularMomentum - procedure :: rotationNormalization => acceleratorRotationNormalization - procedure :: energy => acceleratorEnergy - procedure :: kSpace => acceleratorKSpace - procedure :: freefallRadius => acceleratorFreefallRadius - procedure :: freefallRadiusIncreaseRate => acceleratorFreefallRadiusIncreaseRate + final :: acceleratorDestructor + procedure :: get => acceleratorGet end type darkMatterProfileDMOAccelerator interface darkMatterProfileDMOAccelerator @@ -108,7 +78,7 @@ function acceleratorConstructorParameters(parameters) result(self) self=darkMatterProfileDMOAccelerator(toleranceRelative,factorRadiusMaximum,darkMatterProfileDMO_) !![ - + !!] return end function acceleratorConstructorParameters @@ -125,343 +95,84 @@ function acceleratorConstructorInternal(toleranceRelative,factorRadiusMaximum,da !!] - self%uniqueIDPrevious =-1_kind_int8 - self%treePrevious =+1 - self%factorRadiusLogarithmicMaximum=+log(sqrt(factorRadiusMaximum)) return end function acceleratorConstructorInternal - subroutine acceleratorAutoHook(self) - !!{ - Attach to the calculation reset event. - !!} - use :: Events_Hooks, only : calculationResetEvent, openMPThreadBindingAllLevels - implicit none - class(darkMatterProfileDMOAccelerator), intent(inout) :: self - - call calculationResetEvent%attach(self,acceleratorCalculationReset,openMPThreadBindingAllLevels,label='darkMatterProfileDMOAccelerator') - return - end subroutine acceleratorAutoHook - subroutine acceleratorDestructor(self) !!{ Destructor for the {\normalfont \ttfamily accelerator} dark matter halo profile class. !!} - use :: Events_Hooks, only : calculationResetEvent implicit none type(darkMatterProfileDMOAccelerator), intent(inout) :: self !![ !!] - if (calculationResetEvent%isAttached(self,acceleratorCalculationReset)) call calculationResetEvent%detach(self,acceleratorCalculationReset) return end subroutine acceleratorDestructor - subroutine acceleratorCalculationReset(self,node,uniqueID) - !!{ - Reset the dark matter profile calculation. - !!} - use :: Kind_Numbers, only : kind_int8 - implicit none - class (darkMatterProfileDMOAccelerator), intent(inout) :: self - type (treeNode ), intent(inout) :: node - integer(kind_int8 ), intent(in ) :: uniqueID - integer :: i - - ! Trees are maintained for two nodes - this is often advantageous as queries are often made for satellite and host nodes - ! together. If the current node is one for which we currently have a tree, invalidate that tree. Otherwise, if the current - ! node is a satellite in the current host node, place it into the second tree, otherwise, into the first. - if (uniqueID == self%uniqueIDPrevious(1)) then - i =+1 - else if (uniqueID == self%uniqueIDPrevious(2)) then - i =+2 - else - if (node%isSatellite() .and. node%parent%uniqueID() == self%uniqueIDPrevious(1)) then - i=2 - else - i=1 - end if - end if - self%uniqueIDPrevious(i)=uniqueID - self%treePrevious =i - if (associated(self%treeMassEnclosed(i)%root)) deallocate(self%treeMassEnclosed(i)%root) - return - end subroutine acceleratorCalculationReset - - double precision function acceleratorDensity(self,node,radius) - !!{ - Returns the density (in $M_\odot$ Mpc$^{-3}$) in the dark matter profile of {\normalfont \ttfamily node} at the given - {\normalfont \ttfamily radius} (given in units of Mpc). - !!} - implicit none - class (darkMatterProfileDMOAccelerator), intent(inout) :: self - type (treeNode ), intent(inout) :: node - double precision , intent(in ) :: radius - - acceleratorDensity=self%darkMatterProfileDMO_%density(node,radius) - return - end function acceleratorDensity - - double precision function acceleratorDensityLogSlope(self,node,radius) - !!{ - Returns the logarithmic slope of the density in the dark matter profile of {\normalfont \ttfamily node} at the given - {\normalfont \ttfamily radius} (given in units of Mpc). - !!} - implicit none - class (darkMatterProfileDMOAccelerator), intent(inout) :: self - type (treeNode ), intent(inout) :: node - double precision , intent(in ) :: radius - - acceleratorDensityLogSlope=self%darkMatterProfileDMO_%densityLogSlope(node,radius) - return - end function acceleratorDensityLogSlope - - double precision function acceleratorRadiusEnclosingDensity(self,node,density) - !!{ - Returns the radius (in Mpc) in the dark matter profile of {\normalfont \ttfamily node} which encloses the given - {\normalfont \ttfamily density} (given in units of $M_\odot/$Mpc$^{-3}$). - !!} - implicit none - class (darkMatterProfileDMOAccelerator), intent(inout), target :: self - type (treeNode ), intent(inout), target :: node - double precision , intent(in ) :: density - - acceleratorRadiusEnclosingDensity=self%darkMatterProfileDMO_%radiusEnclosingDensity(node,density) - return - end function acceleratorRadiusEnclosingDensity - - double precision function acceleratorRadiusEnclosingMass(self,node,mass) - !!{ - Returns the radius (in Mpc) in the dark matter profile of {\normalfont \ttfamily node} which encloses the given - {\normalfont \ttfamily mass} (given in units of $M_\odot$). - !!} - implicit none - class (darkMatterProfileDMOAccelerator), intent(inout), target :: self - type (treeNode ), intent(inout), target :: node - double precision , intent(in ) :: mass - - acceleratorRadiusEnclosingMass=self%darkMatterProfileDMO_%radiusEnclosingMass(node,mass) - return - end function acceleratorRadiusEnclosingMass - - double precision function acceleratorRadialMoment(self,node,moment,radiusMinimum,radiusMaximum) - !!{ - Returns the density (in $M_\odot$ Mpc$^{-3}$) in the dark matter profile of {\normalfont \ttfamily node} at the given - {\normalfont \ttfamily radius} (given in units of Mpc). - !!} - implicit none - class (darkMatterProfileDMOAccelerator), intent(inout) :: self - type (treeNode ), intent(inout) :: node - double precision , intent(in ) :: moment - double precision , intent(in ), optional :: radiusMinimum, radiusMaximum - - acceleratorRadialMoment=self%darkMatterProfileDMO_%radialMoment(node,moment,radiusMinimum,radiusMaximum) - return - end function acceleratorRadialMoment - - double precision function acceleratorEnclosedMass(self,node,radius) - !!{ - Returns the enclosed mass (in $M_\odot$) in the dark matter profile of {\normalfont \ttfamily node} at the given {\normalfont \ttfamily radius} (given in - units of Mpc). - !!} - use :: Binary_Search_Trees, only : binaryTreeNode - use :: Numerical_Comparison, only : Values_Agree - implicit none - class (darkMatterProfileDMOAccelerator), intent(inout) :: self - type (treeNode ), intent(inout) :: node - double precision , intent(in ) :: radius - type (binaryTreeNode ), pointer :: left1 , left2 , & - & right1 , right2 - double precision :: massEnclosed1 , massEnclosed2, & - & radiusLogarithmic - logical :: found - integer :: i - - if (node%uniqueID() == self%uniqueIDPrevious(1)) then - i=1 - else if (node%uniqueID() == self%uniqueIDPrevious(2)) then - i=2 - else - call self%calculationReset(node,node%uniqueID()) - i=self%treePrevious - end if - found=.false. - radiusLogarithmic=log(radius) - call self%treeMassEnclosed(i)%bracket(radiusLogarithmic,left1,right1) - if (associated(left1).and.associated(right1)) then - if (associated(left1,right1)) then - acceleratorEnclosedMass=exp(left1%value) - found =.true. - else - if ( & - & +radiusLogarithmic- left1%key < self%factorRadiusLogarithmicMaximum & - & .and. & - & -radiusLogarithmic+right1%key < self%factorRadiusLogarithmicMaximum & - & ) then - left2 => left1%predecessor() - right2 => right1% successor() - if (associated(left2).and.associated(right2)) then - massEnclosed1=(radiusLogarithmic-left1%key)*(right1%value-left1%value)/(right1%key-left1%key)+left1%value - massEnclosed2=(radiusLogarithmic-left2%key)*(right2%value-left2%value)/(right2%key-left2%key)+left2%value - if (Values_Agree(massEnclosed1,massEnclosed2,relTol=self%toleranceRelative)) then - acceleratorEnclosedMass=exp(massEnclosed1) - found =.true. - end if - end if - end if - end if - end if - if (.not.found) then - acceleratorEnclosedMass=self%darkMatterProfileDMO_%enclosedMass(node,radius) - call self%treeMassEnclosed(i)%insert(radiusLogarithmic,log(acceleratorEnclosedMass)) - end if - return - end function acceleratorEnclosedMass - - double precision function acceleratorPotential(self,node,radius,status) - !!{ - Returns the potential (in (km/s)$^2$) in the dark matter profile of {\normalfont \ttfamily node} at the given {\normalfont - \ttfamily radius} (given in units of Mpc). - !!} - implicit none - class (darkMatterProfileDMOAccelerator ), intent(inout) :: self - type (treeNode ), intent(inout), target :: node - double precision , intent(in ) :: radius - type (enumerationStructureErrorCodeType), intent( out), optional :: status - - acceleratorPotential=self%darkMatterProfileDMO_%potential(node,radius,status) - return - end function acceleratorPotential - - double precision function acceleratorCircularVelocity(self,node,radius) - !!{ - Returns the circular velocity (in km/s) in the dark matter profile of {\normalfont \ttfamily node} at the given - {\normalfont \ttfamily radius} (given in units of Mpc). - !!} - implicit none - class (darkMatterProfileDMOAccelerator), intent(inout) :: self - type (treeNode ), intent(inout) :: node - double precision , intent(in ) :: radius - - acceleratorCircularVelocity=self%darkMatterProfileDMO_%circularVelocity(node,radius) - return - end function acceleratorCircularVelocity - - double precision function acceleratorRadiusCircularVelocityMaximum(self,node) - !!{ - Returns the radius (in Mpc) at which the maximum circular velocity is achieved in the dark matter profile of {\normalfont \ttfamily node}. - !!} - implicit none - class(darkMatterProfileDMOAccelerator), intent(inout) :: self - type (treeNode ), intent(inout) :: node - - acceleratorRadiusCircularVelocityMaximum=self%darkMatterProfileDMO_%radiusCircularVelocityMaximum(node) - return - end function acceleratorRadiusCircularVelocityMaximum - - double precision function acceleratorCircularVelocityMaximum(self,node) - !!{ - Returns the maximum circular velocity (in km/s) in the dark matter profile of {\normalfont \ttfamily node}. - !!} - implicit none - class(darkMatterProfileDMOAccelerator), intent(inout) :: self - type (treeNode ), intent(inout) :: node - - acceleratorCircularVelocityMaximum=self%darkMatterProfileDMO_%circularVelocityMaximum(node) - return - end function acceleratorCircularVelocityMaximum - - double precision function acceleratorRadialVelocityDispersion(self,node,radius) + function acceleratorGet(self,node,weightBy,weightIndex) result(massDistribution_) !!{ - Returns the radial velocity dispersion (in km/s) in the dark matter profile of {\normalfont \ttfamily node} at the given - {\normalfont \ttfamily radius} (given in units of Mpc). + Return the dark matter mass distribution for the given {\normalfont \ttfamily node}. !!} + use :: Galactic_Structure_Options, only : componentTypeDarkHalo , massTypeDark , weightByMass + use :: Mass_Distributions , only : massDistributionSphericalAccelerator, kinematicsDistributionCollisionless, massDistributionSpherical, nonAnalyticSolversNumerical implicit none - class (darkMatterProfileDMOAccelerator), intent(inout) :: self - type (treeNode ), intent(inout) :: node - double precision , intent(in ) :: radius - - acceleratorRadialVelocityDispersion=self%darkMatterProfileDMO_%radialVelocityDispersion(node,radius) - return - end function acceleratorRadialVelocityDispersion - - double precision function acceleratorRadiusFromSpecificAngularMomentum(self,node,specificAngularMomentum) - !!{ - Returns the radius (in Mpc) in {\normalfont \ttfamily node} at which a circular orbit has the given {\normalfont \ttfamily specificAngularMomentum} (given - in units of km s$^{-1}$ Mpc). - !!} - implicit none - class (darkMatterProfileDMOAccelerator), intent(inout) :: self - type (treeNode ), intent(inout) :: node - double precision , intent(in ) :: specificAngularMomentum - - acceleratorRadiusFromSpecificAngularMomentum=self%darkMatterProfileDMO_%radiusFromSpecificAngularMomentum(node,specificAngularMomentum) - return - end function acceleratorRadiusFromSpecificAngularMomentum - - double precision function acceleratorRotationNormalization(self,node) - !!{ - Return the normalization of the rotation velocity vs. specific angular momentum relation. - !!} - implicit none - class(darkMatterProfileDMOAccelerator), intent(inout) :: self - type (treeNode ), intent(inout) :: node - - acceleratorRotationNormalization=self%darkMatterProfileDMO_%rotationNormalization(node) - return - end function acceleratorRotationNormalization - - double precision function acceleratorEnergy(self,node) - !!{ - Return the energy of a accelerator halo density profile. - !!} - implicit none - class(darkMatterProfileDMOAccelerator), intent(inout) :: self - type (treeNode ), intent(inout) :: node - - acceleratorEnergy=self%darkMatterProfileDMO_%energy(node) - return - end function acceleratorEnergy - - double precision function acceleratorKSpace(self,node,waveNumber) - !!{ - Returns the Fourier transform of the accelerator density profile at the specified {\normalfont \ttfamily waveNumber} - (given in Mpc$^{-1}$). - !!} - implicit none - class (darkMatterProfileDMOAccelerator), intent(inout) :: self - type (treeNode ), intent(inout), target :: node - double precision , intent(in ) :: waveNumber - - acceleratorKSpace=self%darkMatterProfileDMO_%kSpace(node,waveNumber) - return - end function acceleratorKSpace - - double precision function acceleratorFreefallRadius(self,node,time) - !!{ - Returns the freefall radius in the accelerator density profile at the specified {\normalfont \ttfamily time} (given in - Gyr). - !!} - implicit none - class (darkMatterProfileDMOAccelerator), intent(inout), target :: self - type (treeNode ), intent(inout), target :: node - double precision , intent(in ) :: time - - acceleratorFreefallRadius=self%darkMatterProfileDMO_%freefallRadius(node,time) - return - end function acceleratorFreefallRadius - - double precision function acceleratorFreefallRadiusIncreaseRate(self,node,time) - !!{ - Returns the rate of increase of the freefall radius in the accelerator density profile at the specified {\normalfont - \ttfamily time} (given in Gyr). - !!} - implicit none - class (darkMatterProfileDMOAccelerator), intent(inout), target :: self - type (treeNode ), intent(inout), target :: node - double precision , intent(in ) :: time + class (massDistributionClass ), pointer :: massDistribution_ + type (kinematicsDistributionCollisionless), pointer :: kinematicsDistribution_ + class (darkMatterProfileDMOAccelerator ), intent(inout) :: self + type (treeNode ), intent(inout) :: node + type (enumerationWeightByType ), intent(in ), optional :: weightBy + integer , intent(in ), optional :: weightIndex + class (massDistributionClass ), pointer :: massDistributionDecorated + !![ + + !!] - acceleratorFreefallRadiusIncreaseRate=self%darkMatterProfileDMO_%freefallRadiusIncreaseRate(node,time) + ! Assume a null distribution by default. + massDistribution_ => null() + ! If weighting is not by mass, return a null profile. + if (weightBy_ /= weightByMass) return + ! Create the mass distribution. + allocate(massDistributionSphericalAccelerator :: massDistribution_) + select type(massDistribution_) + type is (massDistributionSphericalAccelerator) + massDistributionDecorated => self%darkMatterProfileDMO_%get(node,weightBy,weightIndex) + select type (massDistributionDecorated) + class is (massDistributionSpherical) + !![ + + + massDistributionSphericalAccelerator( & + & toleranceRelative =self%toleranceRelative , & + & factorRadiusMaximum=self%factorRadiusMaximum , & + & massDistribution_ = massDistributionDecorated , & + & nonAnalyticSolver = nonAnalyticSolversNumerical, & + & componentType = componentTypeDarkHalo , & + & massType = massTypeDark & + & ) + + + !!] + class default + call Error_Report('expected a spherical mass distribution'//{introspection:location}) + end select + !![ + + !!] + end select + allocate(kinematicsDistribution_) + !![ + + + kinematicsDistributionCollisionless( & + & ) + + + !!] + call massDistribution_%setKinematicsDistribution(kinematicsDistribution_) + !![ + + !!] return - end function acceleratorFreefallRadiusIncreaseRate + end function acceleratorGet diff --git a/source/dark_matter_profiles_DMO.accretion_flow.DiemerKravtsov2014.F90 b/source/dark_matter_profiles_DMO.accretion_flow.DiemerKravtsov2014.F90 new file mode 100644 index 0000000000..0789b1a707 --- /dev/null +++ b/source/dark_matter_profiles_DMO.accretion_flow.DiemerKravtsov2014.F90 @@ -0,0 +1,294 @@ +!! Copyright 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, +!! 2019, 2020, 2021, 2022, 2023 +!! Andrew Benson +!! +!! This file is part of Galacticus. +!! +!! Galacticus is free software: you can redistribute it and/or modify +!! it under the terms of the GNU General Public License as published by +!! the Free Software Foundation, either version 3 of the License, or +!! (at your option) any later version. +!! +!! Galacticus is distributed in the hope that it will be useful, +!! but WITHOUT ANY WARRANTY; without even the implied warranty of +!! MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +!! GNU General Public License for more details. +!! +!! You should have received a copy of the GNU General Public License +!! along with Galacticus. If not, see . + + !!{ + An implementation of a dark matter density profile which includes the accretion flow surrounding the halo. + !!} + + use :: Cosmology_Functions , only : cosmologyFunctionsClass + use :: Cosmological_Density_Field, only : cosmologicalMassVarianceClass, criticalOverdensityClass + + !![ + + + An accretion flow class which models the accretion flow using the fitting function of + \cite{diemer_dependence_2014}. Specifically, \refClass{massDistributionDiemerKravtsov2014} objects are built with + parameters chosen using fits to the redshift and $\nu$ dependencies of the fitting parameters $b_\mathrm{e}$ and + $s_\mathrm{e}$ chosen to match the results of their figure~18. + + + !!] + type, extends(darkMatterProfileDMOClass) :: darkMatterProfileDMOAccretionFlowDiemerKravtsov2014 + !!{ + A dark matter halo profile class which implements a dark matter density profile which includes the accretion flow using the + fitting function of \cite{diemer_dependence_2014}. + !!} + private + class (cosmologyFunctionsClass ), pointer :: cosmologyFunctions_ => null() + class (criticalOverdensityClass ), pointer :: criticalOverdensity_ => null() + class (cosmologicalMassVarianceClass), pointer :: cosmologicalMassVariance_ => null() + class (darkMatterProfileDMOClass ), pointer :: darkMatterProfileDMO_ => null() + double precision :: b0 , s0 , & + & bz , sz , & + & bnu , snu + contains + final :: accretionFlowDiemerKravtsov2014Destructor + procedure :: get => accretionFlowDiemerKravtsov2014Get + end type darkMatterProfileDMOAccretionFlowDiemerKravtsov2014 + + interface darkMatterProfileDMOAccretionFlowDiemerKravtsov2014 + !!{ + Constructors for the {\normalfont \ttfamily accretionFlowDiemerKravtsov2014} dark matter halo profile class. + !!} + module procedure accretionFlowDiemerKravtsov2014ConstructorParameters + module procedure accretionFlowDiemerKravtsov2014ConstructorInternal + end interface darkMatterProfileDMOAccretionFlowDiemerKravtsov2014 + +contains + + function accretionFlowDiemerKravtsov2014ConstructorParameters(parameters) result(self) + !!{ + Constructor for the {\normalfont \ttfamily accretionFlowDiemerKravtsov2014} dark matter halo profile class which takes a parameter set as input. + !!} + use :: Input_Parameters, only : inputParameter, inputParameters + implicit none + type (darkMatterProfileDMOAccretionFlowDiemerKravtsov2014) :: self + type (inputParameters ), intent(inout) :: parameters + class (darkMatterProfileDMOClass ), pointer :: darkMatterProfileDMO_ + class (cosmologyFunctionsClass ), pointer :: cosmologyFunctions_ + class (criticalOverdensityClass ), pointer :: criticalOverdensity_ + class (cosmologicalMassVarianceClass ), pointer :: cosmologicalMassVariance_ + double precision :: b0 , s0 , & + & bz , sz , & + & bnu , snu + + !![ + + b0 + parameters + +1.1250d0 + Derived by Andrew Benson by constructing simple functional forms which fit the plots in figure 18 of \cite{diemer_dependence_2014}. + The parameter $b_0$ in the fitting function $b(\nu,z)=b_0 (1+z)^{b_z} \nu^{b_\nu}$ for the parameter $b(\nu,z)$ appearing in equation (4) of \cite{diemer_dependence_2014}. + + + bz + parameters + +0.625d0 + Derived by Andrew Benson by constructing simple functional forms which fit the plots in figure 18 of \cite{diemer_dependence_2014}. + The parameter $b_z$ in the fitting function $b(\nu,z)=b_0 (1+z)^{b_z} \nu^{b_\nu}$ for the parameter $b(\nu,z)$ appearing in equation (4) of \cite{diemer_dependence_2014}. + + + bnu + parameters + -0.2250d0 + Derived by Andrew Benson by constructing simple functional forms which fit the plots in figure 18 of \cite{diemer_dependence_2014}. + The parameter $b_\nu$ in the fitting function $b(\nu,z)=b_0 (1+z)^{b_z} \nu^{b_\nu}$ for the parameter $b(\nu,z)$ appearing in equation (4) of \cite{diemer_dependence_2014}. + + + s0 + parameters + +1.3925d0 + Derived by Andrew Benson by constructing simple functional forms which fit the plots in figure 18 of \cite{diemer_dependence_2014}. + The parameter $s_0$ in the fitting function $s(\nu,z)=s_0 (1+z)^{s_z} \nu^{s_\nu}$ for the parameter $s(\nu,z)$ appearing in equation (4) of \cite{diemer_dependence_2014}. + + + sz + parameters + -0.199d0 + Derived by Andrew Benson by constructing simple functional forms which fit the plots in figure 18 of \cite{diemer_dependence_2014}. + The parameter $s_z$ in the fitting function $s(\nu,z)=s_0 (1+z)^{s_z} \nu^{s_\nu}$ for the parameter $s(\nu,z)$ appearing in equation (4) of \cite{diemer_dependence_2014}. + + + snu + parameters + +0.0875d0 + Derived by Andrew Benson by constructing simple functional forms which fit the plots in figure 18 of \cite{diemer_dependence_2014}. + The parameter $s_\nu$ in the fitting function $s(\nu,z)=s_0 (1+z)^{s_z} \nu^{s_\nu}$ for the parameter $s(\nu,z)$ appearing in equation (4) of \cite{diemer_dependence_2014}. + + + + + + !!] + self=darkMatterProfileDMOAccretionFlowDiemerKravtsov2014(b0,bz,bnu,s0,sz,snu,cosmologyFunctions_,criticalOverdensity_,cosmologicalMassVariance_,darkMatterProfileDMO_) + !![ + + + + + + !!] + return + end function accretionFlowDiemerKravtsov2014ConstructorParameters + + function accretionFlowDiemerKravtsov2014ConstructorInternal(b0,bz,bnu,s0,sz,snu,cosmologyFunctions_,criticalOverdensity_,cosmologicalMassVariance_,darkMatterProfileDMO_) result(self) + !!{ + Internal constructor for the {\normalfont \ttfamily accretionFlowDiemerKravtsov2014} dark matter profile class. + !!} + implicit none + type (darkMatterProfileDMOAccretionFlowDiemerKravtsov2014) :: self + class (darkMatterProfileDMOClass ), intent(in ), target :: darkMatterProfileDMO_ + class (cosmologyFunctionsClass ), intent(in ), target :: cosmologyFunctions_ + class (criticalOverdensityClass ), intent(in ), target :: criticalOverdensity_ + class (cosmologicalMassVarianceClass ), intent(in ), target :: cosmologicalMassVariance_ + double precision , intent(in ) :: b0 , s0 , & + & bz , sz , & + & bnu , snu + !![ + + !!] + + return + end function accretionFlowDiemerKravtsov2014ConstructorInternal + + subroutine accretionFlowDiemerKravtsov2014Destructor(self) + !!{ + Destructor for the {\normalfont \ttfamily accretionFlowDiemerKravtsov2014} dark matter profile class. + !!} + implicit none + type(darkMatterProfileDMOAccretionFlowDiemerKravtsov2014), intent(inout) :: self + + !![ + + + + + !!] + return + end subroutine accretionFlowDiemerKravtsov2014Destructor + + function accretionFlowDiemerKravtsov2014Get(self,node,weightBy,weightIndex) result(massDistribution_) + !!{ + Return the dark matter mass distribution for the given {\normalfont \ttfamily node}. + !!} + use :: Galacticus_Nodes , only : nodeComponentBasic + use :: Galactic_Structure_Options, only : componentTypeDarkHalo , massTypeDark , weightByMass + use :: Mass_Distributions , only : massDistributionSpherical , massDistributionSphericalAccretionFlow, massDistributionDiemerKravtsov2014, kinematicsDistributionCollisionless, & + & nonAnalyticSolversNumerical + implicit none + class (massDistributionClass ), pointer :: massDistribution_ + class (massDistributionClass ), pointer :: massDistributionAccretionFlow_, massDistributionVirialized_ + type (kinematicsDistributionCollisionless ), pointer :: kinematicsDistribution_ + class (darkMatterProfileDMOAccretionFlowDiemerKravtsov2014), intent(inout) :: self + type (treeNode ), intent(inout) :: node + type (enumerationWeightByType ), intent(in ), optional :: weightBy + integer , intent(in ), optional :: weightIndex + class (nodeComponentBasic ), pointer :: basic + double precision :: time , mass , & + & radius200Mean , densityMean , & + & nu , redshift , & + & b , s , & + & peakHeight , radiusTransition + !![ + + !!] + + ! Assume a null distribution by default. + massDistribution_ => null() + ! If weighting is not by mass, return a null profile. + if (weightBy_ /= weightByMass) return + ! Get the virialized mass distribution. + massDistributionVirialized_ => self%darkMatterProfileDMO_%get(node) + select type (massDistributionVirialized_) + class is (massDistributionSpherical) + ! Create the accretion flow mass distribution. + allocate(massDistributionDiemerKravtsov2014 :: massDistributionAccretionFlow_) + select type(massDistributionAccretionFlow_) + type is (massDistributionDiemerKravtsov2014) + ! Extract basic quantities for the halo. + basic => node %basic() + time = basic%time () + mass = basic%mass () + ! Evaluate the control parameters. + redshift=+self%cosmologyFunctions_ %redshiftFromExpansionFactor( & + & self%cosmologyFunctions_%expansionFactor (time=time ) & + & ) + nu =+self%criticalOverdensity_ %value (time=time,mass=mass,node=node) & + & /self%cosmologicalMassVariance_%rootVariance (time=time,mass=mass ) + ! Evaluate the parameters of the fitting function. These fits were derived by Andrew Benson by constructing simple functional + ! forms which fit the plots in figure 18 of Diemer & Kravtsov (2014). There is no guarantee that these fits will perform + ! sensibly outside the range of that plot (and, of course, they are only approximate even within the range of that plot). + b=+self%b0*(1.0+redshift)**self%bz*nu**self%bnu + s=+self%s0*(1.0+redshift)**self%sz*nu**self%snu + ! Find the radius enclosing 200 times the mean density. + densityMean = self %cosmologyFunctions_ %matterDensityEpochal ( time ) + radius200Mean = massDistributionVirialized_ %radiusEnclosingDensity(+200.0d0*densityMean) + ! Construct the accretion flow mass distribution. Note that we do not include the background density of the universe + ! here, as (being uniform) it should have no effect on halo dynamics. + !![ + + + massDistributionDiemerKravtsov2014( & + & densityMean =densityMean , & + & radius200Mean=radius200Mean , & + & includeMean =.false. , & + & b =b , & + & s =s , & + & componentType=componentTypeDarkHalo, & + & massType =massTypeDark & + & ) + + + !!] + ! Combine the virialized and accretion flow mass distributions. + allocate(massDistributionSphericalAccretionFlow :: massDistribution_) + select type(massDistribution_) + type is (massDistributionSphericalAccretionFlow) + ! Compute the transition radius following Diemer & Kravtsov (2014; equation 6). + peakHeight =+self%criticalOverdensity_ %value (time=time,mass=mass) & + & /self%cosmologicalMassVariance_%rootVariance(time=time,mass=mass) + radiusTransition=+( & + & +1.90d0 & + & -0.18d0 & + & *peakHeight & + & ) & + & *radius200Mean + !![ + + + massDistributionSphericalAccretionFlow( & + & radiusTransition =radiusTransition , & + & nonAnalyticSolver =nonAnalyticSolversNumerical , & + & massDistribution_ =massDistributionVirialized_ , & + & massDistributionAccretionFlow_=massDistributionAccretionFlow_, & + & componentType =componentTypeDarkHalo , & + & massType =massTypeDark & + & ) + + + !!] + end select + end select + end select + allocate(kinematicsDistribution_) + !![ + + + kinematicsDistributionCollisionless() + + + !!] + call massDistribution_%setKinematicsDistribution(kinematicsDistribution_) + !![ + + + + !!] + return + end function accretionFlowDiemerKravtsov2014Get diff --git a/source/dark_matter_profiles_DMO.accretion_flow.F90 b/source/dark_matter_profiles_DMO.accretion_flow.F90 deleted file mode 100644 index f4cbb4698e..0000000000 --- a/source/dark_matter_profiles_DMO.accretion_flow.F90 +++ /dev/null @@ -1,717 +0,0 @@ -!! Copyright 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, -!! 2019, 2020, 2021, 2022, 2023, 2024 -!! Andrew Benson -!! -!! This file is part of Galacticus. -!! -!! Galacticus is free software: you can redistribute it and/or modify -!! it under the terms of the GNU General Public License as published by -!! the Free Software Foundation, either version 3 of the License, or -!! (at your option) any later version. -!! -!! Galacticus is distributed in the hope that it will be useful, -!! but WITHOUT ANY WARRANTY; without even the implied warranty of -!! MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -!! GNU General Public License for more details. -!! -!! You should have received a copy of the GNU General Public License -!! along with Galacticus. If not, see . - - !!{ - An implementation of a dark matter density profile which includes the accretion flow surrounding the halo. - !!} - - use :: Cosmology_Functions , only : cosmologyFunctionsClass - use :: Cosmology_Parameters , only : cosmologyParametersClass - use :: Cosmological_Density_Field, only : cosmologicalMassVarianceClass, criticalOverdensityClass - - !![ - - - An implementation of a dark matter density profile which includes the accretion flow surrounding the halo. The density - profile is modeled as - \begin{equation} - \rho(r) = f_\mathrm{trans}(r) \rho_\mathrm{halo}(r) + \rho_\mathrm{accretion}(r), - \end{equation} - where $\rho_\mathrm{halo}(r)$ is the halo density profile (provided by a \refClass{darkMatterProfileDMOClass} object) and - $\rho_\mathrm{accretion}(r)$ is the accretion flow density profile (provided by a \refClass{accretionFlowsClass} object), - and $f_\mathrm{trans}(r)$ is the transition function as defined by equation~(7) of \cite{diemer_dependence_2014}. - - Note that some \refClass{accretionFlowsClass} objects make use of an \refClass{darkMatterProfileClass} object. As such, to - avoid an infinite recursive loop, it is recommended to use an explicit, separate \refClass{darkMatterProfileClass} for the - relevant \refClass{accretionFlowsClass} object when using this implementation. - - - !!] - type, extends(darkMatterProfileDMOClass) :: darkMatterProfileDMOAccretionFlow - !!{ - A dark matter halo profile class which implements a dark matter density profile which includes the accretion flow surrounding the halo. - !!} - private - class(cosmologyParametersClass ), pointer :: cosmologyParameters_ => null() - class(cosmologyFunctionsClass ), pointer :: cosmologyFunctions_ => null() - class(criticalOverdensityClass ), pointer :: criticalOverdensity_ => null() - class(cosmologicalMassVarianceClass), pointer :: cosmologicalMassVariance_ => null() - class(* ), pointer :: accretionFlows_ => null() - class(darkMatterProfileDMOClass ), pointer :: darkMatterProfileDMO_ => null() - contains - !![ - - - - !!] - final :: accretionFlowDestructor - procedure :: autoHook => accretionFlowAutoHook - procedure :: calculationReset => accretionFlowCalculationReset - procedure :: density => accretionFlowDensity - procedure :: densityLogSlope => accretionFlowDensityLogSlope - procedure :: radiusEnclosingDensity => accretionFlowRadiusEnclosingDensity - procedure :: radiusEnclosingMass => accretionFlowRadiusEnclosingMass - procedure :: radialMoment => accretionFlowRadialMoment - procedure :: enclosedMass => accretionFlowEnclosedMass - procedure :: potential => accretionFlowPotential - procedure :: circularVelocity => accretionFlowCircularVelocity - procedure :: radiusCircularVelocityMaximum => accretionFlowRadiusCircularVelocityMaximum - procedure :: circularVelocityMaximum => accretionFlowCircularVelocityMaximum - procedure :: radialVelocityDispersion => accretionFlowRadialVelocityDispersion - procedure :: radiusFromSpecificAngularMomentum => accretionFlowRadiusFromSpecificAngularMomentum - procedure :: rotationNormalization => accretionFlowRotationNormalization - procedure :: energy => accretionFlowEnergy - procedure :: kSpace => accretionFlowKSpace - procedure :: freefallRadius => accretionFlowFreefallRadius - procedure :: freefallRadiusIncreaseRate => accretionFlowFreefallRadiusIncreaseRate - procedure :: deepCopy => accretionFlowDeepCopy - procedure :: deepCopyReset => accretionFlowDeepCopyReset - procedure :: deepCopyFinalize => accretionFlowDeepCopyFinalize - end type darkMatterProfileDMOAccretionFlow - - interface darkMatterProfileDMOAccretionFlow - !!{ - Constructors for the {\normalfont \ttfamily accretionFlow} dark matter halo profile class. - !!} - module procedure accretionFlowConstructorParameters - module procedure accretionFlowConstructorInternal - end interface darkMatterProfileDMOAccretionFlow - -contains - - function accretionFlowConstructorParameters(parameters) result(self) - !!{ - Constructor for the {\normalfont \ttfamily accretionFlow} dark matter halo profile class which takes a parameter set as input. - !!} - use :: Input_Parameters, only : inputParameter , inputParameters - use :: Functions_Global, only : accretionFlowsConstruct_, accretionFlowsDestruct_ - implicit none - type (darkMatterProfileDMOAccretionFlow) :: self - type (inputParameters ), intent(inout) :: parameters - class (* ), pointer :: accretionFlows_ - class (darkMatterProfileDMOClass ), pointer :: darkMatterProfileDMO_ - class (darkMatterHaloScaleClass ), pointer :: darkMatterHaloScale_ - class (cosmologyParametersClass ), pointer :: cosmologyParameters_ - class (cosmologyFunctionsClass ), pointer :: cosmologyFunctions_ - class (criticalOverdensityClass ), pointer :: criticalOverdensity_ - class (cosmologicalMassVarianceClass ), pointer :: cosmologicalMassVariance_ - double precision :: toleranceRelativePotential - - !![ - - toleranceRelativePotential - 1.0d-6 - parameters - The relative tolerance to use in numerical solutions for the potential in dark-matter-only density profiles. - - - - - - - - !!] - call accretionFlowsConstruct_(parameters,accretionFlows_) - self=darkMatterProfileDMOAccretionFlow(toleranceRelativePotential,cosmologyParameters_,cosmologyFunctions_,criticalOverdensity_,cosmologicalMassVariance_,accretionFlows_,darkMatterProfileDMO_,darkMatterHaloScale_) - !![ - - - - - - - - - !!] - if (associated(accretionFlows_)) call accretionFlowsDestruct_(accretionFlows_) - return - end function accretionFlowConstructorParameters - - function accretionFlowConstructorInternal(toleranceRelativePotential,cosmologyParameters_,cosmologyFunctions_,criticalOverdensity_,cosmologicalMassVariance_,accretionFlows_,darkMatterProfileDMO_,darkMatterHaloScale_) result(self) - !!{ - Internal constructor for the {\normalfont \ttfamily accretionFlow} dark matter profile class. - !!} - implicit none - type (darkMatterProfileDMOAccretionFlow) :: self - class (* ), intent(in ), target :: accretionFlows_ - class (darkMatterProfileDMOClass ), intent(in ), target :: darkMatterProfileDMO_ - class (darkMatterHaloScaleClass ), intent(in ), target :: darkMatterHaloScale_ - class (cosmologyParametersClass ), intent(in ), target :: cosmologyParameters_ - class (cosmologyFunctionsClass ), intent(in ), target :: cosmologyFunctions_ - class (criticalOverdensityClass ), intent(in ), target :: criticalOverdensity_ - class (cosmologicalMassVarianceClass ), intent(in ), target :: cosmologicalMassVariance_ - double precision , intent(in ) :: toleranceRelativePotential - !![ - - !!] - - return - end function accretionFlowConstructorInternal - - subroutine accretionFlowAutoHook(self) - !!{ - Attach to the calculation reset event. - !!} - use :: Events_Hooks, only : calculationResetEvent, openMPThreadBindingAllLevels - implicit none - class(darkMatterProfileDMOAccretionFlow), intent(inout) :: self - - call calculationResetEvent%attach(self,accretionFlowCalculationReset,openMPThreadBindingAllLevels) - return - end subroutine accretionFlowAutoHook - - subroutine accretionFlowDestructor(self) - !!{ - Destructor for the {\normalfont \ttfamily accretionFlow} dark matter halo profile class. - !!} - use :: Functions_Global, only : accretionFlowsDestruct_ - implicit none - type(darkMatterProfileDMOAccretionFlow), intent(inout) :: self - - !![ - - - - - - - !!] - if (associated(self%accretionFlows_)) call accretionFlowsDestruct_(self%accretionFlows_) - return - end subroutine accretionFlowDestructor - - subroutine accretionFlowCalculationReset(self,node,uniqueID) - !!{ - Reset the dark matter profile calculation. - !!} - use :: Kind_Numbers, only : kind_int8 - implicit none - class (darkMatterProfileDMOAccretionFlow), intent(inout) :: self - type (treeNode ), intent(inout) :: node - integer(kind_int8 ), intent(in ) :: uniqueID - !$GLC attributes unused :: node - - self%genericLastUniqueID =uniqueID - self%genericEnclosedMassRadiusMinimum =+huge(0.0d0) - self%genericEnclosedMassRadiusMaximum =-huge(0.0d0) - self%genericVelocityDispersionRadialRadiusMinimum=+huge(0.0d0) - self%genericVelocityDispersionRadialRadiusMaximum=-huge(0.0d0) - if (allocated(self%genericVelocityDispersionRadialVelocity)) deallocate(self%genericVelocityDispersionRadialVelocity) - if (allocated(self%genericVelocityDispersionRadialRadius )) deallocate(self%genericVelocityDispersionRadialRadius ) - if (allocated(self%genericEnclosedMassMass )) deallocate(self%genericEnclosedMassMass ) - if (allocated(self%genericEnclosedMassRadius )) deallocate(self%genericEnclosedMassRadius ) - return - end subroutine accretionFlowCalculationReset - - double precision function accretionFlowDensity(self,node,radius) - !!{ - Returns the density (in $M_\odot$ Mpc$^{-3}$) in the dark matter profile of {\normalfont \ttfamily node} at the given - {\normalfont \ttfamily radius} (given in units of Mpc). - !!} - use :: Functions_Global, only : accretionFlowsDensity_ - use :: Galacticus_Nodes, only : nodeComponentBasic - implicit none - class (darkMatterProfileDMOAccretionFlow), intent(inout) :: self - type (treeNode ), intent(inout) :: node - double precision , intent(in ) :: radius - class (nodeComponentBasic ), pointer :: basic - double precision :: radius200Mean , density200Mean , & - & radiusTransition, fractionTransition, & - & peakHeight - - if (node%isSatellite()) then - ! No accretion flow for satellites. - accretionFlowDensity = +self%darkMatterProfileDMO_%density(node,radius) - else - ! Include accretion flow for non-satellites - basic => node%basic() - peakHeight = +self%criticalOverdensity_ %value (time=basic%time(),mass=basic%mass() ) & - & /self%cosmologicalMassVariance_%rootVariance (time=basic%time(),mass=basic%mass() ) - density200Mean = +200.0d0 & - & *self%cosmologyFunctions_ %matterDensityEpochal (time=basic%time() ) - radius200Mean = +self%darkMatterProfileDMO_ %radiusEnclosingDensity( node , density200Mean) - radiusTransition = +( & - & +1.90d0 & - & -0.18d0 & - & *peakHeight & - & ) & - & *radius200Mean - fractionTransition = +1.0d0 & - & /( & - & +1.0d0 & - & +( & - & +radius & - & /radiusTransition & - & )**4 & - & )**2 - accretionFlowDensity = +self%darkMatterProfileDMO_%density ( node,radius) & - & * fractionTransition & - & +accretionFlowsDensity_ (self%accretionFlows_,node,radius) - end if - return - end function accretionFlowDensity - - double precision function accretionFlowDensityLogSlope(self,node,radius) - !!{ - Returns the logarithmic slope of the density (in $M_\odot$ Mpc$^{-3}$) in the dark matter profile of {\normalfont \ttfamily - node} at the given {\normalfont \ttfamily radius} (given in units of Mpc). - !!} - use :: Galacticus_Nodes, only : nodeComponentBasic - implicit none - class (darkMatterProfileDMOAccretionFlow), intent(inout) :: self - type (treeNode ), intent(inout) :: node - double precision , intent(in ) :: radius - - if (node%isSatellite()) then - accretionFlowDensityLogSlope=self%darkMatterProfileDMO_%densityLogSlope (node,radius) - else - accretionFlowDensityLogSlope=self %densityLogSlopeNumerical(node,radius) - end if - return - end function accretionFlowDensityLogSlope - - double precision function accretionFlowRadiusEnclosingDensity(self,node,density) - !!{ - Returns the radius (in Mpc) in the dark matter profile of {\normalfont \ttfamily node} which encloses the given - {\normalfont \ttfamily density} (given in units of $M_\odot/$Mpc$^{-3}$). - !!} - implicit none - class (darkMatterProfileDMOAccretionFlow), intent(inout), target :: self - type (treeNode ), intent(inout), target :: node - double precision , intent(in ) :: density - - if (node%isSatellite()) then - accretionFlowRadiusEnclosingDensity=self%darkMatterProfileDMO_%radiusEnclosingDensity (node,density) - else - accretionFlowRadiusEnclosingDensity=self %radiusEnclosingDensityNumerical(node,density) - end if - return - end function accretionFlowRadiusEnclosingDensity - - double precision function accretionFlowRadiusEnclosingMass(self,node,mass) - !!{ - Returns the radius (in Mpc) in the dark matter profile of {\normalfont \ttfamily node} which encloses the given - {\normalfont \ttfamily mass} (given in units of $M_\odot$). - !!} - implicit none - class (darkMatterProfileDMOAccretionFlow), intent(inout), target :: self - type (treeNode ), intent(inout), target :: node - double precision , intent(in ) :: mass - - if (node%isSatellite()) then - accretionFlowRadiusEnclosingMass=self%darkMatterProfileDMO_%radiusEnclosingMass (node,mass) - else - accretionFlowRadiusEnclosingMass=self %radiusEnclosingMassNumerical(node,mass) - end if - return - end function accretionFlowRadiusEnclosingMass - - double precision function accretionFlowRadialMoment(self,node,moment,radiusMinimum,radiusMaximum) - !!{ - Returns the radial moment of the density profile. - !!} - implicit none - class (darkMatterProfileDMOAccretionFlow), intent(inout) :: self - type (treeNode ), intent(inout) :: node - double precision , intent(in ) :: moment - double precision , intent(in ), optional :: radiusMinimum, radiusMaximum - - if (node%isSatellite()) then - accretionFlowRadialMoment=self%darkMatterProfileDMO_%radialMoment (node,moment,radiusMinimum,radiusMaximum) - else - accretionFlowRadialMoment=self %radialMomentNumerical(node,moment,radiusMinimum,radiusMaximum) - end if - return - end function accretionFlowRadialMoment - - double precision function accretionFlowEnclosedMass(self,node,radius) - !!{ - Returns the enclosed mass (in $M_\odot$) in the dark matter profile of {\normalfont \ttfamily node} at the given {\normalfont \ttfamily radius} (given in - units of Mpc). - !!} - implicit none - class (darkMatterProfileDMOAccretionFlow), intent(inout) :: self - type (treeNode ), intent(inout) :: node - double precision , intent(in ) :: radius - - if (node%isSatellite()) then - accretionFlowEnclosedMass=self%darkMatterProfileDMO_%enclosedMass (node,radius) - else - accretionFlowEnclosedMass=self %enclosedMassNumerical(node,radius) - end if - return - end function accretionFlowEnclosedMass - - double precision function accretionFlowPotential(self,node,radius,status) - !!{ - Returns the potential (in (km/s)$^2$) in the dark matter profile of {\normalfont \ttfamily node} at the given {\normalfont - \ttfamily radius} (given in units of Mpc). - !!} - implicit none - class (darkMatterProfileDMOAccretionFlow), intent(inout) :: self - type (treeNode ), intent(inout), target :: node - double precision , intent(in ) :: radius - type (enumerationStructureErrorCodeType), intent( out), optional :: status - - if (node%isSatellite()) then - accretionFlowPotential=self%darkMatterProfileDMO_%potential (node,radius,status) - else - accretionFlowPotential=self %potentialNumerical(node,radius,status) - end if - return - end function accretionFlowPotential - - double precision function accretionFlowCircularVelocity(self,node,radius) - !!{ - Returns the circular velocity (in km/s) in the dark matter profile of {\normalfont \ttfamily node} at the given - {\normalfont \ttfamily radius} (given in units of Mpc). - !!} - use :: Numerical_Constants_Astronomical, only : gravitationalConstantGalacticus - implicit none - class (darkMatterProfileDMOAccretionFlow), intent(inout) :: self - type (treeNode ), intent(inout) :: node - double precision , intent(in ) :: radius - - if (radius > 0.0d0) then - accretionFlowCircularVelocity=sqrt( & - & +gravitationalConstantGalacticus & - & *self%enclosedMass(node,radius) & - & / radius & - & ) - else - accretionFlowCircularVelocity=0.0d0 - end if - return - end function accretionFlowCircularVelocity - - double precision function accretionFlowRadiusCircularVelocityMaximum(self,node) - !!{ - Returns the radius (in Mpc) at which the maximum circular velocity is achieved in the dark matter profile of {\normalfont \ttfamily node}. - !!} - implicit none - class(darkMatterProfileDMOAccretionFlow), intent(inout) :: self - type (treeNode ), intent(inout) :: node - - if (node%isSatellite()) then - accretionFlowRadiusCircularVelocityMaximum=self%darkMatterProfileDMO_%radiusCircularVelocityMaximum (node) - else - accretionFlowRadiusCircularVelocityMaximum=self %radiusCircularVelocityMaximumNumerical(node) - end if - return - end function accretionFlowRadiusCircularVelocityMaximum - - double precision function accretionFlowCircularVelocityMaximum(self,node) - !!{ - Returns the maximum circular velocity (in km/s) in the dark matter profile of {\normalfont \ttfamily node}. - !!} - implicit none - class(darkMatterProfileDMOAccretionFlow), intent(inout) :: self - type (treeNode ), intent(inout) :: node - - if (node%isSatellite()) then - accretionFlowCircularVelocityMaximum=self%darkMatterProfileDMO_%circularVelocityMaximum (node) - else - accretionFlowCircularVelocityMaximum=self %circularVelocityMaximumNumerical(node) - end if - return - end function accretionFlowCircularVelocityMaximum - - double precision function accretionFlowRadialVelocityDispersion(self,node,radius) - !!{ - Returns the radial velocity dispersion (in km/s) in the dark matter profile of {\normalfont \ttfamily node} at the given - {\normalfont \ttfamily radius} (given in units of Mpc). - !!} - implicit none - class (darkMatterProfileDMOAccretionFlow), intent(inout) :: self - type (treeNode ), intent(inout) :: node - double precision , intent(in ) :: radius - - if (node%isSatellite()) then - accretionFlowRadialVelocityDispersion=self%darkMatterProfileDMO_%radialVelocityDispersion (node,radius) - else - accretionFlowRadialVelocityDispersion=self %radialVelocityDispersionNumerical(node,radius) - end if - return - end function accretionFlowRadialVelocityDispersion - - double precision function accretionFlowRadiusFromSpecificAngularMomentum(self,node,specificAngularMomentum) - !!{ - Returns the radius (in Mpc) in {\normalfont \ttfamily node} at which a circular orbit has the given {\normalfont \ttfamily specificAngularMomentum} (given - in units of km s$^{-1}$ Mpc). - !!} - implicit none - class (darkMatterProfileDMOAccretionFlow), intent(inout) :: self - type (treeNode ), intent(inout) :: node - double precision , intent(in ) :: specificAngularMomentum - - if (node%isSatellite()) then - accretionFlowRadiusFromSpecificAngularMomentum=self%darkMatterProfileDMO_%radiusFromSpecificAngularMomentum (node,specificAngularMomentum) - else - accretionFlowRadiusFromSpecificAngularMomentum=self %radiusFromSpecificAngularMomentumNumerical(node,specificAngularMomentum) - end if - return - end function accretionFlowRadiusFromSpecificAngularMomentum - - double precision function accretionFlowRotationNormalization(self,node) - !!{ - Return the normalization of the rotation velocity vs. specific angular momentum relation. - !!} - implicit none - class (darkMatterProfileDMOAccretionFlow), intent(inout) :: self - type (treeNode ), intent(inout) :: node - double precision :: radiusVirial - - radiusVirial =+self%darkMatterHaloScale_%radiusVirial(node ) - accretionFlowRotationNormalization=+self %radialMoment(node,moment=2.0d0,radiusMinimum=0.0d0,radiusMaximum=radiusVirial) & - & /self %radialMoment(node,moment=3.0d0,radiusMinimum=0.0d0,radiusMaximum=radiusVirial) - return - end function accretionFlowRotationNormalization - - double precision function accretionFlowEnergy(self,node) - !!{ - Return the energy of a accretionFlow halo density profile. - !!} - implicit none - class(darkMatterProfileDMOAccretionFlow), intent(inout) :: self - type (treeNode ), intent(inout) :: node - - if (node%isSatellite()) then - accretionFlowEnergy=self%darkMatterProfileDMO_%energy (node) - else - accretionFlowEnergy=self %energyNumerical(node) - end if - return - end function accretionFlowEnergy - - double precision function accretionFlowKSpace(self,node,waveNumber) - !!{ - Returns the Fourier transform of the accretionFlow density profile at the specified {\normalfont \ttfamily waveNumber} - (given in Mpc$^{-1}$), using the expression given in \citeauthor{cooray_halo_2002}~(\citeyear{cooray_halo_2002}; eqn.~81). - !!} - implicit none - class (darkMatterProfileDMOAccretionFlow), intent(inout) :: self - type (treeNode ), intent(inout), target :: node - double precision , intent(in ) :: waveNumber - - if (node%isSatellite()) then - accretionFlowKSpace=self%darkMatterProfileDMO_%kSpace (node,waveNumber) - else - accretionFlowKSpace=self %kSpaceNumerical(node,waveNumber) - end if - return - end function accretionFlowKSpace - - double precision function accretionFlowFreefallRadius(self,node,time) - !!{ - Returns the freefall radius in the accretionFlow density profile at the specified {\normalfont \ttfamily time} (given in - Gyr). - !!} - implicit none - class (darkMatterProfileDMOAccretionFlow), intent(inout), target :: self - type (treeNode ), intent(inout), target :: node - double precision , intent(in ) :: time - - if (node%isSatellite()) then - accretionFlowFreefallRadius=self%darkMatterProfileDMO_%freefallRadius (node,time) - else - accretionFlowFreefallRadius=self %freefallRadiusNumerical(node,time) - end if - return - end function accretionFlowFreefallRadius - - double precision function accretionFlowFreefallRadiusIncreaseRate(self,node,time) - !!{ - Returns the rate of increase of the freefall radius in the accretionFlow density profile at the specified {\normalfont - \ttfamily time} (given in Gyr). - !!} - implicit none - class (darkMatterProfileDMOAccretionFlow), intent(inout), target :: self - type (treeNode ), intent(inout), target :: node - double precision , intent(in ) :: time - - if (node%isSatellite()) then - accretionFlowFreefallRadiusIncreaseRate=self%darkMatterProfileDMO_%freefallRadiusIncreaseRate (node,time) - else - accretionFlowFreefallRadiusIncreaseRate=self %freefallRadiusIncreaseRateNumerical(node,time) - end if - return - end function accretionFlowFreefallRadiusIncreaseRate - - subroutine accretionFlowDeepCopyReset(self) - !!{ - Perform a deep copy reset of the object. - !!} - use :: Functions_Global, only : accretionFlowsDeepCopyReset_ - implicit none - class(darkMatterProfileDMOAccretionFlow), intent(inout) :: self - - self%copiedSelf => null() - if (associated(self%cosmologyParameters_ )) call self%cosmologyParameters_ %deepCopyReset() - if (associated(self%cosmologyFunctions_ )) call self%cosmologyFunctions_ %deepCopyReset() - if (associated(self%criticalOverdensity_ )) call self%criticalOverdensity_ %deepCopyReset() - if (associated(self%cosmologicalMassVariance_)) call self%cosmologicalMassVariance_%deepCopyReset() - if (associated(self%darkMatterHaloScale_ )) call self%darkMatterHaloScale_ %deepCopyReset() - if (associated(self%darkMatterProfileDMO_ )) call self%darkMatterProfileDMO_ %deepCopyReset() - if (associated(self%accretionFlows_ )) call accretionFlowsDeepCopyReset_(self%accretionFlows_) - return - end subroutine accretionFlowDeepCopyReset - - subroutine accretionFlowDeepCopyFinalize(self) - !!{ - Finalize a deep reset of the object. - !!} - use :: Functions_Global, only : accretionFlowsDeepCopyFinalize_ - implicit none - class(darkMatterProfileDMOAccretionFlow), intent(inout) :: self - - if (associated(self%cosmologyParameters_ )) call self%cosmologyParameters_ %deepCopyFinalize() - if (associated(self%cosmologyFunctions_ )) call self%cosmologyFunctions_ %deepCopyFinalize() - if (associated(self%criticalOverdensity_ )) call self%criticalOverdensity_ %deepCopyFinalize() - if (associated(self%cosmologicalMassVariance_)) call self%cosmologicalMassVariance_%deepCopyFinalize() - if (associated(self%darkMatterHaloScale_ )) call self%darkMatterHaloScale_ %deepCopyFinalize() - if (associated(self%darkMatterProfileDMO_ )) call self%darkMatterProfileDMO_ %deepCopyFinalize() - if (associated(self%accretionFlows_ )) call accretionFlowsDeepCopyFinalize_(self%accretionFlows_) - return - end subroutine accretionFlowDeepCopyFinalize - - subroutine accretionFlowDeepCopy(self,destination) - !!{ - Perform a deep copy of the object. - !!} - use :: Error , only : Error_Report - use :: Functions_Global, only : accretionFlowsDeepCopy_ - implicit none - class(darkMatterProfileDMOAccretionFlow), intent(inout), target :: self - class(darkMatterProfileDMOClass ), intent(inout) :: destination - - call self%darkMatterProfileDMOClass%deepCopy(destination) - select type (destination) - type is (darkMatterProfileDMOAccretionFlow) - nullify(destination%cosmologyParameters_) - if (associated(self%cosmologyParameters_)) then - if (associated(self%cosmologyParameters_%copiedSelf)) then - select type(s => self%cosmologyParameters_%copiedSelf) - class is (cosmologyParametersClass) - destination%cosmologyParameters_ => s - class default - call Error_Report('copiedSelf has incorrect type'//{introspection:location}) - end select - call self%cosmologyParameters_%copiedSelf%referenceCountIncrement() - else - allocate(destination%cosmologyParameters_,mold=self%cosmologyParameters_) - call self%cosmologyParameters_%deepCopy(destination%cosmologyParameters_) - self%cosmologyParameters_%copiedSelf => destination%cosmologyParameters_ - call destination%cosmologyParameters_%autoHook() - end if - end if - nullify(destination%cosmologyFunctions_) - if (associated(self%cosmologyFunctions_)) then - if (associated(self%cosmologyFunctions_%copiedSelf)) then - select type(s => self%cosmologyFunctions_%copiedSelf) - class is (cosmologyFunctionsClass) - destination%cosmologyFunctions_ => s - class default - call Error_Report('copiedSelf has incorrect type'//{introspection:location}) - end select - call self%cosmologyFunctions_%copiedSelf%referenceCountIncrement() - else - allocate(destination%cosmologyFunctions_,mold=self%cosmologyFunctions_) - call self%cosmologyFunctions_%deepCopy(destination%cosmologyFunctions_) - self%cosmologyFunctions_%copiedSelf => destination%cosmologyFunctions_ - call destination%cosmologyFunctions_%autoHook() - end if - end if - nullify(destination%criticalOverdensity_) - if (associated(self%criticalOverdensity_)) then - if (associated(self%criticalOverdensity_%copiedSelf)) then - select type(s => self%criticalOverdensity_%copiedSelf) - class is (criticalOverdensityClass) - destination%criticalOverdensity_ => s - class default - call Error_Report('copiedSelf has incorrect type'//{introspection:location}) - end select - call self%criticalOverdensity_%copiedSelf%referenceCountIncrement() - else - allocate(destination%criticalOverdensity_,mold=self%criticalOverdensity_) - call self%criticalOverdensity_%deepCopy(destination%criticalOverdensity_) - self%criticalOverdensity_%copiedSelf => destination%criticalOverdensity_ - call destination%criticalOverdensity_%autoHook() - end if - end if - nullify(destination%cosmologicalMassVariance_) - if (associated(self%cosmologicalMassVariance_)) then - if (associated(self%cosmologicalMassVariance_%copiedSelf)) then - select type(s => self%cosmologicalMassVariance_%copiedSelf) - class is (cosmologicalMassVarianceClass) - destination%cosmologicalMassVariance_ => s - class default - call Error_Report('copiedSelf has incorrect type'//{introspection:location}) - end select - call self%cosmologicalMassVariance_%copiedSelf%referenceCountIncrement() - else - allocate(destination%cosmologicalMassVariance_,mold=self%cosmologicalMassVariance_) - call self%cosmologicalMassVariance_%deepCopy(destination%cosmologicalMassVariance_) - self%cosmologicalMassVariance_%copiedSelf => destination%cosmologicalMassVariance_ - call destination%cosmologicalMassVariance_%autoHook() - end if - end if - nullify(destination%darkMatterHaloScale_) - if (associated(self%darkMatterHaloScale_)) then - if (associated(self%darkMatterHaloScale_%copiedSelf)) then - select type(s => self%darkMatterHaloScale_%copiedSelf) - class is (darkMatterHaloScaleClass) - destination%darkMatterHaloScale_ => s - class default - call Error_Report('copiedSelf has incorrect type'//{introspection:location}) - end select - call self%darkMatterHaloScale_%copiedSelf%referenceCountIncrement() - else - allocate(destination%darkMatterHaloScale_,mold=self%darkMatterHaloScale_) - call self%darkMatterHaloScale_%deepCopy(destination%darkMatterHaloScale_) - self%darkMatterHaloScale_%copiedSelf => destination%darkMatterHaloScale_ - call destination%darkMatterHaloScale_%autoHook() - end if - end if - nullify(destination%darkMatterProfileDMO_) - if (associated(self%darkMatterProfileDMO_)) then - if (associated(self%darkMatterProfileDMO_%copiedSelf)) then - select type(s => self%darkMatterProfileDMO_%copiedSelf) - class is (darkMatterProfileDMOClass) - destination%darkMatterProfileDMO_ => s - class default - call Error_Report('copiedSelf has incorrect type'//{introspection:location}) - end select - call self%darkMatterProfileDMO_%copiedSelf%referenceCountIncrement() - else - allocate(destination%darkMatterProfileDMO_,mold=self%darkMatterProfileDMO_) - call self%darkMatterProfileDMO_%deepCopy(destination%darkMatterProfileDMO_) - self%darkMatterProfileDMO_%copiedSelf => destination%darkMatterProfileDMO_ - call destination%darkMatterProfileDMO_%autoHook() - end if - end if - nullify(destination%accretionFlows_) - if (associated(self%accretionFlows_)) then - allocate(destination%accretionFlows_,mold=self%accretionFlows_) - call accretionFlowsDeepCopy_(self%accretionFlows_,destination%accretionFlows_) - end if - class default - call Error_Report('destination and source types do not match'//{introspection:location}) - end select - return - end subroutine accretionFlowDeepCopy diff --git a/source/dark_matter_profiles_DMO.accretion_flow.Shi2016.F90 b/source/dark_matter_profiles_DMO.accretion_flow.Shi2016.F90 new file mode 100644 index 0000000000..124536d56d --- /dev/null +++ b/source/dark_matter_profiles_DMO.accretion_flow.Shi2016.F90 @@ -0,0 +1,262 @@ +!! Copyright 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, +!! 2019, 2020, 2021, 2022, 2023 +!! Andrew Benson +!! +!! This file is part of Galacticus. +!! +!! Galacticus is free software: you can redistribute it and/or modify +!! it under the terms of the GNU General Public License as published by +!! the Free Software Foundation, either version 3 of the License, or +!! (at your option) any later version. +!! +!! Galacticus is distributed in the hope that it will be useful, +!! but WITHOUT ANY WARRANTY; without even the implied warranty of +!! MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +!! GNU General Public License for more details. +!! +!! You should have received a copy of the GNU General Public License +!! along with Galacticus. If not, see . + + !!{ + An implementation of a dark matter density profile which includes the accretion flow surrounding the halo. + !!} + + use :: Cosmology_Functions , only : cosmologyFunctionsClass + use :: Cosmological_Density_Field , only : cosmologicalMassVarianceClass , criticalOverdensityClass + use :: Spherical_Collapse_Solvers , only : sphericalCollapseSolverClass + use :: Dark_Matter_Halo_Mass_Accretion_Histories, only : darkMatterHaloMassAccretionHistoryClass + use :: Dark_Matter_Halo_Scales , only : darkMatterHaloScaleClass + + !![ + + + A dark matter profile class which builds \refClass{massDistributionShi2016} objects to model accretion flows using the + model of \cite{shi_outer_2016}. + + + !!] + type, extends(darkMatterProfileDMOClass) :: darkMatterProfileDMOAccretionFlowShi2016 + !!{ + A dark matter halo profile class which implements a dark matter density profile which includes the accretion flow using the + 2-halo correlation function. + !!} + private + class (darkMatterHaloScaleClass ), pointer :: darkMatterHaloScale_ => null() + class (darkMatterProfileDMOClass ), pointer :: darkMatterProfileDMO_ => null() + class (cosmologyFunctionsClass ), pointer :: cosmologyFunctions_ => null() + class (criticalOverdensityClass ), pointer :: criticalOverdensity_ => null() + class (cosmologicalMassVarianceClass ), pointer :: cosmologicalMassVariance_ => null() + class (darkMatterHaloMassAccretionHistoryClass), pointer :: darkMatterHaloMassAccretionHistory_ => null() + class (sphericalCollapseSolverClass ), pointer :: sphericalCollapseSolver_ => null() + double precision :: scaleFactorVelocity + contains + final :: accretionFlowShi2016Destructor + procedure :: get => accretionFlowShi2016Get + end type darkMatterProfileDMOAccretionFlowShi2016 + + interface darkMatterProfileDMOAccretionFlowShi2016 + !!{ + Constructors for the {\normalfont \ttfamily accretionFlowShi2016} dark matter halo profile class. + !!} + module procedure accretionFlowShi2016ConstructorParameters + module procedure accretionFlowShi2016ConstructorInternal + end interface darkMatterProfileDMOAccretionFlowShi2016 + +contains + + function accretionFlowShi2016ConstructorParameters(parameters) result(self) + !!{ + Constructor for the {\normalfont \ttfamily accretionFlowShi2016} dark matter halo profile class which takes a parameter set as input. + !!} + use :: Input_Parameters, only : inputParameter, inputParameters + implicit none + type (darkMatterProfileDMOAccretionFlowShi2016) :: self + type (inputParameters ), intent(inout) :: parameters + class (darkMatterProfileDMOClass ), pointer :: darkMatterProfileDMO_ + class (cosmologyFunctionsClass ), pointer :: cosmologyFunctions_ + class (criticalOverdensityClass ), pointer :: criticalOverdensity_ + class (cosmologicalMassVarianceClass ), pointer :: cosmologicalMassVariance_ + class (darkMatterHaloScaleClass ), pointer :: darkMatterHaloScale_ + class (sphericalCollapseSolverClass ), pointer :: sphericalCollapseSolver_ + class (darkMatterHaloMassAccretionHistoryClass ), pointer :: darkMatterHaloMassAccretionHistory_ + double precision :: scaleFactorVelocity + + !![ + + scaleFactorVelocity + parameters + 1.0d0 + A scale factor to be applied to inflow velocities. + + + + + + + + + !!] + self=darkMatterProfileDMOAccretionFlowShi2016(scaleFactorVelocity,cosmologyFunctions_,criticalOverdensity_,cosmologicalMassVariance_,darkMatterProfileDMO_,darkMatterHaloScale_,darkMatterHaloMassAccretionHistory_,sphericalCollapseSolver_) + !![ + + + + + + + + + !!] + return + end function accretionFlowShi2016ConstructorParameters + + function accretionFlowShi2016ConstructorInternal(scaleFactorVelocity,cosmologyFunctions_,criticalOverdensity_,cosmologicalMassVariance_,darkMatterProfileDMO_,darkMatterHaloScale_,darkMatterHaloMassAccretionHistory_,sphericalCollapseSolver_) result(self) + !!{ + Internal constructor for the {\normalfont \ttfamily accretionFlowShi2016} dark matter profile class. + !!} + implicit none + type (darkMatterProfileDMOAccretionFlowShi2016) :: self + class (darkMatterProfileDMOClass ), intent(in ), target :: darkMatterProfileDMO_ + class (cosmologyFunctionsClass ), intent(in ), target :: cosmologyFunctions_ + class (criticalOverdensityClass ), intent(in ), target :: criticalOverdensity_ + class (cosmologicalMassVarianceClass ), intent(in ), target :: cosmologicalMassVariance_ + class (darkMatterHaloScaleClass ), intent(in ), target :: darkMatterHaloScale_ + class (darkMatterHaloMassAccretionHistoryClass ), intent(in ), target :: darkMatterHaloMassAccretionHistory_ + class (sphericalCollapseSolverClass ), intent(in ), target :: sphericalCollapseSolver_ + double precision , intent(in ) :: scaleFactorVelocity + !![ + + !!] + + return + end function accretionFlowShi2016ConstructorInternal + + subroutine accretionFlowShi2016Destructor(self) + !!{ + Destructor for the {\normalfont \ttfamily accretionFlowShi2016} dark matter profile class. + !!} + implicit none + type(darkMatterProfileDMOAccretionFlowShi2016), intent(inout) :: self + + !![ + + + + + + + !!] + return + end subroutine accretionFlowShi2016Destructor + + function accretionFlowShi2016Get(self,node,weightBy,weightIndex) result(massDistribution_) + !!{ + Return the dark matter mass distribution for the given {\normalfont \ttfamily node}. + !!} + use :: Galacticus_Nodes , only : nodeComponentBasic + use :: Galactic_Structure_Options, only : componentTypeDarkHalo , massTypeDark , weightByMass + use :: Mass_Distributions , only : massDistributionSpherical , massDistributionSphericalAccretionFlow, massDistributionShi2016, kinematicsDistributionShi2016, & + & nonAnalyticSolversNumerical + use :: Numerical_Ranges , only : Make_Range , rangeTypeLogarithmic + use :: Tables , only : table1D + implicit none + class (massDistributionClass ), pointer :: massDistribution_ + class (massDistributionClass ), pointer :: massDistributionAccretionFlow_ , massDistributionVirialized_ + type (kinematicsDistributionShi2016 ), pointer :: kinematicsDistribution_ + class (darkMatterProfileDMOAccretionFlowShi2016), intent(inout) :: self + type (treeNode ), intent(inout) :: node + type (enumerationWeightByType ), intent(in ), optional :: weightBy + integer , intent(in ), optional :: weightIndex + class (nodeComponentBasic ), pointer :: basic + class (table1D ), allocatable :: ratioRadiusTurnaroundVirialTable + double precision :: time , mass , & + & densityMean , radius200Mean , & + & peakHeight , radiusTransition , & + & massAccretionRate , radiusVirial , & + & ratioRadiusTurnaroundVirial + !![ + + !!] + + ! Assume a null distribution by default. + massDistribution_ => null() + ! If weighting is not by mass, return a null profile. + if (weightBy_ /= weightByMass) return + ! Combine the virialized and accretion flow mass distributions. + allocate(massDistributionSphericalAccretionFlow :: massDistribution_) + select type(massDistribution_) + type is (massDistributionSphericalAccretionFlow) + ! Get the virialized mass distribution. + massDistributionVirialized_ => self%darkMatterProfileDMO_%get(node) + select type (massDistributionVirialized_) + class is (massDistributionSpherical) + ! Find the radius enclosing 200 times the mean density. + densityMean = self %cosmologyFunctions_ %matterDensityEpochal ( time ) + radius200Mean = massDistributionVirialized_ %radiusEnclosingDensity(+200.0d0*densityMean) + ! Compute the transition radius following Diemer & Kravtsov (2014; equation 6). + peakHeight =+self%criticalOverdensity_ %value (time=time,mass=mass) & + & /self%cosmologicalMassVariance_%rootVariance(time=time,mass=mass) + radiusTransition=+( & + & +1.90d0 & + & -0.18d0 & + & *peakHeight & + & ) & + & *radius200Mean + ! Create the accretion flow mass distribution. + allocate(massDistributionShi2016 :: massDistributionAccretionFlow_) + select type(massDistributionAccretionFlow_) + type is (massDistributionShi2016) + ! Extract basic quantities for the halo. + basic => node %basic ( ) + time = basic %time ( ) + mass = basic %mass ( ) + radiusVirial = self %darkMatterHaloScale_ %radiusVirial (node ) + massAccretionRate = self %darkMatterHaloMassAccretionHistory_%massAccretionRate(node,time) + call self%sphericalCollapseSolver_%radiusTurnaround(time,tableStore=.false.,radiusTurnaround_=ratioRadiusTurnaroundVirialTable) + ratioRadiusTurnaroundVirial=ratioRadiusTurnaroundVirialTable%interpolate(time) + !![ + + + massDistributionShi2016( & + & mass =mass , & + & massAccretionRate =massAccretionRate , & + & radiusVirial =radiusVirial , & + & ratioRadiusTurnaroundVirial=ratioRadiusTurnaroundVirial, & + & time =time , & + & scaleFactorVelocity =self%scaleFactorVelocity , & + & cosmologyFunctions_ =self%cosmologyFunctions_ , & + & componentType =componentTypeDarkHalo , & + & massType =massTypeDark & + & ) + + + !!] + !![ + + + massDistributionSphericalAccretionFlow( & + & radiusTransition =radiusTransition , & + & nonAnalyticSolver =nonAnalyticSolversNumerical , & + & massDistribution_ =massDistributionVirialized_ , & + & massDistributionAccretionFlow_=massDistributionAccretionFlow_, & + & componentType =componentTypeDarkHalo , & + & massType =massTypeDark & + & ) + + + !!] + end select + end select + end select + allocate(kinematicsDistribution_) + !![ + + !!] + call massDistribution_%setKinematicsDistribution(kinematicsDistribution_) + !![ + + + + !!] + return + end function accretionFlowShi2016Get diff --git a/source/dark_matter_profiles_DMO.accretion_flow.correlation_function.F90 b/source/dark_matter_profiles_DMO.accretion_flow.correlation_function.F90 new file mode 100644 index 0000000000..53f8fcc967 --- /dev/null +++ b/source/dark_matter_profiles_DMO.accretion_flow.correlation_function.F90 @@ -0,0 +1,293 @@ +!! Copyright 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, +!! 2019, 2020, 2021, 2022, 2023 +!! Andrew Benson +!! +!! This file is part of Galacticus. +!! +!! Galacticus is free software: you can redistribute it and/or modify +!! it under the terms of the GNU General Public License as published by +!! the Free Software Foundation, either version 3 of the License, or +!! (at your option) any later version. +!! +!! Galacticus is distributed in the hope that it will be useful, +!! but WITHOUT ANY WARRANTY; without even the implied warranty of +!! MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +!! GNU General Public License for more details. +!! +!! You should have received a copy of the GNU General Public License +!! along with Galacticus. If not, see . + + !!{ + An implementation of a dark matter density profile which includes the accretion flow surrounding the halo. + !!} + + use :: Cosmology_Functions , only : cosmologyFunctionsClass + use :: Cosmological_Density_Field , only : cosmologicalMassVarianceClass , criticalOverdensityClass + use :: Correlation_Functions_Two_Point, only : correlationFunctionTwoPointClass + use :: Dark_Matter_Halo_Biases , only : darkMatterHaloBiasClass + use :: Dark_Matter_Halo_Scales , only : darkMatterHaloScaleClass + use :: Linear_Growth , only : linearGrowthClass + + !![ + + + An accretion flow class which models the accretion flow using the 2-halo correlation function by building + \refClass{massDistributionCorrelationFunction} objects. + + + !!] + type, extends(darkMatterProfileDMOClass) :: darkMatterProfileDMOAccretionFlowCorrelationFunction + !!{ + A dark matter halo profile class which implements a dark matter density profile which includes the accretion flow using the + 2-halo correlation function. + !!} + private + class (darkMatterHaloScaleClass ), pointer :: darkMatterHaloScale_ => null() + class (darkMatterProfileDMOClass ), pointer :: darkMatterProfileDMO_ => null() + class (cosmologyFunctionsClass ), pointer :: cosmologyFunctions_ => null() + class (criticalOverdensityClass ), pointer :: criticalOverdensity_ => null() + class (cosmologicalMassVarianceClass ), pointer :: cosmologicalMassVariance_ => null() + class (correlationFunctionTwoPointClass), pointer :: correlationFunctionTwoPoint_ => null() + class (darkMatterHaloBiasClass ), pointer :: darkMatterHaloBias_ => null() + class (linearGrowthClass ), pointer :: linearGrowth_ => null() + double precision :: scaleFactorVelocity + contains + final :: accretionFlowCorrelationFunctionDestructor + procedure :: get => accretionFlowCorrelationFunctionGet + end type darkMatterProfileDMOAccretionFlowCorrelationFunction + + interface darkMatterProfileDMOAccretionFlowCorrelationFunction + !!{ + Constructors for the {\normalfont \ttfamily accretionFlowCorrelationFunction} dark matter halo profile class. + !!} + module procedure accretionFlowCorrelationFunctionConstructorParameters + module procedure accretionFlowCorrelationFunctionConstructorInternal + end interface darkMatterProfileDMOAccretionFlowCorrelationFunction + +contains + + function accretionFlowCorrelationFunctionConstructorParameters(parameters) result(self) + !!{ + Constructor for the {\normalfont \ttfamily accretionFlowCorrelationFunction} dark matter halo profile class which takes a parameter set as input. + !!} + use :: Input_Parameters, only : inputParameter, inputParameters + implicit none + type (darkMatterProfileDMOAccretionFlowCorrelationFunction) :: self + type (inputParameters ), intent(inout) :: parameters + class (darkMatterProfileDMOClass ), pointer :: darkMatterProfileDMO_ + class (cosmologyFunctionsClass ), pointer :: cosmologyFunctions_ + class (criticalOverdensityClass ), pointer :: criticalOverdensity_ + class (cosmologicalMassVarianceClass ), pointer :: cosmologicalMassVariance_ + class (correlationFunctionTwoPointClass ), pointer :: correlationFunctionTwoPoint_ + class (darkMatterHaloBiasClass ), pointer :: darkMatterHaloBias_ + class (darkMatterHaloScaleClass ), pointer :: darkMatterHaloScale_ + class (linearGrowthClass ), pointer :: linearGrowth_ + double precision :: scaleFactorVelocity + + !![ + + scaleFactorVelocity + parameters + 1.0d0 + A scale factor to be applied to inflow velocities. + + + + + + + + + + !!] + self=darkMatterProfileDMOAccretionFlowCorrelationFunction(scaleFactorVelocity,cosmologyFunctions_,criticalOverdensity_,cosmologicalMassVariance_,darkMatterHaloBias_,correlationFunctionTwoPoint_,darkMatterProfileDMO_,darkMatterHaloScale_,linearGrowth_) + !![ + + + + + + + + + + !!] + return + end function accretionFlowCorrelationFunctionConstructorParameters + + function accretionFlowCorrelationFunctionConstructorInternal(scaleFactorVelocity,cosmologyFunctions_,criticalOverdensity_,cosmologicalMassVariance_,darkMatterHaloBias_,correlationFunctionTwoPoint_,darkMatterProfileDMO_,darkMatterHaloScale_,linearGrowth_) result(self) + !!{ + Internal constructor for the {\normalfont \ttfamily accretionFlowCorrelationFunction} dark matter profile class. + !!} + implicit none + type (darkMatterProfileDMOAccretionFlowCorrelationFunction) :: self + class (darkMatterProfileDMOClass ), intent(in ), target :: darkMatterProfileDMO_ + class (cosmologyFunctionsClass ), intent(in ), target :: cosmologyFunctions_ + class (criticalOverdensityClass ), intent(in ), target :: criticalOverdensity_ + class (cosmologicalMassVarianceClass ), intent(in ), target :: cosmologicalMassVariance_ + class (correlationFunctionTwoPointClass ), intent(in ), target :: correlationFunctionTwoPoint_ + class (darkMatterHaloBiasClass ), intent(in ), target :: darkMatterHaloBias_ + class (darkMatterHaloScaleClass ), intent(in ), target :: darkMatterHaloScale_ + class (linearGrowthClass ), intent(in ), target :: linearGrowth_ + double precision, intent(in ) :: scaleFactorVelocity + !![ + + !!] + + return + end function accretionFlowCorrelationFunctionConstructorInternal + + subroutine accretionFlowCorrelationFunctionDestructor(self) + !!{ + Destructor for the {\normalfont \ttfamily accretionFlowCorrelationFunction} dark matter profile class. + !!} + implicit none + type(darkMatterProfileDMOAccretionFlowCorrelationFunction), intent(inout) :: self + + !![ + + + + + + + + !!] + return + end subroutine accretionFlowCorrelationFunctionDestructor + + function accretionFlowCorrelationFunctionGet(self,node,weightBy,weightIndex) result(massDistribution_) + !!{ + Return the dark matter mass distribution for the given {\normalfont \ttfamily node}. + !!} + use :: Galacticus_Nodes , only : nodeComponentBasic + use :: Galactic_Structure_Options, only : componentTypeDarkHalo , massTypeDark , weightByMass + use :: Mass_Distributions , only : massDistributionSpherical , massDistributionSphericalAccretionFlow, massDistributionCorrelationFunction, kinematicsDistributionLam2013, & + & nonAnalyticSolversNumerical + use :: Numerical_Ranges , only : Make_Range , rangeTypeLogarithmic + implicit none + class (massDistributionClass ), pointer :: massDistribution_ + class (massDistributionClass ), pointer :: massDistributionAccretionFlow_ , massDistributionVirialized_ + type (kinematicsDistributionLam2013 ), pointer :: kinematicsDistribution_ + class (darkMatterProfileDMOAccretionFlowCorrelationFunction), intent(inout) :: self + type (treeNode ), intent(inout) :: node + type (enumerationWeightByType ), intent(in ), optional :: weightBy + integer , intent(in ), optional :: weightIndex + class (nodeComponentBasic ), pointer :: basic + double precision , allocatable , dimension(:) :: radius , correlationFunction , & + & correlationFunctionVolumeAveraged + integer , parameter :: countRadiiPerDecade =10 + double precision , parameter :: factorRadiusMinimum =10.0d0, factorRadiusMaximum =10.0d0 + integer :: countRadii , i + double precision :: time , mass , & + & radiusMinimum , radiusMaximum , & + & densityMean , radius200Mean , & + & peakHeight , radiusTransition + !![ + + !!] + + ! Assume a null distribution by default. + massDistribution_ => null() + ! If weighting is not by mass, return a null profile. + if (weightBy_ /= weightByMass) return + ! Combine the virialized and accretion flow mass distributions. + allocate(massDistributionSphericalAccretionFlow :: massDistribution_) + select type(massDistribution_) + type is (massDistributionSphericalAccretionFlow) + ! Get the virialized mass distribution. + massDistributionVirialized_ => self%darkMatterProfileDMO_%get(node) + select type (massDistributionVirialized_) + class is (massDistributionSpherical) + ! Find the radius enclosing 200 times the mean density. + densityMean = self %cosmologyFunctions_ %matterDensityEpochal ( time ) + radius200Mean = massDistributionVirialized_ %radiusEnclosingDensity(+200.0d0*densityMean) + ! Compute the transition radius following Diemer & Kravtsov (2014; equation 6). + peakHeight =+self%criticalOverdensity_ %value (time=time,mass=mass) & + & /self%cosmologicalMassVariance_%rootVariance(time=time,mass=mass) + radiusTransition=+( & + & +1.90d0 & + & -0.18d0 & + & *peakHeight & + & ) & + & *radius200Mean + ! Create the accretion flow mass distribution. + allocate(massDistributionCorrelationFunction :: massDistributionAccretionFlow_) + select type(massDistributionAccretionFlow_) + type is (massDistributionCorrelationFunction) + ! Extract basic quantities for the halo. + basic => node %basic() + time = basic%time () + mass = basic%mass () + ! Build a correlation function. + radiusMinimum=radius200Mean/factorRadiusMinimum + radiusMaximum=radius200Mean*factorRadiusMaximum + countRadii =int(log10(radiusMaximum/radiusMinimum)*countRadiiPerDecade)+1 + allocate(radius (countRadii)) + allocate(correlationFunction (countRadii)) + allocate(correlationFunctionVolumeAveraged(countRadii)) + radius=Make_Range(radiusMinimum,radiusMaximum,countRadii,rangeTypeLogarithmic) + do i=1,countRadii + correlationFunction (i)=+self%correlationFunctionTwoPoint_%correlation ( radius(i),time) & + & *self%darkMatterHaloBias_ %bias (node,radius(i) ) + correlationFunctionVolumeAveraged(i)=+self%correlationFunctionTwoPoint_%correlationVolumeAveraged( radius(i),time) & + & *self%darkMatterHaloBias_ %bias (node,radius(i) ) + end do + !![ + + + massDistributionCorrelationFunction( & + & mass =mass , & + & time =time , & + & radius =radius , & + & correlationFunction=correlationFunction , & + & cosmologyFunctions_=self%cosmologyFunctions_, & + & componentType =componentTypeDarkHalo , & + & massType =massTypeDark & + & ) + + + !!] + !![ + + + massDistributionSphericalAccretionFlow( & + & radiusTransition =radiusTransition , & + & nonAnalyticSolver =nonAnalyticSolversNumerical , & + & massDistribution_ =massDistributionVirialized_ , & + & massDistributionAccretionFlow_=massDistributionAccretionFlow_, & + & componentType =componentTypeDarkHalo , & + & massType =massTypeDark & + & ) + + + !!] + end select + end select + end select + allocate(kinematicsDistribution_) + !![ + + + kinematicsDistributionLam2013( & + & massVirial = mass , & + & radiusVirial =self%darkMatterHaloScale_%radiusVirial (node=node ), & + & time = time , & + & overdensityCritical =self%criticalOverdensity_%value (time=time,mass=mass), & + & rateLinearGrowth =self%linearGrowth_ %logarithmicDerivativeExpansionFactor(time=time ), & + & scaleFactorVelocity =self%scaleFactorVelocity , & + & radius = radius , & + & correlationFunctionVolumeAveraged= correlationFunctionVolumeAveraged , & + & cosmologyFunctions_ =self%cosmologyFunctions_ & + & ) + + + !!] + call massDistribution_%setKinematicsDistribution(kinematicsDistribution_) + !![ + + + + !!] + return + end function accretionFlowCorrelationFunctionGet diff --git a/source/dark_matter_profiles_DMO.finite_resolution.F90 b/source/dark_matter_profiles_DMO.finite_resolution.F90 index 0c609fbff1..6c0132c449 100644 --- a/source/dark_matter_profiles_DMO.finite_resolution.F90 +++ b/source/dark_matter_profiles_DMO.finite_resolution.F90 @@ -22,23 +22,15 @@ simulations for example). !!} - use :: Cosmology_Functions , only : cosmologyFunctionsClass - use :: Dark_Matter_Profiles_Generic, only : enumerationNonAnalyticSolversType, enumerationNonAnalyticSolversEncode, enumerationNonAnalyticSolversIsValid, nonAnalyticSolversFallThrough + use :: Cosmology_Functions, only : cosmologyFunctionsClass + use :: Mass_Distributions , only : enumerationNonAnalyticSolversType !![ - - A dark matter profile DMO class which applies a finite resolution to some other DMO class, typically to mimic the effects - of finite resolution in an N-body simulation. Specifically, the density profile is given by - \begin{equation} - \rho(r) = \rho^\prime(r) \left( 1 + \left[ \frac{\Delta x}{r} \right]^2 \right)^{-1/2}, - \end{equation} - where $\Delta x$ is the larger of the resolution length, {\normalfont \ttfamily [lengthResolution]}, and the radius in the - original profile enclosing the mass resolution, {\normalfont \ttfamily [massResolution]}. - - Note that this choice was constructed to give a constant density core in an NFW density profile. For a density profile, $\rho^\prime(r)$, which - rises more steeply than $r^{-1}$ as $r \rightarrow 0$ we will still have a cuspy density profile under this model. - + + A dark matter profile DMO class which builds \refClass{} objects to mimic the effects of finite resolution in an N-body + simulation. + !!] type, extends(darkMatterProfileDMOClass) :: darkMatterProfileDMOFiniteResolution @@ -46,14 +38,13 @@ A dark matter halo profile class implementing finiteResolution dark matter halos. !!} private - class (darkMatterProfileDMOClass ), pointer :: darkMatterProfileDMO_ => null() - class (cosmologyFunctionsClass ), pointer :: cosmologyFunctions_ => null() + class (darkMatterProfileDMOClass ), pointer :: darkMatterProfileDMO_ => null() + class (cosmologyFunctionsClass ), pointer :: cosmologyFunctions_ => null() type (enumerationNonAnalyticSolversType) :: nonAnalyticSolver - integer (kind=kind_int8 ) :: lastUniqueID logical :: resolutionIsComoving - double precision :: lengthResolution , massResolution , & - & lengthResolutionPrevious , enclosedMassPrevious, & - & enclosedMassRadiusPrevious + double precision :: lengthResolution , massResolution, & + & lengthResolutionPrevious + integer (kind_int8 ) :: lastUniqueID contains !![ @@ -61,30 +52,14 @@ !!] - final :: finiteResolutionDestructor - procedure :: autoHook => finiteResolutionAutoHook - procedure :: calculationReset => finiteResolutionCalculationReset - procedure :: density => finiteResolutionDensity - procedure :: densityLogSlope => finiteResolutionDensityLogSlope - procedure :: radiusEnclosingDensity => finiteResolutionRadiusEnclosingDensity - procedure :: radiusEnclosingMass => finiteResolutionRadiusEnclosingMass - procedure :: radialMoment => finiteResolutionRadialMoment - procedure :: enclosedMass => finiteResolutionEnclosedMass - procedure :: potential => finiteResolutionPotential - procedure :: circularVelocity => finiteResolutionCircularVelocity - procedure :: radiusCircularVelocityMaximum => finiteResolutionRadiusCircularVelocityMaximum - procedure :: circularVelocityMaximum => finiteResolutionCircularVelocityMaximum - procedure :: radialVelocityDispersion => finiteResolutionRadialVelocityDispersion - procedure :: radiusFromSpecificAngularMomentum => finiteResolutionRadiusFromSpecificAngularMomentum - procedure :: rotationNormalization => finiteResolutionRotationNormalization - procedure :: energy => finiteResolutionEnergy - procedure :: kSpace => finiteResolutionKSpace - procedure :: freefallRadius => finiteResolutionFreefallRadius - procedure :: freefallRadiusIncreaseRate => finiteResolutionFreefallRadiusIncreaseRate - procedure :: lengthResolutionPhysical => finiteResolutionLengthResolutionPhysical + final :: finiteResolutionDestructor + procedure :: autoHook => finiteResolutionAutoHook + procedure :: calculationReset => finiteResolutionCalculationReset + procedure :: get => finiteResolutionGet + procedure :: lengthResolutionPhysical => finiteResolutionLengthResolutionPhysical end type darkMatterProfileDMOFiniteResolution - interface darkMatterProfileDMOFiniteResolution + interface darkMatterProfileDMOFiniteResolution !!{ Constructors for the {\normalfont \ttfamily finiteResolution} dark matter halo profile class. !!} @@ -100,12 +75,12 @@ function finiteResolutionConstructorParameters(parameters) result(self) !!{ Default constructor for the {\normalfont \ttfamily finiteResolution} dark matter halo profile class. !!} - use :: Input_Parameters, only : inputParameter, inputParameters + use :: Input_Parameters , only : inputParameter , inputParameters + use :: Mass_Distributions, only : enumerationNonAnalyticSolversEncode implicit none type (darkMatterProfileDMOFiniteResolution) :: self type (inputParameters ), intent(inout) :: parameters class (darkMatterProfileDMOClass ), pointer :: darkMatterProfileDMO_ - class (darkMatterHaloScaleClass ), pointer :: darkMatterHaloScale_ class (cosmologyFunctionsClass ), pointer :: cosmologyFunctions_ double precision :: lengthResolution , massResolution type (varying_string ) :: nonAnalyticSolver @@ -120,7 +95,7 @@ function finiteResolutionConstructorParameters(parameters) result(self) massResolution parameters - The resolution mass, $\Delta M$. + The resolution mass, $\Delta M$. resolutionIsComoving @@ -134,20 +109,18 @@ function finiteResolutionConstructorParameters(parameters) result(self) Selects how solutions are computed when no analytic solution is available. If set to ``{\normalfont \ttfamily fallThrough}'' then the solution ignoring heating is used, while if set to ``{\normalfont \ttfamily numerical}'' then numerical solvers are used to find solutions. - !!] - self=darkMatterProfileDMOFiniteResolution(lengthResolution,massResolution,resolutionIsComoving,enumerationNonAnalyticSolversEncode(char(nonAnalyticSolver),includesPrefix=.false.),darkMatterProfileDMO_,darkMatterHaloScale_,cosmologyFunctions_) + self=darkMatterProfileDMOFiniteResolution(lengthResolution,massResolution,resolutionIsComoving,enumerationNonAnalyticSolversEncode(char(nonAnalyticSolver),includesPrefix=.false.),darkMatterProfileDMO_,cosmologyFunctions_) !![ - !!] return end function finiteResolutionConstructorParameters - function finiteResolutionConstructorInternal(lengthResolution,massResolution,resolutionIsComoving,nonAnalyticSolver,darkMatterProfileDMO_,darkMatterHaloScale_,cosmologyFunctions_) result(self) + function finiteResolutionConstructorInternal(lengthResolution,massResolution,resolutionIsComoving,nonAnalyticSolver,darkMatterProfileDMO_,cosmologyFunctions_) result(self) !!{ Generic constructor for the {\normalfont \ttfamily finiteResolution} dark matter profile class. !!} @@ -155,21 +128,15 @@ function finiteResolutionConstructorInternal(lengthResolution,massResolution,res implicit none type (darkMatterProfileDMOFiniteResolution) :: self class (darkMatterProfileDMOClass ), intent(in ), target :: darkMatterProfileDMO_ - class (darkMatterHaloScaleClass ), intent(in ), target :: darkMatterHaloScale_ class (cosmologyFunctionsClass ), intent(in ), target :: cosmologyFunctions_ double precision , intent(in ) :: lengthResolution , massResolution type (enumerationNonAnalyticSolversType ), intent(in ) :: nonAnalyticSolver logical , intent(in ) :: resolutionIsComoving !![ - + !!] - if (.not.enumerationNonAnalyticSolversIsValid(nonAnalyticSolver)) call Error_Report('invalid non-analytic solver type'//{introspection:location}) - self%lastUniqueID =-1_kind_int8 - self%genericLastUniqueID =-1_kind_int8 - self%lengthResolutionPrevious =-huge(0.0d0) - self%enclosedMassPrevious =-huge(0.0d0) - self%enclosedMassRadiusPrevious=-huge(0.0d0) + self%lastUniqueID=-huge(1_kind_int8) return end function finiteResolutionConstructorInternal @@ -180,23 +147,24 @@ subroutine finiteResolutionAutoHook(self) use :: Events_Hooks, only : calculationResetEvent, openMPThreadBindingAllLevels implicit none class(darkMatterProfileDMOFiniteResolution), intent(inout) :: self - + call calculationResetEvent%attach(self,finiteResolutionCalculationReset,openMPThreadBindingAllLevels,label='darkMatterProfileDMOFiniteResolution') return end subroutine finiteResolutionAutoHook - + subroutine finiteResolutionDestructor(self) !!{ Destructor for the {\normalfont \ttfamily finiteResolution} dark matter halo profile class. !!} + use :: Events_Hooks, only : calculationResetEvent implicit none type(darkMatterProfileDMOFiniteResolution), intent(inout) :: self !![ - !!] + if (calculationResetEvent%isAttached(self,finiteResolutionCalculationReset)) call calculationResetEvent%detach(self,finiteResolutionCalculationReset) return end subroutine finiteResolutionDestructor @@ -211,344 +179,75 @@ subroutine finiteResolutionCalculationReset(self,node,uniqueID) integer(kind_int8 ), intent(in ) :: uniqueID !$GLC attributes unused :: node - self%lastUniqueID =uniqueID - self%genericLastUniqueID =uniqueID - self%lengthResolutionPrevious =-huge(0.0d0) - self%enclosedMassPrevious =-huge(0.0d0) - self%enclosedMassRadiusPrevious =-huge(0.0d0) - self%genericEnclosedMassRadiusMinimum =+huge(0.0d0) - self%genericEnclosedMassRadiusMaximum =-huge(0.0d0) - self%genericVelocityDispersionRadialRadiusMinimum=+huge(0.0d0) - self%genericVelocityDispersionRadialRadiusMaximum=-huge(0.0d0) - if (allocated(self%genericVelocityDispersionRadialVelocity)) deallocate(self%genericVelocityDispersionRadialVelocity) - if (allocated(self%genericVelocityDispersionRadialRadius )) deallocate(self%genericVelocityDispersionRadialRadius ) - if (allocated(self%genericEnclosedMassMass )) deallocate(self%genericEnclosedMassMass ) - if (allocated(self%genericEnclosedMassRadius )) deallocate(self%genericEnclosedMassRadius ) + self%lastUniqueID =uniqueID + self%lengthResolutionPrevious=-huge(0.0d0) return end subroutine finiteResolutionCalculationReset - double precision function finiteResolutionDensity(self,node,radius) - !!{ - Returns the density (in $M_\odot$ Mpc$^{-3}$) in the dark matter profile of {\normalfont \ttfamily node} at the given - {\normalfont \ttfamily radius} (given in units of Mpc). - !!} - use :: Numerical_Constants_Math, only : Pi - implicit none - class (darkMatterProfileDMOFiniteResolution), intent(inout) :: self - type (treeNode ), intent(inout) :: node - double precision , intent(in ) :: radius - double precision :: lengthResolution - - lengthResolution =+self %lengthResolutionPhysical(node ) - finiteResolutionDensity=+self%darkMatterProfileDMO_%density (node,radius) & - & /sqrt( & - & +1.0d0 & - & +( & - & +lengthResolution & - & /radius & - & )**2 & - & ) - return - end function finiteResolutionDensity - - double precision function finiteResolutionDensityLogSlope(self,node,radius) - !!{ - Returns the logarithmic slope of the density in the dark matter profile of {\normalfont \ttfamily node} at the given - {\normalfont \ttfamily radius} (given in units of Mpc). - !!} - implicit none - class (darkMatterProfileDMOFiniteResolution), intent(inout) :: self - type (treeNode ), intent(inout) :: node - double precision , intent(in ) :: radius - double precision :: lengthResolution - - lengthResolution =+self %lengthResolutionPhysical(node ) - finiteResolutionDensityLogSlope=+self%darkMatterProfileDMO_%densityLogSlope (node,radius) & - & +( & - & + lengthResolution & - & / radius & - & ) **2 & - & /( & - & +1.0d0 & - & +( & - & +lengthResolution & - & /radius & - & )**2 & - & ) - return - end function finiteResolutionDensityLogSlope - - double precision function finiteResolutionRadiusEnclosingDensity(self,node,density) - !!{ - Returns the radius (in Mpc) in the dark matter profile of {\normalfont \ttfamily node} which encloses the given - {\normalfont \ttfamily density} (given in units of $M_\odot/$Mpc$^{-3}$). - !!} - implicit none - class (darkMatterProfileDMOFiniteResolution), intent(inout), target :: self - type (treeNode ), intent(inout), target :: node - double precision , intent(in ) :: density - - if (self%nonAnalyticSolver == nonAnalyticSolversFallThrough) then - finiteResolutionRadiusEnclosingDensity=self%darkMatterProfileDMO_%radiusEnclosingDensity (node,density) - else - finiteResolutionRadiusEnclosingDensity=self %radiusEnclosingDensityNumerical(node,density) - end if - return - end function finiteResolutionRadiusEnclosingDensity - - double precision function finiteResolutionRadiusEnclosingMass(self,node,mass) + function finiteResolutionGet(self,node,weightBy,weightIndex) result(massDistribution_) !!{ - Returns the radius (in Mpc) in the dark matter profile of {\normalfont \ttfamily node} which encloses the given - {\normalfont \ttfamily mass} (given in units of $M_\odot$). - !!} - implicit none - class (darkMatterProfileDMOFiniteResolution), intent(inout), target :: self - type (treeNode ), intent(inout), target :: node - double precision , intent(in ) :: mass - - if (self%nonAnalyticSolver == nonAnalyticSolversFallThrough) then - finiteResolutionRadiusEnclosingMass=self%darkMatterProfileDMO_%radiusEnclosingMass (node,mass) - else - finiteResolutionRadiusEnclosingMass=self %radiusEnclosingMassNumerical(node,mass) - end if - return - end function finiteResolutionRadiusEnclosingMass - - double precision function finiteResolutionRadialMoment(self,node,moment,radiusMinimum,radiusMaximum) - !!{ - Returns the radial moment of the density profile. + Return the dark matter mass distribution for the given {\normalfont \ttfamily node}. !!} + use :: Galactic_Structure_Options, only : componentTypeDarkHalo , massTypeDark , weightByMass + use :: Mass_Distributions , only : massDistributionSphericalFiniteResolution, kinematicsDistributionCollisionless, massDistributionSpherical implicit none + class (massDistributionClass ), pointer :: massDistribution_ + type (kinematicsDistributionCollisionless ), pointer :: kinematicsDistribution_ class (darkMatterProfileDMOFiniteResolution), intent(inout) :: self type (treeNode ), intent(inout) :: node - double precision , intent(in ) :: moment - double precision , intent(in ), optional :: radiusMinimum, radiusMaximum - - if (self%nonAnalyticSolver == nonAnalyticSolversFallThrough) then - finiteResolutionRadialMoment=self%darkMatterProfileDMO_%radialMoment (node,moment,radiusMinimum,radiusMaximum) - else - finiteResolutionRadialMoment=self %radialMomentNumerical(node,moment,radiusMinimum,radiusMaximum) - end if - return - end function finiteResolutionRadialMoment - - double precision function finiteResolutionEnclosedMass(self,node,radius) - !!{ - Returns the enclosed mass (in $M_\odot$) in the dark matter profile of {\normalfont \ttfamily node} at the given {\normalfont \ttfamily radius} (given in - units of Mpc). - !!} - implicit none - class (darkMatterProfileDMOFiniteResolution), intent(inout) :: self - type (treeNode ), intent(inout) :: node - double precision , intent(in ) :: radius - - if (node%uniqueID() /= self%lastUniqueID ) call self%calculationReset(node,node%uniqueID()) - if ( radius /= self%enclosedMassRadiusPrevious) then - self%enclosedMassRadiusPrevious=radius - if (self%nonAnalyticSolver == nonAnalyticSolversFallThrough .or. radius > radiusLengthResolutionRatioMaximum*self%lengthResolutionPhysical(node)) then - self%enclosedMassPrevious=self%darkMatterProfileDMO_%enclosedMass (node,radius) - else - self%enclosedMassPrevious=self %enclosedMassNumerical(node,radius) - end if - end if - finiteResolutionEnclosedMass=self%enclosedMassPrevious - return - end function finiteResolutionEnclosedMass - - double precision function finiteResolutionPotential(self,node,radius,status) - !!{ - Returns the potential (in (km/s)$^2$) in the dark matter profile of {\normalfont \ttfamily node} at the given {\normalfont - \ttfamily radius} (given in units of Mpc). - !!} - implicit none - class (darkMatterProfileDMOFiniteResolution), intent(inout) :: self - type (treeNode ), intent(inout), target :: node - double precision , intent(in ) :: radius - type (enumerationStructureErrorCodeType ), intent( out), optional :: status - - if (self%nonAnalyticSolver == nonAnalyticSolversFallThrough .or. radius > radiusLengthResolutionRatioMaximum*self%lengthResolutionPhysical(node)) then - finiteResolutionPotential=self%darkMatterProfileDMO_%potential (node,radius,status) - else - finiteResolutionPotential=self %potentialNumerical(node,radius,status) - end if - return - end function finiteResolutionPotential - - double precision function finiteResolutionCircularVelocity(self,node,radius) - !!{ - Returns the circular velocity (in km/s) in the dark matter profile of {\normalfont \ttfamily node} at the given - {\normalfont \ttfamily radius} (given in units of Mpc). - !!} - use :: Numerical_Constants_Astronomical, only : gravitationalConstantGalacticus - implicit none - class (darkMatterProfileDMOFiniteResolution), intent(inout) :: self - type (treeNode ), intent(inout) :: node - double precision , intent(in ) :: radius - - if (radius > 0.0d0) then - finiteResolutionCircularVelocity=sqrt( & - & +gravitationalConstantGalacticus & - & *self%enclosedMass(node,radius) & - & / radius & - & ) - else - finiteResolutionCircularVelocity=0.0d0 - end if - return - end function finiteResolutionCircularVelocity - - double precision function finiteResolutionRadiusCircularVelocityMaximum(self,node) - !!{ - Returns the radius (in Mpc) at which the maximum circular velocity is achieved in the dark matter profile of {\normalfont \ttfamily node}. - !!} - implicit none - class(darkMatterProfileDMOFiniteResolution), intent(inout) :: self - type (treeNode ), intent(inout) :: node - - if (self%nonAnalyticSolver == nonAnalyticSolversFallThrough) then - finiteResolutionRadiusCircularVelocityMaximum=self%darkMatterProfileDMO_%radiusCircularVelocityMaximum (node) - else - finiteResolutionRadiusCircularVelocityMaximum=self %radiusCircularVelocityMaximumNumerical(node) - end if - return - end function finiteResolutionRadiusCircularVelocityMaximum - - double precision function finiteResolutionCircularVelocityMaximum(self,node) - !!{ - Returns the maximum circular velocity (in km/s) in the dark matter profile of {\normalfont \ttfamily node}. - !!} - implicit none - class(darkMatterProfileDMOFiniteResolution), intent(inout) :: self - type (treeNode ), intent(inout) :: node - - if (self%nonAnalyticSolver == nonAnalyticSolversFallThrough) then - finiteResolutionCircularVelocityMaximum=self%darkMatterProfileDMO_%circularVelocityMaximum (node) - else - finiteResolutionCircularVelocityMaximum=self %circularVelocityMaximumNumerical(node) - end if - return - end function finiteResolutionCircularVelocityMaximum - - double precision function finiteResolutionRadialVelocityDispersion(self,node,radius) - !!{ - Returns the radial velocity dispersion (in km/s) in the dark matter profile of {\normalfont \ttfamily node} at the given - {\normalfont \ttfamily radius} (given in units of Mpc). - !!} - implicit none - class (darkMatterProfileDMOFiniteResolution), intent(inout) :: self - type (treeNode ), intent(inout) :: node - double precision , intent(in ) :: radius - - if (self%nonAnalyticSolver == nonAnalyticSolversFallThrough .or. radius > radiusLengthResolutionRatioMaximum*self%lengthResolutionPhysical(node)) then - finiteResolutionRadialVelocityDispersion=self%darkMatterProfileDMO_%radialVelocityDispersion(node,radius) - else - finiteResolutionRadialVelocityDispersion=self %radialVelocityDispersionNumerical(node,radius) - end if - return - end function finiteResolutionRadialVelocityDispersion - - double precision function finiteResolutionRadiusFromSpecificAngularMomentum(self,node,specificAngularMomentum) - !!{ - Returns the radius (in Mpc) in {\normalfont \ttfamily node} at which a circular orbit has the given {\normalfont \ttfamily specificAngularMomentum} (given - in units of km s$^{-1}$ Mpc). - !!} - implicit none - class (darkMatterProfileDMOFiniteResolution), intent(inout) :: self - type (treeNode ), intent(inout) :: node - double precision , intent(in ) :: specificAngularMomentum - - if (self%nonAnalyticSolver == nonAnalyticSolversFallThrough) then - finiteResolutionRadiusFromSpecificAngularMomentum=self%darkMatterProfileDMO_%radiusFromSpecificAngularMomentum (node,specificAngularMomentum) - else - finiteResolutionRadiusFromSpecificAngularMomentum=self %radiusFromSpecificAngularMomentumNumerical(node,specificAngularMomentum) - end if - return - end function finiteResolutionRadiusFromSpecificAngularMomentum - - double precision function finiteResolutionRotationNormalization(self,node) - !!{ - Return the normalization of the rotation velocity vs. specific angular momentum relation. - !!} - implicit none - class (darkMatterProfileDMOFiniteResolution), intent(inout) :: self - type (treeNode ), intent(inout) :: node - double precision :: radiusVirial - - radiusVirial =+self%darkMatterHaloScale_%radiusVirial(node ) - finiteResolutionRotationNormalization=+self %radialMoment(node,moment=2.0d0,radiusMinimum=0.0d0,radiusMaximum=radiusVirial) & - & /self %radialMoment(node,moment=3.0d0,radiusMinimum=0.0d0,radiusMaximum=radiusVirial) - return - end function finiteResolutionRotationNormalization - - double precision function finiteResolutionEnergy(self,node) - !!{ - Return the energy of a finiteResolution halo density profile. - !!} - implicit none - class(darkMatterProfileDMOFiniteResolution), intent(inout) :: self - type (treeNode ), intent(inout) :: node - - if (self%nonAnalyticSolver == nonAnalyticSolversFallThrough) then - finiteResolutionEnergy=self%darkMatterProfileDMO_%energy (node) - else - finiteResolutionEnergy=self %energyNumerical(node) - end if - return - end function finiteResolutionEnergy - - double precision function finiteResolutionKSpace(self,node,waveNumber) - !!{ - Returns the Fourier transform of the finiteResolution density profile at the specified {\normalfont \ttfamily waveNumber} - (given in Mpc$^{-1}$), using the expression given in \citeauthor{cooray_halo_2002}~(\citeyear{cooray_halo_2002}; eqn.~81). - !!} - implicit none - class (darkMatterProfileDMOFiniteResolution), intent(inout) :: self - type (treeNode ), intent(inout), target :: node - double precision , intent(in ) :: waveNumber - - if (self%nonAnalyticSolver == nonAnalyticSolversFallThrough) then - finiteResolutionKSpace=self%darkMatterProfileDMO_%kSpace (node,waveNumber) - else - finiteResolutionKSpace=self %kSpaceNumerical(node,waveNumber) - end if - return - end function finiteResolutionKSpace - - double precision function finiteResolutionFreefallRadius(self,node,time) - !!{ - Returns the freefall radius in the finiteResolution density profile at the specified {\normalfont \ttfamily time} (given in - Gyr). - !!} - implicit none - class (darkMatterProfileDMOFiniteResolution), intent(inout), target :: self - type (treeNode ), intent(inout), target :: node - double precision , intent(in ) :: time - - if (self%nonAnalyticSolver == nonAnalyticSolversFallThrough) then - finiteResolutionFreefallRadius=self%darkMatterProfileDMO_%freefallRadius (node,time) - else - finiteResolutionFreefallRadius=self %freefallRadiusNumerical(node,time) - end if - return - end function finiteResolutionFreefallRadius - - double precision function finiteResolutionFreefallRadiusIncreaseRate(self,node,time) - !!{ - Returns the rate of increase of the freefall radius in the finiteResolution density profile at the specified {\normalfont - \ttfamily time} (given in Gyr). - !!} - implicit none - class (darkMatterProfileDMOFiniteResolution), intent(inout), target :: self - type (treeNode ), intent(inout), target :: node - double precision , intent(in ) :: time + type (enumerationWeightByType ), intent(in ), optional :: weightBy + integer , intent(in ), optional :: weightIndex + class (massDistributionClass ), pointer :: massDistributionDecorated + !![ + + !!] - if (self%nonAnalyticSolver == nonAnalyticSolversFallThrough) then - finiteResolutionFreefallRadiusIncreaseRate=self%darkMatterProfileDMO_%freefallRadiusIncreaseRate (node,time) - else - finiteResolutionFreefallRadiusIncreaseRate=self %freefallRadiusIncreaseRateNumerical(node,time) - end if + ! Assume a null distribution by default. + massDistribution_ => null() + ! If weighting is not by mass, return a null profile. + if (weightBy_ /= weightByMass) return + ! Create the mass distribution. + allocate(massDistributionSphericalFiniteResolution :: massDistribution_) + select type(massDistribution_) + type is (massDistributionSphericalFiniteResolution) + massDistributionDecorated => self%darkMatterProfileDMO_%get(node,weightBy,weightIndex) + select type (massDistributionDecorated) + class is (massDistributionSpherical) + !![ + + + massDistributionSphericalFiniteResolution( & + & lengthResolution =self%lengthResolutionPhysical (node), & + & nonAnalyticSolver=self%nonAnalyticSolver , & + & massDistribution_= massDistributionDecorated , & + & componentType = componentTypeDarkHalo , & + & massType = massTypeDark & + & ) + + + !!] + class default + call Error_Report('expected a spherical mass distribution'//{introspection:location}) + end select + !![ + + !!] + end select + allocate(kinematicsDistribution_) + !![ + + + kinematicsDistributionCollisionless( & + & ) + + + !!] + call massDistribution_%setKinematicsDistribution(kinematicsDistribution_) + !![ + + !!] return - end function finiteResolutionFreefallRadiusIncreaseRate + end function finiteResolutionGet double precision function finiteResolutionLengthResolutionPhysical(self,node) !!{ @@ -559,6 +258,7 @@ double precision function finiteResolutionLengthResolutionPhysical(self,node) class(darkMatterProfileDMOFiniteResolution), intent(inout) :: self type (treeNode ), intent(inout) :: node class(nodeComponentBasic ), pointer :: basic + class(massDistributionClass ), pointer :: massDistribution_ if (node%uniqueID() /= self%lastUniqueID) call self%calculationReset(node,node%uniqueID()) if (self%lengthResolutionPrevious < 0.0d0) then @@ -568,11 +268,16 @@ double precision function finiteResolutionLengthResolutionPhysical(self,node) self%lengthResolutionPrevious = +self%lengthResolutionPrevious & & *self%cosmologyFunctions_%expansionFactor(basic%time ()) end if - if (self%massResolution > 0.0d0) & - & self%lengthResolutionPrevious=max( & - & self %lengthResolutionPrevious , & - & self%darkMatterProfileDMO_%radiusEnclosingMass (node,self%massResolution) & - & ) + if (self%massResolution > 0.0d0) then + massDistribution_ => self%darkMatterProfileDMO_%get(node) + self%lengthResolutionPrevious = max( & + & self %lengthResolutionPrevious , & + & massDistribution_%radiusEnclosingMass (self%massResolution) & + & ) + !![ + + !!] + end if end if finiteResolutionLengthResolutionPhysical=self%lengthResolutionPrevious return diff --git a/source/dark_matter_profiles_DMO.finite_resolution.NFW.F90 b/source/dark_matter_profiles_DMO.finite_resolution.NFW.F90 index edcc145e6c..f85ea136a6 100644 --- a/source/dark_matter_profiles_DMO.finite_resolution.NFW.F90 +++ b/source/dark_matter_profiles_DMO.finite_resolution.NFW.F90 @@ -22,20 +22,13 @@ simulations for example). !!} - use :: Cosmology_Functions , only : cosmologyFunctionsClass - use :: Dark_Matter_Profiles_Generic, only : enumerationNonAnalyticSolversType, enumerationNonAnalyticSolversEncode, enumerationNonAnalyticSolversIsValid, nonAnalyticSolversFallThrough - use :: Numerical_Interpolation , only : interpolator - + use :: Dark_Matter_Halo_Scales, only : darkMatterHaloScaleClass + !![ - A dark matter profile DMO class which applies a finite resolution to an NFW density profile, typically to mimic the effects - of finite resolution in an N-body simulation. Specifically, the density profile is given by - \begin{equation} - \rho(r) = \rho_\mathrm{NFW}(r) \left( 1 + \left[ \frac{\Delta x}{r} \right]^2 \right)^{-1/2}, - \end{equation} - where $\Delta x$ is the larger of the resolution length, {\normalfont \ttfamily [lengthResolution]}, and the radius in the - original profile enclosing the mass resolution, {\normalfont \ttfamily [massResolution]}. + A dark matter profile DMO class which builds \refClass{massDistributionSphericalFiniteResolutionNFW} objects to mimic a finite + resolution to an NFW density profile. @@ -45,91 +38,9 @@ A dark matter halo profile class implementing finiteResolutionNFW dark matter halos. !!} private - double precision :: potentialRadiusPrevious , potentialPrevious , & - & velocityDispersionRadialRadiusPrevious , velocityDispersionRadialPrevious , & - & massNormalizationPrevious , lengthResolutionScaleFreePrevious , & - & lengthResolutionScaleFreePreviousSquared , lengthResolutionScaleFreePreviousCubed , & - & lengthResolutionScaleFreePreviousSqrtTerm , lengthResolutionScaleFreePreviousSqrt2Term , & - & lengthResolutionScaleFreePreviousSqrtCubedTerm , lengthResolutionScaleFreePreviousLowerTerm , & - & lengthResolutionScaleFreePreviousOnePlusTerm , lengthResolutionScaleFreePreviousOnePlus2Term , & - & densityRadiusPrevious , densityPrevious , & - & densityNormalizationPrevious , radiusEnclosingDensityDensityPrevious , & - & radiusEnclosingDensityPrevious , radiusEnclosingMassMassPrevious , & - & radiusEnclosingMassPrevious , energyPrevious - ! Velocity dispersion tabulation. - logical :: velocityDispersionRadialTableInitialized - integer :: velocityDispersionRadialTableRadiusCoreCount , velocityDispersionRadialTableRadiusCount - double precision , allocatable, dimension(: ) :: velocityDispersionRadialTableRadiusCore , velocityDispersionRadialTableRadius - double precision , allocatable, dimension(:,:) :: velocityDispersionRadialTable - type (interpolator), allocatable :: velocityDispersionRadialTableRadiusCoreInterpolator, velocityDispersionRadialTableRadiusInterpolator - double precision :: velocityDispersionRadialRadiusMinimum , velocityDispersionRadialRadiusMaximum , & - & velocityDispersionRadialRadiusCoreMinimum , velocityDispersionRadialRadiusCoreMaximum - ! Radius-enclosing-density tabulation. - logical :: radiusEnclosingDensityTableInitialized - integer :: radiusEnclosingDensityTableRadiusCoreCount , radiusEnclosingDensityTableDensityCount - double precision , allocatable, dimension(: ) :: radiusEnclosingDensityTableRadiusCore , radiusEnclosingDensityTableDensity - double precision , allocatable, dimension(:,:) :: radiusEnclosingDensityTable - type (interpolator), allocatable :: radiusEnclosingDensityTableRadiusCoreInterpolator , radiusEnclosingDensityTableDensityInterpolator - double precision :: radiusEnclosingDensityDensityMinimum , radiusEnclosingDensityDensityMaximum , & - & radiusEnclosingDensityRadiusCoreMinimum , radiusEnclosingDensityRadiusCoreMaximum - ! Radius-enclosing-mass tabulation. - logical :: radiusEnclosingMassTableInitialized - integer :: radiusEnclosingMassTableRadiusCoreCount , radiusEnclosingMassTableMassCount - double precision , allocatable, dimension(: ) :: radiusEnclosingMassTableRadiusCore , radiusEnclosingMassTableMass - double precision , allocatable, dimension(:,:) :: radiusEnclosingMassTable - type (interpolator), allocatable :: radiusEnclosingMassTableRadiusCoreInterpolator , radiusEnclosingMassTableMassInterpolator - double precision :: radiusEnclosingMassMassMinimum , radiusEnclosingMassMassMaximum , & - & radiusEnclosingMassRadiusCoreMinimum , radiusEnclosingMassRadiusCoreMaximum - ! Energy tabulation. - logical :: energyTableInitialized - integer :: energyTableRadiusCoreCount , energyTableConcentrationCount - double precision , allocatable, dimension(: ) :: energyTableRadiusCore , energyTableConcentration - double precision , allocatable, dimension(:,:) :: energyTable - type (interpolator), allocatable :: energyTableRadiusCoreInterpolator , energyTableConcentrationInterpolator - double precision :: energyConcentrationMinimum , energyConcentrationMaximum , & - & energyRadiusCoreMinimum , energyRadiusCoreMaximum - contains - !![ - - - - - - - - - - - - - - - - - !!] - procedure :: autoHook => finiteResolutionNFWAutoHook - procedure :: calculationReset => finiteResolutionNFWCalculationReset - procedure :: density => finiteResolutionNFWDensity - procedure :: enclosedMass => finiteResolutionNFWEnclosedMass - procedure :: potential => finiteResolutionNFWPotential - procedure :: radiusEnclosingDensity => finiteResolutionNFWRadiusEnclosingDensity - procedure :: radiusEnclosingMass => finiteResolutionNFWRadiusEnclosingMass - procedure :: energy => finiteResolutionNFWEnergy - procedure :: radialVelocityDispersion => finiteResolutionNFWRadialVelocityDispersion - procedure :: velocityDispersionRadialTabulate => finiteResolutionNFWVelocityDispersionRadialTabulate - procedure :: radiusEnclosingDensityTabulate => finiteResolutionNFWRadiusEnclosingDensityTabulate - procedure :: radiusEnclosingMassTabulate => finiteResolutionNFWRadiusEnclosingMassTabulate - procedure :: energyTabulate => finiteResolutionNFWEnergyTabulate - procedure :: densityScaleFree => finiteResolutionNFWDensityScaleFree - procedure :: massEnclosedScaleFree => finiteResolutionNFWMassEnclosedScaleFree - procedure :: storeVelocityDispersionTable => finiteResolutionNFWStoreVelocityDispersionTable - procedure :: restoreVelocityDispersionTable => finiteResolutionNFWRestoreVelocityDispersionTable - procedure :: storeDensityTable => finiteResolutionNFWStoreDensityTable - procedure :: restoreDensityTable => finiteResolutionNFWRestoreDensityTable - procedure :: storeMassTable => finiteResolutionNFWStoreMassTable - procedure :: restoreMassTable => finiteResolutionNFWRestoreMassTable - procedure :: storeEnergyTable => finiteResolutionNFWStoreEnergyTable - procedure :: restoreEnergyTable => finiteResolutionNFWRestoreEnergyTable + class(darkMatterHaloScaleClass), pointer :: darkMatterHaloScale_ => null() + contains + procedure :: get => finiteResolutionNFWGet end type darkMatterProfileDMOFiniteResolutionNFW interface darkMatterProfileDMOFiniteResolutionNFW @@ -140,28 +51,14 @@ module procedure finiteResolutionNFWConstructorInternal end interface darkMatterProfileDMOFiniteResolutionNFW - ! Tabulation resolution parameters. - integer, parameter :: velocityDispersionRadialTableRadiusPointsPerDecade =100 - integer, parameter :: velocityDispersionRadialTableRadiusCorePointsPerDecade=100 - integer, parameter :: radiusEnclosingDensityTableDensityPointsPerDecade =100 - integer, parameter :: radiusEnclosingDensityTableRadiusCorePointsPerDecade =100 - integer, parameter :: radiusEnclosingMassTableMassPointsPerDecade =100 - integer, parameter :: radiusEnclosingMassTableRadiusCorePointsPerDecade =100 - integer, parameter :: energyTableConcentrationPointsPerDecade =100 - integer, parameter :: energyTableRadiusCorePointsPerDecade =100 - - ! Sub-module-scope variables used in integrations. - class (darkMatterProfileDMOFiniteResolutionNFW), pointer :: self_ - integer :: iRadiusCore_, iDensity_, iMass_ - !$omp threadprivate(self_,iRadiusCore_,iDensity_,iMass_) - contains function finiteResolutionNFWConstructorParameters(parameters) result(self) !!{ Default constructor for the {\normalfont \ttfamily finiteResolutionNFW} dark matter halo profile class. !!} - use :: Input_Parameters, only : inputParameter, inputParameters + use :: Mass_Distributions, only : enumerationNonAnalyticSolversEncode + use :: Input_Parameters , only : inputParameter, inputParameters implicit none type (darkMatterProfileDMOFiniteResolutionNFW) :: self type (inputParameters ), intent(inout) :: parameters @@ -209,7 +106,7 @@ function finiteResolutionNFWConstructorInternal(lengthResolution,massResolution, !!{ Generic constructor for the {\normalfont \ttfamily finiteResolutionNFW} dark matter profile class. !!} - use :: Error, only : Error_Report + use :: Mass_Distributions, only : enumerationNonAnalyticSolversEncode implicit none type (darkMatterProfileDMOFiniteResolutionNFW) :: self class (darkMatterHaloScaleClass ), intent(in ), target :: darkMatterHaloScale_ @@ -220,1540 +117,81 @@ function finiteResolutionNFWConstructorInternal(lengthResolution,massResolution, !![ !!] - - if (.not.enumerationNonAnalyticSolversIsValid(nonAnalyticSolver)) call Error_Report('invalid non-analytic solver type'//{introspection:location}) - self%lastUniqueID =-1_kind_int8 - self%genericLastUniqueID =-1_kind_int8 - self%lengthResolutionPrevious =-huge(0.0d0) - self%massNormalizationPrevious =-huge(0.0d0) - self%enclosedMassPrevious =-huge(0.0d0) - self%enclosedMassRadiusPrevious =-huge(0.0d0) - self%potentialPrevious =-huge(0.0d0) - self%potentialRadiusPrevious =-huge(0.0d0) - self%velocityDispersionRadialPrevious =-huge(0.0d0) - self%velocityDispersionRadialRadiusPrevious =-huge(0.0d0) - self%lengthResolutionScaleFreePrevious =-huge(0.0d0) - self%lengthResolutionScaleFreePreviousSquared =-huge(0.0d0) - self%lengthResolutionScaleFreePreviousCubed =-huge(0.0d0) - self%lengthResolutionScaleFreePreviousOnePlusTerm =-huge(0.0d0) - self%lengthResolutionScaleFreePreviousOnePlus2Term =-huge(0.0d0) - self%lengthResolutionScaleFreePreviousSqrtTerm =-huge(0.0d0) - self%lengthResolutionScaleFreePreviousSqrt2Term =-huge(0.0d0) - self%lengthResolutionScaleFreePreviousSqrtCubedTerm=-huge(0.0d0) - self%lengthResolutionScaleFreePreviousLowerTerm =-huge(0.0d0) - self%densityRadiusPrevious =-huge(0.0d0) - self%densityPrevious =-huge(0.0d0) - self%densityNormalizationPrevious =-huge(0.0d0) - self%radiusEnclosingDensityDensityPrevious =-huge(0.0d0) - self%radiusEnclosingDensityPrevious =-huge(0.0d0) - self%radiusEnclosingMassMassPrevious =-huge(0.0d0) - self%radiusEnclosingMassPrevious =-huge(0.0d0) - self%energyPrevious =+huge(0.0d0) - ! Velocity dispersion table initialization. - self%velocityDispersionRadialRadiusMinimum =+huge(0.0d0) - self%velocityDispersionRadialRadiusMaximum =-huge(0.0d0) - self%velocityDispersionRadialRadiusCoreMinimum =+huge(0.0d0) - self%velocityDispersionRadialRadiusCoreMaximum =-huge(0.0d0) - self%velocityDispersionRadialTableInitialized =.false. - ! Radius enclosing density table initialization. - self%radiusEnclosingDensityDensityMinimum =+huge(0.0d0) - self%radiusEnclosingDensityDensityMaximum =-huge(0.0d0) - self%radiusEnclosingDensityRadiusCoreMinimum =+huge(0.0d0) - self%radiusEnclosingDensityRadiusCoreMaximum =-huge(0.0d0) - self%radiusEnclosingDensityTableInitialized =.false. - ! Radius enclosing mass table initialization. - self%radiusEnclosingMassMassMinimum =+huge(0.0d0) - self%radiusEnclosingMassMassMaximum =-huge(0.0d0) - self%radiusEnclosingMassRadiusCoreMinimum =+huge(0.0d0) - self%radiusEnclosingMassRadiusCoreMaximum =-huge(0.0d0) - self%radiusEnclosingMassTableInitialized =.false. - ! Energy table initialization. - self%energyConcentrationMinimum =+huge(0.0d0) - self%energyConcentrationMaximum =-huge(0.0d0) - self%energyRadiusCoreMinimum =+huge(0.0d0) - self%energyRadiusCoreMaximum =-huge(0.0d0) - self%energyTableInitialized =.false. + allocate(darkMatterProfileDMONFW :: self%darkMatterProfileDMO_) select type (darkMatterProfileDMO_ => self%darkMatterProfileDMO_) type is (darkMatterProfileDMONFW) !![ - - darkMatterProfileDMONFW( & - & velocityDispersionUseSeriesExpansion=.false. , & - & darkMatterHaloScale_ =self%darkMatterHaloScale_ & - & ) - + + darkMatterProfileDMONFW( & + & velocityDispersionUseSeriesExpansion=.false. , & + & darkMatterHaloScale_ =self%darkMatterHaloScale_ & + & ) + !!] - end select + end select return end function finiteResolutionNFWConstructorInternal - subroutine finiteResolutionNFWAutoHook(self) - !!{ - Attach to the calculation reset event. - !!} - use :: Events_Hooks, only : calculationResetEvent, openMPThreadBindingAllLevels - implicit none - class(darkMatterProfileDMOFiniteResolutionNFW), intent(inout) :: self - - call calculationResetEvent%attach(self,finiteResolutionNFWCalculationReset,openMPThreadBindingAllLevels,label='darkMatterProfileDMOFiniteResolutionNFW') - return - end subroutine finiteResolutionNFWAutoHook - - subroutine finiteResolutionNFWCalculationReset(self,node,uniqueID) - !!{ - Reset the dark matter profile calculation. - !!} - use :: Kind_Numbers, only : kind_int8 - implicit none - class (darkMatterProfileDMOFiniteResolutionNFW), intent(inout) :: self - type (treeNode ), intent(inout) :: node - integer(kind_int8 ), intent(in ) :: uniqueID - !$GLC attributes unused :: node - - call self%darkMatterProfileDMOFiniteResolution%calculationReset(node,uniqueID) - self%potentialPrevious =-huge(0.0d0) - self%potentialRadiusPrevious =-huge(0.0d0) - self%velocityDispersionRadialPrevious =-huge(0.0d0) - self%velocityDispersionRadialRadiusPrevious=-huge(0.0d0) - self%massNormalizationPrevious =-huge(0.0d0) - self%densityRadiusPrevious =-huge(0.0d0) - self%densityPrevious =-huge(0.0d0) - self%densityNormalizationPrevious =-huge(0.0d0) - self%radiusEnclosingDensityDensityPrevious =-huge(0.0d0) - self%radiusEnclosingDensityPrevious =-huge(0.0d0) - self%radiusEnclosingMassMassPrevious =-huge(0.0d0) - self%radiusEnclosingMassPrevious =-huge(0.0d0) - self%energyPrevious =+huge(0.0d0) - return - end subroutine finiteResolutionNFWCalculationReset - - double precision function finiteResolutionNFWDensity(self,node,radius) - !!{ - Returns the density (in $M_\odot/\mathrm{MPc}^3$) in the dark matter profile of {\normalfont \ttfamily node} at the given {\normalfont \ttfamily radius} (given in - units of Mpc). - !!} - use :: Galacticus_Nodes , only : nodeComponentBasic, nodeComponentDarkMatterProfile - use :: Numerical_Constants_Math, only : Pi - implicit none - class (darkMatterProfileDMOFiniteResolutionNFW), intent(inout) :: self - type (treeNode ), intent(inout) :: node - double precision , intent(in ) :: radius - class (nodeComponentBasic ), pointer :: basic - class (nodeComponentDarkMatterProfile ), pointer :: darkMatterProfile - double precision :: concentration , radiusScaleFree, & - & lengthResolutionScaleFree - - if (node%uniqueID() /= self%lastUniqueID ) call self%calculationReset(node,node%uniqueID()) - if ( radius /= self%densityRadiusPrevious) then - darkMatterProfile => node%darkMatterProfile ( ) - radiusScaleFree = radius /darkMatterProfile%scale() - lengthResolutionScaleFree = self%lengthResolutionPhysical(node)/darkMatterProfile%scale() - self%densityRadiusPrevious = radius - if (self%densityNormalizationPrevious < 0.0d0) then - basic => node %basic ( ) - concentration = self %darkMatterHaloScale_%radiusVirial(node)/darkMatterProfile%scale() - self%densityNormalizationPrevious = +basic %mass ( ) & - & /darkMatterProfile %scale ( )**3 & - & /( & - & - concentration & - & / (1.0d0+concentration) & - & +log(1.0d0+concentration) & - & ) & - & /4.0d0 & - & /Pi - end if - self%densityPrevious=+self%densityNormalizationPrevious & - & *self%densityScaleFree (radiusScaleFree,lengthResolutionScaleFree) - end if - finiteResolutionNFWDensity=self%densityPrevious - return - end function finiteResolutionNFWDensity - - double precision function finiteResolutionNFWDensityScaleFree(self,radius,radiusCore) - !!{ - Returns the scale-free density in the dark matter profile at the given {\normalfont \ttfamily radius}. - !!} - implicit none - class (darkMatterProfileDMOFiniteResolutionNFW), intent(inout) :: self - double precision , intent(in ) :: radius, radiusCore - - finiteResolutionNFWDensityScaleFree=1.0d0/(1.0d0+radius)**2/sqrt(radius**2+radiusCore**2) - return - end function finiteResolutionNFWDensityScaleFree - - double precision function finiteResolutionNFWEnclosedMass(self,node,radius) - !!{ - Returns the enclosed mass (in $M_\odot$) in the dark matter profile of {\normalfont \ttfamily node} at the given {\normalfont \ttfamily radius} (given in - units of Mpc). The analytic solution (computed using Mathematica) is - \begin{equation} - M(x) = M \frac{-\frac{\sqrt{x^2+X^2}}{(1+x) \left(1+X^2\right)}+\tanh ^{-1}\left(\frac{x}{\sqrt{x^2+X^2}}\right)+\frac{\left(1+2X^2\right) \tanh ^{-1}\left(\frac{X^2-x}{\sqrt{1+X^2} \sqrt{x^2+X^2}}\right)}{\left(1+X^2\right)^{3/2}} -\frac{\left(1 + 2 X^2\right) \tanh ^{-1}\left(\sqrt{\frac{X^2}{1 + X^2}}\right)}{\left(1+ X^2\right)^{3/2}}+\frac{\sqrt{X^2}}{1 + X^2}}{\log (1+c)-\frac{c}{1+c}}, - \end{equation} - where $x=r/r_\mathrm{s}$, $X = \Delta x/r_\mathrm{s}$, and $r_\mathrm{s}$ is the NFW scale length. - !!} - use :: Galacticus_Nodes, only : nodeComponentBasic, nodeComponentDarkMatterProfile - implicit none - class (darkMatterProfileDMOFiniteResolutionNFW), intent(inout) :: self - type (treeNode ), intent(inout) :: node - double precision , intent(in ) :: radius - class (nodeComponentBasic ), pointer :: basic - class (nodeComponentDarkMatterProfile ), pointer :: darkMatterProfile - double precision :: concentration , radiusScaleFree, & - & lengthResolutionScaleFree - - if (node%uniqueID() /= self%lastUniqueID ) call self%calculationReset(node,node%uniqueID()) - if ( radius /= self%enclosedMassRadiusPrevious) then - darkMatterProfile => node%darkMatterProfile ( ) - radiusScaleFree = radius /darkMatterProfile%scale() - lengthResolutionScaleFree = self%lengthResolutionPhysical(node)/darkMatterProfile%scale() - self%enclosedMassRadiusPrevious = radius - if (self%massNormalizationPrevious < 0.0d0) then - basic => node %basic ( ) - concentration = self %darkMatterHaloScale_%radiusVirial(node)/darkMatterProfile%scale() - self%massNormalizationPrevious = +basic %mass ( ) & - & /( & - & - concentration & - & / (1.0d0+concentration) & - & +log(1.0d0+concentration) & - & ) - end if - self%enclosedMassPrevious=+self%massNormalizationPrevious & - & *self%massEnclosedScaleFree (radiusScaleFree,lengthResolutionScaleFree) - end if - finiteResolutionNFWEnclosedMass=self%enclosedMassPrevious - return - end function finiteResolutionNFWEnclosedMass - - double precision function finiteResolutionNFWRadiusEnclosingDensity(self,node,density) - !!{ - Returns the radius (in Mpc) in the dark matter profile of {\normalfont \ttfamily node} which encloses the given - {\normalfont \ttfamily density} (given in units of $M_\odot/$Mpc$^{-3}$). - !!} - use :: Numerical_Constants_Math, only : Pi - use :: Galacticus_Nodes , only : nodeComponentBasic, nodeComponentDarkMatterProfile - implicit none - class (darkMatterProfileDMOFiniteResolutionNFW), intent(inout), target :: self - type (treeNode ), intent(inout), target :: node - double precision , intent(in ) :: density - class (nodeComponentBasic ), pointer :: basic - class (nodeComponentDarkMatterProfile ), pointer :: darkMatterProfile - double precision , parameter :: epsilonDensity =1.0d-3 - double precision :: concentration , densityScaleFree , & - & lengthResolutionScaleFree , densityScaleFreeMaximum - integer (c_size_t ), dimension(0:1) :: jRadiusCore - double precision , dimension(0:1) :: hRadiusCore - integer :: iRadiusCore - - if (node%uniqueID() /= self%lastUniqueID ) call self%calculationReset(node,node%uniqueID()) - if ( density /= self%radiusEnclosingDensityDensityPrevious) then - basic => node%basic ( ) - darkMatterProfile => node%darkMatterProfile ( ) - concentration = self%darkMatterHaloScale_%radiusVirial (node)/darkMatterProfile%scale() - lengthResolutionScaleFree = self %lengthResolutionPhysical(node)/darkMatterProfile%scale() - self%radiusEnclosingDensityDensityPrevious = density - if (self%massNormalizationPrevious < 0.0d0) then - basic => node %basic ( ) - concentration = self %darkMatterHaloScale_%radiusVirial(node)/darkMatterProfile%scale() - self%massNormalizationPrevious = +basic %mass ( ) & - & /( & - & - concentration & - & / (1.0d0+concentration) & - & +log(1.0d0+concentration) & - & ) - end if - ! Find scale free density, and the maximum such density reached in the profile. - densityScaleFree =+ density & - & /self %massNormalizationPrevious & - & *darkMatterProfile%scale ()**3 - densityScaleFreeMaximum=+1.0d0 & - & /4.0d0 & - & /Pi & - & /lengthResolutionScaleFree - if (densityScaleFree >= densityScaleFreeMaximum) then - ! Maximum density is exceeded - return zero radius. - self%radiusEnclosingDensityPrevious=0.0d0 - else if (densityScaleFree >= densityScaleFreeMaximum*(1.0d0-epsilonDensity)) then - ! For densities close to the maximum density, use a series solution. - self%radiusEnclosingDensityPrevious=+0.5d0 & - & *( & - & +1.0d0 & - & -densityScaleFree & - & /densityScaleFreeMaximum & - & ) & - & *darkMatterProfile%scale() - else - ! Use a tabulated solution in other regimes. - ! Ensure table is sufficiently extensive. - call self%radiusEnclosingDensityTabulate(densityScaleFree,lengthResolutionScaleFree) - ! Interpolate to get the scale free radius enclosing the scale free density. - call self%radiusEnclosingDensityTableRadiusCoreInterpolator%linearFactors(lengthResolutionScaleFree,jRadiusCore(0),hRadiusCore) - jRadiusCore(1)=jRadiusCore(0)+1 - self%radiusEnclosingDensityPrevious=0.0d0 - do iRadiusCore=0,1 - self%radiusEnclosingDensityPrevious=+self%radiusEnclosingDensityPrevious & - & +self%radiusEnclosingDensityTableDensityInterpolator%interpolate(densityScaleFree,self%radiusEnclosingDensityTable(:,jRadiusCore(iRadiusCore))) & - & * hRadiusCore(iRadiusCore) - end do - self%radiusEnclosingDensityPrevious=+self %radiusEnclosingDensityPrevious & - & *darkMatterProfile%scale () - end if - end if - finiteResolutionNFWRadiusEnclosingDensity=self%radiusEnclosingDensityPrevious - return - end function finiteResolutionNFWRadiusEnclosingDensity - - subroutine finiteResolutionNFWRadiusEnclosingDensityTabulate(self,density,radiusCore) - !!{ - Tabulates the radius enclosing a given density for finite resolution NFW density profiles. - !!} - use :: Numerical_Constants_Math, only : Pi - use :: Numerical_Ranges , only : Make_Range , rangeTypeLogarithmic - use :: Root_Finder , only : rangeExpandMultiplicative, rangeExpandSignExpectNegative, rangeExpandSignExpectPositive, rootFinder - implicit none - class (darkMatterProfileDMOFiniteResolutionNFW), intent(inout), target :: self - double precision , intent(in ) :: density , radiusCore - double precision , parameter :: toleranceAbsolute=0.0d0, toleranceRelative=1.0d-9 - logical :: retabulate - integer :: iRadiusCore , iDensity , & - & i - type (rootFinder ) :: finder - - do i=1,2 - retabulate=.false. - if (.not.self%radiusEnclosingDensityTableInitialized) then - retabulate=.true. - else if ( & - & density < self%radiusEnclosingDensityDensityMinimum & - & .or. & - & density > self%radiusEnclosingDensityDensityMaximum & - & .or. & - & radiusCore < self%radiusEnclosingDensityRadiusCoreMinimum & - & .or. & - & radiusCore > self%radiusEnclosingDensityRadiusCoreMaximum & - & ) then - retabulate=.true. - end if - if (retabulate .and.i==1) call self%restoreDensityTable() - if (.not.retabulate ) exit - end do - if (retabulate) then - ! Decide how many points to tabulate and allocate table arrays. - self%radiusEnclosingDensityDensityMinimum =min(0.5d0*density ,self%radiusEnclosingDensityDensityMinimum ) - self%radiusEnclosingDensityDensityMaximum =max(2.0d0*density ,self%radiusEnclosingDensityDensityMaximum ) - self%radiusEnclosingDensityRadiusCoreMinimum =min(0.5d0*radiusCore,self%radiusEnclosingDensityRadiusCoreMinimum) - self%radiusEnclosingDensityRadiusCoreMaximum =max(2.0d0*radiusCore,self%radiusEnclosingDensityRadiusCoreMaximum) - self%radiusEnclosingDensityTableDensityCount =int(log10(self%radiusEnclosingDensityDensityMaximum /self%radiusEnclosingDensityDensityMinimum )*dble(radiusEnclosingDensityTableDensityPointsPerDecade ))+1 - self%radiusEnclosingDensityTableRadiusCoreCount=int(log10(self%radiusEnclosingDensityRadiusCoreMaximum/self%radiusEnclosingDensityRadiusCoreMinimum)*dble(radiusEnclosingDensityTableRadiusCorePointsPerDecade))+1 - if (allocated(self%radiusEnclosingDensityTableDensity)) then - deallocate(self%radiusEnclosingDensityTableRadiusCore) - deallocate(self%radiusEnclosingDensityTableDensity ) - deallocate(self%radiusEnclosingDensityTable ) - end if - allocate(self%radiusEnclosingDensityTableRadiusCore( self%radiusEnclosingDensityTableRadiusCoreCount)) - allocate(self%radiusEnclosingDensityTableDensity (self%radiusEnclosingDensityTableDensityCount )) - allocate(self%radiusEnclosingDensityTable (self%radiusEnclosingDensityTabledensityCount,self%radiusEnclosingDensityTableRadiusCoreCount)) - ! Create a range of radii and core radii. - self%radiusEnclosingDensityTableDensity =Make_Range(self%radiusEnclosingDensityDensityMinimum ,self%radiusEnclosingDensityDensityMaximum ,self%radiusEnclosingDensityTableDensityCount ,rangeType=rangeTypeLogarithmic) - self%radiusEnclosingDensityTableRadiusCore=Make_Range(self%radiusEnclosingDensityRadiusCoreMinimum,self%radiusEnclosingDensityRadiusCoreMaximum,self%radiusEnclosingDensityTableRadiusCoreCount,rangeType=rangeTypeLogarithmic) - ! Initialize our root finder. - finder=rootFinder( & - & rootFunction =rootDensity , & - & toleranceAbsolute =toleranceAbsolute , & - & toleranceRelative =toleranceRelative , & - & rangeExpandDownward =0.5d0 , & - & rangeExpandUpward =2.0d0 , & - & rangeExpandType =rangeExpandMultiplicative , & - & rangeExpandUpwardSignExpect =rangeExpandSignExpectNegative, & - & rangeExpandDownwardSignExpect=rangeExpandSignExpectPositive & - & ) - ! Loop over density and core radius and populate tables. - self_ => self - do iRadiusCore=1,self%radiusEnclosingDensityTableRadiusCoreCount - iRadiusCore_=iRadiusCore - do iDensity=1,self%radiusEnclosingDensityTableDensityCount - iDensity_=iDensity - if (self%radiusEnclosingDensityTableDensity(iDensity) > 1.0d0/self%radiusEnclosingDensityTableRadiusCore(iRadiusCore)/4.0d0/Pi) then - ! Density exceeds the maximum density in the profile - so set zero radius. - self%radiusEnclosingDensityTable(iDensity,iRadiusCore)=0.0d0 - else - self%radiusEnclosingDensityTable(iDensity,iRadiusCore)=finder%find(rootGuess=1.0d0) - end if - end do - end do - ! Build interpolators. - if (allocated(self%radiusEnclosingDensityTableRadiusCoreInterpolator)) deallocate(self%radiusEnclosingDensityTableRadiusCoreInterpolator) - if (allocated(self%radiusEnclosingDensityTableDensityInterpolator )) deallocate(self%radiusEnclosingDensityTableDensityInterpolator ) - allocate(self%radiusEnclosingDensityTableRadiusCoreInterpolator) - allocate(self%radiusEnclosingDensityTableDensityInterpolator ) - self%radiusEnclosingDensityTableRadiusCoreInterpolator=interpolator(self%radiusEnclosingDensityTableRadiusCore) - self%radiusEnclosingDensityTableDensityInterpolator =interpolator(self%radiusEnclosingDensityTableDensity ) - ! Specify that tabulation has been made. - self%radiusEnclosingDensityTableInitialized=.true. - call self%storeDensityTable() - end if - return - end subroutine finiteResolutionNFWRadiusEnclosingDensityTabulate - - double precision function rootDensity(radius) - !!{ - Root function used in finding the radius enclosing a given mean density. - !!} - use :: Numerical_Constants_Math, only : Pi - implicit none - double precision, intent(in ) :: radius - - rootDensity=+3.0d0 & - & *self_%massEnclosedScaleFree (radius,self_%radiusEnclosingDensityTableRadiusCore(iRadiusCore_)) & - & /4.0d0 & - & /Pi & - & / radius **3 & - & -self_%radiusEnclosingDensityTableDensity( iDensity_ ) - return - end function rootDensity - - double precision function finiteResolutionNFWRadiusEnclosingMass(self,node,mass) - !!{ - Returns the radius (in Mpc) in the dark matter profile of {\normalfont \ttfamily node} which encloses the given - {\normalfont \ttfamily mass} (given in units of $M_\odot/$Mpc$^{-3}$). - !!} - use :: Numerical_Constants_Math, only : Pi - use :: Galacticus_Nodes , only : nodeComponentBasic, nodeComponentDarkMatterProfile - implicit none - class (darkMatterProfileDMOFiniteResolutionNFW), intent(inout), target :: self - type (treeNode ), intent(inout), target :: node - double precision , intent(in ) :: mass - class (nodeComponentBasic ), pointer :: basic - class (nodeComponentDarkMatterProfile ), pointer :: darkMatterProfile - double precision :: concentration , massScaleFree, & - & lengthResolutionScaleFree - integer (c_size_t ), dimension(0:1) :: jRadiusCore - double precision , dimension(0:1) :: hRadiusCore - integer :: iRadiusCore - - if (node%uniqueID() /= self%lastUniqueID ) call self%calculationReset(node,node%uniqueID()) - if ( mass /= self%radiusEnclosingMassMassPrevious) then - basic => node%basic ( ) - darkMatterProfile => node%darkMatterProfile ( ) - concentration = self%darkMatterHaloScale_%radiusVirial (node)/darkMatterProfile%scale() - lengthResolutionScaleFree = self %lengthResolutionPhysical(node)/darkMatterProfile%scale() - self%radiusEnclosingMassMassPrevious = mass - if (self%massNormalizationPrevious < 0.0d0) then - basic => node %basic ( ) - concentration = self %darkMatterHaloScale_%radiusVirial(node)/darkMatterProfile%scale() - self%massNormalizationPrevious = +basic %mass ( ) & - & /( & - & - concentration & - & / (1.0d0+concentration) & - & +log(1.0d0+concentration) & - & ) - end if - ! Find scale free mass, and the maximum such mass reached in the profile. - massScaleFree=+ mass & - & /self%massNormalizationPrevious - ! Ensure table is sufficiently extensive. - call self%radiusEnclosingMassTabulate(massScaleFree,lengthResolutionScaleFree) - ! Interpolate to get the scale free radius enclosing the scale free mass. - call self%radiusEnclosingMassTableRadiusCoreInterpolator%linearFactors(lengthResolutionScaleFree,jRadiusCore(0),hRadiusCore) - jRadiusCore(1)=jRadiusCore(0)+1 - self%radiusEnclosingMassPrevious=0.0d0 - do iRadiusCore=0,1 - self%radiusEnclosingMassPrevious=+self%radiusEnclosingMassPrevious & - & +self%radiusEnclosingMassTableMassInterpolator%interpolate(massScaleFree,self%radiusEnclosingMassTable(:,jRadiusCore(iRadiusCore))) & - & * hRadiusCore(iRadiusCore) - end do - self%radiusEnclosingMassPrevious=+self %radiusEnclosingMassPrevious & - & *darkMatterProfile%scale () - end if - finiteResolutionNFWRadiusEnclosingMass=self%radiusEnclosingMassPrevious - return - end function finiteResolutionNFWRadiusEnclosingMass - - subroutine finiteResolutionNFWRadiusEnclosingMassTabulate(self,mass,radiusCore) - !!{ - Tabulates the radius enclosing a given mass for finite resolution NFW mass profiles. - !!} - use :: Numerical_Constants_Math, only : Pi - use :: Numerical_Ranges , only : Make_Range , rangeTypeLogarithmic - use :: Root_Finder , only : rangeExpandMultiplicative, rangeExpandSignExpectNegative, rangeExpandSignExpectPositive, rootFinder - implicit none - class (darkMatterProfileDMOFiniteResolutionNFW), intent(inout), target :: self - double precision , intent(in ) :: mass , radiusCore - double precision , parameter :: toleranceAbsolute=0.0d0, toleranceRelative=1.0d-9 - logical :: retabulate - integer :: iRadiusCore , iMass , & - & i - type (rootFinder ) :: finder - - do i=1,2 - retabulate=.false. - if (.not.self%radiusEnclosingMassTableInitialized) then - retabulate=.true. - else if ( & - & mass < self%radiusEnclosingMassMassMinimum & - & .or. & - & mass > self%radiusEnclosingMassMassMaximum & - & .or. & - & radiusCore < self%radiusEnclosingMassRadiusCoreMinimum & - & .or. & - & radiusCore > self%radiusEnclosingMassRadiusCoreMaximum & - & ) then - retabulate=.true. - end if - if (retabulate .and.i==1) call self%restoreMassTable() - if (.not.retabulate ) exit - end do - if (retabulate) then - ! Decide how many points to tabulate and allocate table arrays. - self%radiusEnclosingMassMassMinimum =min(0.5d0*mass ,self%radiusEnclosingMassMassMinimum ) - self%radiusEnclosingMassMassMaximum =max(2.0d0*mass ,self%radiusEnclosingMassMassMaximum ) - self%radiusEnclosingMassRadiusCoreMinimum =min(0.5d0*radiusCore,self%radiusEnclosingMassRadiusCoreMinimum) - self%radiusEnclosingMassRadiusCoreMaximum =max(2.0d0*radiusCore,self%radiusEnclosingMassRadiusCoreMaximum) - self%radiusEnclosingMassTableMassCount =int(log10(self%radiusEnclosingMassMassMaximum /self%radiusEnclosingMassMassMinimum )*dble(radiusEnclosingMassTableMassPointsPerDecade ))+1 - self%radiusEnclosingMassTableRadiusCoreCount=int(log10(self%radiusEnclosingMassRadiusCoreMaximum/self%radiusEnclosingMassRadiusCoreMinimum)*dble(radiusEnclosingMassTableRadiusCorePointsPerDecade))+1 - if (allocated(self%radiusEnclosingMassTableMass)) then - deallocate(self%radiusEnclosingMassTableRadiusCore) - deallocate(self%radiusEnclosingMassTableMass ) - deallocate(self%radiusEnclosingMassTable ) - end if - allocate(self%radiusEnclosingMassTableRadiusCore( self%radiusEnclosingMassTableRadiusCoreCount)) - allocate(self%radiusEnclosingMassTableMass (self%radiusEnclosingMassTableMassCount )) - allocate(self%radiusEnclosingMassTable (self%radiusEnclosingMassTablemassCount,self%radiusEnclosingMassTableRadiusCoreCount)) - ! Create a range of radii and core radii. - self%radiusEnclosingMassTableMass =Make_Range(self%radiusEnclosingMassMassMinimum ,self%radiusEnclosingMassMassMaximum ,self%radiusEnclosingMassTableMassCount ,rangeType=rangeTypeLogarithmic) - self%radiusEnclosingMassTableRadiusCore=Make_Range(self%radiusEnclosingMassRadiusCoreMinimum,self%radiusEnclosingMassRadiusCoreMaximum,self%radiusEnclosingMassTableRadiusCoreCount,rangeType=rangeTypeLogarithmic) - ! Initialize our root finder. - finder=rootFinder( & - & rootFunction =rootMass , & - & toleranceAbsolute =toleranceAbsolute , & - & toleranceRelative =toleranceRelative , & - & rangeExpandDownward =0.5d0 , & - & rangeExpandUpward =2.0d0 , & - & rangeExpandType =rangeExpandMultiplicative , & - & rangeExpandUpwardSignExpect =rangeExpandSignExpectPositive, & - & rangeExpandDownwardSignExpect=rangeExpandSignExpectNegative & - & ) - ! Loop over mass and core radius and populate tables. - self_ => self - do iRadiusCore=1,self%radiusEnclosingMassTableRadiusCoreCount - iRadiusCore_=iRadiusCore - do iMass=1,self%radiusEnclosingMassTableMassCount - iMass_=iMass - ! Check that the root condition is satisfied at infinitely large radius. If it is not, then no radius encloses the - ! required mass. Simply set the radius to an infinitely large value in such case. - if (rootMass(radius=huge(0.0d0)) < 0.0d0) then - self%radiusEnclosingMassTable(iMass,iRadiusCore)=huge(0.0d0) - else - self%radiusEnclosingMassTable(iMass,iRadiusCore)=finder%find(rootGuess=1.0d0) - end if - end do - end do - ! Build interpolators. - if (allocated(self%radiusEnclosingMassTableRadiusCoreInterpolator)) deallocate(self%radiusEnclosingMassTableRadiusCoreInterpolator) - if (allocated(self%radiusEnclosingMassTableMassInterpolator )) deallocate(self%radiusEnclosingMassTableMassInterpolator ) - allocate(self%radiusEnclosingMassTableRadiusCoreInterpolator) - allocate(self%radiusEnclosingMassTableMassInterpolator ) - self%radiusEnclosingMassTableRadiusCoreInterpolator=interpolator(self%radiusEnclosingMassTableRadiusCore) - self%radiusEnclosingMassTableMassInterpolator =interpolator(self%radiusEnclosingMassTableMass ) - ! Specify that tabulation has been made. - self%radiusEnclosingMassTableInitialized=.true. - call self%storeMassTable() - end if - return - end subroutine finiteResolutionNFWRadiusEnclosingMassTabulate - - double precision function rootMass(radius) - !!{ - Root function used in finding the radius enclosing a given mean mass. - !!} - implicit none - double precision, intent(in ) :: radius - - rootMass=+self_%massEnclosedScaleFree (radius,self_%radiusEnclosingMassTableRadiusCore(iRadiusCore_)) & - & -self_%radiusEnclosingMassTableMass( iMass_ ) - return - end function rootMass - - double precision function finiteResolutionNFWEnergy(self,node) - !!{ - Returns the energy (in $M_\odot$ km$^2$/s$^2$) in the dark matter profile of {\normalfont \ttfamily node}. - !!} - use :: Numerical_Constants_Astronomical, only : gravitationalConstantGalacticus - use :: Galacticus_Nodes , only : nodeComponentBasic , nodeComponentDarkMatterProfile - implicit none - class (darkMatterProfileDMOFiniteResolutionNFW), intent(inout) :: self - type (treeNode ), intent(inout) :: node - class (nodeComponentBasic ), pointer :: basic - class (nodeComponentDarkMatterProfile ), pointer :: darkMatterProfile - double precision :: concentration , lengthResolutionScaleFree - integer (c_size_t ), dimension(0:1) :: jRadiusCore - double precision , dimension(0:1) :: hRadiusCore - integer :: iRadiusCore - - if (node%uniqueID() /= self%lastUniqueID) call self%calculationReset(node,node%uniqueID()) - if (self%energyPrevious > 0.0d0) then - basic => node%basic ( ) - darkMatterProfile => node%darkMatterProfile ( ) - concentration = self%darkMatterHaloScale_%radiusVirial (node)/darkMatterProfile%scale() - lengthResolutionScaleFree = self %lengthResolutionPhysical(node)/darkMatterProfile%scale() - if (self%massNormalizationPrevious < 0.0d0) then - basic => node %basic ( ) - concentration = self %darkMatterHaloScale_%radiusVirial(node)/darkMatterProfile%scale() - self%massNormalizationPrevious = +basic %mass ( ) & - & /( & - & - concentration & - & / (1.0d0+concentration) & - & +log(1.0d0+concentration) & - & ) - end if - ! Ensure table is sufficiently extensive. - call self%energyTabulate(concentration,lengthResolutionScaleFree) - ! Interpolate to get the scale free energy. - call self%energyTableRadiusCoreInterpolator%linearFactors(lengthResolutionScaleFree,jRadiusCore(0),hRadiusCore) - jRadiusCore(1)=jRadiusCore(0)+1 - self%energyPrevious=0.0d0 - do iRadiusCore=0,1 - self%energyPrevious=+self%energyPrevious & - & +self%energyTableConcentrationInterpolator%interpolate(concentration,self%energyTable(:,jRadiusCore(iRadiusCore))) & - & * hRadiusCore(iRadiusCore) - end do - self%energyPrevious=+self %energyPrevious & - & *gravitationalConstantGalacticus & - & *self %massNormalizationPrevious **2 & - & /darkMatterProfile%scale () - end if - finiteResolutionNFWEnergy=self%energyPrevious - return - end function finiteResolutionNFWEnergy - - subroutine finiteResolutionNFWEnergyTabulate(self,concentration,radiusCore) - !!{ - Tabulates the energy for finite resolution NFW mass profiles. - !!} - use :: Numerical_Constants_Math, only : Pi - use :: Numerical_Integration , only : integrator - use :: Numerical_Ranges , only : Make_Range, rangeTypeLogarithmic - implicit none - class (darkMatterProfileDMOFiniteResolutionNFW), intent(inout), target :: self - double precision , intent(in ) :: concentration , radiusCore - double precision , parameter :: multiplierRadius =100.0d0 - type (integrator ) :: integratorPotential , integratorKinetic , & - & integratorPressure - double precision :: pseudoPressure , energyKinetic , & - & energyPotential , concentration_ - logical :: retabulate - integer :: iRadiusCore , iConcentration , & - & i - - do i=1,2 - retabulate=.false. - if (.not.self%energyTableInitialized) then - retabulate=.true. - else if ( & - & concentration < self%energyConcentrationMinimum & - & .or. & - & concentration > self%energyConcentrationMaximum & - & .or. & - & radiusCore < self%energyRadiusCoreMinimum & - & .or. & - & radiusCore > self%energyRadiusCoreMaximum & - & ) then - retabulate=.true. - end if - if ( retabulate.and.i==1) call self%restoreEnergyTable() - if (.not.retabulate ) exit - end do - if (retabulate) then - ! Decide how many points to tabulate and allocate table arrays. - self%energyConcentrationMinimum =min(0.5d0*concentration,self%energyConcentrationMinimum) - self%energyConcentrationMaximum =max(2.0d0*concentration,self%energyConcentrationMaximum) - self%energyRadiusCoreMinimum =min(0.5d0*radiusCore ,self%energyRadiusCoreMinimum ) - self%energyRadiusCoreMaximum =max(2.0d0*radiusCore ,self%energyRadiusCoreMaximum ) - self%energyTableConcentrationCount=int(log10(self%energyConcentrationMaximum/self%energyConcentrationMinimum)*dble(energyTableConcentrationPointsPerDecade))+1 - self%energyTableRadiusCoreCount =int(log10(self%energyRadiusCoreMaximum /self%energyRadiusCoreMinimum )*dble(energyTableRadiusCorePointsPerDecade ))+1 - if (allocated(self%energyTableConcentration)) then - deallocate(self%energyTableRadiusCore ) - deallocate(self%energyTableConcentration) - deallocate(self%energyTable ) - end if - allocate(self%energyTableRadiusCore ( self%energyTableRadiusCoreCount)) - allocate(self%energyTableConcentration(self%energyTableConcentrationCount )) - allocate(self%energyTable (self%energyTableconcentrationCount,self%energyTableRadiusCoreCount)) - ! Create a range of radii and core radii. - self%energyTableConcentration=Make_Range(self%energyConcentrationMinimum,self%energyConcentrationMaximum,self%energyTableConcentrationCount,rangeType=rangeTypeLogarithmic) - self%energyTableRadiusCore =Make_Range(self%energyRadiusCoreMinimum ,self%energyRadiusCoreMaximum ,self%energyTableRadiusCoreCount ,rangeType=rangeTypeLogarithmic) - ! Initialize integrators. - integratorPotential=integrator(integrandEnergyPotential,toleranceRelative=1.0d-3) - integratorKinetic =integrator(integrandEnergyKinetic ,toleranceRelative=1.0d-3) - integratorPressure =integrator(integrandPseudoPressure ,toleranceRelative=1.0d-3) - ! Loop over concentration and core radius and populate tables. - self_ => self - do iRadiusCore=1,self%energyTableRadiusCoreCount - iRadiusCore_=iRadiusCore - do iConcentration=1,self%energyTableConcentrationCount - concentration_=self%energyTableConcentration(iConcentration) - energyPotential =+integratorPotential%integrate( 0.0d0, concentration_) - energyKinetic =+integratorKinetic %integrate( 0.0d0, concentration_)/4.0d0/Pi - pseudoPressure =+integratorPressure %integrate(concentration_,multiplierRadius*concentration_)/4.0d0/Pi - self%energyTable(iConcentration,iRadiusCore)=-0.5d0 & - & *( & - & +energyPotential & - & +self%massEnclosedScaleFree(concentration_,self%energyTableRadiusCore(iRadiusCore))**2 & - & /concentration_ & - & ) & - & +2.0d0 & - & *Pi & - & *( & - & +concentration_ **3 & - & *pseudoPressure & - & +energyKinetic & - & ) - end do - end do - ! Build interpolators. - if (allocated(self%energyTableRadiusCoreInterpolator )) deallocate(self%energyTableRadiusCoreInterpolator ) - if (allocated(self%energyTableConcentrationInterpolator)) deallocate(self%energyTableConcentrationInterpolator) - allocate(self%energyTableRadiusCoreInterpolator ) - allocate(self%energyTableConcentrationInterpolator) - self%energyTableRadiusCoreInterpolator =interpolator(self%energyTableRadiusCore ) - self%energyTableConcentrationInterpolator=interpolator(self%energyTableConcentration) - ! Specify that tabulation has been made. - self%energyTableInitialized=.true. - call self%storeEnergyTable() - end if - return - end subroutine finiteResolutionNFWEnergyTabulate - - double precision function integrandEnergyPotential(radius) - !!{ - Integrand for potential energy of the halo. - !!} - implicit none - double precision, intent(in ) :: radius - - if (radius > 0.0d0) then - integrandEnergyPotential=( & - & +self_%massEnclosedScaleFree(radius,self_%energyTableRadiusCore(iRadiusCore_)) & - & / radius & - & )**2 - else - integrandEnergyPotential=+0.0d0 - end if - return - end function integrandEnergyPotential - - double precision function integrandEnergyKinetic(radius) - !!{ - Integrand for kinetic energy of the halo. - !!} - implicit none - double precision, intent(in ) :: radius - - if (radius > 0.0d0) then - integrandEnergyKinetic=+self_%massEnclosedScaleFree(radius,self_%energyTableRadiusCore(iRadiusCore_)) & - & *self_%densityScaleFree (radius,self_%energyTableRadiusCore(iRadiusCore_)) & - & * radius - else - integrandEnergyKinetic=+0.0d0 - end if - return - end function integrandEnergyKinetic - - double precision function integrandPseudoPressure(radius) - !!{ - Integrand for pseudo-pressure ($\rho(r) \sigma^2(r)$) of the halo. - !!} - implicit none - double precision, intent(in ) :: radius - - if (radius > 0.0d0) then - integrandPseudoPressure=+self_%massEnclosedScaleFree(radius,self_%energyTableRadiusCore(iRadiusCore_)) & - & *self_%densityScaleFree (radius,self_%energyTableRadiusCore(iRadiusCore_)) & - & / radius **2 - else - integrandPseudoPressure=+0.0d0 - end if - return - end function integrandPseudoPressure - - double precision function finiteResolutionNFWMassEnclosedScaleFree(self,radiusScaleFree,lengthResolutionScaleFree) - !!{ - Returns the scale-free enclosed mass in the dark matter profile at the given {\normalfont \ttfamily radius}. - !!} - use :: Galacticus_Nodes, only : nodeComponentBasic, nodeComponentDarkMatterProfile - implicit none - class (darkMatterProfileDMOFiniteResolutionNFW), intent(inout) :: self - double precision , intent(in ) :: radiusScaleFree , lengthResolutionScaleFree - double precision , parameter :: radiusScaleFreeSmall =1.0d-3, radiusScaleFreeLarge =1.0d4, & - & radiusScaleFreeLargeATanh=1.0d+6 - double precision :: radiusScaleFreeEffective , arctanhTerm , & - & arctanhTerm1 - - if (lengthResolutionScaleFree /= self%lengthResolutionScaleFreePrevious) then - self%lengthResolutionScaleFreePrevious =lengthResolutionScaleFree - self%lengthResolutionScaleFreePreviousSquared =lengthResolutionScaleFree**2 - self%lengthResolutionScaleFreePreviousCubed =lengthResolutionScaleFree**3 - self%lengthResolutionScaleFreePreviousOnePlusTerm =+1.0d0+ self%lengthResolutionScaleFreePreviousSquared - self%lengthResolutionScaleFreePreviousOnePlus2Term =+1.0d0+2.0d0*self%lengthResolutionScaleFreePreviousSquared - self%lengthResolutionScaleFreePreviousSqrtTerm =sqrt(self%lengthResolutionScaleFreePreviousOnePlusTerm ) - self%lengthResolutionScaleFreePreviousSqrt2Term =sqrt(self%lengthResolutionScaleFreePreviousOnePlus2Term) - self%lengthResolutionScaleFreePreviousSqrtCubedTerm=self%lengthResolutionScaleFreePreviousSqrtTerm**3 - ! For large values of the argument to arctanh(), use a series solution to avoiding floating point errors. - if (lengthResolutionScaleFree > radiusScaleFreeLargeATanh) then - arctanhTerm=-log( & - & +2.0d0 & - & *lengthResolutionScaleFree & - & ) & - & /2.0d0 & - & +1.0d0 & - & /2.0d0 & - & /lengthResolutionScaleFree & - & +1.0d0 & - & /8.0d0 & - & /lengthResolutionScaleFree**2 - else - arctanhTerm=+atanh( & - & +(+1.0d0-lengthResolutionScaleFree) & - & /self%lengthResolutionScaleFreePreviousSqrtTerm & - & ) - end if - self%lengthResolutionScaleFreePreviousLowerTerm =+ lengthResolutionScaleFree & - & / self%lengthResolutionScaleFreePreviousOnePlusTerm & - & + 2.0d0 & - & * self%lengthResolutionScaleFreePreviousOnePlus2Term & - & * arctanhTerm & - & / self%lengthResolutionScaleFreePreviousSqrtCubedTerm - end if - if (radiusScaleFree < radiusScaleFreeSmall) then - ! Series expansion for small radii. - finiteResolutionNFWMassEnclosedScaleFree=+ radiusScaleFree**3 & - & *( & - & +1.0d0 /self%lengthResolutionScaleFreePrevious / 3.0d0 & - & +radiusScaleFree *( +1.0d0 /self%lengthResolutionScaleFreePrevious / 2.0d0 & - & +radiusScaleFree * ( 1.0d0+(+6.0d0*self%lengthResolutionScaleFreePreviousSquared-1.0d0)/self%lengthResolutionScaleFreePreviousCubed/10.0d0 & - & +radiusScaleFree * (1.0d0-(+4.0d0*self%lengthResolutionScaleFreePreviousSquared-1.0d0)/self%lengthResolutionScaleFreePreviousCubed/ 6.0d0 & - & ) & - & ) & - & ) & - & ) - else - ! Full analytic solution. - !! Limit the evaluation to some large radius. - radiusScaleFreeEffective=min(radiusScaleFree,radiusScaleFreeLarge) - if (radiusScaleFreeEffective > radiusScaleFreeLargeATanh*self%lengthResolutionScaleFreePrevious) then - arctanhTerm1=+log ( & - & +4.0d0 & - & * radiusScaleFreeEffective**2 & - & /self%lengthResolutionScaleFreePreviousSquared & - & ) & - & /2.0d0 & - & -self%lengthResolutionScaleFreePreviousSquared & - & /8.0d0 & - & / radiusScaleFreeEffective**2 - else - arctanhTerm1=+atanh( & - & +radiusScaleFreeEffective & - & /sqrt(+radiusScaleFreeEffective**2+self%lengthResolutionScaleFreePreviousSquared) & - & ) - end if - finiteResolutionNFWMassEnclosedScaleFree=- sqrt(+radiusScaleFreeEffective**2+self%lengthResolutionScaleFreePrevious**2) & - & /(+1.0d0+radiusScaleFreeEffective) & - & /self%lengthResolutionScaleFreePreviousOnePlusTerm & - & -2.0d0 & - & *self%lengthResolutionScaleFreePreviousOnePlus2Term & - & *atanh( & - & +( & - & +1.0d0 & - & +radiusScaleFreeEffective & - & -sqrt(+radiusScaleFreeEffective**2+self%lengthResolutionScaleFreePrevious**2) & - & ) & - & /self%lengthResolutionScaleFreePreviousSqrtTerm & - & ) & - & /self%lengthResolutionScaleFreePreviousSqrtCubedTerm & - & +arctanhTerm1 & - & +self%lengthResolutionScaleFreePreviousLowerTerm - !! Beyond the limiting radius assume logarithmic growth in mass as appropriate for an r⁻³ profile. - if (radiusScaleFree > radiusScaleFreeEffective) & - & finiteResolutionNFWMassEnclosedScaleFree=+finiteResolutionNFWMassEnclosedScaleFree & - & *log( & - & +radiusScaleFree & - & /radiusScaleFreeEffective & - & ) - end if - return - end function finiteResolutionNFWMassEnclosedScaleFree - - double precision function finiteResolutionNFWPotential(self,node,radius,status) - !!{ - Returns the potential (in (km/s)$^2$) in the dark matter profile of {\normalfont \ttfamily node} at the given {\normalfont - \ttfamily radius} (given in units of Mpc). The analytic solution (computed using Mathematica) is - \begin{eqnarray} - \Phi(x) &=& -\frac{\mathrm{G} M}{r_\mathrm{s}} \nonumber \\ - & & \left\{ +\frac{\sqrt{x^2+X^2}}{x \left(X^2+1\right)} \right. \nonumber \\ - & & -\frac{X^2 \log \left(\sqrt{X^2+1} \sqrt{x^2+X^2}-x+X^2\right)}{\left(X^2+1\right)^{3/2}} \nonumber \\ - & & -\frac{\tanh ^{-1}\left(\frac{x}{\sqrt{x^2+X^2}}\right)}{x} \nonumber \\ - & & -\frac{\left(2 X^2+1\right) \tanh ^{-1}\left(\frac{X^2-x}{\sqrt{X^2+1} \sqrt{x^2+X^2}}\right)}{x \left(X^2+1\right)^{3/2}} \nonumber \\ - & & -\frac{\sqrt{X^2}}{x \left(X^2+1\right)}+\frac{X^2 \log (x+1)}{\left(X^2+1\right)^{3/2}} \nonumber \\ - & & +\frac{\left(2 X^2+1\right) \tanh ^{-1}\left(\sqrt{\frac{X^2}{X^2+1}}\right)}{x \left(X^2+1\right)^{3/2}} \nonumber \\ - & & \left. +\frac{ \left(\sqrt{X^2+1}-X^2 \log \left(\sqrt{X^2+1}-1\right)\right)}{\left(X^2+1\right)^{3/2}} \right\} \nonumber \\ - & & /\left[\log (1+c)-\frac{c}{1+c}\right] - \end{eqnarray} - !!} - use :: Galactic_Structure_Options , only : enumerationStructureErrorCodeType, structureErrorCodeSuccess - use :: Galacticus_Nodes , only : nodeComponentBasic , nodeComponentDarkMatterProfile - use :: Numerical_Constants_Astronomical, only : gravitationalConstantGalacticus - implicit none - class (darkMatterProfileDMOFiniteResolutionNFW), intent(inout) :: self - type (treeNode ), intent(inout), target :: node - double precision , intent(in ) :: radius - type (enumerationStructureErrorCodeType ), intent( out), optional :: status - class (nodeComponentBasic ), pointer :: basic - class (nodeComponentDarkMatterProfile ), pointer :: darkMatterProfile - double precision :: concentration , radiusScaleFree, & - & lengthResolutionScaleFree - double precision , parameter :: radiusScaleFreeSmall =1.0d-3 - - if (present(status)) status=structureErrorCodeSuccess - if (node%uniqueID() /= self%lastUniqueID ) call self%calculationReset(node,node%uniqueID()) - if ( radius /= self%potentialRadiusPrevious) then - basic => node%basic ( ) - darkMatterProfile => node%darkMatterProfile ( ) - radiusScaleFree = radius /darkMatterProfile%scale() - concentration = self%darkMatterHaloScale_%radiusVirial (node)/darkMatterProfile%scale() - lengthResolutionScaleFree = self %lengthResolutionPhysical(node)/darkMatterProfile%scale() - self%potentialRadiusPrevious = radius - if (radiusScaleFree < radiusScaleFreeSmall) then - ! Series expansion for small radii. - self%potentialPrevious = -gravitationalConstantGalacticus & - & *basic %mass () & - & /darkMatterProfile%scale() & - & *( & - & +(+1.0d0-lengthResolutionScaleFree ) & - & /(+1.0d0+lengthResolutionScaleFree**2) & - & + lengthResolutionScaleFree**2 & - & *( & - & +asinh(lengthResolutionScaleFree ) & - & +log ( & - & +(1.0d0+sqrt(+1.0d0+lengthResolutionScaleFree**2)) & - & / lengthResolutionScaleFree & - & ) & - & ) & - & /(+1.0d0+lengthResolutionScaleFree**2)**1.5d0 & - & - radiusScaleFree**2 & - & *(+1.0d0-radiusScaleFree ) & - & / lengthResolutionScaleFree & - & /6.0d0 & - & ) & - & /( & - & - concentration & - & / (1.0d0+concentration) & - & +log(1.0d0+concentration) & - & ) - else - self%potentialPrevious = -gravitationalConstantGalacticus & - & *basic %mass () & - & /darkMatterProfile%scale() & - & *( & - & + sqrt( lengthResolutionScaleFree**2 ) & - & / radiusScaleFree /(+1.0d0+ lengthResolutionScaleFree**2) & - & - sqrt( +radiusScaleFree**2+lengthResolutionScaleFree**2 ) & - & / radiusScaleFree /(+1.0d0+ lengthResolutionScaleFree**2) & - & - (+1.0d0+2.0d0*lengthResolutionScaleFree**2) & - & *atanh( & - & +sqrt( +lengthResolutionScaleFree**2/(+1.0d0+ lengthResolutionScaleFree**2) ) & - & ) & - & / radiusScaleFree /(+1.0d0+ lengthResolutionScaleFree**2)**1.5d0 & - & +atanh( & - & + radiusScaleFree & - & /sqrt( +radiusScaleFree**2+lengthResolutionScaleFree**2 ) & - & ) & - & / radiusScaleFree & - & + (+1.0d0+2.0d0*lengthResolutionScaleFree**2) & - & *atanh( & - & ( -radiusScaleFree +lengthResolutionScaleFree**2 ) & - & /sqrt (+1.0d0+ lengthResolutionScaleFree**2) & - & /sqrt( +radiusScaleFree**2+lengthResolutionScaleFree**2 ) & - & ) & - & / radiusScaleFree /(+1.0d0+ lengthResolutionScaleFree**2)**1.5d0 & - & - lengthResolutionScaleFree**2 & - & *log ( & - & +1.0d0+radiusScaleFree & - & ) & - & / (+1.0d0+ lengthResolutionScaleFree**2)**1.5d0 & - & + lengthResolutionScaleFree**2/(+1.0d0+ lengthResolutionScaleFree**2)**1.5d0 & - & *log ( & - & -radiusScaleFree +lengthResolutionScaleFree**2 & - & +sqrt(+1.0d0 +lengthResolutionScaleFree**2 ) & - & *sqrt( +radiusScaleFree**2+lengthResolutionScaleFree**2 ) & - & ) & - & +( & - & + sqrt(+1.0d0 +lengthResolutionScaleFree**2 ) & - & - lengthResolutionScaleFree**2 & - & *log( & - & -1.0d0 & - & +sqrt(+1.0d0 +lengthResolutionScaleFree**2 ) & - & ) & - & ) & - & / (+1.0d0+ lengthResolutionScaleFree**2)**1.5d0 & - & ) & - & /( & - & - concentration & - & / (1.0d0+concentration) & - & +log(1.0d0+concentration) & - & ) - end if - end if - finiteResolutionNFWPotential=self%potentialPrevious - return - end function finiteResolutionNFWPotential - - double precision function finiteResolutionNFWRadialVelocityDispersion(self,node,radius) - !!{ - Returns the radial velocity dispersion (in km/s) in the dark matter profile of {\normalfont \ttfamily node} at the given {\normalfont \ttfamily radius} (given in - units of Mpc). - !!} - use :: Galacticus_Nodes , only : nodeComponentBasic , nodeComponentDarkMatterProfile - use :: Numerical_Constants_Astronomical, only : gravitationalConstantGalacticus - implicit none - class (darkMatterProfileDMOFiniteResolutionNFW), intent(inout) :: self - type (treeNode ), intent(inout) :: node - double precision , intent(in ) :: radius - class (nodeComponentBasic ), pointer :: basic - class (nodeComponentDarkMatterProfile ), pointer :: darkMatterProfile - double precision :: concentration , radiusScaleFree , & - & lengthResolutionScaleFree , radiusScaleFreeEffective - double precision , parameter :: lengthResolutionScaleFreeSmall=1.0d-3 - integer (c_size_t ), dimension(0:1) :: jRadiusCore - double precision , dimension(0:1) :: hRadiusCore - integer :: iRadiusCore - - if (node%uniqueID() /= self%lastUniqueID ) call self%calculationReset(node,node%uniqueID()) - if ( radius /= self%velocityDispersionRadialRadiusPrevious) then - basic => node%basic ( ) - darkMatterProfile => node%darkMatterProfile ( ) - radiusScaleFree = radius /darkMatterProfile%scale() - concentration = self%darkMatterHaloScale_%radiusVirial (node)/darkMatterProfile%scale() - lengthResolutionScaleFree = self %lengthResolutionPhysical(node)/darkMatterProfile%scale() - self%velocityDispersionRadialRadiusPrevious = radius - ! Compute the effective radius. In the core of the profile the velocity dispersion must become constant. Therefore, we - ! limit the smallest radius we consider to a small fraction of the core radius. Below this radius a constant velocity - ! dispersion is assumed. - radiusScaleFreeEffective=max(radiusScaleFree,lengthResolutionScaleFreeSmall*lengthResolutionScaleFree) - ! Ensure table is sufficiently extensive. - call self%velocityDispersionRadialTabulate(radiusScaleFreeEffective,lengthResolutionScaleFree) - ! Interpolate to get the velocity dispersion. - call self%velocityDispersionRadialTableRadiusCoreInterpolator%linearFactors(lengthResolutionScaleFree,jRadiusCore(0),hRadiusCore) - jRadiusCore(1)=jRadiusCore(0)+1 - self%velocityDispersionRadialPrevious=0.0d0 - do iRadiusCore=0,1 - self%velocityDispersionRadialPrevious=+self%velocityDispersionRadialPrevious & - & +self%velocityDispersionRadialTableRadiusInterpolator%interpolate(radiusScaleFreeEffective,self%velocityDispersionRadialTable(:,jRadiusCore(iRadiusCore))) & - & * hRadiusCore(iRadiusCore) - end do - self%velocityDispersionRadialPrevious=+self%velocityDispersionRadialPrevious & - & *sqrt( & - & +gravitationalConstantGalacticus & - & *basic %mass () & - & /darkMatterProfile%scale() & - & /( & - & - concentration & - & / (1.0d0+concentration) & - & +log(1.0d0+concentration) & - & ) & - & ) - end if - finiteResolutionNFWRadialVelocityDispersion=self%velocityDispersionRadialPrevious - return - end function finiteResolutionNFWRadialVelocityDispersion - - subroutine finiteResolutionNFWVelocityDispersionRadialTabulate(self,radius,radiusCore) + function finiteResolutionNFWGet(self,node,weightBy,weightIndex) result(massDistribution_) !!{ - Tabulates the mass enclosed within a given radius for finite resolution NFW density profiles. + Return the dark matter mass distribution for the given {\normalfont \ttfamily node}. !!} - use :: Numerical_Ranges , only : Make_Range, rangeTypeLogarithmic - use :: Numerical_Integration, only : integrator + use :: Galacticus_Nodes , only : nodeComponentBasic , nodeComponentDarkMatterProfile + use :: Galactic_Structure_Options, only : componentTypeDarkHalo , massTypeDark , weightByMass + use :: Mass_Distributions , only : massDistributionSphericalFiniteResolutionNFW, kinematicsDistributionFiniteResolutionNFW, massDistributionSpherical implicit none - class (darkMatterProfileDMOFiniteResolutionNFW), intent(inout), target :: self - double precision , intent(in ) :: radius , radiusCore - double precision , parameter :: radiusTiny =1.0d-1 - type (integrator ), save :: integrator_ - logical , save :: initialized =.false. - !$omp threadprivate(integrator_,initialized) - logical :: retabulate - integer :: iRadiusCore , iRadius , & - & i - double precision :: jeansIntegral , jeansIntegralPrevious, & - & radiusLower , radiusUpper , & - & radiusOuter , density - - do i=1,2 - retabulate=.false. - if (.not.self%velocityDispersionRadialTableInitialized) then - retabulate=.true. - else if ( & - & radius < self%velocityDispersionRadialRadiusMinimum & - & .or. & - & radius > self%velocityDispersionRadialRadiusMaximum & - & .or. & - & radiusCore < self%velocityDispersionRadialRadiusCoreMinimum & - & .or. & - & radiusCore > self%velocityDispersionRadialRadiusCoreMaximum & - & ) then - retabulate=.true. - end if - if (retabulate .and.i==1) call self%restoreVelocityDispersionTable() - if (.not.retabulate ) exit - end do - if (retabulate) then - ! Decide how many points to tabulate and allocate table arrays. - self%velocityDispersionRadialRadiusMinimum =min(0.5d0*radius ,self%velocityDispersionRadialRadiusMinimum ) - self%velocityDispersionRadialRadiusMaximum =max(2.0d0*radius ,self%velocityDispersionRadialRadiusMaximum ) - self%velocityDispersionRadialRadiusCoreMinimum =min(0.5d0*radiusCore,self%velocityDispersionRadialRadiusCoreMinimum) - self%velocityDispersionRadialRadiusCoreMaximum =max(2.0d0*radiusCore,self%velocityDispersionRadialRadiusCoreMaximum) - self%velocityDispersionRadialTableRadiusCount =int(log10(self%velocityDispersionRadialRadiusMaximum /self%velocityDispersionRadialRadiusMinimum )*dble(velocityDispersionRadialTableRadiusPointsPerDecade ))+1 - self%velocityDispersionRadialTableRadiusCoreCount=int(log10(self%velocityDispersionRadialRadiusCoreMaximum/self%velocityDispersionRadialRadiusCoreMinimum)*dble(velocityDispersionRadialTableRadiusCorePointsPerDecade))+1 - if (allocated(self%velocityDispersionRadialTableRadius)) then - deallocate(self%velocityDispersionRadialTableRadiusCore) - deallocate(self%velocityDispersionRadialTableRadius ) - deallocate(self%velocityDispersionRadialTable ) - end if - allocate(self%velocityDispersionRadialTableRadiusCore( self%velocityDispersionRadialTableRadiusCoreCount)) - allocate(self%velocityDispersionRadialTableRadius (self%velocityDispersionRadialTableRadiusCount )) - allocate(self%velocityDispersionRadialTable (self%velocityDispersionRadialTableRadiusCount,self%velocityDispersionRadialTableRadiusCoreCount)) - ! Create a range of radii and core radii. - self%velocityDispersionRadialTableRadius =Make_Range(self%velocityDispersionRadialRadiusMinimum ,self%velocityDispersionRadialRadiusMaximum ,self%velocityDispersionRadialTableRadiusCount ,rangeType=rangeTypeLogarithmic) - self%velocityDispersionRadialTableRadiusCore=Make_Range(self%velocityDispersionRadialRadiusCoreMinimum,self%velocityDispersionRadialRadiusCoreMaximum,self%velocityDispersionRadialTableRadiusCoreCount,rangeType=rangeTypeLogarithmic) - ! Initialize integrator if necessary. - if (.not.initialized) then - integrator_=integrator(jeansEquationIntegrand,toleranceRelative=1.0d-2) - initialized=.true. - end if - ! Loop over radii and α and populate tables. - self_ => self - radiusOuter = max(10.0d0*self%velocityDispersionRadialRadiusMaximum,1000.0d0) - do iRadiusCore=1,self%velocityDispersionRadialTableRadiusCoreCount - iRadiusCore_ =iRadiusCore - jeansIntegralPrevious=0.0d0 - do iRadius=self%velocityDispersionRadialTableRadiusCount,1,-1 - ! For radii that are tiny compared to the core radius the velocity dispersion become almost constant. Simply assume this to avoid floating point errors. - if ( & - & self%velocityDispersionRadialTableRadius(iRadius) < radiusTiny & - & .and. & - & self%velocityDispersionRadialTableRadius(iRadius) < radiusTiny*self%velocityDispersionRadialTableRadiusCore(iRadiusCore) & - & .and. & - & iRadius < self%velocityDispersionRadialTableRadiusCount & - & ) then - self%velocityDispersionRadialTable(iRadius,iRadiusCore)=self%velocityDispersionRadialTable(iRadius+1,iRadiusCore) - else - ! Find the limits for the integral. - if (iRadius == self%velocityDispersionRadialTableRadiusCount) then - radiusUpper=radiusOuter - else - radiusUpper=self%velocityDispersionRadialTableRadius(iRadius+1) - end if - radiusLower =self %velocityDispersionRadialTableRadius( iRadius ) - density =self %densityScaleFree (radiusLower,self%velocityDispersionRadialTableRadiusCore(iRadiusCore)) - jeansIntegral =integrator_%integrate (radiusLower, radiusUpper ) - self%velocityDispersionRadialTable(iRadius,iRadiusCore)=+sqrt( & - & +( & - & +jeansIntegral & - & +jeansIntegralPrevious & - & ) & - & /density & - & ) - jeansIntegralPrevious =+jeansIntegralPrevious & - & +jeansIntegral - end if - end do - end do - ! Build interpolators. - if (allocated(self%velocityDispersionRadialTableRadiusCoreInterpolator)) deallocate(self%velocityDispersionRadialTableRadiusCoreInterpolator) - if (allocated(self%velocityDispersionRadialTableRadiusInterpolator )) deallocate(self%velocityDispersionRadialTableRadiusInterpolator ) - allocate(self%velocityDispersionRadialTableRadiusCoreInterpolator) - allocate(self%velocityDispersionRadialTableRadiusInterpolator ) - self%velocityDispersionRadialTableRadiusCoreInterpolator=interpolator(self%velocityDispersionRadialTableRadiusCore) - self%velocityDispersionRadialTableRadiusInterpolator =interpolator(self%velocityDispersionRadialTableRadius ) - ! Specify that tabulation has been made. - self%velocityDispersionRadialTableInitialized=.true. - call self%storeVelocityDispersionTable() - end if - return - end subroutine finiteResolutionNFWVelocityDispersionRadialTabulate - - double precision function jeansEquationIntegrand(radius) - !!{ - Integrand for dark matter profile Jeans equation. - !!} - implicit none - double precision, intent(in ) :: radius - - if (radius > 0.0d0) then - jeansEquationIntegrand=+self_%massEnclosedScaleFree(radius,self_%velocityDispersionRadialTableRadiusCore(iRadiusCore_)) & - & *self_%densityScaleFree (radius,self_%velocityDispersionRadialTableRadiusCore(iRadiusCore_)) & - & / radius **2 - else - jeansEquationIntegrand=0.0d0 - end if - return - end function jeansEquationIntegrand - - subroutine finiteResolutionNFWStoreVelocityDispersionTable(self) - !!{ - Store the tabulated velocity dispersion data to file. - !!} - use :: File_Utilities , only : File_Lock , File_Unlock , lockDescriptor, Directory_Make, & - & File_Path - use :: HDF5_Access , only : hdf5Access - use :: IO_HDF5 , only : hdf5Object - use :: Input_Paths , only : inputPath , pathTypeDataDynamic - use :: ISO_Varying_String, only : varying_string, operator(//) , char - implicit none - class(darkMatterProfileDMOFiniteResolutionNFW), intent(inout) :: self - type (lockDescriptor ) :: fileLock - type (hdf5Object ) :: file - type (varying_string ) :: fileName - - fileName=inputPath(pathTypeDataDynamic) // & - & 'darkMatter/' // & - & self%objectType ( )// & - & 'VelocityDispersion_' // & - & self%hashedDescriptor(includeSourceDigest=.true.,includeFileModificationTimes=.true.)// & - & '.hdf5' - call Directory_Make(char(File_Path(char(fileName)))) - ! Always obtain the file lock before the hdf5Access lock to avoid deadlocks between OpenMP threads. - call File_Lock(char(fileName),fileLock,lockIsShared=.false.) - !$ call hdf5Access%set() - call file%openFile(char(fileName),overWrite=.true.,objectsOverwritable=.true.,readOnly=.false.) - call file%writeDataset(self%velocityDispersionRadialTableRadiusCore,'radiusCore' ) - call file%writeDataset(self%velocityDispersionRadialTableRadius ,'radius' ) - call file%writeDataset(self%velocityDispersionRadialTable ,'velocityDispersion') - call file%close() - !$ call hdf5Access%unset() - call File_Unlock(fileLock) - return - end subroutine finiteResolutionNFWStoreVelocityDispersionTable - - subroutine finiteResolutionNFWRestoreVelocityDispersionTable(self) - !!{ - Restore the tabulated velocity dispersion data from file, returning true if successful. - !!} - use :: File_Utilities , only : File_Exists , File_Lock , File_Unlock, lockDescriptor - use :: HDF5_Access , only : hdf5Access - use :: IO_HDF5 , only : hdf5Object - use :: Input_Paths , only : inputPath , pathTypeDataDynamic - use :: ISO_Varying_String, only : varying_string, operator(//) - implicit none - class(darkMatterProfileDMOFiniteResolutionNFW), intent(inout) :: self - type (lockDescriptor ) :: fileLock - type (hdf5Object ) :: file - type (varying_string ) :: fileName - - fileName=inputPath(pathTypeDataDynamic) // & - & 'darkMatter/' // & - & self%objectType ( )// & - & 'VelocityDispersion_' // & - & self%hashedDescriptor(includeSourceDigest=.true.)// & - & '.hdf5' - if (File_Exists(fileName)) then - if (allocated(self%velocityDispersionRadialTableRadius)) then - deallocate(self%velocityDispersionRadialTableRadiusCore) - deallocate(self%velocityDispersionRadialTableRadius ) - deallocate(self%velocityDispersionRadialTable ) - end if - ! Always obtain the file lock before the hdf5Access lock to avoid deadlocks between OpenMP threads. - call File_Lock(char(fileName),fileLock,lockIsShared=.true.) - !$ call hdf5Access%set() - call file%openFile(char(fileName)) - call file%readDataset('radiusCore' ,self%velocityDispersionRadialTableRadiusCore) - call file%readDataset('radius' ,self%velocityDispersionRadialTableRadius ) - call file%readDataset('velocityDispersion',self%velocityDispersionRadialTable ) - call file%close() - !$ call hdf5Access%unset() - call File_Unlock(fileLock) - self%velocityDispersionRadialTableRadiusCount =size(self%velocityDispersionRadialTableRadius ) - self%velocityDispersionRadialTableRadiusCoreCount=size(self%velocityDispersionRadialTableRadiusCore) - self%velocityDispersionRadialRadiusMinimum =self%velocityDispersionRadialTableRadius ( 1) - self%velocityDispersionRadialRadiusMaximum =self%velocityDispersionRadialTableRadius (self%velocityDispersionRadialTableRadiusCount ) - self%velocityDispersionRadialRadiusCoreMinimum =self%velocityDispersionRadialTableRadiusCore( 1) - self%velocityDispersionRadialRadiusCoreMaximum =self%velocityDispersionRadialTableRadiusCore(self%velocityDispersionRadialTableRadiusCoreCount) - if (allocated(self%velocityDispersionRadialTableRadiusCoreInterpolator)) deallocate(self%velocityDispersionRadialTableRadiusCoreInterpolator) - if (allocated(self%velocityDispersionRadialTableRadiusInterpolator )) deallocate(self%velocityDispersionRadialTableRadiusInterpolator ) - allocate(self%velocityDispersionRadialTableRadiusCoreInterpolator) - allocate(self%velocityDispersionRadialTableRadiusInterpolator ) - self%velocityDispersionRadialTableRadiusCoreInterpolator=interpolator(self%velocityDispersionRadialTableRadiusCore) - self%velocityDispersionRadialTableRadiusInterpolator =interpolator(self%velocityDispersionRadialTableRadius ) - self%velocityDispersionRadialTableInitialized =.true. - end if - return - end subroutine finiteResolutionNFWRestoreVelocityDispersionTable - - subroutine finiteResolutionNFWStoreDensityTable(self) - !!{ - Store the tabulated radius-enclosing-density data to file. - !!} - use :: File_Utilities , only : File_Lock , File_Unlock , lockDescriptor, Directory_Make, & - & File_Path - use :: HDF5_Access , only : hdf5Access - use :: IO_HDF5 , only : hdf5Object - use :: Input_Paths , only : inputPath , pathTypeDataDynamic - use :: ISO_Varying_String, only : varying_string, operator(//) , char - implicit none - class(darkMatterProfileDMOFiniteResolutionNFW), intent(inout) :: self - type (lockDescriptor ) :: fileLock - type (hdf5Object ) :: file - type (varying_string ) :: fileName - - fileName=inputPath(pathTypeDataDynamic) // & - & 'darkMatter/' // & - & self%objectType ( )// & - & 'Density_' // & - & self%hashedDescriptor(includeSourceDigest=.true.)// & - & '.hdf5' - call Directory_Make(char(File_Path(char(fileName)))) - ! Always obtain the file lock before the hdf5Access lock to avoid deadlocks between OpenMP threads. - call File_Lock(char(fileName),fileLock,lockIsShared=.false.) - !$ call hdf5Access%set() - call file%openFile(char(fileName),overWrite=.true.,objectsOverwritable=.true.,readOnly=.false.) - call file%writeDataset(self%radiusEnclosingDensityTableRadiusCore,'radiusCore') - call file%writeDataset(self%radiusEnclosingDensityTableDensity ,'density' ) - call file%writeDataset(self%radiusEnclosingDensityTable ,'radius' ) - call file%close() - !$ call hdf5Access%unset() - call File_Unlock(fileLock) - return - end subroutine finiteResolutionNFWStoreDensityTable - - subroutine finiteResolutionNFWRestoreDensityTable(self) - !!{ - Restore the tabulated radius-enclosing-density data from file, returning true if successful. - !!} - use :: File_Utilities , only : File_Exists , File_Lock , File_Unlock, lockDescriptor - use :: HDF5_Access , only : hdf5Access - use :: IO_HDF5 , only : hdf5Object - use :: Input_Paths , only : inputPath , pathTypeDataDynamic - use :: ISO_Varying_String, only : varying_string, operator(//) - implicit none - class(darkMatterProfileDMOFiniteResolutionNFW), intent(inout) :: self - type (lockDescriptor ) :: fileLock - type (hdf5Object ) :: file - type (varying_string ) :: fileName - - fileName=inputPath(pathTypeDataDynamic) // & - & 'darkMatter/' // & - & self%objectType ( )// & - & 'Density_' // & - & self%hashedDescriptor(includeSourceDigest=.true.)// & - & '.hdf5' - if (File_Exists(fileName)) then - if (allocated(self%radiusEnclosingDensityTableDensity)) then - deallocate(self%radiusEnclosingDensityTableRadiusCore) - deallocate(self%radiusEnclosingDensityTableDensity ) - deallocate(self%radiusEnclosingDensityTable ) - end if - ! Always obtain the file lock before the hdf5Access lock to avoid deadlocks between OpenMP threads. - call File_Lock(char(fileName),fileLock,lockIsShared=.true.) - !$ call hdf5Access%set() - call file%openFile(char(fileName)) - call file%readDataset('radiusCore',self%radiusEnclosingDensityTableRadiusCore) - call file%readDataset('density' ,self%radiusEnclosingDensityTableDensity ) - call file%readDataset('radius' ,self%radiusEnclosingDensityTable ) - call file%close() - !$ call hdf5Access%unset() - call File_Unlock(fileLock) - self%radiusEnclosingDensityTableDensityCount =size(self%radiusEnclosingDensityTableDensity ) - self%radiusEnclosingDensityTableRadiusCoreCount=size(self%radiusEnclosingDensityTableRadiusCore) - self%radiusEnclosingDensityDensityMinimum =self%radiusEnclosingDensityTableDensity ( 1) - self%radiusEnclosingDensityDensityMaximum =self%radiusEnclosingDensityTableDensity (self%radiusEnclosingDensityTableDensityCount ) - self%radiusEnclosingDensityRadiusCoreMinimum =self%radiusEnclosingDensityTableRadiusCore( 1) - self%radiusEnclosingDensityRadiusCoreMaximum =self%radiusEnclosingDensityTableRadiusCore(self%radiusEnclosingDensityTableRadiusCoreCount) - if (allocated(self%radiusEnclosingDensityTableRadiusCoreInterpolator)) deallocate(self%radiusEnclosingDensityTableRadiusCoreInterpolator) - if (allocated(self%radiusEnclosingDensityTableDensityInterpolator )) deallocate(self%radiusEnclosingDensityTableDensityInterpolator ) - allocate(self%radiusEnclosingDensityTableRadiusCoreInterpolator) - allocate(self%radiusEnclosingDensityTableDensityInterpolator ) - self%radiusEnclosingDensityTableRadiusCoreInterpolator=interpolator(self%radiusEnclosingDensityTableRadiusCore) - self%radiusEnclosingDensityTableDensityInterpolator =interpolator(self%radiusEnclosingDensityTableDensity ) - self%radiusEnclosingDensityTableInitialized =.true. - end if - return - end subroutine finiteResolutionNFWRestoreDensityTable - - subroutine finiteResolutionNFWStoreMassTable(self) - !!{ - Store the tabulated radius-enclosing-mass data to file. - !!} - use :: File_Utilities , only : File_Lock , File_Unlock , lockDescriptor, Directory_Make, & - & File_Path - use :: HDF5_Access , only : hdf5Access - use :: IO_HDF5 , only : hdf5Object - use :: Input_Paths , only : inputPath , pathTypeDataDynamic - use :: ISO_Varying_String, only : varying_string, operator(//) , char - implicit none - class(darkMatterProfileDMOFiniteResolutionNFW), intent(inout) :: self - type (lockDescriptor ) :: fileLock - type (hdf5Object ) :: file - type (varying_string ) :: fileName - - fileName=inputPath(pathTypeDataDynamic) // & - & 'darkMatter/' // & - & self%objectType ( )// & - & 'Mass_' // & - & self%hashedDescriptor(includeSourceDigest=.true.)// & - & '.hdf5' - call Directory_Make(char(File_Path(char(fileName)))) - ! Always obtain the file lock before the hdf5Access lock to avoid deadlocks between OpenMP threads. - call File_Lock(char(fileName),fileLock,lockIsShared=.false.) - !$ call hdf5Access%set() - call file%openFile(char(fileName),overWrite=.true.,objectsOverwritable=.true.,readOnly=.false.) - call file%writeDataset(self%radiusEnclosingMassTableRadiusCore,'radiusCore') - call file%writeDataset(self%radiusEnclosingMassTableMass ,'mass' ) - call file%writeDataset(self%radiusEnclosingMassTable ,'radius' ) - call file%close() - !$ call hdf5Access%unset() - call File_Unlock(fileLock) - return - end subroutine finiteResolutionNFWStoreMassTable - - subroutine finiteResolutionNFWRestoreMassTable(self) - !!{ - Restore the tabulated radius-enclosing-mass data from file, returning true if successful. - !!} - use :: File_Utilities , only : File_Exists , File_Lock , File_Unlock, lockDescriptor - use :: HDF5_Access , only : hdf5Access - use :: IO_HDF5 , only : hdf5Object - use :: Input_Paths , only : inputPath , pathTypeDataDynamic - use :: ISO_Varying_String, only : varying_string, operator(//) - implicit none - class(darkMatterProfileDMOFiniteResolutionNFW), intent(inout) :: self - type (lockDescriptor ) :: fileLock - type (hdf5Object ) :: file - type (varying_string ) :: fileName - - fileName=inputPath(pathTypeDataDynamic) // & - & 'darkMatter/' // & - & self%objectType ( )// & - & 'Mass_' // & - & self%hashedDescriptor(includeSourceDigest=.true.)// & - & '.hdf5' - if (File_Exists(fileName)) then - if (allocated(self%radiusEnclosingMassTableMass)) then - deallocate(self%radiusEnclosingMassTableRadiusCore) - deallocate(self%radiusEnclosingMassTableMass ) - deallocate(self%radiusEnclosingMassTable ) - end if - ! Always obtain the file lock before the hdf5Access lock to avoid deadlocks between OpenMP threads. - call File_Lock(char(fileName),fileLock,lockIsShared=.true.) - !$ call hdf5Access%set() - call file%openFile(char(fileName)) - call file%readDataset('radiusCore',self%radiusEnclosingMassTableRadiusCore) - call file%readDataset('mass' ,self%radiusEnclosingMassTableMass ) - call file%readDataset('radius' ,self%radiusEnclosingMassTable ) - call file%close() - !$ call hdf5Access%unset() - call File_Unlock(fileLock) - self%radiusEnclosingMassTableMassCount =size(self%radiusEnclosingMassTableMass ) - self%radiusEnclosingMassTableRadiusCoreCount=size(self%radiusEnclosingMassTableRadiusCore) - self%radiusEnclosingMassMassMinimum =self%radiusEnclosingMassTableMass ( 1) - self%radiusEnclosingMassMassMaximum =self%radiusEnclosingMassTableMass (self%radiusEnclosingMassTableMassCount ) - self%radiusEnclosingMassRadiusCoreMinimum =self%radiusEnclosingMassTableRadiusCore( 1) - self%radiusEnclosingMassRadiusCoreMaximum =self%radiusEnclosingMassTableRadiusCore(self%radiusEnclosingMassTableRadiusCoreCount) - if (allocated(self%radiusEnclosingMassTableRadiusCoreInterpolator)) deallocate(self%radiusEnclosingMassTableRadiusCoreInterpolator) - if (allocated(self%radiusEnclosingMassTableMassInterpolator )) deallocate(self%radiusEnclosingMassTableMassInterpolator ) - allocate(self%radiusEnclosingMassTableRadiusCoreInterpolator) - allocate(self%radiusEnclosingMassTableMassInterpolator ) - self%radiusEnclosingMassTableRadiusCoreInterpolator=interpolator(self%radiusEnclosingMassTableRadiusCore) - self%radiusEnclosingMassTableMassInterpolator =interpolator(self%radiusEnclosingMassTableMass ) - self%radiusEnclosingMassTableInitialized =.true. - end if - return - end subroutine finiteResolutionNFWRestoreMassTable - - subroutine finiteResolutionNFWStoreEnergyTable(self) - !!{ - Store the tabulated energy data to file. - !!} - use :: File_Utilities , only : File_Lock , File_Unlock , lockDescriptor, Directory_Make, & - & File_Path - use :: HDF5_Access , only : hdf5Access - use :: IO_HDF5 , only : hdf5Object - use :: Input_Paths , only : inputPath , pathTypeDataDynamic - use :: ISO_Varying_String, only : varying_string, operator(//) , char - implicit none - class(darkMatterProfileDMOFiniteResolutionNFW), intent(inout) :: self - type (lockDescriptor ) :: fileLock - type (hdf5Object ) :: file - type (varying_string ) :: fileName - - fileName=inputPath(pathTypeDataDynamic) // & - & 'darkMatter/' // & - & self%objectType ( )// & - & 'Energy_' // & - & self%hashedDescriptor(includeSourceDigest=.true.)// & - & '.hdf5' - call Directory_Make(char(File_Path(char(fileName)))) - ! Always obtain the file lock before the hdf5Access lock to avoid deadlocks between OpenMP threads. - call File_Lock(char(fileName),fileLock,lockIsShared=.false.) - !$ call hdf5Access%set() - call file%openFile(char(fileName),overWrite=.true.,objectsOverwritable=.true.,readOnly=.false.) - call file%writeDataset(self%energyTableRadiusCore ,'radiusCore' ) - call file%writeDataset(self%energyTableConcentration,'concentration') - call file%writeDataset(self%energyTable ,'energy' ) - call file%close() - !$ call hdf5Access%unset() - call File_Unlock(fileLock) - return - end subroutine finiteResolutionNFWStoreEnergyTable - - subroutine finiteResolutionNFWRestoreEnergyTable(self) - !!{ - Restore the tabulated radius-enclosing-mass data from file, returning true if successful. - !!} - use :: File_Utilities , only : File_Exists , File_Lock , File_Unlock, lockDescriptor - use :: HDF5_Access , only : hdf5Access - use :: IO_HDF5 , only : hdf5Object - use :: Input_Paths , only : inputPath , pathTypeDataDynamic - use :: ISO_Varying_String, only : varying_string, operator(//) - implicit none - class(darkMatterProfileDMOFiniteResolutionNFW), intent(inout) :: self - type (lockDescriptor ) :: fileLock - type (hdf5Object ) :: file - type (varying_string ) :: fileName + class (massDistributionClass ), pointer :: massDistribution_ + type (kinematicsDistributionFiniteResolutionNFW), pointer :: kinematicsDistribution_ + class (darkMatterProfileDMOFiniteResolutionNFW ), intent(inout) :: self + type (treeNode ), intent(inout) :: node + type (enumerationWeightByType ), intent(in ), optional :: weightBy + integer , intent(in ), optional :: weightIndex + class (nodeComponentBasic ), pointer :: basic + class (nodeComponentDarkMatterProfile ), pointer :: darkMatterProfile + !![ + + !!] - fileName=inputPath(pathTypeDataDynamic) // & - & 'darkMatter/' // & - & self%objectType ( )// & - & 'Energy_' // & - & self%hashedDescriptor(includeSourceDigest=.true.)// & - & '.hdf5' - if (File_Exists(fileName)) then - if (allocated(self%energyTableConcentration)) then - deallocate(self%energyTableRadiusCore ) - deallocate(self%energyTableConcentration) - deallocate(self%energyTable ) - end if - ! Always obtain the file lock before the hdf5Access lock to avoid deadlocks between OpenMP threads. - call File_Lock(char(fileName),fileLock,lockIsShared=.true.) - !$ call hdf5Access%set() - call file%openFile(char(fileName)) - call file%readDataset('radiusCore' ,self%energyTableRadiusCore ) - call file%readDataset('concentration',self%energyTableConcentration) - call file%readDataset('energy' ,self%energyTable ) - call file%close() - !$ call hdf5Access%unset() - call File_Unlock(fileLock) - self%energyTableConcentrationCount=size(self%energyTableConcentration ) - self%energyTableRadiusCoreCount =size(self%energyTableRadiusCore) - self%energyConcentrationMinimum =self%energyTableConcentration( 1) - self%energyConcentrationMaximum =self%energyTableConcentration(self%energyTableConcentrationCount) - self%energyRadiusCoreMinimum =self%energyTableRadiusCore ( 1) - self%energyRadiusCoreMaximum =self%energyTableRadiusCore (self%energyTableRadiusCoreCount ) - if (allocated(self%energyTableRadiusCoreInterpolator )) deallocate(self%energyTableRadiusCoreInterpolator ) - if (allocated(self%energyTableConcentrationInterpolator)) deallocate(self%energyTableConcentrationInterpolator) - allocate(self%energyTableRadiusCoreInterpolator ) - allocate(self%energyTableConcentrationInterpolator) - self%energyTableRadiusCoreInterpolator =interpolator(self%energyTableRadiusCore ) - self%energyTableConcentrationInterpolator=interpolator(self%energyTableConcentration) - self%energyTableInitialized =.true. - end if + ! Assume a null distribution by default. + massDistribution_ => null() + ! If weighting is not by mass, return a null profile. + if (weightBy_ /= weightByMass) return + ! Create the mass distribution. + allocate(massDistributionSphericalFiniteResolutionNFW :: massDistribution_) + select type(massDistribution_) + type is (massDistributionSphericalFiniteResolutionNFW) + basic => node%basic() + darkMatterProfile => node%darkMatterProfile() + !![ + + + massDistributionSphericalFiniteResolutionNFW( & + & lengthResolution =self %lengthResolutionPhysical(node), & + & radiusScale =darkMatterProfile %scale ( ), & + & radiusVirial =self %darkMatterHaloScale_%radiusVirial (node), & + & mass =basic %mass ( ), & + & componentType = componentTypeDarkHalo , & + & massType = massTypeDark & + & ) + + + !!] + end select + allocate(kinematicsDistribution_) + !![ + + + kinematicsDistributionFiniteResolutionNFW( & + & ) + + + !!] + call massDistribution_%setKinematicsDistribution(kinematicsDistribution_) + !![ + + !!] return - end subroutine finiteResolutionNFWRestoreEnergyTable + end function finiteResolutionNFWGet diff --git a/source/dark_matter_profiles_DMO.heated.F90 b/source/dark_matter_profiles_DMO.heated.F90 index 1c23bfc075..a50bb77b80 100644 --- a/source/dark_matter_profiles_DMO.heated.F90 +++ b/source/dark_matter_profiles_DMO.heated.F90 @@ -21,76 +21,29 @@ An implementation of heated dark matter halo profiles. !!} - use :: Root_Finder, only : rootFinder - + use :: Mass_Distributions, only : enumerationNonAnalyticSolversType + !![ - A dark matter profile DMO class in which dark matter halos start out with a density profile defined by another {\normalfont - \ttfamily darkMatterProfileDMO}. This profile is then modified by heating, under the assumption that the - energy of a shell of mass before and after heating are related by - \begin{equation} - -{ \mathrm{G} M^\prime(r^\prime) \over r^\prime } = -{ \mathrm{G} M(r) \over r } + 2 \epsilon(r), - \end{equation} - where $M(r)$ is the mass enclosed within a radius $r$, and $\epsilon(r)$ represents the specific heating in the shell - initially at radius $r$. Primes indicate values after heating, while unprimed variables indicate quantities prior to - heating. With the assumption of no shell crossing, $M^\prime(r^\prime)=M(r)$ and this equation can be solved for $r$ given - $r^\prime$ and $\epsilon(r)$. - - Not all methods have analytic solutions for this profile. If {\normalfont \ttfamily [nonAnalyticSolver]}$=${\normalfont - \ttfamily fallThrough} then attempts to call these methods in heated profiles will simply return the result from the - unheated profile, otherwise a numerical calculation is performed. + A dark matter profile DMO class which builds \refClass{massDistributionSphericalHeated} objects to account for heating of + some other dark matter profile. !!] - - use :: Dark_Matter_Profiles_Generic, only : enumerationNonAnalyticSolversType, enumerationNonAnalyticSolversEncode, enumerationNonAnalyticSolversIsValid, nonAnalyticSolversFallThrough - use :: Kind_Numbers , only : kind_int8 - type, extends(darkMatterProfileDMOClass) :: darkMatterProfileDMOHeated !!{ A dark matter halo profile class implementing heated dark matter halos. !!} private - class (darkMatterProfileDMOClass ), pointer :: darkMatterProfileDMO_ => null() - class (darkMatterProfileHeatingClass ), pointer :: darkMatterProfileHeating_ => null() - integer (kind=kind_int8 ) :: lastUniqueID - double precision :: radiusFinalPrevious , radiusInitialPrevious + class (darkMatterProfileDMOClass ), pointer :: darkMatterProfileDMO_ => null() + class (darkMatterProfileHeatingClass ), pointer :: darkMatterProfileHeating_ => null() + double precision :: toleranceRelativeVelocityDispersion , toleranceRelativeVelocityDispersionMaximum type (enumerationNonAnalyticSolversType) :: nonAnalyticSolver - logical :: velocityDispersionApproximate - type (rootFinder ) :: finder + logical :: velocityDispersionApproximate , tolerateVelocityMaximumFailure contains - !![ - - - - - - !!] - final :: heatedDestructor - procedure :: autoHook => heatedAutoHook - procedure :: calculationReset => heatedCalculationReset - procedure :: radiusInitial => heatedRadiusInitial - procedure :: noShellCrossingIsValid => heatedNoShellCrossingIsValid - procedure :: density => heatedDensity - procedure :: densityLogSlope => heatedDensityLogSlope - procedure :: radiusEnclosingDensity => heatedRadiusEnclosingDensity - procedure :: radiusEnclosingMass => heatedRadiusEnclosingMass - procedure :: radialMoment => heatedRadialMoment - procedure :: enclosedMass => heatedEnclosedMass - procedure :: potential => heatedPotential - procedure :: circularVelocity => heatedCircularVelocity - procedure :: radiusCircularVelocityMaximum => heatedRadiusCircularVelocityMaximum - procedure :: circularVelocityMaximum => heatedCircularVelocityMaximum - procedure :: radialVelocityDispersion => heatedRadialVelocityDispersion - procedure :: jeansEquationIntegrand => heatedJeansEquationIntegrand - procedure :: jeansEquationRadius => heatedJeansEquationRadius - procedure :: radiusFromSpecificAngularMomentum => heatedRadiusFromSpecificAngularMomentum - procedure :: rotationNormalization => heatedRotationNormalization - procedure :: energy => heatedEnergy - procedure :: kSpace => heatedKSpace - procedure :: freefallRadius => heatedFreefallRadius - procedure :: freefallRadiusIncreaseRate => heatedFreefallRadiusIncreaseRate + final :: heatedDestructor + procedure :: get => heatedGet end type darkMatterProfileDMOHeated interface darkMatterProfileDMOHeated @@ -101,27 +54,21 @@ module procedure heatedConstructorInternal end interface darkMatterProfileDMOHeated - ! Global variables used in root solving. - double precision :: radiusFinal_ - type (treeNode ), pointer :: node_ - type (darkMatterProfileDMOHeated), pointer :: self_ - !$omp threadprivate(radiusFinal_,node_,self_) - contains function heatedConstructorParameters(parameters) result(self) !!{ Default constructor for the {\normalfont \ttfamily heated} dark matter halo profile class. !!} - use :: Input_Parameters, only : inputParameter, inputParameters + use :: Mass_Distributions, only : enumerationNonAnalyticSolversEncode + use :: Input_Parameters , only : inputParameter, inputParameters implicit none type (darkMatterProfileDMOHeated ) :: self type (inputParameters ), intent(inout) :: parameters class (darkMatterProfileDMOClass ), pointer :: darkMatterProfileDMO_ - class (darkMatterHaloScaleClass ), pointer :: darkMatterHaloScale_ class (darkMatterProfileHeatingClass ), pointer :: darkMatterProfileHeating_ type (varying_string ) :: nonAnalyticSolver - logical :: velocityDispersionApproximate + logical :: velocityDispersionApproximate , tolerateVelocityMaximumFailure double precision :: toleranceRelativeVelocityDispersion, toleranceRelativeVelocityDispersionMaximum !![ @@ -149,652 +96,127 @@ function heatedConstructorParameters(parameters) result(self) parameters The maximum relative tolerance to use in numerical solutions for the velocity dispersion in dark-matter-only density profiles. + + tolerateVelocityMaximumFailure + .true. + If true, tolerate failures to find the radius of the peak in the rotation curve. + parameters + - !!] - self=darkMatterProfileDMOHeated(enumerationNonAnalyticSolversEncode(char(nonAnalyticSolver),includesPrefix=.false.),velocityDispersionApproximate,toleranceRelativeVelocityDispersion,toleranceRelativeVelocityDispersionMaximum,darkMatterProfileDMO_,darkMatterHaloScale_,darkMatterProfileHeating_) + self=darkMatterProfileDMOHeated(enumerationNonAnalyticSolversEncode(char(nonAnalyticSolver),includesPrefix=.false.),velocityDispersionApproximate,tolerateVelocityMaximumFailure,toleranceRelativeVelocityDispersion,toleranceRelativeVelocityDispersionMaximum,darkMatterProfileDMO_,darkMatterProfileHeating_) !![ - !!] return end function heatedConstructorParameters - function heatedConstructorInternal(nonAnalyticSolver,velocityDispersionApproximate,toleranceRelativeVelocityDispersion,toleranceRelativeVelocityDispersionMaximum,darkMatterProfileDMO_,darkMatterHaloScale_,darkMatterProfileHeating_) result(self) + function heatedConstructorInternal(nonAnalyticSolver,velocityDispersionApproximate,tolerateVelocityMaximumFailure,toleranceRelativeVelocityDispersion,toleranceRelativeVelocityDispersionMaximum,darkMatterProfileDMO_,darkMatterProfileHeating_) result(self) !!{ Generic constructor for the {\normalfont \ttfamily heated} dark matter profile class. !!} - use :: Error, only : Error_Report + use :: Mass_Distributions, only : enumerationNonAnalyticSolversIsValid + use :: Error , only : Error_Report implicit none type (darkMatterProfileDMOHeated ) :: self class (darkMatterProfileDMOClass ), intent(in ), target :: darkMatterProfileDMO_ - class (darkMatterHaloScaleClass ), intent(in ), target :: darkMatterHaloScale_ class (darkMatterProfileHeatingClass ), intent(in ), target :: darkMatterProfileHeating_ type (enumerationNonAnalyticSolversType), intent(in ) :: nonAnalyticSolver - logical , intent(in ) :: velocityDispersionApproximate - double precision , intent(in ) :: toleranceRelativeVelocityDispersion , toleranceRelativeVelocityDispersionMaximum - double precision , parameter :: toleranceAbsolute =0.0d0, toleranceRelative =1.0d-6 + logical , intent(in ) :: velocityDispersionApproximate , tolerateVelocityMaximumFailure + double precision , intent(in ) :: toleranceRelativeVelocityDispersion, toleranceRelativeVelocityDispersionMaximum !![ - + !!] ! Validate. if (.not.enumerationNonAnalyticSolversIsValid(nonAnalyticSolver)) call Error_Report('invalid non-analytic solver type'//{introspection:location}) - ! Construct the object. - self%genericLastUniqueID=-1_kind_int8 - self%lastUniqueID =-1_kind_int8 - self%radiusFinalPrevious=-huge(0.0d0) - self%finder =rootFinder( & - & rootFunction =heatedRadiusInitialRoot, & - & toleranceAbsolute=toleranceAbsolute , & - & toleranceRelative=toleranceRelative & - & ) return end function heatedConstructorInternal - subroutine heatedAutoHook(self) - !!{ - Attach to the calculation reset event. - !!} - use :: Events_Hooks, only : calculationResetEvent, openMPThreadBindingAllLevels - implicit none - class(darkMatterProfileDMOHeated), intent(inout) :: self - - call calculationResetEvent%attach(self,heatedCalculationReset,openMPThreadBindingAllLevels,label='darkMatterProfileDMOHeated') - return - end subroutine heatedAutoHook - subroutine heatedDestructor(self) !!{ Destructor for the {\normalfont \ttfamily heated} dark matter halo profile class. !!} - use :: Events_Hooks, only : calculationResetEvent implicit none type(darkMatterProfileDMOHeated), intent(inout) :: self !![ - !!] - if (calculationResetEvent%isAttached(self,heatedCalculationReset)) call calculationResetEvent%detach(self,heatedCalculationReset) return end subroutine heatedDestructor - subroutine heatedCalculationReset(self,node,uniqueID) - !!{ - Reset the dark matter profile calculation. - !!} - use :: Kind_Numbers, only : kind_int8 - implicit none - class (darkMatterProfileDMOHeated), intent(inout) :: self - type (treeNode ), intent(inout) :: node - integer(kind_int8 ), intent(in ) :: uniqueID - !$GLC attributes unused :: node - - ! Reset calculations for this profile. - self%lastUniqueID =uniqueID - self%genericLastUniqueID =uniqueID - self%radiusFinalPrevious =-huge(0.0d0) - self%genericEnclosedMassRadiusMinimum =+huge(0.0d0) - self%genericEnclosedMassRadiusMaximum =-huge(0.0d0) - self%genericVelocityDispersionRadialRadiusMinimum=+huge(0.0d0) - self%genericVelocityDispersionRadialRadiusMaximum=-huge(0.0d0) - if (allocated(self%genericVelocityDispersionRadialVelocity)) deallocate(self%genericVelocityDispersionRadialVelocity) - if (allocated(self%genericVelocityDispersionRadialRadius )) deallocate(self%genericVelocityDispersionRadialRadius ) - if (allocated(self%genericEnclosedMassMass )) deallocate(self%genericEnclosedMassMass ) - if (allocated(self%genericEnclosedMassRadius )) deallocate(self%genericEnclosedMassRadius ) - return - end subroutine heatedCalculationReset - - double precision function heatedDensity(self,node,radius) - !!{ - Returns the density (in $M_\odot$ Mpc$^{-3}$) in the dark matter profile of {\normalfont \ttfamily node} at the given - {\normalfont \ttfamily radius} (given in units of Mpc). - !!} - use :: Numerical_Constants_Math , only : Pi - use :: Numerical_Constants_Astronomical, only : gravitationalConstantGalacticus - implicit none - class (darkMatterProfileDMOHeated), intent(inout) :: self - type (treeNode ), intent(inout) :: node - double precision , intent(in ) :: radius - double precision :: radiusInitial , massEnclosed, & - & densityInitial, jacobian - - radiusInitial =self %radiusInitial(node,radius ) - densityInitial=self%darkMatterProfileDMO_%density (node,radiusInitial) - if (radius == 0.0d0 .and. radiusInitial == 0.0d0) then - ! At zero radius, the density is unchanged. - heatedDensity =+densityInitial - else if (self%darkMatterProfileHeating_%specificEnergyIsEverywhereZero(node,self%darkMatterProfileDMO_)) then - ! No heating, the density is unchanged. - heatedDensity =+densityInitial - else if (.not.self%noShellCrossingIsValid(node_,radiusInitial,radius)) then - ! Shell crossing assumption is broken - simply return the density unchanged. - heatedDensity =+self%darkMatterProfileDMO_%density (node,radius ) - else - massEnclosed=+self%darkMatterProfileDMO_%enclosedMass (node,radiusInitial) - if (massEnclosed > 0.0d0) then - jacobian =+1.0d0 & - & /( & - & +( & - & +radius & - & /radiusInitial & - & ) **2 & - & +2.0d0 & - & *radius **2 & - & /gravitationalConstantGalacticus & - & /massEnclosed & - & *( & - & +self%darkMatterProfileHeating_%specificEnergyGradient(node,radiusInitial,self%darkMatterProfileDMO_) & - & -4.0d0 & - & *Pi & - & *radiusInitial **2 & - & *densityInitial & - & *self%darkMatterProfileHeating_%specificEnergy (node,radiusInitial,self%darkMatterProfileDMO_) & - & /massEnclosed & - & ) & - & ) - heatedDensity =+densityInitial & - & *( & - & +radiusInitial & - & /radius & - & ) **2 & - & *jacobian - else - heatedDensity =+densityInitial - end if - end if - return - end function heatedDensity - - double precision function heatedDensityLogSlope(self,node,radius) - !!{ - Returns the logarithmic slope of the density in the dark matter profile of {\normalfont \ttfamily node} at the given - {\normalfont \ttfamily radius} (given in units of Mpc). - !!} - implicit none - class (darkMatterProfileDMOHeated), intent(inout) :: self - type (treeNode ), intent(inout) :: node - double precision , intent(in ) :: radius - - if (self%darkMatterProfileHeating_%specificEnergyIsEverywhereZero(node,self%darkMatterProfileDMO_) .or. self%nonAnalyticSolver == nonAnalyticSolversFallThrough) then - heatedDensityLogSlope=self%darkMatterProfileDMO_%densityLogSlope (node,radius) - else - heatedDensityLogSlope=self %densityLogSlopeNumerical(node,radius) - end if - return - end function heatedDensityLogSlope - - double precision function heatedRadiusEnclosingDensity(self,node,density) - !!{ - Returns the radius (in Mpc) in the dark matter profile of {\normalfont \ttfamily node} which encloses the given - {\normalfont \ttfamily density} (given in units of $M_\odot/$Mpc$^{-3}$). - !!} - implicit none - class (darkMatterProfileDMOHeated), intent(inout), target :: self - type (treeNode ), intent(inout), target :: node - double precision , intent(in ) :: density - - if (self%darkMatterProfileHeating_%specificEnergyIsEverywhereZero(node,self%darkMatterProfileDMO_) .or. self%nonAnalyticSolver == nonAnalyticSolversFallThrough) then - heatedRadiusEnclosingDensity=self%darkMatterProfileDMO_%radiusEnclosingDensity (node,density) - else - heatedRadiusEnclosingDensity=self %radiusEnclosingDensityNumerical(node,density) - end if - return - end function heatedRadiusEnclosingDensity - - double precision function heatedRadiusEnclosingMass(self,node,mass) - !!{ - Returns the radius (in Mpc) in the dark matter profile of {\normalfont \ttfamily node} which encloses the given - {\normalfont \ttfamily mass} (given in units of $M_\odot$). - !!} - use :: Galactic_Structure_Options , only : radiusLarge - use :: Numerical_Constants_Astronomical, only : gravitationalConstantGalacticus - implicit none - class (darkMatterProfileDMOHeated), intent(inout), target :: self - type (treeNode ), intent(inout), target :: node - double precision , intent(in ) :: mass - double precision :: radiusInitial - double precision :: energySpecific - - radiusInitial =self%darkMatterProfileDMO_ %radiusEnclosingMass(node,mass ) - energySpecific=self%darkMatterProfileHeating_%specificEnergy (node,radiusInitial,self%darkMatterProfileDMO_) - if (radiusInitial <= 0.0d0) then - heatedRadiusEnclosingMass=radiusLarge - else - heatedRadiusEnclosingMass=+1.0d0 & - & / & - & ( & - & +1.0d0/radiusInitial & - & -2.0d0/gravitationalConstantGalacticus/mass*energySpecific & - & ) - ! If the radius found is negative, which means the initial shell has expanded to infinity, return the largest radius. - if (heatedRadiusEnclosingMass < 0.0d0) heatedRadiusEnclosingMass=radiusLarge - end if - return - end function heatedRadiusEnclosingMass - - double precision function heatedRadialMoment(self,node,moment,radiusMinimum,radiusMaximum) - !!{ - Returns the density (in $M_\odot$ Mpc$^{-3}$) in the dark matter profile of {\normalfont \ttfamily node} at the given - {\normalfont \ttfamily radius} (given in units of Mpc). - !!} - implicit none - class (darkMatterProfileDMOHeated), intent(inout) :: self - type (treeNode ), intent(inout) :: node - double precision , intent(in ) :: moment - double precision , intent(in ), optional :: radiusMinimum, radiusMaximum - - if (self%darkMatterProfileHeating_%specificEnergyIsEverywhereZero(node,self%darkMatterProfileDMO_) .or. self%nonAnalyticSolver == nonAnalyticSolversFallThrough) then - heatedRadialMoment=self%darkMatterProfileDMO_%radialMoment (node,moment,radiusMinimum,radiusMaximum) - else - heatedRadialMoment=self %radialMomentNumerical(node,moment,radiusMinimum,radiusMaximum) - end if - return - end function heatedRadialMoment - - double precision function heatedEnclosedMass(self,node,radius) - !!{ - Returns the enclosed mass (in $M_\odot$) in the dark matter profile of {\normalfont \ttfamily node} at the given {\normalfont \ttfamily radius} (given in - units of Mpc). - !!} - implicit none - class (darkMatterProfileDMOHeated), intent(inout) :: self - type (treeNode ), intent(inout) :: node - double precision , intent(in ) :: radius - - heatedEnclosedMass=self%darkMatterProfileDMO_%enclosedMass(node,self%radiusInitial(node,radius)) - return - end function heatedEnclosedMass - - double precision function heatedRadiusInitial(self,node,radiusFinal) - !!{ - Find the initial radius corresponding to the given {\normalfont \ttfamily radiusFinal} in - the heated dark matter profile. - !!} - use :: Root_Finder, only : rangeExpandMultiplicative, rangeExpandSignExpectNegative, rangeExpandSignExpectPositive - implicit none - class (darkMatterProfileDMOHeated), intent(inout), target :: self - type (treeNode ), intent(inout), target :: node - double precision , intent(in ) :: radiusFinal - double precision , parameter :: epsilonExpand=1.0d-2 - double precision :: factorExpand - - ! If profile is unheated, the initial radius equals the final radius. - if (self%darkMatterProfileHeating_%specificEnergyIsEverywhereZero(node,self%darkMatterProfileDMO_)) then - heatedRadiusInitial=radiusFinal - return - end if - ! Reset calculations if necessary. - if (node%uniqueID() /= self%lastUniqueID) call self%calculationReset(node,node%uniqueID()) - ! Find the initial radius in the unheated profile. - if (radiusFinal /= self%radiusFinalPrevious) then - self_ => self - node_ => node - radiusFinal_ = radiusFinal - if (self%radiusFinalPrevious <= -huge(0.0d0) .or. radiusFinal < self%radiusInitialPrevious .or. radiusFinal > 10.0d0*self%radiusInitialPrevious) then - ! No previous solution is available, or the requested final radius is smaller than the previous initial radius, or the - ! final radius is much larger than the previous initial radius. In this case, our guess for the initial radius is the - ! final radius, and we expand the range downward to find a solution. - call self%finder%rangeExpand( & - & rangeExpandUpward =1.01d0 , & - & rangeExpandDownward =0.50d0 , & - & rangeExpandDownwardSignExpect=rangeExpandSignExpectNegative, & - & rangeExpandUpwardSignExpect =rangeExpandSignExpectPositive, & - & rangeExpandType =rangeExpandMultiplicative & - & ) - self%radiusInitialPrevious=self%finder%find(rootGuess=radiusFinal) - else - ! Previous solution exists, and the requested final radius is larger (but not too much larger) than the previous initial - ! radius. Use the previous initial radius as a guess for the solution, with range expansion in steps determined by the - ! relative values of the current and previous final radii. If the current final radius is close to the previous final - ! radius this should give a guess for the initial radius close to the actual solution. - if (radiusFinal > self%radiusFinalPrevious) then - factorExpand= radiusFinal /self%radiusFinalPrevious - else - factorExpand=self%radiusFinalPrevious/ radiusFinal - end if - factorExpand=max(factorExpand,1.0d0+epsilonExpand) - call self%finder%rangeExpand( & - & rangeExpandUpward =1.0d0*factorExpand , & - & rangeExpandDownward =1.0d0/factorExpand , & - & rangeExpandDownwardSignExpect=rangeExpandSignExpectNegative, & - & rangeExpandUpwardSignExpect =rangeExpandSignExpectPositive, & - & rangeExpandType =rangeExpandMultiplicative & - & ) - self%radiusInitialPrevious=self%finder%find(rootGuess=self%radiusInitialPrevious) - end if - self%radiusFinalPrevious=radiusFinal - end if - heatedRadiusInitial=self%radiusInitialPrevious - return - end function heatedRadiusInitial - - logical function heatedNoShellCrossingIsValid(self,node,radiusInitial,radiusFinal) - !!{ - Determines if the no shell crossing assumption is valid. - !!} - use :: Numerical_Constants_Math , only : Pi - use :: Numerical_Constants_Astronomical, only : gravitationalConstantGalacticus - implicit none - class (darkMatterProfileDMOHeated), intent(inout) :: self - type (treeNode ), intent(inout) :: node - double precision , intent(in ) :: radiusInitial, radiusFinal - double precision :: massEnclosed - - massEnclosed = + self%darkMatterProfileDMO_ %enclosedMass (node,radiusInitial ) - heatedNoShellCrossingIsValid= + self%darkMatterProfileHeating_%specificEnergyGradient(node,radiusInitial,self%darkMatterProfileDMO_) & - & > & - & +0.5d0 & - & *gravitationalConstantGalacticus & - & *( & - & +4.0d0 & - & *Pi & - & *radiusInitial**2 & - & *self%darkMatterProfileDMO_ %density (node,radiusInitial ) & - & *( & - & -1.0d0/radiusFinal & - & +1.0d0/radiusInitial & - & ) & - & -massEnclosed & - & /radiusInitial**2 & - & ) - return - end function heatedNoShellCrossingIsValid - - double precision function heatedRadiusInitialRoot(radiusInitial) - !!{ - Root function used in finding initial radii in heated dark matter halo profiles. - !!} - use :: Numerical_Constants_Astronomical, only : gravitationalConstantGalacticus - implicit none - double precision, intent(in ) :: radiusInitial - double precision, parameter :: fractionRadiusSmall=1.0d-3 - double precision :: massEnclosed - - if (radiusInitial < fractionRadiusSmall*radiusFinal_) then - ! The initial radius is a small fraction of the final radius. Check if the assumption of no shell crossing is locally - ! broken. If the gradient of the heating term is less than that of the gravitational potential term then it is likely that - ! no root exists. In this case shell crossing is likely to be occurring. Simply return a value of zero, which places the - ! root at the current radius. - if (.not.self_%noShellCrossingIsValid(node_,radiusInitial,radiusFinal_)) then - heatedRadiusInitialRoot=0.0d0 - return - end if - end if - massEnclosed =+self_%darkMatterProfileDMO_ %enclosedMass (node_,radiusInitial ) - heatedRadiusInitialRoot=+self_%darkMatterProfileHeating_%specificEnergy(node_,radiusInitial,self_%darkMatterProfileDMO_) & - & +0.5d0 & - & *gravitationalConstantGalacticus & - & *massEnclosed & - & *( & - & +1.0d0/radiusFinal_ & - & -1.0d0/radiusInitial & - & ) - return - end function heatedRadiusInitialRoot - - double precision function heatedPotential(self,node,radius,status) - !!{ - Returns the potential (in (km/s)$^2$) in the dark matter profile of {\normalfont \ttfamily node} at the given {\normalfont - \ttfamily radius} (given in units of Mpc). - !!} - implicit none - class (darkMatterProfileDMOHeated ), intent(inout) :: self - type (treeNode ), intent(inout), target :: node - double precision , intent(in ) :: radius - type (enumerationStructureErrorCodeType), intent( out), optional :: status - - if (self%darkMatterProfileHeating_%specificEnergyIsEverywhereZero(node,self%darkMatterProfileDMO_) .or. self%nonAnalyticSolver == nonAnalyticSolversFallThrough) then - heatedPotential=self%darkMatterProfileDMO_%potential (node,radius,status) - else - heatedPotential=self %potentialNumerical(node,radius,status) - end if - return - end function heatedPotential - - double precision function heatedCircularVelocity(self,node,radius) - !!{ - Returns the circular velocity (in km/s) in the dark matter profile of {\normalfont \ttfamily node} at the given - {\normalfont \ttfamily radius} (given in units of Mpc). - !!} - use :: Numerical_Constants_Astronomical, only : gravitationalConstantGalacticus - implicit none - class (darkMatterProfileDMOHeated), intent(inout) :: self - type (treeNode ), intent(inout) :: node - double precision , intent(in ) :: radius - - if (radius > 0.0d0) then - heatedCircularVelocity=sqrt( & - & +gravitationalConstantGalacticus & - & *self%enclosedMass(node,radius) & - & / radius & - & ) - else - heatedCircularVelocity=0.0d0 - end if - return - end function heatedCircularVelocity - - double precision function heatedRadiusCircularVelocityMaximum(self,node) - !!{ - Returns the radius (in Mpc) at which the maximum circular velocity is achieved in the dark matter profile of {\normalfont \ttfamily node}. - !!} - implicit none - class(darkMatterProfileDMOHeated), intent(inout) :: self - type (treeNode ), intent(inout) :: node - - if (self%darkMatterProfileHeating_%specificEnergyIsEverywhereZero(node,self%darkMatterProfileDMO_) .or. self%nonAnalyticSolver == nonAnalyticSolversFallThrough) then - heatedRadiusCircularVelocityMaximum=self%darkMatterProfileDMO_%radiusCircularVelocityMaximum (node) - else - heatedRadiusCircularVelocityMaximum=self %radiusCircularVelocityMaximumNumerical(node) - end if - return - end function heatedRadiusCircularVelocityMaximum - - double precision function heatedCircularVelocityMaximum(self,node) - !!{ - Returns the maximum circular velocity (in km/s) in the dark matter profile of {\normalfont \ttfamily node}. - !!} - implicit none - class(darkMatterProfileDMOHeated), intent(inout) :: self - type (treeNode ), intent(inout) :: node - - if (self%darkMatterProfileHeating_%specificEnergyIsEverywhereZero(node,self%darkMatterProfileDMO_) .or. self%nonAnalyticSolver == nonAnalyticSolversFallThrough) then - heatedCircularVelocityMaximum=self%darkMatterProfileDMO_%circularVelocityMaximum (node) - else - heatedCircularVelocityMaximum=self %circularVelocityMaximumNumerical(node) - end if - return - end function heatedCircularVelocityMaximum - - double precision function heatedRadialVelocityDispersion(self,node,radius) - !!{ - Returns the radial velocity dispersion (in km/s) in the dark matter profile of {\normalfont \ttfamily node} at the given - {\normalfont \ttfamily radius} (given in units of Mpc). - !!} - implicit none - class (darkMatterProfileDMOHeated), intent(inout) :: self - type (treeNode ), intent(inout) :: node - double precision , intent(in ) :: radius - double precision :: radiusInitial , energySpecific, & - & velocityDispersionSquare - - if (self%darkMatterProfileHeating_%specificEnergyIsEverywhereZero(node,self%darkMatterProfileDMO_) .or. self%nonAnalyticSolver == nonAnalyticSolversFallThrough) then - ! Use the original, unheated profile velocity dispersion. - heatedRadialVelocityDispersion=self%darkMatterProfileDMO_%radialVelocityDispersion(node,radius) - else if (self%velocityDispersionApproximate) then - ! Use the approximate solution for velocity dispersion. - radiusInitial = self%radiusInitial (node,radius ) - energySpecific = self%darkMatterProfileHeating_%specificEnergy (node,radiusInitial,self%darkMatterProfileDMO_) - velocityDispersionSquare =+self%darkMatterProfileDMO_ %radialVelocityDispersion (node,radiusInitial )**2 & - & -2.0d0/3.0d0*energySpecific - heatedRadialVelocityDispersion=sqrt(max(0.0d0,velocityDispersionSquare)) - else - ! Use a numerical solution. - heatedRadialVelocityDispersion=+self %radialVelocityDispersionNumerical(node,radius ) - end if - return - end function heatedRadialVelocityDispersion - - double precision function heatedJeansEquationIntegrand(self,node,radius) - !!{ - Integrand for generic dark matter profile Jeans equation. Here we do the integration with respect to the - initial radius $r_i$. - \begin{eqnarray} - \sigma_r(r) &=& \frac{1}{\rho(r)}\int_r^{r^{\mathrm{max}}} \rho(r) \frac{\mathrm{G} M(r)}{r^2} \mathrm{d} r \nonumber \\ - &=& \frac{1}{\rho(r)}\int_{r_i}^{r_{i}^{\mathrm{max}}} \rho_i(r_i) \frac{\mathrm{G} M(r_i)}{r_i^2}\left(\frac{r_i}{r}\right)^4 \mathrm{d} r_i. - \end{eqnarray} - Here $r$ can be written as a function of $r_i$ - \begin{equation} - r=\frac{1}{1/r_i-2\epsilon(r_i)/(\mathrm{G}M(r_i))}. - \end{equation} - !!} - use :: Numerical_Constants_Astronomical, only : gravitationalConstantGalacticus - implicit none - class (darkMatterProfileDMOHeated), intent(inout) :: self - type (treeNode ), intent(inout) :: node - double precision , intent(in ) :: radius - double precision :: radiusFinal , energySpecific, & - & enclosedMass - - enclosedMass =self%darkMatterProfileDMO_ %enclosedMass (node,radius ) - energySpecific=self%darkMatterProfileHeating_%specificEnergy(node,radius,self%darkMatterProfileDMO_) - radiusFinal =+1.0d0 & - & /( & - & +1.0d0/radius & - & -2.0d0*energySpecific/gravitationalConstantGalacticus/enclosedMass & - & ) - if (radiusFinal > 0.0d0) then - heatedJeansEquationIntegrand=+gravitationalConstantGalacticus & - & *enclosedMass & - & *self%darkMatterProfileDMO_%density(node,radius) & - & / radius **2 & - & *(radius/radiusFinal)**4 - else - heatedJeansEquationIntegrand=0.0d0 - end if - return - end function heatedJeansEquationIntegrand - - double precision function heatedJeansEquationRadius(self,node,radius) - !!{ - Return the radius variable used in solving the Jeans equation that corresponds to a given physical radius. - Here we do the integration with respect to the initial radius, so return the initial radius. - !!} - implicit none - class (darkMatterProfileDMOHeated), intent(inout) :: self - type (treeNode ), intent(inout) :: node - double precision , intent(in ) :: radius - - heatedJeansEquationRadius=self%radiusInitial(node,radius) - return - end function heatedJeansEquationRadius - - double precision function heatedRadiusFromSpecificAngularMomentum(self,node,specificAngularMomentum) - !!{ - Returns the radius (in Mpc) in {\normalfont \ttfamily node} at which a circular orbit has the given {\normalfont \ttfamily specificAngularMomentum} (given - in units of km s$^{-1}$ Mpc). - !!} - implicit none - class (darkMatterProfileDMOHeated), intent(inout) :: self - type (treeNode ), intent(inout) :: node - double precision , intent(in ) :: specificAngularMomentum - - if (self%darkMatterProfileHeating_%specificEnergyIsEverywhereZero(node,self%darkMatterProfileDMO_) .or. self%nonAnalyticSolver == nonAnalyticSolversFallThrough) then - heatedRadiusFromSpecificAngularMomentum=self%darkMatterProfileDMO_%radiusFromSpecificAngularMomentum (node,specificAngularMomentum) - else - heatedRadiusFromSpecificAngularMomentum=self %radiusFromSpecificAngularMomentumNumerical(node,specificAngularMomentum) - end if - return - end function heatedRadiusFromSpecificAngularMomentum - - double precision function heatedRotationNormalization(self,node) - !!{ - Return the normalization of the rotation velocity vs. specific angular momentum relation. - !!} - implicit none - class(darkMatterProfileDMOHeated), intent(inout) :: self - type (treeNode ), intent(inout) :: node - - if (self%darkMatterProfileHeating_%specificEnergyIsEverywhereZero(node,self%darkMatterProfileDMO_) .or. self%nonAnalyticSolver == nonAnalyticSolversFallThrough) then - heatedRotationNormalization=self%darkMatterProfileDMO_%rotationNormalization (node) - else - heatedRotationNormalization=self %rotationNormalizationNumerical(node) - end if - return - end function heatedRotationNormalization - - double precision function heatedEnergy(self,node) - !!{ - Return the energy of a heated halo density profile. - !!} - implicit none - class(darkMatterProfileDMOHeated), intent(inout) :: self - type (treeNode ), intent(inout) :: node - - if (self%darkMatterProfileHeating_%specificEnergyIsEverywhereZero(node,self%darkMatterProfileDMO_) .or. self%nonAnalyticSolver == nonAnalyticSolversFallThrough) then - heatedEnergy=self%darkMatterProfileDMO_%energy (node) - else - heatedEnergy=self %energyNumerical(node) - end if - return - end function heatedEnergy - - double precision function heatedKSpace(self,node,waveNumber) + function heatedGet(self,node,weightBy,weightIndex) result(massDistribution_) !!{ - Returns the Fourier transform of the heated density profile at the specified {\normalfont \ttfamily waveNumber} - (given in Mpc$^{-1}$), using the expression given in \citeauthor{cooray_halo_2002}~(\citeyear{cooray_halo_2002}; eqn.~81). + Return the dark matter mass distribution for the given {\normalfont \ttfamily node}. !!} + use :: Galactic_Structure_Options, only : componentTypeDarkHalo , massTypeDark , weightByMass + use :: Mass_Distributions , only : massDistributionSphericalHeated, kinematicsDistributionHeated, massDistributionSpherical, massDistributionHeatingClass implicit none - class (darkMatterProfileDMOHeated), intent(inout) :: self - type (treeNode ), intent(inout), target :: node - double precision , intent(in ) :: waveNumber - - if (self%darkMatterProfileHeating_%specificEnergyIsEverywhereZero(node,self%darkMatterProfileDMO_) .or. self%nonAnalyticSolver == nonAnalyticSolversFallThrough) then - heatedKSpace=self%darkMatterProfileDMO_%kSpace (node,waveNumber) - else - heatedKSpace=self %kSpaceNumerical(node,waveNumber) - end if - return - end function heatedKSpace - - double precision function heatedFreefallRadius(self,node,time) - !!{ - Returns the freefall radius in the heated density profile at the specified {\normalfont \ttfamily time} (given in - Gyr). - !!} - implicit none - class (darkMatterProfileDMOHeated), intent(inout), target :: self - type (treeNode ), intent(inout), target :: node - double precision , intent(in ) :: time - - if (self%darkMatterProfileHeating_%specificEnergyIsEverywhereZero(node,self%darkMatterProfileDMO_) .or. self%nonAnalyticSolver == nonAnalyticSolversFallThrough) then - heatedFreefallRadius=self%darkMatterProfileDMO_%freefallRadius (node,time) - else - heatedFreefallRadius=self %freefallRadiusNumerical(node,time) - end if - return - end function heatedFreefallRadius - - double precision function heatedFreefallRadiusIncreaseRate(self,node,time) - !!{ - Returns the rate of increase of the freefall radius in the heated density profile at the specified {\normalfont - \ttfamily time} (given in Gyr). - !!} - implicit none - class (darkMatterProfileDMOHeated), intent(inout), target :: self - type (treeNode ), intent(inout), target :: node - double precision , intent(in ) :: time + class (massDistributionClass ), pointer :: massDistribution_ + type (kinematicsDistributionHeated), pointer :: kinematicsDistribution_ + class (darkMatterProfileDMOHeated ), intent(inout) :: self + type (treeNode ), intent(inout) :: node + type (enumerationWeightByType ), intent(in ), optional :: weightBy + integer , intent(in ), optional :: weightIndex + class (massDistributionClass ), pointer :: massDistributionDecorated + class (massDistributionHeatingClass), pointer :: massDistributionHeating_ + !![ + + !!] - if (self%darkMatterProfileHeating_%specificEnergyIsEverywhereZero(node,self%darkMatterProfileDMO_) .or. self%nonAnalyticSolver == nonAnalyticSolversFallThrough) then - heatedFreefallRadiusIncreaseRate=self%darkMatterProfileDMO_%freefallRadiusIncreaseRate (node,time) - else - heatedFreefallRadiusIncreaseRate=self %freefallRadiusIncreaseRateNumerical(node,time) - end if + ! Assume a null distribution by default. + massDistribution_ => null() + ! If weighting is not by mass, return a null profile. + if (weightBy_ /= weightByMass) return + ! Create the mass distribution. + allocate(massDistributionSphericalHeated :: massDistribution_) + select type(massDistribution_) + type is (massDistributionSphericalHeated) + massDistributionDecorated => self%darkMatterProfileDMO_ %get(node,weightBy,weightIndex) + massDistributionHeating_ => self%darkMatterProfileHeating_%get(node ) + select type (massDistributionDecorated) + class is (massDistributionSpherical) + !![ + + + massDistributionSphericalHeated( & + & nonAnalyticSolver =self%nonAnalyticSolver , & + & tolerateVelocityMaximumFailure=self%tolerateVelocityMaximumFailure, & + & massDistribution_ = massDistributionDecorated , & + & massDistributionHeating_ = massDistributionHeating_ , & + & componentType = componentTypeDarkHalo , & + & massType = massTypeDark & + & ) + + + + + !!] + class default + call Error_Report('expected a spherical mass distribution'//{introspection:location}) + end select + end select + allocate(kinematicsDistribution_) + !![ + + + kinematicsDistributionHeated( & + & nonAnalyticSolver =self%nonAnalyticSolver , & + & velocityDispersionApproximate =self%velocityDispersionApproximate , & + & toleranceRelativeVelocityDispersion =self%toleranceRelativeVelocityDispersion , & + & toleranceRelativeVelocityDispersionMaximum=self%toleranceRelativeVelocityDispersionMaximum & + & ) + + + !!] + call massDistribution_%setKinematicsDistribution(kinematicsDistribution_) + !![ + + !!] return - end function heatedFreefallRadiusIncreaseRate + end function heatedGet diff --git a/source/dark_matter_profiles_DMO.heated.monotonic.F90 b/source/dark_matter_profiles_DMO.heated.monotonic.F90 index 5e8c76b2d5..29c4605fcc 100644 --- a/source/dark_matter_profiles_DMO.heated.monotonic.F90 +++ b/source/dark_matter_profiles_DMO.heated.monotonic.F90 @@ -21,79 +21,29 @@ An implementation of heated dark matter halo profiles based on the energy ordering of shells. !!} + use :: Mass_Distributions, only : enumerationNonAnalyticSolversType + !![ - A dark matter profile DMO class in which dark matter halos start out with a density profile defined by another {\normalfont - \ttfamily darkMatterProfileDMO}. This profile is then modified by heating, under the assumption that the - energy of a shell of mass before and after heating are related by - \begin{equation} - -{ \mathrm{G} M^\prime(r^\prime) \over r^\prime } = -{ \mathrm{G} M(r) \over r } + 2 \epsilon(r), - \end{equation} - where $M(r)$ is the mass enclosed within a radius $r$, and $\epsilon(r)$ represents the specific heating in the shell - initially at radius $r$. Primes indicate values after heating, while unprimed variables indicate quantities prior to - heating. - - The above equation can be re-written as - \begin{equation} - -r^{\prime -1} = -r^{-1} + \xi(r), - \end{equation} - where $\xi(r) = 2 \epsilon(r)/[\mathrm{G} M(r)/r]$ measures the perturbation to the shell. To avoid shell crossing a - monotonicity relation $r_1 < r_2 \implies \xi(r_1) \le \xi(r_2)$ is enforced by starting at large radius and stepping inward, - enforcing the condition in the next innermost shell as necessary. - - Not all methods have analytic solutions for this profile. If {\normalfont \ttfamily [nonAnalyticSolver]}$=${\normalfont - \ttfamily fallThrough} then attempts to call these methods in heated profiles will simply return the result from the - unheated profile, otherwise a numerical calculation is performed. + A dark matter profile DMO class in which builds \refClass{massDistributionSphericalHeatedMonotonic} objects to account for + heating of some other dark matter profile. !!] - - use :: Dark_Matter_Profiles_Generic, only : enumerationNonAnalyticSolversType, enumerationNonAnalyticSolversEncode, enumerationNonAnalyticSolversIsValid, nonAnalyticSolversFallThrough - use :: Kind_Numbers , only : kind_int8 - use :: Numerical_Interpolation , only : interpolator - type, extends(darkMatterProfileDMOClass) :: darkMatterProfileDMOHeatedMonotonic !!{ A dark matter halo profile class implementing heated dark matter halos. !!} private - class (darkMatterProfileDMOClass ), pointer :: darkMatterProfileDMO_ => null() - class (darkMatterProfileHeatingClass ), pointer :: darkMatterProfileHeating_ => null() - integer (kind=kind_int8 ) :: lastUniqueID - type (enumerationNonAnalyticSolversType) :: nonAnalyticSolver - double precision :: radiusInitialMinimum , radiusInitialMaximum, & - & radiusFinalMinimum , radiusFinalMaximum - type (interpolator ), allocatable :: massProfile - logical :: isBound + class (darkMatterHaloScaleClass ), pointer :: darkMatterHaloScale_ => null() + class (darkMatterProfileDMOClass ), pointer :: darkMatterProfileDMO_ => null() + class (darkMatterProfileHeatingClass ), pointer :: darkMatterProfileHeating_ => null() + type (enumerationNonAnalyticSolversType) :: nonAnalyticSolver + double precision :: toleranceRelativeVelocityDispersion , toleranceRelativeVelocityDispersionMaximum contains - !![ - - - - - !!] - final :: heatedMonotonicDestructor - procedure :: autoHook => heatedMonotonicAutoHook - procedure :: calculationReset => heatedMonotonicCalculationReset - procedure :: computeSolution => heatedMonotonicComputeSolution - procedure :: density => heatedMonotonicDensity - procedure :: densityLogSlope => heatedMonotonicDensityLogSlope - procedure :: radiusEnclosingDensity => heatedMonotonicRadiusEnclosingDensity - procedure :: radiusEnclosingMass => heatedMonotonicRadiusEnclosingMass - procedure :: radialMoment => heatedMonotonicRadialMoment - procedure :: enclosedMass => heatedMonotonicEnclosedMass - procedure :: potential => heatedMonotonicPotential - procedure :: circularVelocity => heatedMonotonicCircularVelocity - procedure :: radiusCircularVelocityMaximum => heatedMonotonicRadiusCircularVelocityMaximum - procedure :: circularVelocityMaximum => heatedMonotonicCircularVelocityMaximum - procedure :: radialVelocityDispersion => heatedMonotonicRadialVelocityDispersion - procedure :: radiusFromSpecificAngularMomentum => heatedMonotonicRadiusFromSpecificAngularMomentum - procedure :: rotationNormalization => heatedMonotonicRotationNormalization - procedure :: energy => heatedMonotonicEnergy - procedure :: kSpace => heatedMonotonicKSpace - procedure :: freefallRadius => heatedMonotonicFreefallRadius - procedure :: freefallRadiusIncreaseRate => heatedMonotonicFreefallRadiusIncreaseRate + final :: heatedMonotonicDestructor + procedure :: get => heatedMonotonicGet end type darkMatterProfileDMOHeatedMonotonic interface darkMatterProfileDMOHeatedMonotonic @@ -110,7 +60,8 @@ function heatedMonotonicConstructorParameters(parameters) result(self) !!{ Default constructor for the {\normalfont \ttfamily heatedMonotonic} dark matter halo profile class. !!} - use :: Input_Parameters, only : inputParameter, inputParameters + use :: Mass_Distributions, only : enumerationNonAnalyticSolversEncode + use :: Input_Parameters , only : inputParameter, inputParameters implicit none type (darkMatterProfileDMOHeatedMonotonic) :: self type (inputParameters ), intent(inout) :: parameters @@ -118,8 +69,7 @@ function heatedMonotonicConstructorParameters(parameters) result(self) class (darkMatterHaloScaleClass ), pointer :: darkMatterHaloScale_ class (darkMatterProfileHeatingClass ), pointer :: darkMatterProfileHeating_ type (varying_string ) :: nonAnalyticSolver - double precision :: toleranceRelativeVelocityDispersion, toleranceRelativeVelocityDispersionMaximum, & - & toleranceRelativePotential + double precision :: toleranceRelativeVelocityDispersion, toleranceRelativeVelocityDispersionMaximum !![ @@ -138,19 +88,13 @@ function heatedMonotonicConstructorParameters(parameters) result(self) toleranceRelativeVelocityDispersionMaximum 1.0d-3 parameters - The maximum allowed relative tolerance to use in numerical solutions for the velocity dispersion in dark-matter-only density profiles before aborting. - - - toleranceRelativePotential - 1.0d-3 - parameters - The maximum allowed relative tolerance to use in numerical solutions for the gravitational potential in dark-matter-only density profiles before aborting. + The maximum relative tolerance to use in numerical solutions for the velocity dispersion in dark-matter-only density profiles. !!] - self=darkMatterProfileDMOHeatedMonotonic(enumerationNonAnalyticSolversEncode(char(nonAnalyticSolver),includesPrefix=.false.),toleranceRelativeVelocityDispersion,toleranceRelativeVelocityDispersionMaximum,toleranceRelativePotential,darkMatterProfileDMO_,darkMatterHaloScale_,darkMatterProfileHeating_) + self=darkMatterProfileDMOHeatedMonotonic(enumerationNonAnalyticSolversEncode(char(nonAnalyticSolver),includesPrefix=.false.),toleranceRelativeVelocityDispersion,toleranceRelativeVelocityDispersionMaximum,darkMatterProfileDMO_,darkMatterHaloScale_,darkMatterProfileHeating_) !![ @@ -160,53 +104,32 @@ function heatedMonotonicConstructorParameters(parameters) result(self) return end function heatedMonotonicConstructorParameters - function heatedMonotonicConstructorInternal(nonAnalyticSolver,toleranceRelativeVelocityDispersion,toleranceRelativeVelocityDispersionMaximum,toleranceRelativePotential,darkMatterProfileDMO_,darkMatterHaloScale_,darkMatterProfileHeating_) result(self) + function heatedMonotonicConstructorInternal(nonAnalyticSolver,toleranceRelativeVelocityDispersion,toleranceRelativeVelocityDispersionMaximum,darkMatterProfileDMO_,darkMatterHaloScale_,darkMatterProfileHeating_) result(self) !!{ Generic constructor for the {\normalfont \ttfamily heatedMonotonic} dark matter profile class. !!} - use :: Error, only : Error_Report + use :: Mass_Distributions, only : enumerationNonAnalyticSolversIsValid + use :: Error , only : Error_Report implicit none type (darkMatterProfileDMOHeatedMonotonic) :: self class (darkMatterProfileDMOClass ), intent(in ), target :: darkMatterProfileDMO_ class (darkMatterHaloScaleClass ), intent(in ), target :: darkMatterHaloScale_ class (darkMatterProfileHeatingClass ), intent(in ), target :: darkMatterProfileHeating_ type (enumerationNonAnalyticSolversType ), intent(in ) :: nonAnalyticSolver - double precision , intent(in ) :: toleranceRelativeVelocityDispersion, toleranceRelativeVelocityDispersionMaximum, & - & toleranceRelativePotential + double precision , intent(in ) :: toleranceRelativeVelocityDispersion, toleranceRelativeVelocityDispersionMaximum !![ - + !!] ! Validate. - if (.not.enumerationNonAnalyticSolversIsValid(nonAnalyticSolver)) call Error_Report('invalid non-analytic solver type'//{introspection:location}) - ! Construct the object. - self%lastUniqueID =-1_kind_int8 - self%genericLastUniqueID =-1_kind_int8 - self%radiusInitialMinimum=+huge(0.0d0) - self%radiusInitialMaximum=-huge(0.0d0) - self%radiusFinalMinimum =+huge(0.0d0) - self%radiusFinalMaximum =-huge(0.0d0) - self%isBound =.true. + if (.not.enumerationNonAnalyticSolversIsValid(nonAnalyticSolver)) call Error_Report('invalid non-analytic solver type'//{introspection:location}) return end function heatedMonotonicConstructorInternal - subroutine heatedMonotonicAutoHook(self) - !!{ - Attach to the calculation reset event. - !!} - use :: Events_Hooks, only : calculationResetEvent, openMPThreadBindingAllLevels - implicit none - class(darkMatterProfileDMOHeatedMonotonic), intent(inout) :: self - - call calculationResetEvent%attach(self,heatedMonotonicCalculationReset,openMPThreadBindingAllLevels,label='darkMatterProfileDMOHeatedMonotonic') - return - end subroutine heatedMonotonicAutoHook - subroutine heatedMonotonicDestructor(self) !!{ Destructor for the {\normalfont \ttfamily heatedMonotonic} dark matter halo profile class. !!} - use :: Events_Hooks, only : calculationResetEvent implicit none type(darkMatterProfileDMOHeatedMonotonic), intent(inout) :: self @@ -215,487 +138,76 @@ subroutine heatedMonotonicDestructor(self) !!] - if (calculationResetEvent%isAttached(self,heatedMonotonicCalculationReset)) call calculationResetEvent%detach(self,heatedMonotonicCalculationReset) return end subroutine heatedMonotonicDestructor - subroutine heatedMonotonicCalculationReset(self,node,uniqueID) - !!{ - Reset the dark matter profile calculation. - !!} - use :: Kind_Numbers , only : kind_int8 - implicit none - class (darkMatterProfileDMOHeatedMonotonic), intent(inout) :: self - type (treeNode ), intent(inout) :: node - integer(kind_int8 ), intent(in ) :: uniqueID - !$GLC attributes unused :: node - - ! Reset calculations for this profile. - self%lastUniqueID =uniqueID - self%genericLastUniqueID =uniqueID - self%isBound =.true. - self%radiusInitialMinimum =+huge(0.0d0) - self%radiusInitialMaximum =-huge(0.0d0) - self%radiusFinalMinimum =+huge(0.0d0) - self%radiusFinalMaximum =-huge(0.0d0) - self%genericEnclosedMassRadiusMinimum =+huge(0.0d0) - self%genericEnclosedMassRadiusMaximum =-huge(0.0d0) - self%genericEnclosedMassRadiusMinimum =+huge(0.0d0) - self%genericEnclosedMassRadiusMaximum =-huge(0.0d0) - self%genericVelocityDispersionRadialRadiusMinimum=+huge(0.0d0) - self%genericVelocityDispersionRadialRadiusMaximum=-huge(0.0d0) - if (allocated(self%massProfile )) deallocate(self%massProfile ) - if (allocated(self%genericVelocityDispersionRadialVelocity)) deallocate(self%genericVelocityDispersionRadialVelocity) - if (allocated(self%genericVelocityDispersionRadialRadius )) deallocate(self%genericVelocityDispersionRadialRadius ) - if (allocated(self%genericEnclosedMassMass )) deallocate(self%genericEnclosedMassMass ) - if (allocated(self%genericEnclosedMassRadius )) deallocate(self%genericEnclosedMassRadius ) - return - end subroutine heatedMonotonicCalculationReset - - subroutine heatedMonotonicComputeSolution(self,node,radius) - !!{ - Compute the solution for the heated density profile. - !!} - use :: Numerical_Ranges , only : Make_Range , rangeTypeLogarithmic - use :: Numerical_Constants_Astronomical, only : gravitationalConstantGalacticus - use :: Table_Labels , only : extrapolationTypeFix , extrapolationTypeZero - implicit none - class (darkMatterProfileDMOHeatedMonotonic), intent(inout) :: self - type (treeNode ), intent(inout) :: node - double precision , intent(in ) :: radius - double precision , parameter :: radiusFractionMinimum=1.0d-6, radiusFractionMaximum=10.0d0 - integer , parameter :: countPerDecadeRadius =100 - double precision , allocatable, dimension(:) :: massEnclosed , massShell , & - & radiusInitial , radiusFinal , & - & energyFinal , perturbation - logical , allocatable, dimension(:) :: isBound - integer :: i , countRadii - - ! Determine if we need to retabulate. - if (node%uniqueID() /= self%lastUniqueID) call self%calculationReset(node,node%uniqueID()) - ! Nothing to do if profile is already tabulated. - if (allocated(self%massProfile)) return - ! Choose extent of radii at which to tabulate the initial profile. - self%radiusInitialMinimum=radiusFractionMinimum*self%darkMatterHaloScale_%radiusVirial(node) - self%radiusInitialMaximum=radiusFractionMaximum*self%darkMatterHaloScale_%radiusVirial(node) - ! Build grid of radii. - countRadii=int(log10(self%radiusInitialMaximum/self%radiusInitialMinimum)*dble(countPerDecadeRadius)+1.0d0) - if (allocated(radiusInitial)) then - deallocate(radiusInitial) - deallocate(radiusFinal ) - deallocate(massEnclosed ) - deallocate(massShell ) - deallocate(energyFinal ) - deallocate(perturbation ) - end if - allocate(radiusInitial(countRadii)) - allocate(radiusFinal (countRadii)) - allocate(massEnclosed (countRadii)) - allocate(massShell (countRadii)) - allocate(energyFinal (countRadii)) - allocate(perturbation (countRadii)) - radiusInitial=Make_Range(self%radiusInitialMinimum,self%radiusInitialMaximum,countRadii,rangeTypeLogarithmic) - ! Evaluate masses and energies of shells. - do i=countRadii,1,-1 - massEnclosed(i)=+self%darkMatterProfileDMO_ %enclosedMass (node,radiusInitial(i) ) - perturbation(i)=+2.0d0 & - & *self%darkMatterProfileHeating_ %specificEnergy(node,radiusInitial(i),self%darkMatterProfileDMO_) & - & /gravitationalConstantGalacticus & - & / massEnclosed (i) & - & * radiusInitial(i) - ! Limit the perturbation to avoid shell-crossing. - if (i < countRadii) & - & perturbation(i)=min( & - & +perturbation (i ), & - & +1.0d0 & - & -radiusInitial (i ) & - & /radiusInitial (i+1) & - & *( & - & +massEnclosed(i ) & - & /massEnclosed(i+1) & - & )**(-1.0d0/3.0d0) & - & *( & - & +1.0d0 & - & -perturbation(i+1) & - & ) & - & ) - end do - ! Compute the final energy of the heated profile. - energyFinal=+gravitationalConstantGalacticus & - & *massEnclosed & - & /radiusInitial & - & *( & - & -1.0d0 & - & +perturbation & - & ) - ! Find shell masses. - massShell(1 )=+massEnclosed(1 ) - massShell(2:countRadii)=+massEnclosed(2:countRadii ) & - & -massEnclosed(1:countRadii-1) - ! Evaluation boundedness. - isBound= energyFinal < 0.0d0 & - & .and. & - & massShell > 0.0d0 - ! Find final radii. - where (isBound) - radiusFinal=-gravitationalConstantGalacticus & - & *massEnclosed & - & /energyFinal - elsewhere - radiusFinal=+huge(0.0d0) - end where - ! Build the final profile interpolator. - self%isBound=count(isBound) > 2 - if (self%isBound) then - self%radiusFinalMinimum =minval(radiusFinal ,mask=isBound) - self%radiusFinalMaximum =maxval(radiusFinal ,mask=isBound) - ! Construct the interpolator. - if (allocated(self%massProfile)) deallocate(self%massProfile) - allocate(self%massProfile) - self%massProfile=interpolator( & - & x =log(pack(radiusFinal ,mask=isBound)), & - & y =log(pack(massEnclosed,mask=isBound)), & - & extrapolationType=extrapolationTypeFix & - & ) - end if - return - end subroutine heatedMonotonicComputeSolution - - double precision function heatedMonotonicEnclosedMass(self,node,radius) - !!{ - Returns the enclosed mass (in $M_\odot$) in the dark matter profile of {\normalfont \ttfamily node} at the given {\normalfont \ttfamily radius} (given in - units of Mpc). - !!} - implicit none - class (darkMatterProfileDMOHeatedMonotonic), intent(inout) :: self - type (treeNode ), intent(inout) :: node - double precision , intent(in ) :: radius - - if (self%darkMatterProfileHeating_%specificEnergyIsEverywhereZero(node,self%darkMatterProfileDMO_)) then - ! No heating - use the unheated solution. - heatedMonotonicEnclosedMass=self%darkMatterProfileDMO_%enclosedMass(node,radius) - else if (radius <= 0.0d0) then - ! Non-positive radius, mass must be zero. - heatedMonotonicEnclosedMass=0.0d0 - else - ! Compute the solution (as needed). - call self%computeSolution(node,radius) - ! For bound halos, interpolate to find the enclosed mass. For unbound halos the enclosed mass is zero. - if (self%isBound) then - if (radius < self%radiusFinalMinimum) then - ! Assume constant density below the minimum radius. - heatedMonotonicEnclosedMass=+exp(self%massProfile%interpolate(log(self%radiusFinalMinimum))) & - & *( & - & + radius & - & /self%radiusFinalMinimum & - & )**3 - else - heatedMonotonicEnclosedMass=+exp(self%massProfile%interpolate(log(radius))) - end if - else - heatedMonotonicEnclosedMass=0.0d0 - end if - end if - return - end function heatedMonotonicEnclosedMass - - double precision function heatedMonotonicDensity(self,node,radius) + function heatedMonotonicGet(self,node,weightBy,weightIndex) result(massDistribution_) !!{ - Returns the density (in $M_\odot$ Mpc$^{-3}$) in the dark matter profile of {\normalfont \ttfamily node} at the given - {\normalfont \ttfamily radius} (given in units of Mpc). - !!} - use :: Numerical_Constants_Math, only : Pi - implicit none - class (darkMatterProfileDMOHeatedMonotonic), intent(inout) :: self - type (treeNode ), intent(inout) :: node - double precision , intent(in ) :: radius - double precision :: radius_ - - if (self%darkMatterProfileHeating_%specificEnergyIsEverywhereZero(node,self%darkMatterProfileDMO_)) then - ! No heating - use the unheated solution. - heatedMonotonicDensity=self%darkMatterProfileDMO_%density(node,radius) - else - call self%computeSolution(node,radius) - ! For bound halos, interpolate to find the density. For unbound halos the density is zero. - if (self%isBound) then - radius_ =max(radius,self%radiusFinalMinimum) - heatedMonotonicDensity=+ self%massProfile%derivative (log(radius_)) & - & *exp(self%massProfile%interpolate(log(radius_))) & - & /4.0d0 & - & /Pi & - & /radius**3 - else - heatedMonotonicDensity=+0.0d0 - end if - end if - return - end function heatedMonotonicDensity - - double precision function heatedMonotonicDensityLogSlope(self,node,radius) - !!{ - Returns the logarithmic slope of the density in the dark matter profile of {\normalfont \ttfamily node} at the given - {\normalfont \ttfamily radius} (given in units of Mpc). - !!} - implicit none - class (darkMatterProfileDMOHeatedMonotonic), intent(inout) :: self - type (treeNode ), intent(inout) :: node - double precision , intent(in ) :: radius - - if (self%darkMatterProfileHeating_%specificEnergyIsEverywhereZero(node,self%darkMatterProfileDMO_) .or. self%nonAnalyticSolver == nonAnalyticSolversFallThrough) then - heatedMonotonicDensityLogSlope=self%darkMatterProfileDMO_%densityLogSlope (node,radius) - else - heatedMonotonicDensityLogSlope=self %densityLogSlopeNumerical(node,radius) - end if - return - end function heatedMonotonicDensityLogSlope - - double precision function heatedMonotonicRadiusEnclosingDensity(self,node,density) - !!{ - Returns the radius (in Mpc) in the dark matter profile of {\normalfont \ttfamily node} which encloses the given - {\normalfont \ttfamily density} (given in units of $M_\odot/$Mpc$^{-3}$). - !!} - implicit none - class (darkMatterProfileDMOHeatedMonotonic), intent(inout), target :: self - type (treeNode ), intent(inout), target :: node - double precision , intent(in ) :: density - - if (self%darkMatterProfileHeating_%specificEnergyIsEverywhereZero(node,self%darkMatterProfileDMO_) .or. self%nonAnalyticSolver == nonAnalyticSolversFallThrough) then - heatedMonotonicRadiusEnclosingDensity=self%darkMatterProfileDMO_%radiusEnclosingDensity (node,density) - else - heatedMonotonicRadiusEnclosingDensity=self %radiusEnclosingDensityNumerical(node,density) - end if - return - end function heatedMonotonicRadiusEnclosingDensity - - double precision function heatedMonotonicRadiusEnclosingMass(self,node,mass) - !!{ - Returns the radius (in Mpc) in the dark matter profile of {\normalfont \ttfamily node} which encloses the given - {\normalfont \ttfamily density} (given in units of $M_\odot/$Mpc$^{-3}$). - !!} - implicit none - class (darkMatterProfileDMOHeatedMonotonic), intent(inout), target :: self - type (treeNode ), intent(inout), target :: node - double precision , intent(in ) :: mass - - if (self%darkMatterProfileHeating_%specificEnergyIsEverywhereZero(node,self%darkMatterProfileDMO_) .or. self%nonAnalyticSolver == nonAnalyticSolversFallThrough) then - heatedMonotonicRadiusEnclosingMass=self%darkMatterProfileDMO_%radiusEnclosingMass (node,mass) - else - heatedMonotonicRadiusEnclosingMass=self %radiusEnclosingMassNumerical(node,mass) - end if - return - end function heatedMonotonicRadiusEnclosingMass - - double precision function heatedMonotonicRadialMoment(self,node,moment,radiusMinimum,radiusMaximum) - !!{ - Returns the density (in $M_\odot$ Mpc$^{-3}$) in the dark matter profile of {\normalfont \ttfamily node} at the given - {\normalfont \ttfamily radius} (given in units of Mpc). + Return the dark matter mass distribution for the given {\normalfont \ttfamily node}. !!} + use :: Galactic_Structure_Options, only : componentTypeDarkHalo , massTypeDark , weightByMass + use :: Mass_Distributions , only : massDistributionSphericalHeatedMonotonic, kinematicsDistributionCollisionless, massDistributionSpherical, massDistributionHeatingClass implicit none + class (massDistributionClass ), pointer :: massDistribution_ + type (kinematicsDistributionCollisionless), pointer :: kinematicsDistribution_ class (darkMatterProfileDMOHeatedMonotonic), intent(inout) :: self type (treeNode ), intent(inout) :: node - double precision , intent(in ) :: moment - double precision , intent(in ), optional :: radiusMinimum, radiusMaximum - - if (self%darkMatterProfileHeating_%specificEnergyIsEverywhereZero(node,self%darkMatterProfileDMO_) .or. self%nonAnalyticSolver == nonAnalyticSolversFallThrough) then - heatedMonotonicRadialMoment=self%darkMatterProfileDMO_%radialMoment (node,moment,radiusMinimum,radiusMaximum) - else - heatedMonotonicRadialMoment=self %radialMomentNumerical(node,moment,radiusMinimum,radiusMaximum) - end if - return - end function heatedMonotonicRadialMoment - - double precision function heatedMonotonicPotential(self,node,radius,status) - !!{ - Returns the potential (in (km/s)$^2$) in the dark matter profile of {\normalfont \ttfamily node} at the given {\normalfont - \ttfamily radius} (given in units of Mpc). - !!} - implicit none - class (darkMatterProfileDMOHeatedMonotonic), intent(inout) :: self - type (treeNode ), intent(inout), target :: node - double precision , intent(in ) :: radius - type (enumerationStructureErrorCodeType ), intent( out), optional :: status - - if (self%darkMatterProfileHeating_%specificEnergyIsEverywhereZero(node,self%darkMatterProfileDMO_) .or. self%nonAnalyticSolver == nonAnalyticSolversFallThrough) then - heatedMonotonicPotential=self%darkMatterProfileDMO_%potential (node,radius,status) - else - heatedMonotonicPotential=self %potentialNumerical(node,radius,status) - end if - return - end function heatedMonotonicPotential - - double precision function heatedMonotonicCircularVelocity(self,node,radius) - !!{ - Returns the circular velocity (in km/s) in the dark matter profile of {\normalfont \ttfamily node} at the given - {\normalfont \ttfamily radius} (given in units of Mpc). - !!} - use :: Numerical_Constants_Astronomical, only : gravitationalConstantGalacticus - implicit none - class (darkMatterProfileDMOHeatedMonotonic), intent(inout) :: self - type (treeNode ), intent(inout) :: node - double precision , intent(in ) :: radius - - if (radius > 0.0d0) then - heatedMonotonicCircularVelocity=sqrt( & - & +gravitationalConstantGalacticus & - & *self%enclosedMass(node,radius) & - & / radius & - & ) - else - heatedMonotonicCircularVelocity=0.0d0 - end if - return - end function heatedMonotonicCircularVelocity - - double precision function heatedMonotonicRadiusCircularVelocityMaximum(self,node) - !!{ - Returns the radius (in Mpc) at which the maximum circular velocity is achieved in the dark matter profile of {\normalfont \ttfamily node}. - !!} - implicit none - class(darkMatterProfileDMOHeatedMonotonic), intent(inout) :: self - type (treeNode ), intent(inout) :: node - - if (self%darkMatterProfileHeating_%specificEnergyIsEverywhereZero(node,self%darkMatterProfileDMO_) .or. self%nonAnalyticSolver == nonAnalyticSolversFallThrough) then - heatedMonotonicRadiusCircularVelocityMaximum=self%darkMatterProfileDMO_%radiusCircularVelocityMaximum (node) - else - heatedMonotonicRadiusCircularVelocityMaximum=self %radiusCircularVelocityMaximumNumerical(node) - end if - return - end function heatedMonotonicRadiusCircularVelocityMaximum - - double precision function heatedMonotonicCircularVelocityMaximum(self,node) - !!{ - Returns the maximum circular velocity (in km/s) in the dark matter profile of {\normalfont \ttfamily node}. - !!} - implicit none - class(darkMatterProfileDMOHeatedMonotonic), intent(inout) :: self - type (treeNode ), intent(inout) :: node - - if (self%darkMatterProfileHeating_%specificEnergyIsEverywhereZero(node,self%darkMatterProfileDMO_) .or. self%nonAnalyticSolver == nonAnalyticSolversFallThrough) then - heatedMonotonicCircularVelocityMaximum=self%darkMatterProfileDMO_%circularVelocityMaximum (node) - else - heatedMonotonicCircularVelocityMaximum=self %circularVelocityMaximumNumerical(node) - end if - return - end function heatedMonotonicCircularVelocityMaximum - - double precision function heatedMonotonicRadialVelocityDispersion(self,node,radius) - !!{ - Returns the radial velocity dispersion (in km/s) in the dark matter profile of {\normalfont \ttfamily node} at the given - {\normalfont \ttfamily radius} (given in units of Mpc). - !!} - implicit none - class (darkMatterProfileDMOHeatedMonotonic), intent(inout) :: self - type (treeNode ), intent(inout) :: node - double precision , intent(in ) :: radius - - if (self%darkMatterProfileHeating_%specificEnergyIsEverywhereZero(node,self%darkMatterProfileDMO_) .or. self%nonAnalyticSolver == nonAnalyticSolversFallThrough) then - heatedMonotonicRadialVelocityDispersion=self%darkMatterProfileDMO_%radialVelocityDispersion (node,radius) - else - heatedMonotonicRadialVelocityDispersion=self %radialVelocityDispersionNumerical(node,radius) - end if - return - end function heatedMonotonicRadialVelocityDispersion - - double precision function heatedMonotonicRadiusFromSpecificAngularMomentum(self,node,specificAngularMomentum) - !!{ - Returns the radius (in Mpc) in {\normalfont \ttfamily node} at which a circular orbit has the given {\normalfont \ttfamily specificAngularMomentum} (given - in units of km s$^{-1}$ Mpc). - !!} - implicit none - class (darkMatterProfileDMOHeatedMonotonic), intent(inout) :: self - type (treeNode ), intent(inout) :: node - double precision , intent(in ) :: specificAngularMomentum - - if (self%darkMatterProfileHeating_%specificEnergyIsEverywhereZero(node,self%darkMatterProfileDMO_) .or. self%nonAnalyticSolver == nonAnalyticSolversFallThrough) then - heatedMonotonicRadiusFromSpecificAngularMomentum=self%darkMatterProfileDMO_%radiusFromSpecificAngularMomentum (node,specificAngularMomentum) - else - heatedMonotonicRadiusFromSpecificAngularMomentum=self %radiusFromSpecificAngularMomentumNumerical(node,specificAngularMomentum) - end if - return - end function heatedMonotonicRadiusFromSpecificAngularMomentum - - double precision function heatedMonotonicRotationNormalization(self,node) - !!{ - Return the normalization of the rotation velocity vs. specific angular momentum relation. - !!} - implicit none - class(darkMatterProfileDMOHeatedMonotonic), intent(inout) :: self - type (treeNode ), intent(inout) :: node - - if (self%darkMatterProfileHeating_%specificEnergyIsEverywhereZero(node,self%darkMatterProfileDMO_) .or. self%nonAnalyticSolver == nonAnalyticSolversFallThrough) then - heatedMonotonicRotationNormalization=self%darkMatterProfileDMO_%rotationNormalization (node) - else - heatedMonotonicRotationNormalization=self %rotationNormalizationNumerical(node) - end if - return - end function heatedMonotonicRotationNormalization - - double precision function heatedMonotonicEnergy(self,node) - !!{ - Return the energy of a heated halo density profile. - !!} - implicit none - class(darkMatterProfileDMOHeatedMonotonic), intent(inout) :: self - type (treeNode ), intent(inout) :: node - - if (self%darkMatterProfileHeating_%specificEnergyIsEverywhereZero(node,self%darkMatterProfileDMO_) .or. self%nonAnalyticSolver == nonAnalyticSolversFallThrough) then - heatedMonotonicEnergy=self%darkMatterProfileDMO_%energy (node) - else - heatedMonotonicEnergy=self %energyNumerical(node) - end if - return - end function heatedMonotonicEnergy - - double precision function heatedMonotonicKSpace(self,node,waveNumber) - !!{ - Returns the Fourier transform of the heated density profile at the specified {\normalfont \ttfamily waveNumber} - (given in Mpc$^{-1}$), using the expression given in \citeauthor{cooray_halo_2002}~(\citeyear{cooray_halo_2002}; eqn.~81). - !!} - implicit none - class (darkMatterProfileDMOHeatedMonotonic), intent(inout) :: self - type (treeNode ), intent(inout), target :: node - double precision , intent(in ) :: waveNumber - - if (self%darkMatterProfileHeating_%specificEnergyIsEverywhereZero(node,self%darkMatterProfileDMO_) .or. self%nonAnalyticSolver == nonAnalyticSolversFallThrough) then - heatedMonotonicKSpace=self%darkMatterProfileDMO_%kSpace (node,waveNumber) - else - heatedMonotonicKSpace=self %kSpaceNumerical(node,waveNumber) - end if - return - end function heatedMonotonicKSpace - - double precision function heatedMonotonicFreefallRadius(self,node,time) - !!{ - Returns the freefall radius in the heated density profile at the specified {\normalfont \ttfamily time} (given in - Gyr). - !!} - implicit none - class (darkMatterProfileDMOHeatedMonotonic), intent(inout), target :: self - type (treeNode ), intent(inout), target :: node - double precision , intent(in ) :: time - - if (self%darkMatterProfileHeating_%specificEnergyIsEverywhereZero(node,self%darkMatterProfileDMO_) .or. self%nonAnalyticSolver == nonAnalyticSolversFallThrough) then - heatedMonotonicFreefallRadius=self%darkMatterProfileDMO_%freefallRadius (node,time) - else - heatedMonotonicFreefallRadius=self %freefallRadiusNumerical(node,time) - end if - return - end function heatedMonotonicFreefallRadius - - double precision function heatedMonotonicFreefallRadiusIncreaseRate(self,node,time) - !!{ - Returns the rate of increase of the freefall radius in the heated density profile at the specified {\normalfont - \ttfamily time} (given in Gyr). - !!} - implicit none - class (darkMatterProfileDMOHeatedMonotonic), intent(inout), target :: self - type (treeNode ), intent(inout), target :: node - double precision , intent(in ) :: time + type (enumerationWeightByType ), intent(in ), optional :: weightBy + integer , intent(in ), optional :: weightIndex + class (massDistributionClass ), pointer :: massDistributionDecorated + class (massDistributionHeatingClass ), pointer :: massDistributionHeating_ + !![ + + !!] - if (self%darkMatterProfileHeating_%specificEnergyIsEverywhereZero(node,self%darkMatterProfileDMO_) .or. self%nonAnalyticSolver == nonAnalyticSolversFallThrough) then - heatedMonotonicFreefallRadiusIncreaseRate=self%darkMatterProfileDMO_%freefallRadiusIncreaseRate (node,time) - else - heatedMonotonicFreefallRadiusIncreaseRate=self %freefallRadiusIncreaseRateNumerical(node,time) - end if + ! Assume a null distribution by default. + massDistribution_ => null() + ! If weighting is not by mass, return a null profile. + if (weightBy_ /= weightByMass) return + ! Create the mass distribution. + allocate(massDistributionSphericalHeatedMonotonic :: massDistribution_) + select type(massDistribution_) + type is (massDistributionSphericalHeatedMonotonic) + massDistributionDecorated => self%darkMatterProfileDMO_ %get(node,weightBy,weightIndex) + massDistributionHeating_ => self%darkMatterProfileHeating_%get(node ) + select type (massDistributionDecorated) + class is (massDistributionSpherical) + !![ + + + massDistributionSphericalHeatedMonotonic( & + & radiusVirial =self%darkMatterHaloScale_%radiusVirial (node), & + & nonAnalyticSolver =self %nonAnalyticSolver , & + & massDistribution_ = massDistributionDecorated , & + & massDistributionHeating_= massDistributionHeating_ , & + & componentType = componentTypeDarkHalo , & + & massType = massTypeDark & + & ) + + + !!] + class default + call Error_Report('expected a spherical mass distribution'//{introspection:location}) + end select + !![ + + + !!] + end select + allocate(kinematicsDistribution_) + !![ + + + kinematicsDistributionCollisionless( & + & toleranceRelativeVelocityDispersion =self%toleranceRelativeVelocityDispersion , & + & toleranceRelativeVelocityDispersionMaximum=self%toleranceRelativeVelocityDispersionMaximum & + & ) + + + !!] + call massDistribution_%setKinematicsDistribution(kinematicsDistribution_) + !![ + + !!] return - end function heatedMonotonicFreefallRadiusIncreaseRate + end function heatedMonotonicGet diff --git a/source/dark_matter_profiles_DMO.isothermal.F90 b/source/dark_matter_profiles_DMO.isothermal.F90 index d13aa5d97e..5c50c49ebb 100644 --- a/source/dark_matter_profiles_DMO.isothermal.F90 +++ b/source/dark_matter_profiles_DMO.isothermal.F90 @@ -21,14 +21,13 @@ An implementation of isothermal dark matter halo profiles. !!} + use :: Dark_Matter_Halo_Scales, only : darkMatterHaloScaleClass + !![ - A dark matter profile DMO class in which the density profile is given by: - \begin{equation} - \rho_\mathrm{dark matter}(r) \propto r^{-2}, - \end{equation} - normalized such that the total mass of the \gls{node} is enclosed with the virial radius. + A dark matter profile DMO class in which builds \refClass{} objects to implement isothermal density profiles, normalized such + that the total mass of the \gls{node} is enclosed with the virial radius. !!] @@ -37,24 +36,10 @@ A dark matter halo profile class implementing isothermal dark matter halos. !!} private + class(darkMatterHaloScaleClass), pointer :: darkMatterHaloScale_ => null() contains - final :: isothermalDestructor - procedure :: density => isothermalDensity - procedure :: densityLogSlope => isothermalDensityLogSlope - procedure :: radialMoment => isothermalRadialMoment - procedure :: enclosedMass => isothermalEnclosedMass - procedure :: radiusEnclosingDensity => isothermalRadiusEnclosingDensity - procedure :: potential => isothermalPotential - procedure :: circularVelocity => isothermalCircularVelocity - procedure :: radiusCircularVelocityMaximum => isothermalRadiusCircularVelocityMaximum - procedure :: circularVelocityMaximum => isothermalCircularVelocityMaximum - procedure :: radialVelocityDispersion => isothermalRadialVelocityDispersion - procedure :: radiusFromSpecificAngularMomentum => isothermalRadiusFromSpecificAngularMomentum - procedure :: rotationNormalization => isothermalRotationNormalization - procedure :: energy => isothermalEnergy - procedure :: kSpace => isothermalKSpace - procedure :: freefallRadius => isothermalFreefallRadius - procedure :: freefallRadiusIncreaseRate => isothermalFreefallRadiusIncreaseRate + final :: isothermalDestructor + procedure :: get => isothermalGet end type darkMatterProfileDMOIsothermal interface darkMatterProfileDMOIsothermal @@ -115,308 +100,60 @@ subroutine isothermalDestructor(self) return end subroutine isothermalDestructor - double precision function isothermalDensity(self,node,radius) + function isothermalGet(self,node,weightBy,weightIndex) result(massDistribution_) !!{ - Returns the density (in $M_\odot$ Mpc$^{-3}$) in the dark matter profile of {\normalfont \ttfamily node} at the given {\normalfont \ttfamily radius} (given - in units of Mpc). + Return the dark matter mass distribution for the given {\normalfont \ttfamily node}. !!} - use :: Galacticus_Nodes , only : nodeComponentBasic, treeNode - use :: Numerical_Constants_Math, only : Pi + use :: Galacticus_Nodes , only : nodeComponentBasic + use :: Galactic_Structure_Options, only : componentTypeDarkHalo , massTypeDark , weightByMass + use :: Mass_Distributions , only : massDistributionIsothermal, kinematicsDistributionIsothermal implicit none - class (darkMatterProfileDMOIsothermal), intent(inout) :: self - type (treeNode ), intent(inout) :: node - double precision , intent(in ) :: radius - class (nodeComponentBasic ), pointer :: basic - - basic => node %basic() - isothermalDensity = basic%mass ()/4.0d0/Pi/self%darkMatterHaloScale_%radiusVirial(node)/radius**2 - return - end function isothermalDensity - - double precision function isothermalDensityLogSlope(self,node,radius) - !!{ - Returns the logarithmic slope of the density in the dark matter profile of {\normalfont \ttfamily node} at the given - {\normalfont \ttfamily radius} (given in units of Mpc). - !!} - implicit none - class (darkMatterProfileDMOIsothermal), intent(inout) :: self - type (treeNode ), intent(inout) :: node - double precision , intent(in ) :: radius - !$GLC attributes unused :: self, node, radius - - isothermalDensityLogSlope=-2.0d0 - return - end function isothermalDensityLogSlope - - double precision function isothermalRadialMoment(self,node,moment,radiusMinimum,radiusMaximum) - !!{ - Returns the density (in $M_\odot$ Mpc$^{-3}$) in the dark matter profile of {\normalfont \ttfamily node} at the given {\normalfont \ttfamily radius} (given - in units of Mpc). - !!} - use :: Galacticus_Nodes , only : nodeComponentBasic, treeNode - use :: Numerical_Comparison , only : Values_Agree - use :: Numerical_Constants_Math, only : Pi - implicit none - class (darkMatterProfileDMOIsothermal), intent(inout) :: self - type (treeNode ), intent(inout) :: node - double precision , intent(in ) :: moment - double precision , intent(in ), optional :: radiusMinimum , radiusMaximum - class (nodeComponentBasic ) , pointer :: basic - double precision :: radiusMinimumActual, radiusMaximumActual - - radiusMinimumActual=0.0d0 - radiusMaximumActual=self%darkMatterHaloScale_%radiusVirial(node) - if (present(radiusMinimum)) radiusMinimumActual=radiusMinimum - if (present(radiusMaximum)) radiusMaximumActual=radiusMaximum - basic => node%basic () - if (Values_Agree(moment,1.0d0,absTol=1.0d-6)) then - isothermalRadialMoment=+basic%mass() & - & /4.0d0 & - & /Pi & - & /self%darkMatterHaloScale_%radiusVirial(node) & - & *log( & - & +radiusMaximumActual & - & /radiusMinimumActual & - & ) - else - isothermalRadialMoment=+basic%mass() & - & /4.0d0 & - & /Pi & - & /self%darkMatterHaloScale_%radiusVirial(node) & - & / (moment-1.0d0) & - & *( & - & +radiusMaximumActual**(moment-1.0d0) & - & -radiusMinimumActual**(moment-1.0d0) & - & ) - end if - return - end function isothermalRadialMoment - - double precision function isothermalEnclosedMass(self,node,radius) - !!{ - Returns the enclosed mass (in $M_\odot$) in the dark matter profile of {\normalfont \ttfamily node} at the given {\normalfont \ttfamily radius} (given in - units of Mpc). - !!} - use :: Galacticus_Nodes, only : nodeComponentBasic, treeNode - implicit none - class (darkMatterProfileDMOIsothermal), intent(inout) :: self - type (treeNode ), intent(inout) :: node - double precision , intent(in ) :: radius - class (nodeComponentBasic ), pointer :: basic - - basic => node%basic () - isothermalEnclosedMass=basic%mass()*(radius/self%darkMatterHaloScale_%radiusVirial(node)) - return - end function isothermalEnclosedMass - - double precision function isothermalPotential(self,node,radius,status) - !!{ - Returns the potential (in (km/s)$^2$) in the dark matter profile of {\normalfont \ttfamily node} at the given {\normalfont \ttfamily radius} (given in - units of Mpc). - !!} - use :: Galactic_Structure_Options, only : enumerationStructureErrorCodeType, structureErrorCodeInfinite, structureErrorCodeSuccess - implicit none - class (darkMatterProfileDMOIsothermal ), intent(inout) :: self - type (treeNode ), intent(inout), target :: node - double precision , intent(in ) :: radius - type (enumerationStructureErrorCodeType), intent( out), optional :: status - double precision , parameter :: radiusFractionalMinimum=1.0d-30 - double precision :: radiusFractional - - radiusFractional = radius/self%darkMatterHaloScale_%radiusVirial(node) - if (radiusFractional <= 0.0d0) then - isothermalPotential=0.0d0 - if (present(status)) status=structureErrorCodeInfinite - else - isothermalPotential=log(radiusFractional)*self%darkMatterHaloScale_%velocityVirial(node)**2 - if (present(status)) status=structureErrorCodeSuccess - end if - return - end function isothermalPotential - - double precision function isothermalCircularVelocity(self,node,radius) - !!{ - Returns the circular velocity (in km/s) in the dark matter profile of {\normalfont \ttfamily node} at the given {\normalfont \ttfamily radius} (given in - units of Mpc). For an isothermal halo this is independent of radius and therefore equal to the virial velocity. - !!} - implicit none - class (darkMatterProfileDMOIsothermal), intent(inout) :: self - type (treeNode ), intent(inout) :: node - double precision , intent(in ) :: radius - !$GLC attributes unused :: radius - - isothermalCircularVelocity=self%darkMatterHaloScale_%velocityVirial(node) - return - end function isothermalCircularVelocity - - double precision function isothermalRadiusCircularVelocityMaximum(self,node) - !!{ - Returns the radius (in Mpc) at which the maximum circular velocity is achieved in the dark matter profile of {\normalfont \ttfamily node}. For an isothermal halo circular - velocity is independent of radius, so a value of the virial radius is returned. - !!} - implicit none - class(darkMatterProfileDMOIsothermal), intent(inout) :: self - type (treeNode ), intent(inout) :: node - - isothermalRadiusCircularVelocityMaximum=self%darkMatterHaloScale_%radiusVirial(node) - return - end function isothermalRadiusCircularVelocityMaximum - - double precision function isothermalCircularVelocityMaximum(self,node) - !!{ - Returns the maximum circular velocity (in km/s) in the dark matter profile of {\normalfont \ttfamily node}. For an isothermal halo circular - velocity is independent of radius. - !!} - implicit none - class(darkMatterProfileDMOIsothermal), intent(inout) :: self - type (treeNode ), intent(inout) :: node - - isothermalCircularVelocityMaximum=self%circularVelocity(node,0.0d0) - return - end function isothermalCircularVelocityMaximum - - double precision function isothermalRadialVelocityDispersion(self,node,radius) - !!{ - Returns the radial velocity dispersion (in km/s) in the dark matter profile of {\normalfont \ttfamily node} at the given {\normalfont \ttfamily radius} - (given in units of Mpc). For an isothermal halo this is independent of radius and equal to the virial velocity divided by $\sqrt(2)$. - !!} - implicit none - class (darkMatterProfileDMOIsothermal), intent(inout) :: self - type (treeNode ), intent(inout) :: node - double precision , intent(in ) :: radius - !$GLC attributes unused :: radius - - isothermalRadialVelocityDispersion=self%darkMatterHaloScale_%velocityVirial(node)/sqrt(2.0d0) - return - end function isothermalRadialVelocityDispersion - - double precision function isothermalRadiusFromSpecificAngularMomentum(self,node,specificAngularMomentum) - !!{ - Returns the radius (in Mpc) in {\normalfont \ttfamily node} at which a circular orbit has the given {\normalfont \ttfamily specificAngularMomentum} (given - in units of km s$^{-1}$ Mpc). For an isothermal halo, the circular velocity is constant (and therefore equal to the virial - velocity). Therefore, $r = j/V_\mathrm{virial}$ where $j$(={\normalfont \ttfamily specificAngularMomentum}) is the specific angular momentum and - $r$ the required radius. - !!} - implicit none - class (darkMatterProfileDMOIsothermal), intent(inout) :: self - type (treeNode ), intent(inout) :: node - double precision , intent(in ) :: specificAngularMomentum - - isothermalRadiusFromSpecificAngularMomentum=specificAngularMomentum/self%darkMatterHaloScale_%velocityVirial(node) - return - end function isothermalRadiusFromSpecificAngularMomentum - - double precision function isothermalRotationNormalization(self,node) - !!{ - Return the normalization of the rotation velocity vs. specific angular momentum relation. - !!} - implicit none - class(darkMatterProfileDMOIsothermal), intent(inout) :: self - type (treeNode ), intent(inout) :: node - - isothermalRotationNormalization=2.0d0/self%darkMatterHaloScale_%radiusVirial(node) - return - end function isothermalRotationNormalization - - double precision function isothermalEnergy(self,node) - !!{ - Return the energy of an isothermal halo density profile. - !!} - use :: Galacticus_Nodes, only : nodeComponentBasic, treeNode - implicit none - class(darkMatterProfileDMOIsothermal), intent(inout) :: self - type (treeNode ), intent(inout) :: node - class(nodeComponentBasic ), pointer :: basic - - basic => node%basic () - isothermalEnergy = -0.5d0*basic%mass()*self%darkMatterHaloScale_%velocityVirial(node)**2 - return - end function isothermalEnergy - - double precision function isothermalKSpace(self,node,waveNumber) - !!{ - Returns the Fourier transform of the isothermal density profile at the specified {\normalfont \ttfamily waveNumber} (given in Mpc$^{-1}$), using the - expression given in \citeauthor{cooray_halo_2002}~(\citeyear{cooray_halo_2002}; table~1). - !!} - use :: Exponential_Integrals, only : Sine_Integral - implicit none - class (darkMatterProfileDMOIsothermal), intent(inout) :: self - type (treeNode ), intent(inout), target :: node - double precision , intent(in ) :: waveNumber - double precision :: radiusScale, waveNumberScaleFree - - ! Get the scale radius (for which we use the virial radius). - radiusScale = self%darkMatterHaloScale_%radiusVirial(node) - - ! Get the dimensionless wavenumber. - waveNumberScaleFree=waveNumber*radiusScale - - ! Compute the Fourier transformed profile. - isothermalKSpace=Sine_Integral(waveNumberScaleFree)/waveNumberScaleFree - - return - end function isothermalKSpace - - double precision function isothermalFreefallRadius(self,node,time) - !!{ - Returns the freefall radius in the isothermal density profile at the specified {\normalfont \ttfamily time} (given in Gyr). For an isothermal - potential, the freefall radius, $r_\mathrm{ff}(t)$, is: - \begin{equation} - r_\mathrm{ff}(t) = \sqrt{{2 \over \pi}} V_\mathrm{virial} t. - \end{equation} - !!} - use :: Numerical_Constants_Astronomical, only : Mpc_per_km_per_s_To_Gyr - implicit none - class (darkMatterProfileDMOIsothermal), intent(inout), target :: self - type (treeNode ), intent(inout), target :: node - double precision , intent(in ) :: time - - isothermalFreefallRadius=sqrt(2.0d0/Pi)*self%darkMatterHaloScale_%velocityVirial(node)*time& - &/Mpc_per_km_per_s_To_Gyr - return - end function isothermalFreefallRadius - - double precision function isothermalFreefallRadiusIncreaseRate(self,node,time) - !!{ - Returns the rate of increase of the freefall radius in the isothermal density profile at the specified {\normalfont \ttfamily time} (given in - Gyr). For an isothermal potential, the rate of increase of the freefall radius, $\dot{r}_\mathrm{ff}(t)$, is: - \begin{equation} - \dot{r}_\mathrm{ff}(t) = \sqrt{{2 \over \pi}} V_\mathrm{virial}. - \end{equation} - !!} - use :: Numerical_Constants_Astronomical, only : Mpc_per_km_per_s_To_Gyr - implicit none - class (darkMatterProfileDMOIsothermal), intent(inout), target :: self - type (treeNode ), intent(inout), target :: node - double precision , intent(in ) :: time - !$GLC attributes unused :: time - - isothermalFreefallRadiusIncreaseRate=sqrt(2.0d0/Pi)*self%darkMatterHaloScale_%velocityVirial(node) & - & /Mpc_per_km_per_s_To_Gyr - return - end function isothermalFreefallRadiusIncreaseRate - - double precision function isothermalRadiusEnclosingDensity(self,node,density) - !!{ - Null implementation of function to compute the radius enclosing a given density for isothermal dark matter halo profiles. - !!} - use :: Galacticus_Nodes , only : nodeComponentBasic, treeNode - use :: Numerical_Constants_Math, only : Pi - implicit none - class (darkMatterProfileDMOIsothermal), intent(inout), target :: self - type (treeNode ), intent(inout), target :: node - double precision , intent(in ) :: density - class (nodeComponentBasic ), pointer :: basic + class (massDistributionClass ), pointer :: massDistribution_ + type (kinematicsDistributionIsothermal), pointer :: kinematicsDistribution_ + class (darkMatterProfileDMOIsothermal ), intent(inout) :: self + type (treeNode ), intent(inout) :: node + type (enumerationWeightByType ), intent(in ), optional :: weightBy + integer , intent(in ), optional :: weightIndex + class (nodeComponentBasic ), pointer :: basic + !![ + + !!] - basic => node %basic ( ) - isothermalRadiusEnclosingDensity = +sqrt( & - & + basic %mass ( ) & - & /4.0d0 & - & *3.0d0 & - & /Pi & - & /self%darkMatterHaloScale_%radiusVirial(node) & - & /density & - & ) + ! Assume a null distribution by default. + massDistribution_ => null() + ! If weighting is not by mass, return a null profile. + if (weightBy_ /= weightByMass) return + ! Create the mass distribution. + allocate(massDistributionIsothermal :: massDistribution_) + select type(massDistribution_) + type is (massDistributionIsothermal) + basic => node%basic() + !![ + + + massDistributionIsothermal( & + & mass =basic %mass ( ), & + & lengthReference=self %darkMatterHaloScale_%radiusVirial (node), & + & componentType = componentTypeDarkHalo , & + & massType = massTypeDark & + & ) + + + !!] + end select + allocate(kinematicsDistribution_) + !![ + + + kinematicsDistributionIsothermal( & + & velocityDispersion_=self %darkMatterHaloScale_%velocityVirial(node)/sqrt(2.0d0) & + & ) + + + !!] + call massDistribution_%setKinematicsDistribution(kinematicsDistribution_) + !![ + + !!] return - end function isothermalRadiusEnclosingDensity - + end function isothermalGet diff --git a/source/dark_matter_profiles_DMO.multiple.F90 b/source/dark_matter_profiles_DMO.multiple.F90 index 0f30ad3ca9..10adb6a5dc 100644 --- a/source/dark_matter_profiles_DMO.multiple.F90 +++ b/source/dark_matter_profiles_DMO.multiple.F90 @@ -38,24 +38,8 @@ private class(darkMatterProfileDMOClass), pointer :: darkMatterProfileDMOHost_ => null(), darkMatterProfileDMOSatellite_ => null() contains - final :: multipleDestructor - procedure :: density => multipleDensity - procedure :: densityLogSlope => multipleDensityLogSlope - procedure :: radiusEnclosingDensity => multipleRadiusEnclosingDensity - procedure :: radiusEnclosingMass => multipleRadiusEnclosingMass - procedure :: radialMoment => multipleRadialMoment - procedure :: enclosedMass => multipleEnclosedMass - procedure :: potential => multiplePotential - procedure :: circularVelocity => multipleCircularVelocity - procedure :: radiusCircularVelocityMaximum => multipleRadiusCircularVelocityMaximum - procedure :: circularVelocityMaximum => multipleCircularVelocityMaximum - procedure :: radialVelocityDispersion => multipleRadialVelocityDispersion - procedure :: radiusFromSpecificAngularMomentum => multipleRadiusFromSpecificAngularMomentum - procedure :: rotationNormalization => multipleRotationNormalization - procedure :: energy => multipleEnergy - procedure :: kSpace => multipleKSpace - procedure :: freefallRadius => multipleFreefallRadius - procedure :: freefallRadiusIncreaseRate => multipleFreefallRadiusIncreaseRate + final :: multipleDestructor + procedure :: get => multipleGet end type darkMatterProfileDMOMultiple interface darkMatterProfileDMOMultiple @@ -118,302 +102,21 @@ subroutine multipleDestructor(self) return end subroutine multipleDestructor - double precision function multipleDensity(self,node,radius) + function multipleGet(self,node,weightBy,weightIndex) result(massDistribution_) !!{ - Returns the density (in $M_\odot$ Mpc$^{-3}$) in the dark matter profile of {\normalfont \ttfamily node} at the given - {\normalfont \ttfamily radius} (given in units of Mpc). + Return the dark matter mass distribution for the given {\normalfont \ttfamily node}. !!} implicit none - class (darkMatterProfileDMOMultiple), intent(inout) :: self - type (treeNode ), intent(inout) :: node - double precision , intent(in ) :: radius + class (massDistributionClass ), pointer :: massDistribution_ + class (darkMatterProfileDMOMultiple), intent(inout) :: self + type (treeNode ), intent(inout) :: node + type (enumerationWeightByType ), intent(in ), optional :: weightBy + integer , intent(in ), optional :: weightIndex if (node%isSatellite()) then - multipleDensity=self%darkMatterProfileDMOSatellite_%density(node,radius) + massDistribution_ => self%darkMatterProfileDMOSatellite_%get(node,weightBy,weightIndex) else - multipleDensity=self%darkMatterProfileDMOHost_ %density(node,radius) + massDistribution_ => self%darkMatterProfileDMOHost_ %get(node,weightBy,weightIndex) end if return - end function multipleDensity - - double precision function multipleDensityLogSlope(self,node,radius) - !!{ - Returns the logarithmic slope of the density in the dark matter profile of {\normalfont \ttfamily node} at the given - {\normalfont \ttfamily radius} (given in units of Mpc). - !!} - implicit none - class (darkMatterProfileDMOMultiple), intent(inout) :: self - type (treeNode ), intent(inout) :: node - double precision , intent(in ) :: radius - - if (node%isSatellite()) then - multipleDensityLogSlope=self%darkMatterProfileDMOSatellite_%densityLogSlope(node,radius) - else - multipleDensityLogSlope=self%darkMatterProfileDMOHost_ %densityLogSlope(node,radius) - end if - return - end function multipleDensityLogSlope - - double precision function multipleRadiusEnclosingDensity(self,node,density) - !!{ - Returns the radius (in Mpc) in the dark matter profile of {\normalfont \ttfamily node} which encloses the given - {\normalfont \ttfamily density} (given in units of $M_\odot/$Mpc$^{-3}$). - !!} - implicit none - class (darkMatterProfileDMOMultiple), intent(inout), target :: self - type (treeNode ), intent(inout), target :: node - double precision , intent(in ) :: density - - if (node%isSatellite()) then - multipleRadiusEnclosingDensity=self%darkMatterProfileDMOSatellite_%radiusEnclosingDensity(node,density) - else - multipleRadiusEnclosingDensity=self%darkMatterProfileDMOHost_ %radiusEnclosingDensity(node,density) - end if - return - end function multipleRadiusEnclosingDensity - - double precision function multipleRadiusEnclosingMass(self,node,mass) - !!{ - Returns the radius (in Mpc) in the dark matter profile of {\normalfont \ttfamily node} which encloses the given - {\normalfont \ttfamily mass} (given in units of $M_\odot$). - !!} - implicit none - class (darkMatterProfileDMOMultiple), intent(inout), target :: self - type (treeNode ), intent(inout), target :: node - double precision , intent(in ) :: mass - - if (node%isSatellite()) then - multipleRadiusEnclosingMass=self%darkMatterProfileDMOSatellite_%radiusEnclosingMass(node,mass) - else - multipleRadiusEnclosingMass=self%darkMatterProfileDMOHost_ %radiusEnclosingMass(node,mass) - end if - return - end function multipleRadiusEnclosingMass - - double precision function multipleRadialMoment(self,node,moment,radiusMinimum,radiusMaximum) - !!{ - Returns the density (in $M_\odot$ Mpc$^{-3}$) in the dark matter profile of {\normalfont \ttfamily node} at the given - {\normalfont \ttfamily radius} (given in units of Mpc). - !!} - implicit none - class (darkMatterProfileDMOMultiple), intent(inout) :: self - type (treeNode ), intent(inout) :: node - double precision , intent(in ) :: moment - double precision , intent(in ), optional :: radiusMinimum, radiusMaximum - - if (node%isSatellite()) then - multipleRadialMoment=self%darkMatterProfileDMOSatellite_%radialMoment(node,moment,radiusMinimum,radiusMaximum) - else - multipleRadialMoment=self%darkMatterProfileDMOHost_ %radialMoment(node,moment,radiusMinimum,radiusMaximum) - end if - return - end function multipleRadialMoment - - double precision function multipleEnclosedMass(self,node,radius) - !!{ - Returns the enclosed mass (in $M_\odot$) in the dark matter profile of {\normalfont \ttfamily node} at the given {\normalfont \ttfamily radius} (given in - units of Mpc). - !!} - implicit none - class (darkMatterProfileDMOMultiple), intent(inout) :: self - type (treeNode ), intent(inout) :: node - double precision , intent(in ) :: radius - - if (node%isSatellite()) then - multipleEnclosedMass=self%darkMatterProfileDMOSatellite_%enclosedMass(node,radius) - else - multipleEnclosedMass=self%darkMatterProfileDMOHost_ %enclosedMass(node,radius) - end if - return - end function multipleEnclosedMass - - double precision function multiplePotential(self,node,radius,status) - !!{ - Returns the potential (in (km/s)$^2$) in the dark matter profile of {\normalfont \ttfamily node} at the given {\normalfont - \ttfamily radius} (given in units of Mpc). - !!} - implicit none - class (darkMatterProfileDMOMultiple ), intent(inout) :: self - type (treeNode ), intent(inout), target :: node - double precision , intent(in ) :: radius - type (enumerationStructureErrorCodeType), intent( out), optional :: status - - if (node%isSatellite()) then - multiplePotential=self%darkMatterProfileDMOSatellite_%potential(node,radius,status) - else - multiplePotential=self%darkMatterProfileDMOHost_ %potential(node,radius,status) - end if - return - end function multiplePotential - - double precision function multipleCircularVelocity(self,node,radius) - !!{ - Returns the circular velocity (in km/s) in the dark matter profile of {\normalfont \ttfamily node} at the given - {\normalfont \ttfamily radius} (given in units of Mpc). - !!} - implicit none - class (darkMatterProfileDMOMultiple), intent(inout) :: self - type (treeNode ), intent(inout) :: node - double precision , intent(in ) :: radius - - if (node%isSatellite()) then - multipleCircularVelocity=self%darkMatterProfileDMOSatellite_%circularVelocity(node,radius) - else - multipleCircularVelocity=self%darkMatterProfileDMOHost_ %circularVelocity(node,radius) - end if - return - end function multipleCircularVelocity - - double precision function multipleRadiusCircularVelocityMaximum(self,node) - !!{ - Returns the radius (in Mpc) at which the maximum circular velocity is achieved in the dark matter profile of {\normalfont \ttfamily node}. - !!} - implicit none - class(darkMatterProfileDMOMultiple), intent(inout) :: self - type (treeNode ), intent(inout) :: node - - if (node%isSatellite()) then - multipleRadiusCircularVelocityMaximum=self%darkMatterProfileDMOSatellite_%radiusCircularVelocityMaximum(node) - else - multipleRadiusCircularVelocityMaximum=self%darkMatterProfileDMOHost_ %radiusCircularVelocityMaximum(node) - end if - return - end function multipleRadiusCircularVelocityMaximum - - double precision function multipleCircularVelocityMaximum(self,node) - !!{ - Returns the maximum circular velocity (in km/s) in the dark matter profile of {\normalfont \ttfamily node}. - !!} - implicit none - class(darkMatterProfileDMOMultiple), intent(inout) :: self - type (treeNode ), intent(inout) :: node - - if (node%isSatellite()) then - multipleCircularVelocityMaximum=self%darkMatterProfileDMOSatellite_%circularVelocityMaximum(node) - else - multipleCircularVelocityMaximum=self%darkMatterProfileDMOHost_ %circularVelocityMaximum(node) - end if - return - end function multipleCircularVelocityMaximum - - double precision function multipleRadialVelocityDispersion(self,node,radius) - !!{ - Returns the radial velocity dispersion (in km/s) in the dark matter profile of {\normalfont \ttfamily node} at the given - {\normalfont \ttfamily radius} (given in units of Mpc). - !!} - implicit none - class (darkMatterProfileDMOMultiple), intent(inout) :: self - type (treeNode ), intent(inout) :: node - double precision , intent(in ) :: radius - - if (node%isSatellite()) then - multipleRadialVelocityDispersion=self%darkMatterProfileDMOSatellite_%radialVelocityDispersion(node,radius) - else - multipleRadialVelocityDispersion=self%darkMatterProfileDMOHost_ %radialVelocityDispersion(node,radius) - end if - return - end function multipleRadialVelocityDispersion - - double precision function multipleRadiusFromSpecificAngularMomentum(self,node,specificAngularMomentum) - !!{ - Returns the radius (in Mpc) in {\normalfont \ttfamily node} at which a circular orbit has the given {\normalfont \ttfamily specificAngularMomentum} (given - in units of km s$^{-1}$ Mpc). - !!} - implicit none - class (darkMatterProfileDMOMultiple), intent(inout) :: self - type (treeNode ), intent(inout) :: node - double precision , intent(in ) :: specificAngularMomentum - - if (node%isSatellite()) then - multipleRadiusFromSpecificAngularMomentum=self%darkMatterProfileDMOSatellite_%radiusFromSpecificAngularMomentum(node,specificAngularMomentum) - else - multipleRadiusFromSpecificAngularMomentum=self%darkMatterProfileDMOHost_ %radiusFromSpecificAngularMomentum(node,specificAngularMomentum) - end if - return - end function multipleRadiusFromSpecificAngularMomentum - - double precision function multipleRotationNormalization(self,node) - !!{ - Return the normalization of the rotation velocity vs. specific angular momentum relation. - !!} - implicit none - class(darkMatterProfileDMOMultiple), intent(inout) :: self - type (treeNode ), intent(inout) :: node - - if (node%isSatellite()) then - multipleRotationNormalization=self%darkMatterProfileDMOSatellite_%rotationNormalization(node) - else - multipleRotationNormalization=self%darkMatterProfileDMOHost_ %rotationNormalization(node) - end if - return - end function multipleRotationNormalization - - double precision function multipleEnergy(self,node) - !!{ - Return the energy of a multiple halo density profile. - !!} - implicit none - class(darkMatterProfileDMOMultiple), intent(inout) :: self - type (treeNode ), intent(inout) :: node - - if (node%isSatellite()) then - multipleEnergy=self%darkMatterProfileDMOSatellite_%energy(node) - else - multipleEnergy=self%darkMatterProfileDMOHost_ %energy(node) - end if - return - end function multipleEnergy - - double precision function multipleKSpace(self,node,waveNumber) - !!{ - Returns the Fourier transform of the multiple density profile at the specified {\normalfont \ttfamily waveNumber} - (given in Mpc$^{-1}$). - !!} - implicit none - class (darkMatterProfileDMOMultiple), intent(inout) :: self - type (treeNode ), intent(inout), target :: node - double precision , intent(in ) :: waveNumber - - if (node%isSatellite()) then - multipleKSpace=self%darkMatterProfileDMOSatellite_%kSpace(node,waveNumber) - else - multipleKSpace=self%darkMatterProfileDMOHost_ %kSpace(node,waveNumber) - end if - return - end function multipleKSpace - - double precision function multipleFreefallRadius(self,node,time) - !!{ - Returns the freefall radius in the multiple density profile at the specified {\normalfont \ttfamily time} (given in - Gyr). - !!} - implicit none - class (darkMatterProfileDMOMultiple), intent(inout), target :: self - type (treeNode ), intent(inout), target :: node - double precision , intent(in ) :: time - - if (node%isSatellite()) then - multipleFreefallRadius=self%darkMatterProfileDMOSatellite_%freefallRadius(node,time) - else - multipleFreefallRadius=self%darkMatterProfileDMOHost_ %freefallRadius(node,time) - end if - return - end function multipleFreefallRadius - - double precision function multipleFreefallRadiusIncreaseRate(self,node,time) - !!{ - Returns the rate of increase of the freefall radius in the multiple density profile at the specified {\normalfont - \ttfamily time} (given in Gyr). - !!} - implicit none - class (darkMatterProfileDMOMultiple), intent(inout), target :: self - type (treeNode ), intent(inout), target :: node - double precision , intent(in ) :: time - - if (node%isSatellite()) then - multipleFreefallRadiusIncreaseRate=self%darkMatterProfileDMOSatellite_%freefallRadiusIncreaseRate(node,time) - else - multipleFreefallRadiusIncreaseRate=self%darkMatterProfileDMOHost_ %freefallRadiusIncreaseRate(node,time) - end if - return - end function multipleFreefallRadiusIncreaseRate + end function multipleGet diff --git a/source/dark_matter_profiles_DMO.truncated.F90 b/source/dark_matter_profiles_DMO.truncated.F90 index 3269efcdfa..db4bf40542 100644 --- a/source/dark_matter_profiles_DMO.truncated.F90 +++ b/source/dark_matter_profiles_DMO.truncated.F90 @@ -21,11 +21,14 @@ An implementation of truncated dark matter halo profiles. !!} - use :: Dark_Matter_Profiles_Generic, only : enumerationNonAnalyticSolversType, enumerationNonAnalyticSolversEncode, enumerationNonAnalyticSolversIsValid, nonAnalyticSolversFallThrough - + use :: Mass_Distributions , only : enumerationNonAnalyticSolversType + use :: Dark_Matter_Halo_Scales, only : darkMatterHaloScaleClass + !![ - truncated dark matter halo profiles. + + Truncated dark matter halo profiles are built via the \refClass{massDistributionSphericalTruncated} class. + !!] type, extends(darkMatterProfileDMOClass) :: darkMatterProfileDMOTruncated @@ -33,43 +36,13 @@ A dark matter halo profile class implementing truncated dark matter halos. !!} private - class (darkMatterProfileDMOClass ), pointer :: darkMatterProfileDMO_ => null() - double precision :: radiusFractionalTruncateMinimum , radiusFractionalTruncateMaximum + class (darkMatterProfileDMOClass ), pointer :: darkMatterProfileDMO_ => null() + class (darkMatterHaloScaleClass ), pointer :: darkMatterHaloScale_ => null() + double precision :: radiusFractionalTruncateMinimum , radiusFractionalTruncateMaximum type (enumerationNonAnalyticSolversType) :: nonAnalyticSolver - ! Record of unique ID of node which we last computed results for. - integer (kind=kind_int8 ) :: lastUniqueID - ! Stored values of computed quantities. - double precision :: enclosedMassTruncateMinimumPrevious , enclosedMassTruncateMaximumPrevious , & - & enclosingMassRadiusPrevious , radialVelocityDispersionTruncateMinimumPrevious, & - & radialVelocityDispersionTruncateMinimumUntruncatedPrevious contains - !![ - - - - - !!] - final :: truncatedDestructor - procedure :: autoHook => truncatedAutoHook - procedure :: calculationReset => truncatedCalculationReset - procedure :: density => truncatedDensity - procedure :: densityLogSlope => truncatedDensityLogSlope - procedure :: radiusEnclosingDensity => truncatedRadiusEnclosingDensity - procedure :: radiusEnclosingMass => truncatedRadiusEnclosingMass - procedure :: radialMoment => truncatedRadialMoment - procedure :: enclosedMass => truncatedEnclosedMass - procedure :: potential => truncatedPotential - procedure :: circularVelocity => truncatedCircularVelocity - procedure :: circularVelocityMaximum => truncatedCircularVelocityMaximum - procedure :: radiusCircularVelocityMaximum => truncatedRadiusCircularVelocityMaximum - procedure :: radialVelocityDispersion => truncatedRadialVelocityDispersion - procedure :: radiusFromSpecificAngularMomentum => truncatedRadiusFromSpecificAngularMomentum - procedure :: rotationNormalization => truncatedRotationNormalization - procedure :: energy => truncatedEnergy - procedure :: kSpace => truncatedKSpace - procedure :: freefallRadius => truncatedFreefallRadius - procedure :: freefallRadiusIncreaseRate => truncatedFreefallRadiusIncreaseRate - procedure :: truncationFunction => truncatedTruncationFunction + final :: truncatedDestructor + procedure :: get => truncatedGet end type darkMatterProfileDMOTruncated interface darkMatterProfileDMOTruncated @@ -86,7 +59,8 @@ function truncatedConstructorParameters(parameters) result(self) !!{ Constructor for the {\normalfont \ttfamily truncated} dark matter halo profile class which takes a parameter set as input. !!} - use :: Input_Parameters, only : inputParameter, inputParameters + use :: Input_Parameters , only : inputParameters + use :: Mass_Distributions, only : enumerationNonAnalyticSolversEncode implicit none type (darkMatterProfileDMOTruncated) :: self type (inputParameters ), intent(inout) :: parameters @@ -130,7 +104,8 @@ function truncatedConstructorInternal(radiusFractionalTruncateMinimum,radiusFrac !!{ Internal constructor for the {\normalfont \ttfamily truncated} dark matter profile class. !!} - use :: Error, only : Error_Report + use :: Error , only : Error_Report + use :: Mass_Distributions, only : enumerationNonAnalyticSolversIsValid implicit none type (darkMatterProfileDMOTruncated ) :: self class (darkMatterProfileDMOClass ), intent(in ), target :: darkMatterProfileDMO_ @@ -143,28 +118,13 @@ function truncatedConstructorInternal(radiusFractionalTruncateMinimum,radiusFrac ! Validate. if (.not.enumerationNonAnalyticSolversIsValid(nonAnalyticSolver)) call Error_Report('invalid non-analytic solver type'//{introspection:location}) - self%lastUniqueID =-1_kind_int8 - self%genericLastUniqueID=-1_kind_int8 return end function truncatedConstructorInternal - subroutine truncatedAutoHook(self) - !!{ - Attach to the calculation reset event. - !!} - use :: Events_Hooks, only : calculationResetEvent, openMPThreadBindingAllLevels - implicit none - class(darkMatterProfileDMOTruncated), intent(inout) :: self - - call calculationResetEvent%attach(self,truncatedCalculationReset,openMPThreadBindingAllLevels,label='darkMatterProfileDMOTruncated)') - return - end subroutine truncatedAutoHook - subroutine truncatedDestructor(self) !!{ Destructor for the {\normalfont \ttfamily truncated} dark matter halo profile class. !!} - use :: Events_Hooks, only : calculationResetEvent implicit none type(darkMatterProfileDMOTruncated), intent(inout) :: self @@ -172,410 +132,73 @@ subroutine truncatedDestructor(self) !!] - if (calculationResetEvent%isAttached(self,truncatedCalculationReset)) call calculationResetEvent%detach(self,truncatedCalculationReset) return end subroutine truncatedDestructor - subroutine truncatedCalculationReset(self,node,uniqueID) - !!{ - Reset the dark matter profile calculation. - !!} - use :: Kind_Numbers, only : kind_int8 - implicit none - class (darkMatterProfileDMOTruncated), intent(inout) :: self - type (treeNode ), intent(inout) :: node - integer(kind_int8 ), intent(in ) :: uniqueID - !$GLC attributes unused :: node - - self%lastUniqueID =uniqueID - self%genericLastUniqueID =uniqueID - self%enclosingMassRadiusPrevious =-1.0d0 - self%enclosedMassTruncateMinimumPrevious =-1.0d0 - self%enclosedMassTruncateMaximumPrevious =-1.0d0 - self%radialVelocityDispersionTruncateMinimumPrevious =-1.0d0 - self%radialVelocityDispersionTruncateMinimumUntruncatedPrevious=-1.0d0 - self%genericEnclosedMassRadiusMinimum =+huge(0.0d0) - self%genericEnclosedMassRadiusMaximum =-huge(0.0d0) - self%genericVelocityDispersionRadialRadiusMinimum =+huge(0.0d0) - self%genericVelocityDispersionRadialRadiusMaximum =-huge(0.0d0) - if (allocated(self%genericVelocityDispersionRadialVelocity)) deallocate(self%genericVelocityDispersionRadialVelocity) - if (allocated(self%genericVelocityDispersionRadialRadius )) deallocate(self%genericVelocityDispersionRadialRadius ) - if (allocated(self%genericEnclosedMassMass )) deallocate(self%genericEnclosedMassMass ) - if (allocated(self%genericEnclosedMassRadius )) deallocate(self%genericEnclosedMassRadius ) - return - end subroutine truncatedCalculationReset - - subroutine truncatedTruncationFunction(self,node,radius,x,multiplier,multiplierGradient) - !!{ - Return the scaled truncation radial coordinate, and the truncation multiplier. - !!} - implicit none - class (darkMatterProfileDMOTruncated), intent(inout) :: self - type (treeNode ), intent(inout) :: node - double precision , intent(in ) :: radius - double precision , intent( out), optional :: x , multiplier, & - & multiplierGradient - double precision :: radiusVirial , x_ - - if (node%uniqueID() /= self%lastUniqueID) call self%calculationReset(node,node%uniqueID()) - radiusVirial=self%darkMatterHaloScale_%radiusVirial(node) - if (radius <= radiusVirial*self%radiusFractionalTruncateMinimum) then - if (present(x )) x =+0.0d0 - if (present(multiplier )) multiplier =+1.0d0 - if (present(multiplierGradient)) multiplierGradient=+0.0d0 - else if (radius >= radiusVirial*self%radiusFractionalTruncateMaximum) then - if (present(x )) x =+1.0d0 - if (present(multiplier )) multiplier =+0.0d0 - if (present(multiplierGradient)) multiplierGradient=+0.0d0 - else - x_ =+( radius /radiusVirial-self%radiusFractionalTruncateMinimum) & - & /(self%radiusFractionalTruncateMaximum -self%radiusFractionalTruncateMinimum) - if (present(x )) x =x_ - if (present(multiplier )) multiplier = +1.0d0 & - & -3.0d0*x_**2 & - & +2.0d0*x_**3 - if (present(multiplierGradient)) multiplierGradient=+( & - & -6.0d0*x_ & - & +6.0d0*x_**2 & - & ) & - & / radiusvirial & - & /(self%radiusFractionalTruncateMaximum -self%radiusFractionalTruncateMinimum) - end if - return - end subroutine truncatedTruncationFunction - - double precision function truncatedDensity(self,node,radius) - !!{ - Returns the density (in $M_\odot$ Mpc$^{-3}$) in the dark matter profile of {\normalfont \ttfamily node} at the given - {\normalfont \ttfamily radius} (given in units of Mpc). - !!} - implicit none - class (darkMatterProfileDMOTruncated), intent(inout) :: self - type (treeNode ), intent(inout) :: node - double precision , intent(in ) :: radius - double precision :: multiplier - - call self%truncationFunction(node,radius,multiplier=multiplier) - truncatedDensity=+self%darkMatterProfileDMO_%density(node,radius) & - & *multiplier - return - end function truncatedDensity - - double precision function truncatedDensityLogSlope(self,node,radius) - !!{ - Returns the logarithmic slope of the density in the dark matter profile of {\normalfont \ttfamily node} at the given - {\normalfont \ttfamily radius} (given in units of Mpc). - !!} - implicit none - class (darkMatterProfileDMOTruncated), intent(inout) :: self - type (treeNode ), intent(inout) :: node - double precision , intent(in ) :: radius - double precision :: multiplier, multiplierGradient - - call self%truncationFunction(node,radius,multiplier=multiplier,multiplierGradient=multiplierGradient) - if (multiplier > 0.0d0) then - truncatedDensityLogSlope=+self%darkMatterProfileDMO_%densityLogSlope(node,radius) & - & +radius & - & *multiplierGradient & - & /multiplier - else - truncatedDensityLogSlope=+0.0d0 - end if - return - end function truncatedDensityLogSlope - - double precision function truncatedRadiusEnclosingDensity(self,node,density) - !!{ - Returns the radius (in Mpc) in the dark matter profile of {\normalfont \ttfamily node} which encloses the given - {\normalfont \ttfamily density} (given in units of $M_\odot/$Mpc$^{-3}$). - !!} - implicit none - class (darkMatterProfileDMOTruncated), intent(inout), target :: self - type (treeNode ), intent(inout), target :: node - double precision , intent(in ) :: density - - if (self%nonAnalyticSolver == nonAnalyticSolversFallThrough) then - truncatedRadiusEnclosingDensity=self%darkMatterProfileDMO_%radiusEnclosingDensity (node,density) - else - truncatedRadiusEnclosingDensity=self %radiusEnclosingDensityNumerical(node,density) - end if - return - end function truncatedRadiusEnclosingDensity - - double precision function truncatedRadiusEnclosingMass(self,node,mass) - !!{ - Returns the radius (in Mpc) in the dark matter profile of {\normalfont \ttfamily node} which encloses the given - {\normalfont \ttfamily mass} (given in units of $M_\odot$). - !!} - implicit none - class (darkMatterProfileDMOTruncated), intent(inout), target :: self - type (treeNode ), intent(inout), target :: node - double precision , intent(in ) :: mass - double precision :: radiusVirial, radiusTruncateMinimum - - if (node%uniqueID() /= self%lastUniqueID) call self%calculationReset(node,node%uniqueID()) - radiusVirial =self%darkMatterHaloScale_%radiusVirial(node) - radiusTruncateMinimum=radiusVirial*self%radiusFractionalTruncateMinimum - if (self%enclosedMassTruncateMinimumPrevious < 0.0d0) then - self%enclosedMassTruncateMinimumPrevious=self%enclosedMass(node,radiusTruncateMinimum) - end if - if (self%nonAnalyticSolver == nonAnalyticSolversFallThrough .or. mass <= self%enclosedMassTruncateMinimumPrevious) then - truncatedRadiusEnclosingMass=self%darkMatterProfileDMO_%radiusEnclosingMass (node,mass) - else - truncatedRadiusEnclosingMass=self %radiusEnclosingMassNumerical(node,mass) - end if - return - end function truncatedRadiusEnclosingMass - - double precision function truncatedRadialMoment(self,node,moment,radiusMinimum,radiusMaximum) - !!{ - Returns the density (in $M_\odot$ Mpc$^{-3}$) in the dark matter profile of {\normalfont \ttfamily node} at the given - {\normalfont \ttfamily radius} (given in units of Mpc). - !!} - implicit none - class (darkMatterProfileDMOTruncated), intent(inout) :: self - type (treeNode ), intent(inout) :: node - double precision , intent(in ) :: moment - double precision , intent(in ), optional :: radiusMinimum, radiusMaximum - - if (self%nonAnalyticSolver == nonAnalyticSolversFallThrough) then - truncatedRadialMoment=self%darkMatterProfileDMO_%radialMoment (node,moment,radiusMinimum,radiusMaximum) - else - truncatedRadialMoment=self %radialMomentNumerical(node,moment,radiusMinimum,radiusMaximum) - end if - return - end function truncatedRadialMoment - - double precision function truncatedEnclosedMass(self,node,radius) - !!{ - Returns the enclosed mass (in $M_\odot$) in the dark matter profile of {\normalfont \ttfamily node} at the given {\normalfont \ttfamily radius} (given in - units of Mpc). - !!} - implicit none - class (darkMatterProfileDMOTruncated), intent(inout) :: self - type (treeNode ), intent(inout) :: node - double precision , intent(in ) :: radius - double precision :: radiusVirial, radiusTruncateMinimum - - if (node%uniqueID() /= self%lastUniqueID) call self%calculationReset(node,node%uniqueID()) - radiusVirial =self%darkMatterHaloScale_%radiusVirial(node) - radiusTruncateMinimum=radiusVirial*self%radiusFractionalTruncateMinimum - if (self%nonAnalyticSolver == nonAnalyticSolversFallThrough .or. radius <= radiusTruncateMinimum) then - truncatedEnclosedMass=+self%darkMatterProfileDMO_%enclosedMass (node,radius ) - else - truncatedEnclosedMass=+self%darkMatterProfileDMO_%enclosedMass (node,radiusTruncateMinimum ) & - & +self %enclosedMassDifferenceNumerical(node,radiusTruncateMinimum,radius) - end if - return - end function truncatedEnclosedMass - - double precision function truncatedPotential(self,node,radius,status) - !!{ - Returns the potential (in (km/s)$^2$) in the dark matter profile of {\normalfont \ttfamily node} at the given {\normalfont - \ttfamily radius} (given in units of Mpc). - !!} - implicit none - class (darkMatterProfileDMOTruncated ), intent(inout) :: self - type (treeNode ), intent(inout), target :: node - double precision , intent(in ) :: radius - type (enumerationStructureErrorCodeType), intent( out), optional :: status - - if (self%nonAnalyticSolver == nonAnalyticSolversFallThrough) then - truncatedPotential=self%darkMatterProfileDMO_%potential (node,radius,status) - else - truncatedPotential=self %potentialNumerical(node,radius,status) - end if - return - end function truncatedPotential - - double precision function truncatedCircularVelocity(self,node,radius) + function truncatedGet(self,node,weightBy,weightIndex) result(massDistribution_) !!{ - Returns the circular velocity (in km/s) in the dark matter profile of {\normalfont \ttfamily node} at the given - {\normalfont \ttfamily radius} (given in units of Mpc). + Return the dark matter mass distribution for the given {\normalfont \ttfamily node}. !!} + use :: Galactic_Structure_Options, only : componentTypeDarkHalo , massTypeDark , weightByMass + use :: Mass_Distributions , only : massDistributionSphericalTruncated, kinematicsDistributionTruncated, massDistributionSpherical implicit none - class (darkMatterProfileDMOTruncated), intent(inout) :: self - type (treeNode ), intent(inout) :: node - double precision , intent(in ) :: radius - - if (self%nonAnalyticSolver == nonAnalyticSolversFallThrough) then - truncatedCircularVelocity=self%darkMatterProfileDMO_%circularVelocity (node,radius) - else - truncatedCircularVelocity=self %circularVelocityNumerical(node,radius) - end if - return - end function truncatedCircularVelocity - - double precision function truncatedCircularVelocityMaximum(self,node) - !!{ - Returns the maximum circular velocity (in km/s) in the dark matter profile of {\normalfont \ttfamily node}. - !!} - implicit none - class(darkMatterProfileDMOTruncated), intent(inout) :: self - type (treeNode ), intent(inout) :: node - - if (self%nonAnalyticSolver == nonAnalyticSolversFallThrough) then - truncatedCircularVelocityMaximum=self%darkMatterProfileDMO_%circularVelocityMaximum (node) - else - truncatedCircularVelocityMaximum=self %circularVelocityMaximumNumerical(node) - end if - return - end function truncatedCircularVelocityMaximum - - double precision function truncatedRadiusCircularVelocityMaximum(self,node) - !!{ - Returns the radius (in Mpc) at which the maximum circular velocity is achieved in the dark matter profile of {\normalfont \ttfamily node}. - !!} - implicit none - class(darkMatterProfileDMOTruncated), intent(inout) :: self - type (treeNode ), intent(inout) :: node - - if (self%nonAnalyticSolver == nonAnalyticSolversFallThrough) then - truncatedRadiusCircularVelocityMaximum=self%darkMatterProfileDMO_%radiusCircularVelocityMaximum (node) - else - truncatedRadiusCircularVelocityMaximum=self %radiusCircularVelocityMaximumNumerical(node) - end if - return - end function truncatedRadiusCircularVelocityMaximum - - double precision function truncatedRadialVelocityDispersion(self,node,radius) - !!{ - Returns the radial velocity dispersion (in km/s) in the dark matter profile of {\normalfont \ttfamily node} at the given - {\normalfont \ttfamily radius} (given in units of Mpc). - !!} - implicit none - class (darkMatterProfileDMOTruncated), intent(inout) :: self - type (treeNode ), intent(inout) :: node - double precision , intent(in ) :: radius - double precision :: radiusVirial, radiusTruncateMinimum - - if (self%nonAnalyticSolver == nonAnalyticSolversFallThrough) then - truncatedRadialVelocityDispersion=self%darkMatterProfileDMO_%radialVelocityDispersion(node,radius) - else - if (node%uniqueID() /= self%lastUniqueID) call self%calculationReset(node,node%uniqueID()) - radiusVirial =self%darkMatterHaloScale_%radiusVirial(node) - radiusTruncateMinimum=radiusVirial*self%radiusFractionalTruncateMinimum - if (radius >= radiusTruncateMinimum) then - truncatedRadialVelocityDispersion=self%radialVelocityDispersionNumerical(node,radius) - else - if (self%radialVelocityDispersionTruncateMinimumPrevious < 0.0d0 .or. self%radialVelocityDispersionTruncateMinimumUntruncatedPrevious < 0.0d0) then - self%radialVelocityDispersionTruncateMinimumPrevious =self%radialVelocityDispersionNumerical (node,radiusTruncateMinimum) - self%radialVelocityDispersionTruncateMinimumUntruncatedPrevious=self%darkMatterProfileDMO_%radialVelocityDispersion(node,radiusTruncateMinimum) - end if - truncatedRadialVelocityDispersion=sqrt( & - & +self%darkMatterProfileDMO_%radialVelocityDispersion (node,radius )**2 & - & +self%density (node,radiusTruncateMinimum) & - & /self%density (node,radius ) & - & *( & - & +self%radialVelocityDispersionTruncateMinimumPrevious **2 & - & -self%radialVelocityDispersionTruncateMinimumUntruncatedPrevious **2 & - & ) & - & ) - end if - end if - return - end function truncatedRadialVelocityDispersion - - double precision function truncatedRadiusFromSpecificAngularMomentum(self,node,specificAngularMomentum) - !!{ - Returns the radius (in Mpc) in {\normalfont \ttfamily node} at which a circular orbit has the given {\normalfont \ttfamily specificAngularMomentum} (given - in units of km s$^{-1}$ Mpc). - !!} - implicit none - class (darkMatterProfileDMOTruncated), intent(inout) :: self - type (treeNode ), intent(inout) :: node - double precision , intent(in ) :: specificAngularMomentum - - if (self%nonAnalyticSolver == nonAnalyticSolversFallThrough) then - truncatedRadiusFromSpecificAngularMomentum=self%darkMatterProfileDMO_%radiusFromSpecificAngularMomentum (node,specificAngularMomentum) - else - truncatedRadiusFromSpecificAngularMomentum=self %radiusFromSpecificAngularMomentumNumerical(node,specificAngularMomentum) - end if - return - end function truncatedRadiusFromSpecificAngularMomentum - - double precision function truncatedRotationNormalization(self,node) - !!{ - Return the normalization of the rotation velocity vs. specific angular momentum relation. - !!} - implicit none - class(darkMatterProfileDMOTruncated), intent(inout) :: self - type (treeNode ), intent(inout) :: node - - if (self%nonAnalyticSolver == nonAnalyticSolversFallThrough) then - truncatedRotationNormalization=self%darkMatterProfileDMO_%rotationNormalization (node) - else - truncatedRotationNormalization=self %rotationNormalizationNumerical(node) - end if - return - end function truncatedRotationNormalization - - double precision function truncatedEnergy(self,node) - !!{ - Return the energy of a truncated halo density profile. - !!} - implicit none - class(darkMatterProfileDMOTruncated), intent(inout) :: self - type (treeNode ), intent(inout) :: node - - if (self%nonAnalyticSolver == nonAnalyticSolversFallThrough) then - truncatedEnergy=self%darkMatterProfileDMO_%energy (node) - else - truncatedEnergy=self %energyNumerical(node) - end if - return - end function truncatedEnergy - - double precision function truncatedKSpace(self,node,waveNumber) - !!{ - Returns the Fourier transform of the truncated density profile at the specified {\normalfont \ttfamily waveNumber} - (given in Mpc$^{-1}$). - !!} - implicit none - class (darkMatterProfileDMOTruncated), intent(inout) :: self - type (treeNode ), intent(inout), target :: node - double precision , intent(in ) :: waveNumber - - if (self%nonAnalyticSolver == nonAnalyticSolversFallThrough) then - truncatedKSpace=self%darkMatterProfileDMO_%kSpace (node,waveNumber) - else - truncatedKSpace=self %kSpaceNumerical(node,waveNumber) - end if - return - end function truncatedKSpace - - double precision function truncatedFreefallRadius(self,node,time) - !!{ - Returns the freefall radius in the truncated density profile at the specified {\normalfont \ttfamily time} (given in - Gyr). - !!} - implicit none - class (darkMatterProfileDMOTruncated), intent(inout), target :: self - type (treeNode ), intent(inout), target :: node - double precision , intent(in ) :: time - - if (self%nonAnalyticSolver == nonAnalyticSolversFallThrough) then - truncatedFreefallRadius=self%darkMatterProfileDMO_%freefallRadius (node,time) - else - truncatedFreefallRadius=self %freefallRadiusNumerical(node,time) - end if - return - end function truncatedFreefallRadius - - double precision function truncatedFreefallRadiusIncreaseRate(self,node,time) - !!{ - Returns the rate of increase of the freefall radius in the truncated density profile at the specified {\normalfont - \ttfamily time} (given in Gyr). - !!} - implicit none - class (darkMatterProfileDMOTruncated), intent(inout), target :: self - type (treeNode ), intent(inout), target :: node - double precision , intent(in ) :: time + class (massDistributionClass ), pointer :: massDistribution_ + type (kinematicsDistributionTruncated), pointer :: kinematicsDistribution_ + class (darkMatterProfileDMOTruncated ), intent(inout) :: self + type (treeNode ), intent(inout) :: node + type (enumerationWeightByType ), intent(in ), optional :: weightBy + integer , intent(in ), optional :: weightIndex + double precision :: radiusVirial + class (massDistributionClass ), pointer :: massDistributionDecorated + !![ + + !!] - if (self%nonAnalyticSolver == nonAnalyticSolversFallThrough) then - truncatedFreefallRadiusIncreaseRate=self%darkMatterProfileDMO_%freefallRadiusIncreaseRate (node,time) - else - truncatedFreefallRadiusIncreaseRate=self %freefallRadiusIncreaseRateNumerical(node,time) - end if + ! Assume a null distribution by default. + massDistribution_ => null() + ! If weighting is not by mass, return a null profile. + if (weightBy_ /= weightByMass) return + ! Create the mass distribution. + allocate(massDistributionSphericalTruncated :: massDistribution_) + select type(massDistribution_) + type is (massDistributionSphericalTruncated) + radiusVirial = self%darkMatterHaloScale_ %radiusVirial(node ) + massDistributionDecorated => self%darkMatterProfileDMO_%get (node,weightBy,weightIndex) + select type (massDistributionDecorated) + class is (massDistributionSpherical) + !![ + + + massDistributionSphericalTruncated( & + & radiusTruncateMinimum=self%radiusFractionalTruncateMinimum*radiusVirial, & + & radiusTruncateMaximum=self%radiusFractionalTruncateMaximum*radiusVirial, & + & nonAnalyticSolver =self%nonAnalyticSolver , & + & massDistribution_ = massDistributionDecorated , & + & componentType = componentTypeDarkHalo , & + & massType = massTypeDark & + & ) + + + !!] + class default + call Error_Report('expected a spherical mass distribution'//{introspection:location}) + end select + !![ + + !!] + end select + allocate(kinematicsDistribution_) + !![ + + + kinematicsDistributionTruncated( & + & ) + + + !!] + call massDistribution_%setKinematicsDistribution(kinematicsDistribution_) + !![ + + !!] return - end function truncatedFreefallRadiusIncreaseRate + end function truncatedGet diff --git a/source/dark_matter_profiles_DMO.truncated.exponential.F90 b/source/dark_matter_profiles_DMO.truncated.exponential.F90 index 968613a669..30987b07aa 100644 --- a/source/dark_matter_profiles_DMO.truncated.exponential.F90 +++ b/source/dark_matter_profiles_DMO.truncated.exponential.F90 @@ -23,11 +23,15 @@ An implementation of exponentially truncated dark matter halo profiles \cite{kazantzidis_2006}. !!} - use :: Dark_Matter_Profiles_Generic, only : enumerationNonAnalyticSolversType, enumerationNonAnalyticSolversEncode, enumerationNonAnalyticSolversIsValid, nonAnalyticSolversFallThrough + use :: Dark_Matter_Halo_Scales, only : darkMatterHaloScaleClass + use :: Mass_Distributions , only : enumerationNonAnalyticSolversType !![ - exponentially truncated dark matter halo profiles \cite{kazantzidis_2006}. + + Exponentially truncated dark matter halo profiles \cite{kazantzidis_2006} are constructed via the + \refClass{massDistributionSphericalTruncatedExponential} class. + !!] type, extends(darkMatterProfileDMOClass) :: darkMatterProfileDMOTruncatedExponential @@ -36,44 +40,12 @@ !!} private class (darkMatterProfileDMOClass ), pointer :: darkMatterProfileDMO_ => null() - double precision :: radiusFractionalDecay , alpha , & - & beta , gamma + class (darkMatterHaloScaleClass ), pointer :: darkMatterHaloScale_ => null() + double precision :: radiusFractionalDecay type (enumerationNonAnalyticSolversType) :: nonAnalyticSolver - ! Record of unique ID of node which we last computed results for. - integer (kind=kind_int8 ) :: lastUniqueID - ! Stored values of computed quantities. - double precision :: radialVelocityDispersionVirialRadiusPrevious, radialVelocityDispersionVirialRadiusUntruncatedPrevious, & - & enclosingMassRadiusPrevious , kappaPrevious , & - & gammaFunctionIncompletePrevious , densityNormalizationPrevious , & - & massVirialPrevious contains - !![ - - - - - !!] - final :: truncatedExponentialDestructor - procedure :: autoHook => truncatedExponentialAutoHook - procedure :: calculationReset => truncatedExponentialCalculationReset - procedure :: density => truncatedExponentialDensity - procedure :: densityLogSlope => truncatedExponentialDensityLogSlope - procedure :: radiusEnclosingDensity => truncatedExponentialRadiusEnclosingDensity - procedure :: radiusEnclosingMass => truncatedExponentialRadiusEnclosingMass - procedure :: radialMoment => truncatedExponentialRadialMoment - procedure :: enclosedMass => truncatedExponentialEnclosedMass - procedure :: potential => truncatedExponentialPotential - procedure :: circularVelocity => truncatedExponentialCircularVelocity - procedure :: radiusCircularVelocityMaximum => truncatedExponentialRadiusCircularVelocityMaximum - procedure :: circularVelocityMaximum => truncatedExponentialCircularVelocityMaximum - procedure :: radialVelocityDispersion => truncatedExponentialRadialVelocityDispersion - procedure :: radiusFromSpecificAngularMomentum => truncatedExponentialRadiusFromSpecificAngularMomentum - procedure :: rotationNormalization => truncatedExponentialRotationNormalization - procedure :: energy => truncatedExponentialEnergy - procedure :: kSpace => truncatedExponentialKSpace - procedure :: freefallRadius => truncatedExponentialFreefallRadius - procedure :: freefallRadiusIncreaseRate => truncatedExponentialFreefallRadiusIncreaseRate - procedure :: truncationFunction => truncatedExponentialTruncationFunction + final :: truncatedExponentialDestructor + procedure :: get => truncatedExponentialGet end type darkMatterProfileDMOTruncatedExponential interface darkMatterProfileDMOTruncatedExponential @@ -90,15 +62,15 @@ function truncatedExponentialConstructorParameters(parameters) result(self) !!{ Constructor for the {\normalfont \ttfamily exponentially truncated} dark matter halo profile class which takes a parameter set as input. !!} - use :: Input_Parameters, only : inputParameter, inputParameters + use :: Mass_Distributions, only : enumerationNonAnalyticSolversEncode + use :: Input_Parameters , only : inputParameter , inputParameters implicit none type (darkMatterProfileDMOTruncatedExponential) :: self type (inputParameters ), intent(inout) :: parameters class (darkMatterProfileDMOClass ), pointer :: darkMatterProfileDMO_ class (darkMatterHaloScaleClass ), pointer :: darkMatterHaloScale_ type (varying_string ) :: nonAnalyticSolver - double precision :: radiusFractionalDecay, alpha, & - & beta , gamma + double precision :: radiusFractionalDecay !![ @@ -112,29 +84,11 @@ function truncatedExponentialConstructorParameters(parameters) result(self) 1.0d0 parameters The truncation scale (in units of the virial radius). - - - alpha - 1.0d0 - parameters - Parameter $\alpha$ in the \cite{kazantzidis_2006} truncated profile. - - - beta - 3.0d0 - parameters - Parameter $\beta$ in the \cite{kazantzidis_2006} truncated profile. - - - gamma - 1.0d0 - parameters - Parameter $\gamma$ in the \cite{kazantzidis_2006} truncated profile. - + !!] - self=darkMatterProfileDMOTruncatedExponential(radiusFractionalDecay,alpha,beta,gamma,enumerationNonAnalyticSolversEncode(char(nonAnalyticSolver),includesPrefix=.false.),darkMatterProfileDMO_,darkMatterHaloScale_) + self=darkMatterProfileDMOTruncatedExponential(radiusFractionalDecay,enumerationNonAnalyticSolversEncode(char(nonAnalyticSolver),includesPrefix=.false.),darkMatterProfileDMO_,darkMatterHaloScale_) !![ @@ -143,50 +97,31 @@ function truncatedExponentialConstructorParameters(parameters) result(self) return end function truncatedExponentialConstructorParameters - function truncatedExponentialConstructorInternal(radiusFractionalDecay,alpha,beta,gamma,nonAnalyticSolver,darkMatterProfileDMO_,darkMatterHaloScale_) result(self) + function truncatedExponentialConstructorInternal(radiusFractionalDecay,nonAnalyticSolver,darkMatterProfileDMO_,darkMatterHaloScale_) result(self) !!{ Internal constructor for the {\normalfont \ttfamily exponentially truncated} dark matter profile class. !!} - use :: Error, only : Error_Report + use :: Error , only : Error_Report + use :: Mass_Distributions, only : enumerationNonAnalyticSolversIsValid implicit none type (darkMatterProfileDMOTruncatedExponential) :: self class (darkMatterProfileDMOClass ), intent(in ), target :: darkMatterProfileDMO_ class (darkMatterHaloScaleClass ), intent(in ), target :: darkMatterHaloScale_ - double precision , intent(in ) :: radiusFractionalDecay, alpha, & - & beta , gamma + double precision , intent(in ) :: radiusFractionalDecay type (enumerationNonAnalyticSolversType ), intent(in ) :: nonAnalyticSolver !![ - + !!] ! Validate. - if (.not.enumerationNonAnalyticSolversIsValid(nonAnalyticSolver)) call Error_Report('invalid non-analytic solver type'//{introspection:location}) - self%lastUniqueID =-1_kind_int8 - self%genericLastUniqueID =-1_kind_int8 - self%kappaPrevious =-huge(0.0d0) - self%enclosingMassRadiusPrevious =-1.0d0 - self%radialVelocityDispersionVirialRadiusPrevious =-1.0d0 - self%radialVelocityDispersionVirialRadiusUntruncatedPrevious=-1.0d0 + if (.not.enumerationNonAnalyticSolversIsValid(nonAnalyticSolver)) call Error_Report('invalid non-analytic solver type'//{introspection:location}) return end function truncatedExponentialConstructorInternal - subroutine truncatedExponentialAutoHook(self) - !!{ - Attach to the calculation reset event. - !!} - use :: Events_Hooks, only : calculationResetEvent, openMPThreadBindingAllLevels - implicit none - class(darkMatterProfileDMOTruncatedExponential), intent(inout) :: self - - call calculationResetEvent%attach(self,truncatedExponentialCalculationReset,openMPThreadBindingAllLevels,label='darkMatterProfileDMOTruncatedExponential') - return - end subroutine truncatedExponentialAutoHook - subroutine truncatedExponentialDestructor(self) !!{ Destructor for the {\normalfont \ttfamily exponentially truncated} dark matter halo profile class. !!} - use :: Events_Hooks, only : calculationResetEvent implicit none type(darkMatterProfileDMOTruncatedExponential), intent(inout) :: self @@ -194,459 +129,73 @@ subroutine truncatedExponentialDestructor(self) !!] - if (calculationResetEvent%isAttached(self,truncatedExponentialCalculationReset)) call calculationResetEvent%detach(self,truncatedExponentialCalculationReset) return end subroutine truncatedExponentialDestructor - subroutine truncatedExponentialCalculationReset(self,node,uniqueID) + function truncatedExponentialGet(self,node,weightBy,weightIndex) result(massDistribution_) !!{ - Reset the dark matter profile calculation. - !!} - use :: Kind_Numbers, only : kind_int8 - implicit none - class (darkMatterProfileDMOTruncatedExponential), intent(inout) :: self - type (treeNode ), intent(inout) :: node - integer(kind_int8 ), intent(in ) :: uniqueID - !$GLC attributes unused :: node - - self%lastUniqueID =uniqueID - self%genericLastUniqueID =uniqueID - self%kappaPrevious =-huge(0.0d0) - self%enclosingMassRadiusPrevious =-1.0d0 - self%radialVelocityDispersionVirialRadiusPrevious =-1.0d0 - self%radialVelocityDispersionVirialRadiusUntruncatedPrevious=-1.0d0 - self%genericEnclosedMassRadiusMinimum =+huge(0.0d0) - self%genericEnclosedMassRadiusMaximum =-huge(0.0d0) - self%genericVelocityDispersionRadialRadiusMinimum =+huge(0.0d0) - self%genericVelocityDispersionRadialRadiusMaximum =-huge(0.0d0) - if (allocated(self%genericVelocityDispersionRadialVelocity)) deallocate(self%genericVelocityDispersionRadialVelocity) - if (allocated(self%genericVelocityDispersionRadialRadius )) deallocate(self%genericVelocityDispersionRadialRadius ) - if (allocated(self%genericEnclosedMassMass )) deallocate(self%genericEnclosedMassMass ) - if (allocated(self%genericEnclosedMassRadius )) deallocate(self%genericEnclosedMassRadius ) - return - end subroutine truncatedExponentialCalculationReset - - subroutine truncatedExponentialTruncationFunction(self,node,radius,multiplier,multiplierGradient) - !!{ - Return the scaled truncation radial coordinate, and the truncation multiplier. + Return the dark matter mass distribution for the given {\normalfont \ttfamily node}. !!} + use :: Galactic_Structure_Options, only : componentTypeDarkHalo , massTypeDark , weightByMass + use :: Mass_Distributions , only : massDistributionSphericalTruncatedExponential, kinematicsDistributionTruncated, massDistributionSpherical implicit none + class (massDistributionClass ), pointer :: massDistribution_ + type (kinematicsDistributionTruncated ), pointer :: kinematicsDistribution_ class (darkMatterProfileDMOTruncatedExponential), intent(inout) :: self type (treeNode ), intent(inout) :: node - double precision , intent(in ) :: radius - double precision , intent( out), optional :: multiplier , multiplierGradient - double precision :: radiusVirial, radiusDecay , & - & multiplier_ - - if (node%uniqueID() /= self%lastUniqueID) call self%calculationReset(node,node%uniqueID()) - radiusVirial=self%darkMatterHaloScale_%radiusVirial(node) - if (radius <= radiusVirial) then - if (present(multiplier )) multiplier =+1.0d0 - if (present(multiplierGradient)) multiplierGradient=+0.0d0 - else - if (self%kappaPrevious == -huge(0.0d0)) call recomputeKappa(self,node) - radiusDecay =+self%radiusFractionalDecay*radiusVirial - multiplier_ =+self%darkMatterProfileDMO_%density(node,radiusVirial) & - & /self%darkMatterProfileDMO_%density(node,radius ) & - & *( & - & +radius & - & /radiusVirial & - & )**self%kappaPrevious & - & *exp( & - & -( & - & +radius & - & -radiusVirial & - & ) & - & / radiusDecay & - & ) - if (present(multiplier )) multiplier =+multiplier_ - if (present(multiplierGradient)) multiplierGradient=+multiplier_ & - & *( & - & +self%kappaPrevious /radius & - & -1.0d0 /radiusDecay & - & -self%darkMatterProfileDMO_%densityLogSlope(node,radius)/radius & - & ) - end if - return - end subroutine truncatedExponentialTruncationFunction - - subroutine recomputeKappa (self,node) - !!{ - Recompute parameter kappa in the truncation function. - !!} - use :: Galacticus_Nodes, only : nodeComponentDarkMatterProfile , treeNode - use :: Gamma_Functions , only : Gamma_Function_Incomplete_Unnormalized - implicit none - class (darkMatterProfileDMOTruncatedExponential), intent(inout) :: self - type (treeNode ), intent(inout) :: node - class (nodeComponentDarkMatterProfile ), pointer :: darkMatterProfile - double precision :: radiusVirial , scaleRadius, & - & concentration - - if (node%uniqueID() /= self%lastUniqueID) call self%calculationReset(node,node%uniqueID()) - radiusVirial = self %darkMatterHaloScale_%radiusVirial(node ) - darkMatterProfile => node %darkMatterProfile (autoCreate=.true.) - scaleRadius = darkMatterProfile%scale ( ) - concentration = +radiusVirial & - & /scaleRadius - self%kappaPrevious = -( & - & +self%gamma & - & +self%beta & - & *concentration**self%alpha & - & ) & - & /( & - & +1.0d0 & - & +concentration**self%alpha & - & ) & - & +1.0d0 & - & /self%radiusFractionalDecay - self%massVirialPrevious =+self%darkMatterProfileDMO_%enclosedMass(node ,radiusVirial ) - self%densityNormalizationPrevious =+4.0d0 & - & *Pi & - & *self%darkMatterProfileDMO_%density (node ,radiusVirial ) & - & *radiusVirial**3 & - & *self%radiusFractionalDecay** (3.0d0+self%kappaPrevious ) & - & *exp ( 1.0d0/self%radiusFractionalDecay) - self%gammaFunctionIncompletePrevious=+Gamma_Function_Incomplete_Unnormalized (3.0d0+self%kappaPrevious,1.0d0/self%radiusFractionalDecay) - return - end subroutine recomputeKappa - - double precision function truncatedExponentialDensity(self,node,radius) - !!{ - Returns the density (in $M_\odot$ Mpc$^{-3}$) in the dark matter profile of {\normalfont \ttfamily node} at the given - {\normalfont \ttfamily radius} (given in units of Mpc). - !!} - implicit none - class (darkMatterProfileDMOTruncatedExponential), intent(inout) :: self - type (treeNode ), intent(inout) :: node - double precision , intent(in ) :: radius - double precision :: multiplier - - call self%truncationFunction(node,radius,multiplier=multiplier) - truncatedExponentialDensity=+self%darkMatterProfileDMO_%density(node,radius) & - & *multiplier - return - end function truncatedExponentialDensity - - double precision function truncatedExponentialDensityLogSlope(self,node,radius) - !!{ - Returns the logarithmic slope of the density in the dark matter profile of {\normalfont \ttfamily node} at the given - {\normalfont \ttfamily radius} (given in units of Mpc). - !!} - implicit none - class (darkMatterProfileDMOTruncatedExponential), intent(inout) :: self - type (treeNode ), intent(inout) :: node - double precision , intent(in ) :: radius - double precision :: multiplier, multiplierGradient - - call self%truncationFunction(node,radius,multiplier=multiplier,multiplierGradient=multiplierGradient) - if (multiplier > 0.0d0) then - truncatedExponentialDensityLogSlope=+self%darkMatterProfileDMO_%densityLogSlope(node,radius) & - & +radius & - & *multiplierGradient & - & /multiplier - else - truncatedExponentialDensityLogSlope=+0.0d0 - end if - return - end function truncatedExponentialDensityLogSlope - - double precision function truncatedExponentialRadiusEnclosingDensity(self,node,density) - !!{ - Returns the radius (in Mpc) in the dark matter profile of {\normalfont \ttfamily node} which encloses the given - {\normalfont \ttfamily density} (given in units of $M_\odot/$Mpc$^{-3}$). - !!} - implicit none - class (darkMatterProfileDMOTruncatedExponential), intent(inout), target :: self - type (treeNode ), intent(inout), target :: node - double precision , intent(in ) :: density - - if (self%nonAnalyticSolver == nonAnalyticSolversFallThrough) then - truncatedExponentialRadiusEnclosingDensity=self%darkMatterProfileDMO_%radiusEnclosingDensity (node,density) - else - truncatedExponentialRadiusEnclosingDensity=self %radiusEnclosingDensityNumerical(node,density) - end if - return - end function truncatedExponentialRadiusEnclosingDensity - - double precision function truncatedExponentialRadiusEnclosingMass(self,node,mass) - !!{ - Returns the radius (in Mpc) in the dark matter profile of {\normalfont \ttfamily node} which encloses the given - {\normalfont \ttfamily mass} (given in units of $M_\odot$). - !!} - use :: Galacticus_Nodes, only : nodeComponentBasic, treeNode - implicit none - class (darkMatterProfileDMOTruncatedExponential), intent(inout), target :: self - type (treeNode ), intent(inout), target :: node - double precision , intent(in ) :: mass - class (nodeComponentBasic ), pointer :: basic - double precision :: massVirial - - basic => node %basic() - massVirial = basic%mass () - if (self%nonAnalyticSolver == nonAnalyticSolversFallThrough .or. mass <= massVirial) then - truncatedExponentialRadiusEnclosingMass=self%darkMatterProfileDMO_%radiusEnclosingMass (node,mass) - else - truncatedExponentialRadiusEnclosingMass=self %radiusEnclosingMassNumerical(node,mass) - end if - return - end function truncatedExponentialRadiusEnclosingMass - - double precision function truncatedExponentialRadialMoment(self,node,moment,radiusMinimum,radiusMaximum) - !!{ - Returns the density (in $M_\odot$ Mpc$^{-3}$) in the dark matter profile of {\normalfont \ttfamily node} at the given - {\normalfont \ttfamily radius} (given in units of Mpc). - !!} - implicit none - class (darkMatterProfileDMOTruncatedExponential), intent(inout) :: self - type (treeNode ), intent(inout) :: node - double precision , intent(in ) :: moment - double precision , intent(in ), optional :: radiusMinimum, radiusMaximum - - if (self%nonAnalyticSolver == nonAnalyticSolversFallThrough) then - truncatedExponentialRadialMoment=self%darkMatterProfileDMO_%radialMoment (node,moment,radiusMinimum,radiusMaximum) - else - truncatedExponentialRadialMoment=self %radialMomentNumerical(node,moment,radiusMinimum,radiusMaximum) - end if - return - end function truncatedExponentialRadialMoment - - double precision function truncatedExponentialEnclosedMass(self,node,radius) - !!{ - Returns the enclosed mass (in $M_\odot$) in the dark matter profile of {\normalfont \ttfamily node} at the given {\normalfont \ttfamily radius} (given in - units of Mpc). - !!} - use :: Gamma_Functions , only : Gamma_Function_Incomplete_Unnormalized - use :: Numerical_Constants_Math, only : Pi - implicit none - class (darkMatterProfileDMOTruncatedExponential), intent(inout) :: self - type (treeNode ), intent(inout) :: node - double precision , intent(in ) :: radius - double precision :: radiusVirial, radiusDecay - - if (node%uniqueID() /= self%lastUniqueID) call self%calculationReset(node,node%uniqueID()) - radiusVirial=self%darkMatterHaloScale_%radiusVirial(node) - if (radius <= radiusVirial) then - truncatedExponentialEnclosedMass=+self%darkMatterProfileDMO_%enclosedMass(node,radius ) - else - if (self%kappaPrevious == -huge(0.0d0)) call recomputeKappa(self,node) - radiusDecay =+self%radiusFractionalDecay*radiusVirial - truncatedExponentialEnclosedMass=+self%massVirialPrevious & - & +self%densityNormalizationPrevious & - & *( & - & +self%gammaFunctionIncompletePrevious & - & -Gamma_Function_Incomplete_Unnormalized(3.0d0+self%kappaPrevious,radius/radiusDecay) & - & ) - end if - return - end function truncatedExponentialEnclosedMass - - double precision function truncatedExponentialPotential(self,node,radius,status) - !!{ - Returns the potential (in (km/s)$^2$) in the dark matter profile of {\normalfont \ttfamily node} at the given {\normalfont - \ttfamily radius} (given in units of Mpc). - !!} - implicit none - class (darkMatterProfileDMOTruncatedExponential), intent(inout) :: self - type (treeNode ), intent(inout), target :: node - double precision , intent(in ) :: radius - type (enumerationStructureErrorCodeType ), intent( out), optional :: status - - if (self%nonAnalyticSolver == nonAnalyticSolversFallThrough) then - truncatedExponentialPotential=self%darkMatterProfileDMO_%potential (node,radius,status) - else - truncatedExponentialPotential=self %potentialNumerical(node,radius,status) - end if - return - end function truncatedExponentialPotential - - double precision function truncatedExponentialCircularVelocity(self,node,radius) - !!{ - Returns the circular velocity (in km/s) in the dark matter profile of {\normalfont \ttfamily node} at the given - {\normalfont \ttfamily radius} (given in units of Mpc). - !!} - implicit none - class (darkMatterProfileDMOTruncatedExponential), intent(inout) :: self - type (treeNode ), intent(inout) :: node - double precision , intent(in ) :: radius - - if (self%nonAnalyticSolver == nonAnalyticSolversFallThrough) then - truncatedExponentialCircularVelocity=self%darkMatterProfileDMO_%circularVelocity (node,radius) - else - truncatedExponentialCircularVelocity=self %circularVelocityNumerical(node,radius) - end if - return - end function truncatedExponentialCircularVelocity - - double precision function truncatedExponentialRadiusCircularVelocityMaximum(self,node) - !!{ - Returns the radius (in Mpc) at which the maximum circular velocity is achieved in the dark matter profile of {\normalfont \ttfamily node}. - !!} - implicit none - class(darkMatterProfileDMOTruncatedExponential), intent(inout) :: self - type (treeNode ), intent(inout) :: node - - if (self%nonAnalyticSolver == nonAnalyticSolversFallThrough) then - truncatedExponentialRadiusCircularVelocityMaximum=self%darkMatterProfileDMO_%radiusCircularVelocityMaximum (node) - else - truncatedExponentialRadiusCircularVelocityMaximum=self %radiusCircularVelocityMaximumNumerical(node) - end if - return - end function truncatedExponentialRadiusCircularVelocityMaximum - - double precision function truncatedExponentialCircularVelocityMaximum(self,node) - !!{ - Returns the maximum circular velocity (in km/s) in the dark matter profile of {\normalfont \ttfamily node}. - !!} - implicit none - class(darkMatterProfileDMOTruncatedExponential), intent(inout) :: self - type (treeNode ), intent(inout) :: node - - if (self%nonAnalyticSolver == nonAnalyticSolversFallThrough) then - truncatedExponentialCircularVelocityMaximum=self%darkMatterProfileDMO_%circularVelocityMaximum (node) - else - truncatedExponentialCircularVelocityMaximum=self %circularVelocityMaximumNumerical(node) - end if - return - end function truncatedExponentialCircularVelocityMaximum - - double precision function truncatedExponentialRadialVelocityDispersion(self,node,radius) - !!{ - Returns the radial velocity dispersion (in km/s) in the dark matter profile of {\normalfont \ttfamily node} at the given - {\normalfont \ttfamily radius} (given in units of Mpc). - !!} - implicit none - class (darkMatterProfileDMOTruncatedExponential), intent(inout) :: self - type (treeNode ), intent(inout) :: node - double precision , intent(in ) :: radius - double precision :: radiusVirial - - if (self%nonAnalyticSolver == nonAnalyticSolversFallThrough) then - truncatedExponentialRadialVelocityDispersion=self%darkMatterProfileDMO_%radialVelocityDispersion(node,radius) - else - if (node%uniqueID() /= self%lastUniqueID) call self%calculationReset(node,node%uniqueID()) - radiusVirial=self%darkMatterHaloScale_%radiusVirial(node) - if (radius >= radiusVirial) then - truncatedExponentialRadialVelocityDispersion=self%radialVelocityDispersionNumerical(node,radius) - else - if (self%radialVelocityDispersionVirialRadiusPrevious < 0.0d0 .or. self%radialVelocityDispersionVirialRadiusUntruncatedPrevious < 0.0d0) then - self%radialVelocityDispersionVirialRadiusPrevious =self%radialVelocityDispersionNumerical (node,radiusVirial) - self%radialVelocityDispersionVirialRadiusUntruncatedPrevious=self%darkMatterProfileDMO_%radialVelocityDispersion(node,radiusVirial) - end if - truncatedExponentialRadialVelocityDispersion=sqrt( & - & +self%darkMatterProfileDMO_%radialVelocityDispersion (node,radius )**2 & - & +self%density (node,radiusVirial) & - & /self%density (node,radius ) & - & *( & - & +self%radialVelocityDispersionVirialRadiusPrevious **2 & - & -self%radialVelocityDispersionVirialRadiusUntruncatedPrevious **2 & - & ) & - & ) - end if - end if - return - end function truncatedExponentialRadialVelocityDispersion - - double precision function truncatedExponentialRadiusFromSpecificAngularMomentum(self,node,specificAngularMomentum) - !!{ - Returns the radius (in Mpc) in {\normalfont \ttfamily node} at which a circular orbit has the given {\normalfont \ttfamily specificAngularMomentum} (given - in units of km s$^{-1}$ Mpc). - !!} - implicit none - class (darkMatterProfileDMOTruncatedExponential), intent(inout) :: self - type (treeNode ), intent(inout) :: node - double precision , intent(in ) :: specificAngularMomentum - - if (self%nonAnalyticSolver == nonAnalyticSolversFallThrough) then - truncatedExponentialRadiusFromSpecificAngularMomentum=self%darkMatterProfileDMO_%radiusFromSpecificAngularMomentum (node,specificAngularMomentum) - else - truncatedExponentialRadiusFromSpecificAngularMomentum=self %radiusFromSpecificAngularMomentumNumerical(node,specificAngularMomentum) - end if - return - end function truncatedExponentialRadiusFromSpecificAngularMomentum - - double precision function truncatedExponentialRotationNormalization(self,node) - !!{ - Return the normalization of the rotation velocity vs. specific angular momentum relation. - !!} - implicit none - class(darkMatterProfileDMOTruncatedExponential), intent(inout) :: self - type (treeNode ), intent(inout) :: node - - if (self%nonAnalyticSolver == nonAnalyticSolversFallThrough) then - truncatedExponentialRotationNormalization=self%darkMatterProfileDMO_%rotationNormalization (node) - else - truncatedExponentialRotationNormalization=self %rotationNormalizationNumerical(node) - end if - return - end function truncatedExponentialRotationNormalization - - double precision function truncatedExponentialEnergy(self,node) - !!{ - Return the energy of a truncatedExponential halo density profile. - !!} - implicit none - class(darkMatterProfileDMOTruncatedExponential), intent(inout) :: self - type (treeNode ), intent(inout) :: node - - if (self%nonAnalyticSolver == nonAnalyticSolversFallThrough) then - truncatedExponentialEnergy=self%darkMatterProfileDMO_%energy (node) - else - truncatedExponentialEnergy=self %energyNumerical(node) - end if - return - end function truncatedExponentialEnergy - - double precision function truncatedExponentialKSpace(self,node,waveNumber) - !!{ - Returns the Fourier transform of the truncatedExponential density profile at the specified {\normalfont \ttfamily waveNumber} - (given in Mpc$^{-1}$). - !!} - implicit none - class (darkMatterProfileDMOTruncatedExponential), intent(inout) :: self - type (treeNode ), intent(inout), target :: node - double precision , intent(in ) :: waveNumber - - if (self%nonAnalyticSolver == nonAnalyticSolversFallThrough) then - truncatedExponentialKSpace=self%darkMatterProfileDMO_%kSpace (node,waveNumber) - else - truncatedExponentialKSpace=self %kSpaceNumerical(node,waveNumber) - end if - return - end function truncatedExponentialKSpace - - double precision function truncatedExponentialFreefallRadius(self,node,time) - !!{ - Returns the freefall radius in the truncatedExponential density profile at the specified {\normalfont \ttfamily time} (given in - Gyr). - !!} - implicit none - class (darkMatterProfileDMOTruncatedExponential), intent(inout), target :: self - type (treeNode ), intent(inout), target :: node - double precision , intent(in ) :: time - - if (self%nonAnalyticSolver == nonAnalyticSolversFallThrough) then - truncatedExponentialFreefallRadius=self%darkMatterProfileDMO_%freefallRadius (node,time) - else - truncatedExponentialFreefallRadius=self %freefallRadiusNumerical(node,time) - end if - return - end function truncatedExponentialFreefallRadius - - double precision function truncatedExponentialFreefallRadiusIncreaseRate(self,node,time) - !!{ - Returns the rate of increase of the freefall radius in the truncatedExponential density profile at the specified {\normalfont - \ttfamily time} (given in Gyr). - !!} - implicit none - class (darkMatterProfileDMOTruncatedExponential), intent(inout), target :: self - type (treeNode ), intent(inout), target :: node - double precision , intent(in ) :: time + type (enumerationWeightByType ), intent(in ), optional :: weightBy + integer , intent(in ), optional :: weightIndex + double precision :: radiusVirial + class (massDistributionClass ), pointer :: massDistributionDecorated + !![ + + !!] - if (self%nonAnalyticSolver == nonAnalyticSolversFallThrough) then - truncatedExponentialFreefallRadiusIncreaseRate=self%darkMatterProfileDMO_%freefallRadiusIncreaseRate (node,time) - else - truncatedExponentialFreefallRadiusIncreaseRate=self %freefallRadiusIncreaseRateNumerical(node,time) - end if + ! Assume a null distribution by default. + massDistribution_ => null() + ! If weighting is not by mass, return a null profile. + if (weightBy_ /= weightByMass) return + ! Create the mass distribution. + allocate(massDistributionSphericalTruncatedExponential :: massDistribution_) + select type(massDistribution_) + type is (massDistributionSphericalTruncatedExponential) + radiusVirial = self%darkMatterHaloScale_ %radiusVirial(node ) + massDistributionDecorated => self%darkMatterProfileDMO_%get (node,weightBy,weightIndex) + select type (massDistributionDecorated) + class is (massDistributionSpherical) + !![ + + + massDistributionSphericalTruncatedExponential( & + & radiusTruncateMinimum= radiusVirial, & + & radiusTruncateDecay =self%radiusFractionalDecay*radiusVirial, & + & nonAnalyticSolver =self%nonAnalyticSolver , & + & massDistribution_ = massDistributionDecorated , & + & componentType = componentTypeDarkHalo , & + & massType = massTypeDark & + & ) + + + !!] + class default + call Error_Report('expected a spherical mass distribution'//{introspection:location}) + end select + !![ + + !!] + end select + allocate(kinematicsDistribution_) + !![ + + + kinematicsDistributionTruncated( & + & ) + + + !!] + call massDistribution_%setKinematicsDistribution(kinematicsDistribution_) + !![ + + !!] return - end function truncatedExponentialFreefallRadiusIncreaseRate + end function truncatedExponentialGet diff --git a/source/display.F90 b/source/display.F90 index 4d15b7905a..2c548e7ccc 100644 --- a/source/display.F90 +++ b/source/display.F90 @@ -57,10 +57,10 @@ module Display character(len=10 ), allocatable, dimension(:) :: indentationFormat character(len=10 ), allocatable, dimension(:) :: indentationFormatNoNewLine - character(len=20 ) :: threadFormat , masterFormat + character(len=20 ) :: threadFormat , masterFormat - logical :: displayInitialized =.false. , verbositySet=.false. - type (enumerationVerbosityLevelType) :: verbosityLevel =verbosityLevelSilent + logical :: displayInitialized =.false. , verbositySet=.false. + type (enumerationVerbosityLevelType) :: verbosityLevel =verbosityLevelStandard ! Progress bar state. logical :: barVisible =.false. diff --git a/source/galactic.filters.halo_mass.F90 b/source/galactic.filters.halo_mass.F90 index 88ad410a31..5fc4629d56 100644 --- a/source/galactic.filters.halo_mass.F90 +++ b/source/galactic.filters.halo_mass.F90 @@ -21,10 +21,9 @@ Contains a module which implements a galactic high-pass filter for halo mass under a given definition. !!} - use :: Cosmology_Parameters , only : cosmologyParametersClass - use :: Cosmology_Functions , only : cosmologyFunctionsClass - use :: Dark_Matter_Profiles_DMO, only : darkMatterProfileDMOClass - use :: Virial_Density_Contrast , only : virialDensityContrastClass + use :: Cosmology_Parameters , only : cosmologyParametersClass + use :: Cosmology_Functions , only : cosmologyFunctionsClass + use :: Virial_Density_Contrast, only : virialDensityContrastClass !![ @@ -40,7 +39,6 @@ !!} private double precision :: massThreshold - class (darkMatterProfileDMOClass ), pointer :: darkMatterProfileDMO_ => null() class (virialDensityContrastClass), pointer :: virialDensityContrast_ => null(), virialDensityContrastDefinition_ => null() class (cosmologyParametersClass ), pointer :: cosmologyParameters_ => null() class (cosmologyFunctionsClass ), pointer :: cosmologyFunctions_ => null() @@ -69,7 +67,6 @@ function haloMassConstructorParameters(parameters) result(self) type (inputParameters ), intent(inout) :: parameters class (cosmologyFunctionsClass ), pointer :: cosmologyFunctions_ class (cosmologyParametersClass ), pointer :: cosmologyParameters_ - class (darkMatterProfileDMOClass ), pointer :: darkMatterProfileDMO_ class (virialDensityContrastClass), pointer :: virialDensityContrast_, virialDensityContrastDefinition_ double precision :: massThreshold @@ -82,23 +79,21 @@ function haloMassConstructorParameters(parameters) result(self) - !!] - self=galacticFilterHaloMass(massThreshold,cosmologyFunctions_,cosmologyParameters_,darkMatterProfileDMO_,virialDensityContrast_,virialDensityContrastDefinition_) + self=galacticFilterHaloMass(massThreshold,cosmologyFunctions_,cosmologyParameters_,virialDensityContrast_,virialDensityContrastDefinition_) !![ - !!] return end function haloMassConstructorParameters - function haloMassConstructorInternal(massThreshold,cosmologyFunctions_,cosmologyParameters_,darkMatterProfileDMO_,virialDensityContrast_,virialDensityContrastDefinition_) result(self) + function haloMassConstructorInternal(massThreshold,cosmologyFunctions_,cosmologyParameters_,virialDensityContrast_,virialDensityContrastDefinition_) result(self) !!{ Internal constructor for the ``haloMass'' galactic filter class. !!} @@ -108,9 +103,8 @@ function haloMassConstructorInternal(massThreshold,cosmologyFunctions_,cosmology class (cosmologyParametersClass ), intent(in ), target :: cosmologyParameters_ class (cosmologyFunctionsClass ), intent(in ), target :: cosmologyFunctions_ class (virialDensityContrastClass), intent(in ), target :: virialDensityContrast_, virialDensityContrastDefinition_ - class (darkMatterProfileDMOClass ), intent(in ), target :: darkMatterProfileDMO_ !![ - + !!] return @@ -127,7 +121,6 @@ subroutine haloMassDestructor(self) - !!] return @@ -153,7 +146,6 @@ logical function haloMassPasses(self,node) & ) , & & cosmologyParameters_ =self%cosmologyParameters_ , & & cosmologyFunctions_ =self%cosmologyFunctions_ , & - & darkMatterProfileDMO_ =self%darkMatterProfileDMO_ , & & virialDensityContrast_=self%virialDensityContrast_ & & ) & & >= & diff --git a/source/galactic.filters.halo_mass_range.F90 b/source/galactic.filters.halo_mass_range.F90 index fd1035f465..ff4c70adb6 100644 --- a/source/galactic.filters.halo_mass_range.F90 +++ b/source/galactic.filters.halo_mass_range.F90 @@ -21,10 +21,9 @@ Contains a module which implements a galactic filter for halo mass under a given definition. !!} - use :: Cosmology_Parameters , only : cosmologyParametersClass - use :: Cosmology_Functions , only : cosmologyFunctionsClass - use :: Dark_Matter_Profiles_DMO, only : darkMatterProfileDMOClass - use :: Virial_Density_Contrast , only : virialDensityContrastClass + use :: Cosmology_Parameters , only : cosmologyParametersClass + use :: Cosmology_Functions , only : cosmologyFunctionsClass + use :: Virial_Density_Contrast, only : virialDensityContrastClass !![ @@ -39,7 +38,6 @@ !!} private double precision :: massLow , massHigh - class (darkMatterProfileDMOClass ), pointer :: darkMatterProfileDMO_ => null() class (virialDensityContrastClass), pointer :: virialDensityContrast_ => null(), virialDensityContrastDefinition_ => null() class (cosmologyParametersClass ), pointer :: cosmologyParameters_ => null() class (cosmologyFunctionsClass ), pointer :: cosmologyFunctions_ => null() @@ -68,7 +66,6 @@ function haloMassRangeConstructorParameters(parameters) result(self) type (inputParameters ), intent(inout) :: parameters class (cosmologyFunctionsClass ), pointer :: cosmologyFunctions_ class (cosmologyParametersClass ), pointer :: cosmologyParameters_ - class (darkMatterProfileDMOClass ), pointer :: darkMatterProfileDMO_ class (virialDensityContrastClass ), pointer :: virialDensityContrast_, virialDensityContrastDefinition_ double precision :: massLow , massHigh @@ -86,23 +83,21 @@ function haloMassRangeConstructorParameters(parameters) result(self) - !!] - self=galacticFilterHaloMassRange(massLow,massHigh,cosmologyFunctions_,cosmologyParameters_,darkMatterProfileDMO_,virialDensityContrast_,virialDensityContrastDefinition_) + self=galacticFilterHaloMassRange(massLow,massHigh,cosmologyFunctions_,cosmologyParameters_,virialDensityContrast_,virialDensityContrastDefinition_) !![ - !!] return end function haloMassRangeConstructorParameters - function haloMassRangeConstructorInternal(massLow,massHigh,cosmologyFunctions_,cosmologyParameters_,darkMatterProfileDMO_,virialDensityContrast_,virialDensityContrastDefinition_) result(self) + function haloMassRangeConstructorInternal(massLow,massHigh,cosmologyFunctions_,cosmologyParameters_,virialDensityContrast_,virialDensityContrastDefinition_) result(self) !!{ Internal constructor for the ``haloMassRange'' galactic filter class. !!} @@ -112,9 +107,8 @@ function haloMassRangeConstructorInternal(massLow,massHigh,cosmologyFunctions_,c class (cosmologyParametersClass ), intent(in ), target :: cosmologyParameters_ class (cosmologyFunctionsClass ), intent(in ), target :: cosmologyFunctions_ class (virialDensityContrastClass ), intent(in ), target :: virialDensityContrast_, virialDensityContrastDefinition_ - class (darkMatterProfileDMOClass ), intent(in ), target :: darkMatterProfileDMO_ !![ - + !!] return @@ -131,7 +125,6 @@ subroutine haloMassRangeDestructor(self) - !!] return @@ -158,7 +151,6 @@ logical function haloMassRangePasses(self,node) & ) , & & cosmologyParameters_ =self%cosmologyParameters_ , & & cosmologyFunctions_ =self%cosmologyFunctions_ , & - & darkMatterProfileDMO_ =self%darkMatterProfileDMO_ , & & virialDensityContrast_=self%virialDensityContrast_ & & ) haloMassRangePasses = massHalo >= self%massLow & diff --git a/source/galactic.filters.stellar_absolute_magnitudes.F90 b/source/galactic.filters.stellar_absolute_magnitudes.F90 index 4efe9780f0..20ba1cebd9 100644 --- a/source/galactic.filters.stellar_absolute_magnitudes.F90 +++ b/source/galactic.filters.stellar_absolute_magnitudes.F90 @@ -20,8 +20,6 @@ !!{ Contains a module which implements a galactic low-pass (i.e. bright-pass) filter for stellar absolute magnitudes. !!} - - use :: Galactic_Structure, only : galacticStructureClass !![ @@ -36,10 +34,8 @@ A galactic low-pass (i.e. bright pass) filter class for stellar absolute magnitudes. !!} private - class (galacticStructureClass), pointer :: galacticStructure_ => null() - double precision , allocatable, dimension(:) :: absoluteMagnitudeThreshold + double precision, allocatable, dimension(:) :: absoluteMagnitudeThreshold contains - final :: stellarAbsoluteMagnitudesDestructor procedure :: passes => stellarAbsoluteMagnitudesPasses end type galacticFilterStellarAbsoluteMagnitudes @@ -64,7 +60,6 @@ function stellarAbsoluteMagnitudesConstructorParameters(parameters) result(self) type (galacticFilterStellarAbsoluteMagnitudes) :: self type (inputParameters ), intent(inout) :: parameters double precision , allocatable , dimension(:) :: absoluteMagnitudeThreshold - class (galacticStructureClass ), pointer :: galacticStructure_ ! Check and read parameters. if (parameters%count('absoluteMagnitudeThreshold') /= unitStellarLuminosities%luminosityCount(unmapped=.true.)) & @@ -79,17 +74,15 @@ function stellarAbsoluteMagnitudesConstructorParameters(parameters) result(self) parameters The parameter $M_0$ appearing in the stellar absolute magnitude threshold for the stellar absolute magnitude galactic filter class. - !!] - self=galacticFilterStellarAbsoluteMagnitudes(absoluteMagnitudeThreshold,galacticStructure_) + self=galacticFilterStellarAbsoluteMagnitudes(absoluteMagnitudeThreshold) !![ - !!] return end function stellarAbsoluteMagnitudesConstructorParameters - function stellarAbsoluteMagnitudesConstructorInternal(absoluteMagnitudeThreshold,galacticStructure_) result(self) + function stellarAbsoluteMagnitudesConstructorInternal(absoluteMagnitudeThreshold) result(self) !!{ Internal constructor for the ``stellarAbsoluteMagnitudes'' galactic filter class. !!} @@ -97,10 +90,9 @@ function stellarAbsoluteMagnitudesConstructorInternal(absoluteMagnitudeThreshold use :: Stellar_Luminosities_Structure, only : Stellar_Luminosities_Parameter_Map, unitStellarLuminosities implicit none type (galacticFilterStellarAbsoluteMagnitudes) :: self - class (galacticStructureClass ), intent(in ), target :: galacticStructure_ double precision , intent(in ), dimension(:) :: absoluteMagnitudeThreshold !![ - + !!] if (size(absoluteMagnitudeThreshold) /= unitStellarLuminosities%luminosityCount(unmapped=.true.)) & @@ -113,31 +105,20 @@ function stellarAbsoluteMagnitudesConstructorInternal(absoluteMagnitudeThreshold return end function stellarAbsoluteMagnitudesConstructorInternal - subroutine stellarAbsoluteMagnitudesDestructor(self) - !!{ - Destructor for the ``stellarAbsoluteMagnitudes'' galactic filter class. - !!} - implicit none - type(galacticFilterStellarAbsoluteMagnitudes), intent(inout) :: self - - !![ - - !!] - return - end subroutine stellarAbsoluteMagnitudesDestructor - logical function stellarAbsoluteMagnitudesPasses(self,node) !!{ Implement a stellar absolute magnitude low-pass galactic filter. !!} use :: Galactic_Structure_Options , only : massTypeStellar , weightByLuminosity - use :: Galacticus_Nodes , only : nodeComponentBasic , treeNode + use :: Galacticus_Nodes , only : nodeComponentBasic + use :: Mass_Distributions , only : massDistributionClass use :: Stellar_Luminosities_Structure, only : unitStellarLuminosities implicit none class (galacticFilterStellarAbsoluteMagnitudes), intent(inout) :: self type (treeNode ), intent(inout), target :: node class (nodeComponentBasic ), pointer :: basic - double precision :: time , luminosity, & + class (massDistributionClass ), pointer :: massDistribution_ + double precision :: time , luminosity, & & abMagnitude integer :: iLuminosity @@ -150,7 +131,11 @@ logical function stellarAbsoluteMagnitudesPasses(self,node) ! Only check those luminosities which are being output at this output time. if (unitStellarLuminosities%isOutput(iLuminosity,time)) then ! Get the total stellar luminosity of the galaxy. - luminosity=self%galacticStructure_%massEnclosed(node,massType=massTypeStellar,weightBy=weightByLuminosity,weightIndex=iLuminosity) + massDistribution_ => node %massDistribution(massType=massTypeStellar,weightBy=weightByLuminosity,weightIndex=iLuminosity) + luminosity = massDistribution_%massTotal ( ) + !![ + + !!] ! Test only if the luminosity is greater than zero. if (luminosity > 0.0d0) then ! Convert to absolute magnitude. diff --git a/source/galactic.filters.stellar_apparent_magnitudes.F90 b/source/galactic.filters.stellar_apparent_magnitudes.F90 index 6806d28bd0..413539d8e6 100644 --- a/source/galactic.filters.stellar_apparent_magnitudes.F90 +++ b/source/galactic.filters.stellar_apparent_magnitudes.F90 @@ -22,7 +22,6 @@ !!} use :: Cosmology_Functions, only : cosmologyFunctionsClass - use :: Galactic_Structure , only : galacticStructureClass !![ @@ -38,7 +37,6 @@ !!} private class (cosmologyFunctionsClass), pointer :: cosmologyFunctions_ => null() - class (galacticStructureClass ), pointer :: galacticStructure_ => null() double precision , allocatable, dimension(:) :: apparentMagnitudeThreshold contains final :: stellarApparentMagnitudesDestructor @@ -67,7 +65,6 @@ function stellarApparentMagnitudesConstructorParameters(parameters) result(self) type (inputParameters ), intent(inout) :: parameters double precision , allocatable , dimension(:) :: apparentMagnitudeThreshold class (cosmologyFunctionsClass ), pointer :: cosmologyFunctions_ - class (galacticStructureClass ), pointer :: galacticStructure_ ! Check and read parameters. if (parameters%count('apparentMagnitudeThreshold') /= unitStellarLuminosities%luminosityCount(unmapped=.true.)) & @@ -83,18 +80,16 @@ function stellarApparentMagnitudesConstructorParameters(parameters) result(self) The parameter $m_0$ appearing in the stellar apparent magnitude threshold for the stellar apparent magnitude galactic filter class. - !!] - self=galacticFilterStellarApparentMagnitudes(apparentMagnitudeThreshold,cosmologyFunctions_,galacticStructure_) + self=galacticFilterStellarApparentMagnitudes(apparentMagnitudeThreshold,cosmologyFunctions_) !![ - !!] return end function stellarApparentMagnitudesConstructorParameters - function stellarApparentMagnitudesConstructorInternal(apparentMagnitudeThreshold,cosmologyFunctions_,galacticStructure_) result(self) + function stellarApparentMagnitudesConstructorInternal(apparentMagnitudeThreshold,cosmologyFunctions_) result(self) !!{ Internal constructor for the ``stellarApparentMagnitudes'' galactic filter class. !!} @@ -104,9 +99,8 @@ function stellarApparentMagnitudesConstructorInternal(apparentMagnitudeThreshold type (galacticFilterStellarApparentMagnitudes) :: self double precision , intent(in ), dimension(:) :: apparentMagnitudeThreshold class (cosmologyFunctionsClass ), intent(in ), target :: cosmologyFunctions_ - class (galacticStructureClass ), intent(in ), target :: galacticStructure_ !![ - + !!] if (size(apparentMagnitudeThreshold) /= unitStellarLuminosities%luminosityCount(unmapped=.true.)) & @@ -128,7 +122,6 @@ subroutine stellarApparentMagnitudesDestructor(self) !![ - !!] return end subroutine stellarApparentMagnitudesDestructor @@ -138,12 +131,14 @@ logical function stellarApparentMagnitudesPasses(self,node) Implement a stellar apparent magnitude low-pass galactic filter. !!} use :: Galactic_Structure_Options , only : massTypeStellar , weightByLuminosity - use :: Galacticus_Nodes , only : nodeComponentBasic , treeNode + use :: Galacticus_Nodes , only : nodeComponentBasic + use :: Mass_Distributions , only : massDistributionClass use :: Stellar_Luminosities_Structure, only : unitStellarLuminosities implicit none class (galacticFilterStellarApparentMagnitudes), intent(inout) :: self type (treeNode ), intent(inout), target :: node class (nodeComponentBasic ), pointer :: basic + class (massDistributionClass ), pointer :: massDistribution_ double precision , parameter :: expansionFactorTolerance=1.0d-6 double precision :: time , luminosity , & & abMagnitude , expansionFactor, & @@ -167,7 +162,11 @@ logical function stellarApparentMagnitudesPasses(self,node) ! Only check those luminosities which are being output at this output time. if (unitStellarLuminosities%isOutput(iLuminosity,time)) then ! Get the total stellar luminosity of the galaxy. - luminosity=self%galacticStructure_%massEnclosed(node,massType=massTypeStellar,weightBy=weightByLuminosity,weightIndex=iLuminosity) + massDistribution_ => node %massDistribution(massType=massTypeStellar,weightBy=weightByLuminosity,weightIndex=iLuminosity) + luminosity = massDistribution_%massTotal ( ) + !![ + + !!] ! Test only if the luminosity is greater than zero. if (luminosity > 0.0d0) then ! Convert to apparent magnitude. diff --git a/source/galactic.structure.F90 b/source/galactic.structure.F90 deleted file mode 100644 index d209648c6b..0000000000 --- a/source/galactic.structure.F90 +++ /dev/null @@ -1,177 +0,0 @@ -!! Copyright 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, -!! 2019, 2020, 2021, 2022, 2023, 2024 -!! Andrew Benson -!! -!! This file is part of Galacticus. -!! -!! Galacticus is free software: you can redistribute it and/or modify -!! it under the terms of the GNU General Public License as published by -!! the Free Software Foundation, either version 3 of the License, or -!! (at your option) any later version. -!! -!! Galacticus is distributed in the hope that it will be useful, -!! but WITHOUT ANY WARRANTY; without even the implied warranty of -!! MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -!! GNU General Public License for more details. -!! -!! You should have received a copy of the GNU General Public License -!! along with Galacticus. If not, see . - -!!{ -Contains a module which provides a class that implements galactic structure functions. -!!} - -module Galactic_Structure - !!{ - Provides an object that implements galactic structure functions. - !!} - use :: Galacticus_Nodes , only : treeNode - use :: Galactic_Structure_Options, only : enumerationCoordinateSystemType , enumerationComponentTypeType, enumerationMassTypeType, enumerationWeightByType, & - & enumerationStructureErrorCodeType - private - - !![ - - galacticStructure - Galactic Structure - Object providing galactic structure functions. - standard - - Return the density at the given {\normalfont \ttfamily position} in {\normalfont \ttfamily node}. - double precision - yes - type (treeNode ), intent(inout) :: node - double precision , intent(in ), dimension(3) :: position - type (enumerationCoordinateSystemType), intent(in ), optional :: coordinateSystem - type (enumerationComponentTypeType ), intent(in ), optional :: componentType - type (enumerationMassTypeType ), intent(in ), optional :: massType - type (enumerationWeightByType ), intent(in ), optional :: weightBy - integer , intent(in ), optional :: weightIndex - - - Return the spherically-averaged density at the given {\normalfont \ttfamily radius} in {\normalfont \ttfamily node}. - double precision - yes - type (treeNode ), intent(inout) :: node - double precision , intent(in ) :: radius - type (enumerationComponentTypeType ), intent(in ), optional :: componentType - type (enumerationMassTypeType ), intent(in ), optional :: massType - type (enumerationWeightByType ), intent(in ), optional :: weightBy - integer , intent(in ), optional :: weightIndex - - - Return the mass enclosed within the given {\normalfont \ttfamily radius} in {\normalfont \ttfamily node}. - double precision - yes - type (treeNode ), intent(inout) :: node - double precision , intent(in ), optional :: radius - type (enumerationComponentTypeType ), intent(in ), optional :: componentType - type (enumerationMassTypeType ), intent(in ), optional :: massType - type (enumerationWeightByType ), intent(in ), optional :: weightBy - integer , intent(in ), optional :: weightIndex - - - Return the radius enclosing a given mass (or fractional mass) in {\normalfont \ttfamily node}. - double precision - yes - yes - type (treeNode ), intent(inout), target :: node - double precision , intent(in ), optional :: mass , massFractional - type (enumerationComponentTypeType ), intent(in ), optional :: componentType - type (enumerationMassTypeType ), intent(in ), optional :: massType - type (enumerationWeightByType ), intent(in ), optional :: weightBy - integer , intent(in ), optional :: weightIndex - - - Return the rotation velocity for a circular orbit at the given {\normalfont \ttfamily radius} in {\normalfont \ttfamily node}. - double precision - yes - type (treeNode ), intent(inout) :: node - double precision , intent(in ) :: radius - type (enumerationComponentTypeType ), intent(in ), optional :: componentType - type (enumerationMassTypeType ), intent(in ), optional :: massType - - - Return the gradient of the rotation velocity for a circular orbit at the given {\normalfont \ttfamily radius} in {\normalfont \ttfamily node}. - double precision - yes - type (treeNode ), intent(inout) :: node - double precision , intent(in ) :: radius - type (enumerationComponentTypeType ), intent(in ), optional :: componentType - type (enumerationMassTypeType ), intent(in ), optional :: massType - - - Return the gravitational potential at the given {\normalfont \ttfamily radius} in {\normalfont \ttfamily node}. - double precision - yes - type (treeNode ), intent(inout) :: node - double precision , intent(in ) :: radius - type (enumerationComponentTypeType ), intent(in ), optional :: componentType - type (enumerationMassTypeType ), intent(in ), optional :: massType - type (enumerationStructureErrorCodeType), intent( out), optional :: status - - - Return the surface density of given {\normalfont \ttfamily massType}) at the specified {\normalfont \ttfamily position}. - double precision - yes - type (treeNode ), intent(inout) :: node - double precision , intent(in ), dimension(3) :: position - type (enumerationCoordinateSystemType), intent(in ), optional :: coordinateSystem - type (enumerationComponentTypeType ), intent(in ), optional :: componentType - type (enumerationMassTypeType ), intent(in ), optional :: massType - type (enumerationWeightByType ), intent(in ), optional :: weightBy - integer , intent(in ), optional :: weightIndex - - - Return the radius enclosing a given surface density in {\normalfont \ttfamily node}. - double precision - yes - yes - type (treeNode ), intent(inout), target :: node - double precision , intent(in ) :: surfaceDensity - type (enumerationComponentTypeType ), intent(in ), optional :: componentType - type (enumerationMassTypeType ), intent(in ), optional :: massType - type (enumerationWeightByType ), intent(in ), optional :: weightBy - integer , intent(in ), optional :: weightIndex - - - Compute the gravitational acceleration at a given position in {\normalfont \ttfamily node}. - double precision, dimension(3) - yes - type (treeNode), intent(inout) :: node - double precision , intent(in ), dimension(3) :: positionCartesian - type (enumerationComponentTypeType ), intent(in ), optional :: componentType - type (enumerationMassTypeType ), intent(in ), optional :: massType - - - Compute the gravitational tidal tensor at a given position in {\normalfont \ttfamily node}. - type(tensorRank2Dimension3Symmetric) - yes - type (treeNode ), intent(inout) :: node - double precision , intent(in ), dimension(3) :: positionCartesian - type (enumerationComponentTypeType ), intent(in ), optional :: componentType - type (enumerationMassTypeType ), intent(in ), optional :: massType - - - Compute the integral appearing in the \cite{chandrasekhar_dynamical_1943} dynamical friction model in {\normalfont \ttfamily node}. - double precision, dimension(3) - yes - type (treeNode ), intent(inout), target :: node , nodeSatellite - double precision , intent(in ), dimension(3) :: positionCartesian, velocityCartesian - type (enumerationComponentTypeType ), intent(in ), optional :: componentType - type (enumerationMassTypeType ), intent(in ), optional :: massType - - - Return the velocity dispersion at the given {\normalfont \ttfamily radius} in {\normalfont \ttfamily node}. - double precision - yes - yes - type (treeNode ), intent(inout), target :: node - double precision , intent(in ) :: radius , radiusOuter - type (enumerationComponentTypeType ), intent(in ), optional :: componentType - type (enumerationMassTypeType ), intent(in ), optional :: massType - - - !!] - -end module Galactic_Structure diff --git a/source/galactic.structure.standard.F90 b/source/galactic.structure.standard.F90 deleted file mode 100644 index d1b799bf1a..0000000000 --- a/source/galactic.structure.standard.F90 +++ /dev/null @@ -1,1098 +0,0 @@ -!! Copyright 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, -!! 2019, 2020, 2021, 2022, 2023, 2024 -!! Andrew Benson -!! -!! This file is part of Galacticus. -!! -!! Galacticus is free software: you can redistribute it and/or modify -!! it under the terms of the GNU General Public License as published by -!! the Free Software Foundation, either version 3 of the License, or -!! (at your option) any later version. -!! -!! Galacticus is distributed in the hope that it will be useful, -!! but WITHOUT ANY WARRANTY; without even the implied warranty of -!! MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -!! GNU General Public License for more details. -!! -!! You should have received a copy of the GNU General Public License -!! along with Galacticus. If not, see . - -!!{ -Contains a module which implements the standard galactic structure functions. -!!} - - use :: Cosmology_Functions , only : cosmologyFunctionsClass - use :: Dark_Matter_Halo_Scales , only : darkMatterHaloScaleClass - use :: Dark_Matter_Profiles , only : darkMatterProfileClass - use :: Root_Finder , only : rootFinder - use :: Kind_Numbers , only : kind_int8 - use :: Galactic_Structure_Options, only : enumerationComponentTypeType, enumerationMassTypeType, enumerationWeightByType, enumerationStructureErrorCodeType - !![ - - - The standard galactic structure functions. - - - !!] - type, extends(galacticStructureClass) :: galacticStructureStandard - !!{ - The standard galactic structure functions. - !!} - private - class (cosmologyFunctionsClass ), pointer :: cosmologyFunctions_ => null() - class (darkMatterHaloScaleClass), pointer :: darkMatterHaloScale_ => null() - class (darkMatterProfileClass ), pointer :: darkMatterProfile_ => null() - type (rootFinder ) :: finderMass , finderSurfaceDensity - double precision :: radiusEnclosingMassPrevious , potentialOffset , & - & radiusEnclosingSurfaceDensityPrevious - integer (kind_int8 ) :: uniqueIDPrevious - logical :: potentialOffsetComputed - contains - !![ - - - - - - !!] - final :: standardDestructor - procedure :: autoHook => standardAutoHook - procedure :: density => standardDensity - procedure :: densitySphericalAverage => standardDensitySphericalAverage - procedure :: massEnclosed => standardMassEnclosed - procedure :: radiusEnclosingMass => standardRadiusEnclosingMass - procedure :: velocityRotation => standardVelocityRotation - procedure :: velocityRotationGradient => standardVelocityRotationGradient - procedure :: potential => standardPotential - procedure :: surfaceDensity => standardSurfaceDensity - procedure :: radiusEnclosingSurfaceDensity => standardRadiusEnclosingSurfaceDensity - procedure :: acceleration => standardAcceleration - procedure :: tidalTensor => standardTidalTensor - procedure :: chandrasekharIntegral => standardChandrasekharIntegral - procedure :: velocityDispersion => standardVelocityDispersion - procedure :: defaults => standardDefaults - procedure :: restore => standardRestore - procedure :: calculationReset => standardCalculationReset - end type galacticStructureStandard - - interface galacticStructureStandard - !!{ - Constructors for the ``standard'' galactic structure class. - !!} - module procedure standardConstructorParameters - module procedure standardConstructorInternal - end interface galacticStructureStandard - - ! Types used to store state to allow recursive calling of these functions. - type :: galacticStructureState - type (enumerationComponentTypeType) :: componentType_ - type (enumerationMassTypeType ) :: massType_ - type (enumerationWeightByType ) :: weightBy_ - integer :: weightIndex_ - double precision :: radius_ - end type galacticStructureState - - type :: galacticStructureStateList - type(galacticStructureState ) :: state - type(galacticStructureStateList), pointer :: next => null(), previous => null() - end type galacticStructureStateList - - ! State stack used to allow recursive calling of these functions. - type (galacticStructureStateList ), pointer :: galacticStructureState_, galacticStructureStateHead_ - !$omp threadprivate(galacticStructureState_,galacticStructureStateHead_) - - ! Submodule-scope variables used in callback functions and root-finding. - type (enumerationStructureErrorCodeType), :: status_ - double precision , dimension(3) :: positionSpherical_ , positionCartesian_ , & - & positionCylindrical_ , velocityCartesian_ - !$omp threadprivate(status_,positionSpherical_,positionCartesian_,positionCylindrical_,velocityCartesian_) - - ! Submodule-scope variables used in root finding. - double precision :: massTarget , surfaceDensityTarget - type (treeNode ), pointer :: node_ , nodeSatellite_ - class (galacticStructureStandard ), pointer :: self_ - !$omp threadprivate(self_,node_,nodeSatellite_,massTarget,surfaceDensityTarget) - -contains - - function standardConstructorParameters(parameters) result(self) - !!{ - Constructor for the ``standard'' galactic structure class which takes a parameter set as input. - !!} - use :: Input_Parameters, only : inputParameters - implicit none - type (galacticStructureStandard) :: self - type (inputParameters ), intent(inout) :: parameters - class(cosmologyFunctionsClass ), pointer :: cosmologyFunctions_ - class(darkMatterHaloScaleClass ), pointer :: darkMatterHaloScale_ - class(darkMatterProfileClass ), pointer :: darkMatterProfile_ - - !![ - - - - !!] - self=galacticStructureStandard(cosmologyFunctions_,darkMatterHaloScale_,darkMatterProfile_) - !![ - - - - - !!] - return - end function standardConstructorParameters - - function standardConstructorInternal(cosmologyFunctions_,darkMatterHaloScale_,darkMatterProfile_) result(self) - !!{ - Internal constructor for the ``standard'' galactic structure class. - !!} - use :: Root_Finder, only : rangeExpandMultiplicative, rangeExpandSignExpectNegative, rangeExpandSignExpectPositive - implicit none - type(galacticStructureStandard) :: self - class(cosmologyFunctionsClass ), intent(in ), target :: cosmologyFunctions_ - class(darkMatterHaloScaleClass), intent(in ), target :: darkMatterHaloScale_ - class(darkMatterProfileClass ), intent(in ), target :: darkMatterProfile_ - !![ - - !!] - - self%potentialOffsetComputed =.false. - self%radiusEnclosingMassPrevious =-huge(0.0d0) - self%radiusEnclosingSurfaceDensityPrevious=-huge(0.0d0) - self%uniqueIDPrevious =-1_kind_int8 - self%finderMass =rootFinder( & - & rootFunction =massEnclosedRoot , & - & rangeExpandDownward =0.5d0 , & - & rangeExpandUpward =2.0d0 , & - & rangeExpandDownwardSignExpect=rangeExpandSignExpectNegative, & - & rangeExpandUpwardSignExpect =rangeExpandSignExpectPositive, & - & rangeExpandType =rangeExpandMultiplicative , & - & toleranceAbsolute =0.0d+0 , & - & toleranceRelative =1.0d-6 & - & ) - self%finderSurfaceDensity =rootFinder( & - & rootFunction =surfaceDensityRoot , & - & rangeExpandDownward =0.5d0 , & - & rangeExpandUpward =2.0d0 , & - & rangeExpandDownwardSignExpect=rangeExpandSignExpectPositive, & - & rangeExpandUpwardSignExpect =rangeExpandSignExpectNegative, & - & rangeExpandType =rangeExpandMultiplicative , & - & toleranceAbsolute =0.0d+0 , & - & toleranceRelative =1.0d-6 & - & ) - return - end function standardConstructorInternal - - subroutine standardAutoHook(self) - !!{ - Attach to the calculation reset event. - !!} - use :: Events_Hooks, only : calculationResetEvent, openMPThreadBindingAllLevels - implicit none - class(galacticStructureStandard), intent(inout) :: self - - call calculationResetEvent%attach(self,standardCalculationReset,openMPThreadBindingAllLevels,label='galacticStructureStandard') - return - end subroutine standardAutoHook - - subroutine standardDestructor(self) - !!{ - Destructor for the ``standard'' galactic structure class. - !!} - use :: Events_Hooks, only : calculationResetEvent - implicit none - type(galacticStructureStandard), intent(inout) :: self - - !![ - - - - !!] - if (calculationResetEvent%isAttached(self,standardCalculationReset)) call calculationResetEvent%detach(self,standardCalculationReset) - return - end subroutine standardDestructor - - subroutine standardCalculationReset(self,node,uniqueID) - !!{ - Reset calculations for galactic structure potentials. - !!} - use :: Galacticus_Nodes, only : treeNode - use :: Kind_Numbers , only : kind_int8 - implicit none - class (galacticStructureStandard), intent(inout) :: self - type (treeNode ), intent(in ) :: node - integer(kind_int8 ), intent(in ) :: uniqueID - !$GLC attributes unused :: node - - self%potentialOffsetComputed =.false. - self%radiusEnclosingMassPrevious =-huge(0.0d0) - self%radiusEnclosingSurfaceDensityPrevious=-huge(0.0d0) - self%uniqueIDPrevious =uniqueID - return - end subroutine standardCalculationReset - - double precision function standardDensity(self,node,position,coordinateSystem,componentType,massType,weightBy,weightIndex) result(density) - !!{ - Compute the density (of given {\normalfont \ttfamily massType}) at the specified {\normalfont \ttfamily position}. Assumes that galactic structure has already - been computed. - !!} - use :: Coordinate_Systems , only : Coordinates_Cartesian_To_Spherical, Coordinates_Cylindrical_To_Spherical - use :: Galactic_Structure_Options, only : componentTypeAll , coordinateSystemCartesian , coordinateSystemCylindrical, coordinateSystemSpherical , & - & massTypeAll , weightByLuminosity , weightByMass , enumerationCoordinateSystemType - use :: Error , only : Error_Report - use :: Galacticus_Nodes , only : optimizeForDensitySummation , reductionSummation , treeNode - !![ - - !!] - include 'galactic_structure.density.tasks.modules.inc' - !![ - - !!] - implicit none - class (galacticStructureStandard ), intent(inout) :: self - type (treeNode ), intent(inout) :: node - type (enumerationComponentTypeType ), intent(in ), optional :: componentType - type (enumerationMassTypeType ), intent(in ), optional :: massType - type (enumerationWeightByType ), intent(in ), optional :: weightBy - integer , intent(in ), optional :: weightIndex - type (enumerationCoordinateSystemType), intent(in ), optional :: coordinateSystem - double precision , intent(in ), dimension(3) :: position - procedure (densityComponent ), pointer :: densityComponent_ - type (enumerationCoordinateSystemType) :: coordinateSystemActual - double precision :: densityComponent__ - - ! Determine position in spherical coordinate system to use. - if (present(coordinateSystem)) then - coordinateSystemActual=coordinateSystem - else - coordinateSystemActual=coordinateSystemSpherical - end if - select case (coordinateSystemActual%ID) - case (coordinateSystemSpherical %ID) - positionSpherical_=position - case (coordinateSystemCylindrical%ID) - positionSpherical_=Coordinates_Cylindrical_To_Spherical(position) - case (coordinateSystemCartesian %ID) - positionSpherical_=Coordinates_Cartesian_To_Spherical (position) - case default - call Error_Report('unknown coordinate system type'//{introspection:location}) - end select - call self%defaults(componentType=componentType,massType=massType,weightBy=weightBy,weightIndex=weightIndex) - ! Call routines to supply the densities for all components. - densityComponent_ => densityComponent - density = node%mapDouble0(densityComponent_,reductionSummation,optimizeFor=optimizeForDensitySummation) - !![ - - node,positionSpherical_,galacticStructureState_%state%componentType_,galacticStructureState_%state%massType_,galacticStructureState_%state%weightBy_,galacticStructureState_%state%weightIndex_ - density=density+densityComponent__ - !!] - include 'galactic_structure.density.tasks.inc' - !![ - - !!] - call self%restore() - return - end function standardDensity - - double precision function densityComponent(component) - !!{ - Unary function returning the density in a component. Suitable for mapping over components. - !!} - use :: Galacticus_Nodes, only : nodeComponent - implicit none - class(nodeComponent), intent(inout) :: component - - densityComponent=component%density(positionSpherical_,galacticStructureState_%state%componentType_,galacticStructureState_%state%massType_,galacticStructureState_%state%weightBy_,galacticStructureState_%state%weightIndex_) - return - end function densityComponent - - double precision function standardDensitySphericalAverage(self,node,radius,componentType,massType,weightBy,weightIndex) result(density) - !!{ - Compute the density (of given {\normalfont \ttfamily massType}) at the specified {\normalfont \ttfamily position}. Assumes that galactic structure has already - been computed. - !!} - use :: Galactic_Structure_Options, only : componentTypeAll , massTypeAll , weightByLuminosity, weightByMass - use :: Error , only : Error_Report - use :: Galacticus_Nodes , only : optimizeForDensitySphericalAverageSummation, reductionSummation, treeNode - !![ - - !!] - include 'galactic_structure.density_spherical_average.tasks.modules.inc' - !![ - - !!] - implicit none - class (galacticStructureStandard ), intent(inout) :: self - type (treeNode ), intent(inout) :: node - double precision , intent(in ) :: radius - type (enumerationComponentTypeType ), intent(in ), optional :: componentType - type (enumerationMassTypeType ), intent(in ), optional :: massType - type (enumerationWeightByType ), intent(in ), optional :: weightBy - integer , intent(in ), optional :: weightIndex - procedure (densitySphericalAverageComponent), pointer :: densitySphericalAverageComponent_ - double precision :: densitySphericalAverageComponent__ - - call self%defaults(radius=radius,componentType=componentType,massType=massType,weightBy=weightBy,weightIndex=weightIndex) - ! Call routines to supply the densities for all components. - densitySphericalAverageComponent_ => densitySphericalAverageComponent - density = node%mapDouble0(densitySphericalAverageComponent_,reductionSummation,optimizeFor=optimizeForDensitySphericalAverageSummation) - !![ - - node,radius,galacticStructureState_%state%componentType_,galacticStructureState_%state%massType_,galacticStructureState_%state%weightBy_,galacticStructureState_%state%weightIndex_ - density=density+densitySphericalAverageComponent__ - !!] - include 'galactic_structure.density_spherical_average.tasks.inc' - !![ - - !!] - call self%restore() - return - end function standardDensitySphericalAverage - - double precision function densitySphericalAverageComponent(component) - !!{ - Unary function returning the spherically-averaged density in a component. Suitable for mapping over components. - !!} - use :: Galacticus_Nodes, only : nodeComponent - implicit none - class(nodeComponent), intent(inout) :: component - - densitySphericalAverageComponent=component%densitySphericalAverage(galacticStructureState_%state%radius_,galacticStructureState_%state%componentType_,galacticStructureState_%state%massType_,galacticStructureState_%state%weightBy_,galacticStructureState_%state%weightIndex_) - return - end function densitySphericalAverageComponent - - double precision function standardMassEnclosed(self,node,radius,componentType,massType,weightBy,weightIndex) result(massEnclosed) - !!{ - Compute the mass within a given radius, or the total mass if no radius is specified. - !!} - use :: Galacticus_Nodes, only : optimizeForEnclosedMassSummation, reductionSummation - !![ - - !!] - include 'galactic_structure.enclosed_mass.tasks.modules.inc' - !![ - - !!] - implicit none - class (galacticStructureStandard ), intent(inout) :: self - type (treeNode ), intent(inout) :: node - type (enumerationComponentTypeType), intent(in ), optional :: componentType - type (enumerationMassTypeType ), intent(in ), optional :: massType - type (enumerationWeightByType ), intent(in ), optional :: weightBy - integer , intent(in ), optional :: weightIndex - double precision , intent(in ), optional :: radius - procedure (massComponentEnclosed ), pointer :: massComponentEnclosed_ - double precision :: massComponent - - call self%defaults(radius,componentType,massType,weightBy,weightIndex) - ! Compute the contribution from components directly, by mapping a function over all components. - massComponentEnclosed_ => massComponentEnclosed - massEnclosed = node %mapDouble0(massComponentEnclosed_,reductionSummation,optimizeFor=optimizeForEnclosedMassSummation) - ! Call routines to supply the masses for all components. - !![ - - node,galacticStructureState_%state%radius_,galacticStructureState_%state%componentType_,galacticStructureState_%state%massType_,galacticStructureState_%state%weightBy_,galacticStructureState_%state%weightIndex_ - massEnclosed=massEnclosed+massComponent - !!] - include 'galactic_structure.enclosed_mass.tasks.inc' - !![ - - !!] - call self%restore() - return - end function standardMassEnclosed - - double precision function massComponentEnclosed(component) - !!{ - Unary function returning the enclosed mass in a component. Suitable for mapping over components. - !!} - use :: Galacticus_Nodes, only : nodeComponent - implicit none - class(nodeComponent), intent(inout) :: component - - massComponentEnclosed=component%enclosedMass(galacticStructureState_%state%radius_,galacticStructureState_%state%componentType_,galacticStructureState_%state%massType_,galacticStructureState_%state%weightBy_,galacticStructureState_%state%weightIndex_) - return - end function massComponentEnclosed - - double precision function standardRadiusEnclosingMass(self,node,mass,massFractional,componentType,massType,weightBy,weightIndex) - !!{ - Return the radius enclosing a given mass (or fractional mass) in {\normalfont \ttfamily node}. - !!} - use :: Dark_Matter_Profile_Structure_Tasks, only : Dark_Matter_Profile_Radius_Enclosing_Mass - use :: Display , only : displayMessage , verbosityLevelWarn - use :: Galactic_Structure_Options , only : componentTypeDarkHalo , massTypeDark - use :: Error , only : Error_Report - use :: ISO_Varying_String , only : assignment(=) , operator(//) , varying_string - use :: String_Handling , only : operator(//) - implicit none - class (galacticStructureStandard ), intent(inout), target :: self - type (treeNode ), intent(inout), target :: node - type (enumerationComponentTypeType), intent(in ), optional :: componentType - type (enumerationMassTypeType ), intent(in ), optional :: massType - type (enumerationWeightByType ), intent(in ), optional :: weightBy - integer , intent(in ), optional :: weightIndex - double precision , intent(in ), optional :: massFractional, mass - double precision :: radiusGuess - type (varying_string ) :: message - character (len=11 ) :: massLabel - - call self%defaults(componentType=componentType,massType=massType,weightBy=weightBy,weightIndex=weightIndex) - ! Determine what mass to use. - if (present(mass)) then - if (present(massFractional)) call Error_Report('only one mass or massFractional can be specified'//{introspection:location}) - massTarget=mass - else if (present(massFractional)) then - massTarget=massFractional*self%massEnclosed(node,componentType=galacticStructureState_%state%componentType_,massType=galacticStructureState_%state%massType_,weightBy=galacticStructureState_%state%weightBy_,weightIndex=galacticStructureState_%state%weightIndex_) - else - call Error_Report('either mass or massFractional must be specified'//{introspection:location}) - end if - if (massTarget <= 0.0d0) then - standardRadiusEnclosingMass=0.0d0 - call self%restore() - return - end if - self_ => self - node_ => node - ! If the dark matter component is queried and its density profile is unaffected by baryons, compute the radius from the dark - ! matter profile. Otherwise, find the radius numerically. - if ( & - & galacticStructureState_%state%componentType_ == componentTypeDarkHalo & - & .or. & - & galacticStructureState_%state%massType_ == massTypeDark & - & ) then - if (.not.associated(self%darkMatterProfile_)) call Error_Report('object is not expecting dark matter requests'//{introspection:location}) - ! Use the function provided by the dark matter profile structure tasks module here. This ensures precise consistency - ! between calculations here and in the enclosed mass function. - standardRadiusEnclosingMass=Dark_Matter_Profile_Radius_Enclosing_Mass(node_,massTarget) - else - ! Solve for the radius. - if (massEnclosedRoot(0.0d0) >= 0.0d0) then - message='Enclosed mass in galaxy (ID=' - write (massLabel,'(e10.4)') self%massEnclosed(node_,0.0d0,galacticStructureState_%state%componentType_,galacticStructureState_%state%massType_,galacticStructureState_%state%weightBy_,galacticStructureState_%state%weightIndex_) - message=message//node%index()//') seems to be finite ('//trim(massLabel) - write (massLabel,'(e10.4)') massTarget - message=message//') at zero radius (was seeking ' //trim(massLabel) - message=message//') - returning zero radius.' - call displayMessage(message,verbosityLevelWarn) - standardRadiusEnclosingMass=0.0d0 - call self%restore() - return - end if - if (self%radiusEnclosingMassPrevious >= 0.0d0) then - radiusGuess=self %radiusEnclosingMassPrevious - else - radiusGuess=self%darkMatterHaloScale_%radiusVirial (node) - end if - self%radiusEnclosingMassPrevious=self%finderMass%find (rootGuess=radiusGuess) - self%uniqueIDPrevious =node %uniqueID ( ) - standardRadiusEnclosingMass =self %radiusEnclosingMassPrevious - end if - call self%restore() - return - end function standardRadiusEnclosingMass - - double precision function massEnclosedRoot(radius) - !!{ - Root function used in solving for the radius that encloses a given mass. - !!} - double precision, intent(in ) :: radius - - massEnclosedRoot=+self_%massEnclosed(node_,radius,galacticStructureState_%state%componentType_,galacticStructureState_%state%massType_,galacticStructureState_%state%weightBy_,galacticStructureState_%state%weightIndex_) & - & - massTarget - return - end function massEnclosedRoot - - double precision function standardVelocityRotation(self,node,radius,componentType,massType) result(velocityRotation) - !!{ - Compute the rotation curve a given radius. - !!} - use :: Galactic_Structure_Options, only : componentTypeAll , massTypeAll - use :: Galacticus_Nodes , only : optimizeForRotationCurveSummation, reductionSummation, treeNode - !![ - - !!] - include 'galactic_structure.rotation_curve.tasks.modules.inc' - !![ - - !!] - implicit none - class (galacticStructureStandard ), intent(inout) :: self - type (treeNode ), intent(inout) :: node - type (enumerationComponentTypeType), intent(in ), optional :: componentType - type (enumerationMassTypeType ), intent(in ), optional :: massType - double precision , intent(in ) :: radius - procedure (velocityRotationComponent ) , pointer :: velocityRotationComponent_ - double precision :: velocityRotationComponent__, rotationCurveSquared - - call self%defaults(radius=radius,componentType=componentType,massType=massType) - velocityRotationComponent_ => velocityRotationComponent - rotationCurveSquared = node%mapDouble0(velocityRotationComponent_,reductionSummation,optimizeFor=optimizeForRotationCurveSummation) - !![ - - node,galacticStructureState_%state%radius_,galacticStructureState_%state%componentType_,galacticStructureState_%state%massType_ - rotationCurveSquared=rotationCurveSquared+velocityRotationComponent__**2 - !!] - include 'galactic_structure.rotation_curve.tasks.inc' - !![ - - !!] - ! We've added velocities in quadrature, so now take the square root. - velocityRotation=sqrt(rotationCurveSquared) - call self%restore() - return - end function standardVelocityRotation - - double precision function velocityRotationComponent(component) - !!{ - Unary function returning the squared rotation curve in a component. Suitable for mapping over components. - !!} - use :: Galacticus_Nodes, only : nodeComponent - implicit none - class(nodeComponent), intent(inout) :: component - - velocityRotationComponent=component%rotationCurve(galacticStructureState_%state%radius_,galacticStructureState_%state%componentType_,galacticStructureState_%state%massType_)**2 - return - end function velocityRotationComponent - - double precision function standardVelocityRotationGradient(self,node,radius,componentType,massType) result(velocityRotationGradient) - !!{ - Solve for the rotation curve gradient at a given radius. Assumes the galactic structure has already been computed. - !!} - use :: Error , only : Error_Report - use :: Galactic_Structure_Options, only : componentTypeAll , massTypeAll - use :: Galacticus_Nodes , only : optimizeForRotationCurveGradientSummation, reductionSummation, treeNode - !![ - - !!] - include 'galactic_structure.rotation_curve.gradient.tasks.modules.inc' - !![ - - !!] - implicit none - class (galacticStructureStandard ), intent(inout) :: self - type (treeNode ), intent(inout) :: node - type (enumerationComponentTypeType ), intent(in ), optional :: componentType - type (enumerationMassTypeType ), intent(in ), optional :: massType - double precision , intent(in ) :: radius - procedure (velocityRotationGradientComponent) , pointer :: velocityRotationGradientComponent_ - double precision :: velocityRotationGradientComponent__, velocityRotation - - call self%defaults(radius=radius,componentType=componentType,massType=massType) - ! Call routines to supply the gradient for all components' rotation curves. Specifically, the returned quantities are - ! dV²/dr so that they can be summed directly. - velocityRotationGradientComponent_ => velocityRotationGradientComponent - velocityRotationGradient = node%mapDouble0(velocityRotationGradientComponent_,reductionSummation,optimizeFor=optimizeForRotationCurveGradientSummation) - !![ - - node,galacticStructureState_%state%radius_,galacticStructureState_%state%componentType_,galacticStructureState_%state%massType_ - velocityRotationGradient=velocityRotationGradient+velocityRotationGradientComponent__ - !!] - include 'galactic_structure.rotation_curve.gradient.tasks.inc' - !![ - - !!] - ! Convert the summed dV²/dr to dV/dr. - velocityRotation=+self%velocityRotation(node,radius,componentType,massType) - if (velocityRotation > 0.0d0) then - velocityRotationGradient=+0.5d0 & - & *velocityRotationGradient & - & /velocityRotation - else if (velocityRotationGradient /= 0.0d0) then - call Error_Report('rotation curve is zero, but gradient is non-zero'//{introspection:location}) - end if - call self%restore() - return - end function standardVelocityRotationGradient - - double precision function velocityRotationGradientComponent(component) - !!{ - Unary function returning the gradient of the squared rotation curve in a component. Suitable for mapping over components. - !!} - use :: Galacticus_Nodes, only : nodeComponent - implicit none - class(nodeComponent), intent(inout) :: component - - velocityRotationGradientComponent=component%rotationCurveGradient(galacticStructureState_%state%radius_,galacticStructureState_%state%componentType_,galacticStructureState_%state%massType_) - return - end function velocityRotationGradientComponent - - double precision function standardPotential(self,node,radius,componentType,massType,status) result(potential) - !!{ - Solve for the gravitational potential at a given radius. Assumes the galactic structure has already been computed. - !!} - use :: Galactic_Structure_Options, only : componentTypeAll , massTypeAll , structureErrorCodeSuccess - use :: Galacticus_Nodes , only : optimizeForPotentialSummation, reductionSummation - !![ - - !!] - include 'galactic_structure.potential.tasks.modules.inc' - !![ - - !!] - implicit none - class (galacticStructureStandard ), intent(inout) :: self - type (treeNode ), intent(inout) :: node - type (enumerationComponentTypeType ), intent(in ), optional :: componentType - type (enumerationMassTypeType ), intent(in ), optional :: massType - double precision , intent(in ) :: radius - type (enumerationStructureErrorCodeType), intent( out), optional :: status - procedure (potentialComponent ), pointer :: potentialComponent_ - double precision :: potentialComponent__ - - ! Initialize status. - if (present(status)) status=structureErrorCodeSuccess - ! Initialize pointer to function that supplies the potential for all components. - potentialComponent_ => potentialComponent - ! Reset calculations if this is a new node. - if (node%uniqueID() /= self%uniqueIDPrevious) call self%calculationReset(node,node%uniqueID()) - ! Evaluate the potential at the halo virial radius. - if (.not.self%potentialOffsetComputed) then - call self%defaults(componentType=componentTypeAll,massType=massTypeAll,radius=self%darkMatterHaloScale_%radiusVirial(node)) - status_ =structureErrorCodeSuccess - potential =node%mapDouble0(potentialComponent_,reductionSummation,optimizeFor=optimizeForPotentialSummation) - if (status_ /= structureErrorCodeSuccess) status=status_ - !![ - - node,galacticStructureState_%state%radius_,galacticStructureState_%state%componentType_,galacticStructureState_%state%massType_,status - potential=potential+potentialComponent__ - !!] - include 'galactic_structure.potential.tasks.inc' - !![ - - !!] - call self%restore() - ! Compute the potential offset such that the total gravitational potential at the virial radius is -V² where V is the - ! virial velocity. - self%potentialOffset =-potential-self%darkMatterHaloScale_%velocityVirial(node)**2 - self%potentialOffsetComputed=.true. - end if - call self%defaults(radius=radius,componentType=componentType,massType=massType) - ! Determine which component type to use. - if (present(componentType)) then - galacticStructureState_%state%componentType_=componentType - else - galacticStructureState_%state%componentType_=componentTypeAll - end if - status_ = structureErrorCodeSuccess - potential=+node%mapDouble0(potentialComponent_,reductionSummation,optimizeFor=optimizeForPotentialSummation) & - & +self%potentialOffset - if (status_ /= structureErrorCodeSuccess) status=status_ - include 'galactic_structure.potential.tasks.inc' - call self%restore() - return - end function standardPotential - - double precision function potentialComponent(component) - !!{ - Unary function returning the potential in a component. Suitable for mapping over components. - !!} - use :: Galacticus_Nodes, only : nodeComponent - implicit none - class(nodeComponent), intent(inout) :: component - - potentialComponent=component%potential(galacticStructureState_%state%radius_,galacticStructureState_%state%componentType_,galacticStructureState_%state%massType_,status_) - return - end function potentialComponent - - double precision function standardSurfaceDensity(self,node,position,coordinateSystem,componentType,massType,weightBy,weightIndex) result(surfaceDensity) - !!{ - Compute the surface density of given {\normalfont \ttfamily massType}) at the specified {\normalfont \ttfamily position}. - !!} - use :: Coordinate_Systems , only : Coordinates_Cartesian_To_Cylindrical, Coordinates_Spherical_To_Cylindrical - use :: Galactic_Structure_Options, only : componentTypeAll , coordinateSystemCartesian , coordinateSystemCylindrical, coordinateSystemSpherical , & - & massTypeAll , weightByMass , weightIndexNull , enumerationCoordinateSystemType - use :: Error , only : Error_Report - use :: Galacticus_Nodes , only : optimizeForSurfaceDensitySummation , optimizeforsurfacedensitysummation , reductionSummation , reductionsummation , & - & treeNode - implicit none - class (galacticStructureStandard ), intent(inout) :: self - type (treeNode ), intent(inout) :: node - type (enumerationComponentTypeType ), intent(in ), optional :: componentType - type (enumerationMassTypeType ), intent(in ), optional :: massType - type (enumerationWeightByType ), intent(in ), optional :: weightBy - integer , intent(in ), optional :: weightIndex - type (enumerationCoordinateSystemType), intent(in ), optional :: coordinateSystem - double precision , intent(in ), dimension(3) :: position - procedure (surfaceDensityComponent ), pointer :: surfaceDensityComponent_ - !![ - - !!] - - call self%defaults(componentType=componentType,massType=massType,weightBy=weightBy,weightIndex=weightIndex) - ! Determine position in cylindrical coordinate system to use. - select case (coordinateSystem_%ID) - case (coordinateSystemSpherical %ID) - positionCylindrical_=Coordinates_Spherical_To_Cylindrical (position) - case (coordinateSystemCylindrical%ID) - positionCylindrical_=position - case (coordinateSystemCartesian %ID) - positionCylindrical_=Coordinates_Cartesian_To_Cylindrical(position) - case default - call Error_Report('unknown coordinate system type'//{introspection:location}) - end select - ! Call routines to supply the densities for all components. - surfaceDensityComponent_ => surfaceDensityComponent - surfaceDensity = node%mapDouble0(surfaceDensityComponent_,reductionSummation,optimizeFor=optimizeForSurfaceDensitySummation) - call self%restore() - return - end function standardSurfaceDensity - - double precision function surfaceDensityComponent(component) - !!{ - Unary function returning the surface density in a component. Suitable for mapping over components. - !!} - use :: Galacticus_Nodes, only : nodeComponent - implicit none - class(nodeComponent), intent(inout) :: component - - surfaceDensityComponent=component%surfaceDensity(positionCylindrical_,galacticStructureState_%state%componentType_,galacticStructureState_%state%massType_,galacticStructureState_%state%weightBy_,galacticStructureState_%state%weightIndex_) - return - end function surfaceDensityComponent - - double precision function standardRadiusEnclosingSurfaceDensity(self,node,surfaceDensity,componentType,massType,weightBy,weightIndex) result(radius) - !!{ - Return the radius enclosing a given surface density in {\normalfont \ttfamily node}. - !!} - use :: Kind_Numbers, only : kind_int8 - use :: Root_Finder , only : rangeExpandMultiplicative, rangeExpandSignExpectNegative, rangeExpandSignExpectPositive, rootFinder - implicit none - class (galacticStructureStandard ), intent(inout), target :: self - type (treeNode ), intent(inout), target :: node - type (enumerationComponentTypeType), intent(in ), optional :: componentType - type (enumerationMassTypeType ), intent(in ), optional :: massType - type (enumerationWeightByType ), intent(in ), optional :: weightBy - integer , intent(in ), optional :: weightIndex - double precision , intent(in ) :: surfaceDensity - double precision :: radiusGuess - - call self%defaults(componentType=componentType,massType=massType,weightBy=weightBy,weightIndex=weightIndex) - self_ => self - node_ => node - surfaceDensityTarget = surfaceDensity - if (self%radiusEnclosingSurfaceDensityPrevious >= 0.0d0) then - radiusGuess=self %radiusEnclosingSurfaceDensityPrevious - else - radiusGuess=self%darkMatterHaloScale_%radiusVirial (node) - end if - self%radiusEnclosingSurfaceDensityPrevious=self%finderSurfaceDensity%find(rootGuess=radiusGuess) - radius=self%radiusEnclosingSurfaceDensityPrevious - call self%restore() - return - end function standardRadiusEnclosingSurfaceDensity - - double precision function surfaceDensityRoot(radius) - !!{ - Root function used in solving for the radius that encloses a given surface density. - !!} - use :: Galactic_Structure_Options, only : coordinateSystemCylindrical - implicit none - double precision, intent(in ) :: radius - - ! Evaluate the root function. - surfaceDensityRoot=+self_%surfaceDensity (node_,[radius,0.0d0,0.0d0],coordinateSystemCylindrical,galacticStructureState_%state%componentType_,galacticStructureState_%state%massType_,galacticStructureState_%state%weightBy_,galacticStructureState_%state%weightIndex_) & - & - surfaceDensityTarget - return - end function surfaceDensityRoot - - function standardAcceleration(self,node,positionCartesian,componentType,massType) result(acceleration) - !!{ - Compute the gravitational acceleration at a given position. - !!} - use :: Galactic_Structure_Options, only : componentTypeAll , massTypeAll - use :: Galacticus_Nodes , only : optimizeForAccelerationSummation, reductionSummation, treeNode - !![ - - !!] - include 'galactic_structure.acceleration.tasks.modules.inc' - !![ - - !!] - implicit none - class (galacticStructureStandard ), intent(inout) :: self - double precision , dimension(3) :: acceleration - type (treeNode ), intent(inout) :: node - double precision , intent(in ), dimension(3) :: positionCartesian - type (enumerationComponentTypeType), intent(in ), optional :: componentType - type (enumerationMassTypeType ), intent(in ), optional :: massType - integer , parameter :: accelerationSize =3 - procedure (accelerationComponent ), pointer :: accelerationComponent_ - double precision , dimension(3) :: accelerationComponent__ - - call self%defaults(componentType=componentType,massType=massType) - positionCartesian_ = positionCartesian - accelerationComponent_ => accelerationComponent - acceleration = node%mapDouble1(accelerationComponent_,accelerationSize,reductionSummation,optimizeFor=optimizeForAccelerationSummation) - !![ - - node,positionCartesian_,galacticStructureState_%state%componentType_,galacticStructureState_%state%massType_ - acceleration=acceleration+accelerationComponent__ - !!] - include 'galactic_structure.acceleration.tasks.inc' - !![ - - !!] - call self%restore() - return - end function standardAcceleration - - function accelerationComponent(component,resultSize) - !!{ - Function returning the acceleration in a component. Suitable for mapping over components. - !!} - use :: Galacticus_Nodes, only : nodeComponent - implicit none - integer , intent(in ) :: resultSize - class (nodeComponent), intent(inout) :: component - double precision , dimension(resultSize) :: accelerationComponent - - accelerationComponent=component%acceleration(positionCartesian_,galacticStructureState_%state%componentType_,galacticStructureState_%state%massType_) - return - end function accelerationComponent - - function standardTidalTensor(self,node,positionCartesian,componentType,massType) result(tidalTensor) - !!{ - Compute the gravitational tidal tensor at a given position. - !!} - use :: Galactic_Structure_Options, only : componentTypeAll , massTypeAll - use :: Galacticus_Nodes , only : optimizeForTidalTensorSummation, reductionSummation, treeNode - use :: Tensors , only : tensorRank2Dimension3Symmetric , assignment(=) - !![ - - !!] - include 'galactic_structure.tidal_tensor.tasks.modules.inc' - !![ - - !!] - implicit none - class (galacticStructureStandard ), intent(inout) :: self - type (tensorRank2Dimension3Symmetric) :: tidalTensor - type (treeNode ), intent(inout) :: node - double precision , intent(in ), dimension(3) :: positionCartesian - type (enumerationComponentTypeType ), intent(in ), optional :: componentType - type (enumerationMassTypeType ), intent(in ), optional :: massType - procedure (tidalTensorComponent ), pointer :: tidalTensorComponent_ - type (tensorRank2Dimension3Symmetric) :: tidalTensorComponent__ - - call self%defaults(componentType=componentType,massType=massType) - positionCartesian_ = positionCartesian - tidalTensorComponent_ => tidalTensorComponent - tidalTensor = node%mapTensorR2D3(tidalTensorComponent_,reductionSummation,optimizeFor=optimizeForTidalTensorSummation) - !![ - - node,positionCartesian_,galacticStructureState_%state%componentType_,galacticStructureState_%state%massType_ - tidalTensor=tidalTensor+tidalTensorComponent__ - !!] - include 'galactic_structure.tidal_tensor.tasks.inc' - !![ - - !!] - call self%restore() - return - end function standardTidalTensor - - function tidalTensorComponent(component) - !!{ - Function returning the tidal tensor in a component. Suitable for mapping over components. - !!} - use :: Galacticus_Nodes, only : nodeComponent - use :: Tensors , only : tensorRank2Dimension3Symmetric - implicit none - class(nodeComponent ), intent(inout) :: component - type (tensorRank2Dimension3Symmetric) :: tidalTensorComponent - - tidalTensorComponent=component%tidalTensor(positionCartesian_,galacticStructureState_%state%componentType_,galacticStructureState_%state%massType_) - return - end function tidalTensorComponent - - function standardChandrasekharIntegral(self,node,nodeSatellite,positionCartesian,velocityCartesian,componentType,massType) result(chandrasekharIntegral) - !!{ - Compute the integral appearing in the \cite{chandrasekhar_dynamical_1943} dynamical friction model: - \begin{equation} - \rho(\boldsymbol{x}_\mathrm{s}) \int \mathrm{d}\boldsymbol{v} f(\boldsymbol{v}) {\boldsymbol{v}-\boldsymbol{v}_\mathrm{s} \over |\boldsymbol{v}-\boldsymbol{v}_\mathrm{s}|^3}, - \end{equation} - where $\rho(\boldsymbol{x}_\mathrm{s})$ is the density at the position of the perturber, $\boldsymbol{x}_\mathrm{s}$, - $f(\boldsymbol{v})$ is the velocity distribution function at velocity $\boldsymbol{v}$, and $\boldsymbol{v}_\mathrm{s}$ is - the velocity of the perturber. - !!} - use :: Galactic_Structure_Options, only : componentTypeAll , massTypeAll - use :: Galacticus_Nodes , only : optimizeForChandrasekharIntegralSummation, reductionSummation, treeNode - !![ - - !!] - include 'galactic_structure.chandrasekharIntegral.tasks.modules.inc' - !![ - - !!] - implicit none - double precision , dimension(3) :: chandrasekharIntegral - class (galacticStructureStandard ), intent(inout) :: self - type (treeNode ), intent(inout), target :: node , nodeSatellite - double precision , intent(in ), dimension(3) :: positionCartesian , velocityCartesian - type (enumerationComponentTypeType ), intent(in ), optional :: componentType - type (enumerationMassTypeType ), intent(in ), optional :: massType - integer , parameter :: chandrasekharIntegralSize =3 - procedure (chandrasekharIntegralComponent), pointer :: chandrasekharIntegralComponent_ - double precision , dimension(3) :: chandrasekharIntegralComponent__ - - call self%defaults(componentType=componentType,massType=massType) - positionCartesian_ = positionCartesian - velocityCartesian_ = velocityCartesian - nodeSatellite_ => nodeSatellite - chandrasekharIntegralComponent_ => chandrasekharIntegralComponent - chandrasekharIntegral = node%mapDouble1(chandrasekharIntegralComponent_,chandrasekharIntegralSize,reductionSummation,optimizeFor=optimizeForChandrasekharIntegralSummation) - !![ - - node,nodeSatellite,positionCartesian_,velocityCartesian_,galacticStructureState_%state%componentType_,galacticStructureState_%state%massType_ - chandrasekharIntegral=chandrasekharIntegral+chandrasekharIntegralComponent__ - !!] - include 'galactic_structure.chandrasekharIntegral.tasks.inc' - !![ - - !!] - call self%restore() - return - end function standardChandrasekharIntegral - - function chandrasekharIntegralComponent(component,resultSize) - !!{ - Function returning the Chandrasekhar integral in a component. Suitable for mapping over components. - !!} - use :: Galacticus_Nodes, only : nodeComponent - implicit none - integer , intent(in ) :: resultSize - class (nodeComponent), intent(inout) :: component - double precision , dimension(resultSize) :: chandrasekharIntegralComponent - - chandrasekharIntegralComponent=component%chandrasekharIntegral(nodeSatellite_,positionCartesian_,velocityCartesian_,galacticStructureState_%state%componentType_,galacticStructureState_%state%massType_) - return - end function chandrasekharIntegralComponent - - double precision function standardVelocityDispersion(self,node,radius,radiusOuter,componentType,massType) result(velocityDispersion) - !!{ - Returns the velocity dispersion of the specified {\normalfont \ttfamily componentType} in {\normalfont \ttfamily node} at the given {\normalfont \ttfamily radius}. - !!} - use :: Galactic_Structure_Options, only : radiusLarge - use :: Numerical_Integration , only : integrator - use :: Numerical_Constants_Math , only : Pi - class (galacticStructureStandard ), intent(inout), target :: self - type (treeNode ), intent(inout), target :: node - double precision , intent(in ) :: radius , radiusOuter - type (enumerationComponentTypeType), intent(in ), optional :: componentType - type (enumerationMassTypeType ), intent(in ), optional :: massType - double precision :: densitySphericalAverage, densityVelocityVariance, & - & massTotal - type (integrator ) :: integrator_ - - self_ => self - node_ => node - call self%defaults(componentType=componentType,massType=massType) - massTotal=self%massEnclosed(node,radiusLarge,componentType=componentType,massType=massType) - ! Return with zero dispersion if the component is massless. - if (massTotal <= 0.0d0) then - velocityDispersion=0.0d0 - return - end if - ! Integrate the Jeans equation. - integrator_ =integrator (integrandVelocityDispersion,toleranceRelative=1.0d-3) - densityVelocityVariance=integrator_%integrate(radius ,radiusOuter ) - ! Get the density at this radius. - densitySphericalAverage=self_%densitySphericalAverage( & - & node_ , & - & radius , & - & componentType=galacticStructureState_%state%componentType_, & - & massType =galacticStructureState_%state%massType_ & - & ) - ! Check for zero density. - if (densitySphericalAverage <= 0.0d0) then - velocityDispersion=0.0d0 - else - velocityDispersion=sqrt(max(densityVelocityVariance,0.0d0)/densitySphericalAverage) - end if - call self%restore() - return - end function standardVelocityDispersion - - double precision function integrandVelocityDispersion(radius) - !!{ - Integrand function used for finding velocity dispersions using Jeans equation. - !!} - use :: Numerical_Constants_Astronomical, only : gravitationalConstantGalacticus - implicit none - double precision, intent(in ) :: radius - - if (radius == 0.0d0) then - integrandVelocityDispersion=0.0d0 - else - integrandVelocityDispersion=+gravitationalConstantGalacticus & - & *self_%massEnclosed ( & - & node_ , & - & radius & - & ) & - & /radius**2 & - & *self_%densitySphericalAverage( & - & node_ , & - & radius , & - & componentType=galacticStructureState_%state%componentType_, & - & massType =galacticStructureState_%state%massType_ & - & ) - end if - return - end function integrandVelocityDispersion - - subroutine standardDefaults(self,radius,componentType,massType,weightBy,weightIndex) - !!{ - Set the default values for options in the enclosed mass functions. - !!} - use :: Galactic_Structure_Options, only : componentTypeAll, massTypeAll, weightByLuminosity, weightByMass, & - & radiusLarge - use :: Error , only : Error_Report - implicit none - class (galacticStructureStandard ), intent(inout) :: self - double precision , intent(in ), optional :: radius - type (enumerationComponentTypeType), intent(in ), optional :: componentType - type (enumerationMassTypeType ), intent(in ), optional :: massType - type (enumerationWeightByType ), intent(in ), optional :: weightBy - integer , intent(in ), optional :: weightIndex - !![ - - - - - - !!] - - ! Expand the state stack if necessary. - if (.not.associated(galacticStructureState_)) then - if (.not.associated(galacticStructureStateHead_)) allocate(galacticStructureStateHead_) - galacticStructureState_ => galacticStructureStateHead_ - else - if (.not.associated(galacticStructureState_%next)) then - allocate(galacticStructureState_%next) - galacticStructureState_%next%previous => galacticStructureState_ - end if - galacticStructureState_ => galacticStructureState_%next - end if - ! Set defaults. - galacticStructureState_%state%radius_ =radius_ - galacticStructureState_%state%massType_ =massType_ - galacticStructureState_%state%componentType_ =componentType_ - galacticStructureState_%state%weightBy_ =weightBy_ - select case (weightBy_%ID) - case (weightByLuminosity%ID) - if (.not.present(weightIndex)) call Error_Report('weightIndex should be specified for luminosity weighting'//{introspection:location}) - galacticStructureState_%state%weightIndex_=weightIndex_ - end select - return - end subroutine standardDefaults - - subroutine standardRestore(self) - !!{ - Restore the previous state from the stack. - !!} - implicit none - class(galacticStructureStandard), intent(inout) :: self - !$GLC attributes unused :: self - - galacticStructureState_ => galacticStructureState_%previous - return - end subroutine standardRestore diff --git a/source/galactic.structure.utilities.F90 b/source/galactic.structure.utilities.F90 deleted file mode 100644 index 57ab6a1664..0000000000 --- a/source/galactic.structure.utilities.F90 +++ /dev/null @@ -1,417 +0,0 @@ -!! Copyright 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, -!! 2019, 2020, 2021, 2022, 2023, 2024 -!! Andrew Benson -!! -!! This file is part of Galacticus. -!! -!! Galacticus is free software: you can redistribute it and/or modify -!! it under the terms of the GNU General Public License as published by -!! the Free Software Foundation, either version 3 of the License, or -!! (at your option) any later version. -!! -!! Galacticus is distributed in the hope that it will be useful, -!! but WITHOUT ANY WARRANTY; without even the implied warranty of -!! MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -!! GNU General Public License for more details. -!! -!! You should have received a copy of the GNU General Public License -!! along with Galacticus. If not, see . - -!!{ -Contains a module of globally-accessible functions supporting the galactic structure class. -!!} - -module Galactic_Structure_Utilities - !!{ - Provides globally-accessible functions supporting the galactic structure class. - !!} - private - public :: galacticStructureConstruct , galacticStructureMassEnclosed , galacticStructureDestruct , galacticStructureDeepCopy , & - & galacticStructureDeepCopyReset , galacticStructureDeepCopyFinalize , galacticStructureStateStore, galacticStructureStateRestore, & - & galacticStructureVelocityRotation, galacticStructureVelocityRotationGradient, galacticStructureDensity - - ! Module-scope pointer to our task object. This is used for reference counting so that debugging information is consistent - ! between the increments and decrements. - class(*), pointer :: galacticStructure__ - !$omp threadprivate(galacticStructure__) - -contains - - !![ - - galacticStructureConstruct - void - Input_Parameters, only : inputParameters - type (inputParameters), intent(inout), target :: parameters - class(* ), intent( out), pointer :: galacticStructure_ - - !!] - subroutine galacticStructureConstruct(parameters,galacticStructure_) - !!{ - Build a {\normalfont \ttfamily galacticStructure} object from a given parameter set. This is a globally-callable function - to allow us to subvert the class/module hierarchy. - !!} - use :: Error , only : Error_Report - use :: Input_Parameters , only : inputParameter , inputParameters - use :: Galactic_Structure, only : galacticStructureClass, galacticStructure - implicit none - type (inputParameters), intent(inout), target :: parameters - class(* ), intent( out), pointer :: galacticStructure_ - type (inputParameters) , pointer :: parametersCurrent - - parametersCurrent => parameters - do while (.not.parametersCurrent%isPresent('galacticStructure').and.associated(parametersCurrent%parent)) - parametersCurrent => parametersCurrent%parent - end do - if (.not.parametersCurrent%isPresent('galacticStructure')) parametersCurrent => parameters - galacticStructure__ => galacticStructure(parameters) - select type (galacticStructure__) - class is (galacticStructureClass) - !![ - - !!] - call galacticStructure__%autoHook() - class default - call Error_Report('unexpected class'//{introspection:location}) - end select - galacticStructure_ => galacticStructure__ - return - end subroutine galacticStructureConstruct - - !![ - - galacticStructureMassEnclosed - double precision - Galacticus_Nodes , only : treeNode - Galactic_Structure_Options, only : enumerationComponentTypeType, enumerationMassTypeType, enumerationWeightByType - class (* ), intent(inout) :: galacticStructure_ - type (treeNode ), intent(inout) :: node - double precision , intent(in ), optional :: radius - type (enumerationComponentTypeType), intent(in ), optional :: componentType - type (enumerationMassTypeType ), intent(in ), optional :: massType - type (enumerationWeightByType ), intent(in ), optional :: weightBy - integer , intent(in ), optional :: weightIndex - - !!] - double precision function galacticStructureMassEnclosed(galacticStructure_,node,radius,componentType,massType,weightBy,weightIndex) - !!{ - Compute the mass enclosed for a {\normalfont \ttfamily galacticStructure} object passed to us as an unlimited polymorphic object. - !!} - use :: Error , only : Error_Report - use :: Galactic_Structure , only : galacticStructureClass - use :: Galactic_Structure_Options, only : enumerationComponentTypeType, enumerationMassTypeType, enumerationWeightByType - use :: Galacticus_Nodes , only : treeNode - implicit none - class (* ), intent(inout) :: galacticStructure_ - type (treeNode ), intent(inout) :: node - type (enumerationComponentTypeType), intent(in ), optional :: componentType - type (enumerationMassTypeType ), intent(in ), optional :: massType - type (enumerationWeightByType ), intent(in ), optional :: weightBy - integer , intent(in ), optional :: weightIndex - double precision , intent(in ), optional :: radius - - select type (galacticStructure_) - class is (galacticStructureClass) - galacticStructureMassEnclosed=galacticStructure_%massEnclosed(node,radius,componentType,massType,weightBy,weightIndex) - class default - galacticStructureMassEnclosed=0.0d0 - call Error_Report('unexpected class'//{introspection:location}) - end select - return - end function galacticStructureMassEnclosed - - !![ - - galacticStructureDensity - double precision - Galacticus_Nodes , only : treeNode - Galactic_Structure_Options, only : enumerationComponentTypeType, enumerationMassTypeType, enumerationWeightByType, enumerationCoordinateSystemType - class (* ), intent(inout) :: galacticStructure_ - type (treeNode ), intent(inout) :: node - double precision , intent(in ), dimension(3) :: position - type (enumerationCoordinateSystemType), intent(in ), optional :: coordinateSystem - type (enumerationComponentTypeType ), intent(in ), optional :: componentType - type (enumerationMassTypeType ), intent(in ), optional :: massType - type (enumerationWeightByType ), intent(in ), optional :: weightBy - integer , intent(in ), optional :: weightIndex - - !!] - double precision function galacticStructureDensity(galacticStructure_,node,position,coordinateSystem,componentType,massType,weightBy,weightIndex) - !!{ - Compute the density for a {\normalfont \ttfamily galacticStructure} object passed to us as an unlimited polymorphic object. - !!} - use :: Error , only : Error_Report - use :: Galactic_Structure , only : galacticStructureClass - use :: Galactic_Structure_Options, only : enumerationComponentTypeType, enumerationMassTypeType, enumerationWeightByType, enumerationCoordinateSystemType - use :: Galacticus_Nodes , only : treeNode - implicit none - class (* ), intent(inout) :: galacticStructure_ - type (treeNode ), intent(inout) :: node - type (enumerationComponentTypeType ), intent(in ), optional :: componentType - type (enumerationMassTypeType ), intent(in ), optional :: massType - type (enumerationWeightByType ), intent(in ), optional :: weightBy - type (enumerationCoordinateSystemType), intent(in ), optional :: coordinateSystem - integer , intent(in ), optional :: weightIndex - double precision , intent(in ), dimension(3) :: position - - select type (galacticStructure_) - class is (galacticStructureClass) - galacticStructureDensity=galacticStructure_%density(node,position,coordinateSystem,componentType,massType,weightBy,weightIndex) - class default - galacticStructureDensity=0.0d0 - call Error_Report('unexpected class'//{introspection:location}) - end select - return - end function galacticStructureDensity - - !![ - - galacticStructureVelocityRotation - double precision - Galacticus_Nodes , only : treeNode - Galactic_Structure_Options, only : enumerationComponentTypeType, enumerationMassTypeType - class (* ), intent(inout) :: galacticStructure_ - type (treeNode ), intent(inout) :: node - double precision , intent(in ), optional :: radius - type (enumerationComponentTypeType), intent(in ), optional :: componentType - type (enumerationMassTypeType ), intent(in ), optional :: massType - - !!] - double precision function galacticStructureVelocityRotation(galacticStructure_,node,radius,componentType,massType) - !!{ - Compute the rotation velocity for a {\normalfont \ttfamily galacticStructure} object passed to us as an unlimited polymorphic object. - !!} - use :: Error , only : Error_Report - use :: Galactic_Structure , only : galacticStructureClass - use :: Galactic_Structure_Options, only : enumerationComponentTypeType, enumerationMassTypeType - use :: Galacticus_Nodes , only : treeNode - implicit none - class (* ), intent(inout) :: galacticStructure_ - type (treeNode ), intent(inout) :: node - type (enumerationComponentTypeType), intent(in ), optional :: componentType - type (enumerationMassTypeType ), intent(in ), optional :: massType - double precision , intent(in ), optional :: radius - - select type (galacticStructure_) - class is (galacticStructureClass) - galacticStructureVelocityRotation=galacticStructure_%velocityRotation(node,radius,componentType,massType) - class default - galacticStructureVelocityRotation=0.0d0 - call Error_Report('unexpected class'//{introspection:location}) - end select - return - end function galacticStructureVelocityRotation - - !![ - - galacticStructureVelocityRotationGradient - double precision - Galacticus_Nodes , only : treeNode - Galactic_Structure_Options, only : enumerationComponentTypeType, enumerationMassTypeType - class (* ), intent(inout) :: galacticStructure_ - type (treeNode ), intent(inout) :: node - double precision , intent(in ), optional :: radius - type (enumerationComponentTypeType), intent(in ), optional :: componentType - type (enumerationMassTypeType ), intent(in ), optional :: massType - - !!] - double precision function galacticStructureVelocityRotationGradient(galacticStructure_,node,radius,componentType,massType) - !!{ - Compute the rotation velocity gradient for a {\normalfont \ttfamily galacticStructure} object passed to us as an unlimited polymorphic object. - !!} - use :: Error , only : Error_Report - use :: Galactic_Structure , only : galacticStructureClass - use :: Galacticus_Nodes , only : treeNode - use :: Galactic_Structure_Options, only : enumerationComponentTypeType, enumerationMassTypeType - implicit none - class (* ), intent(inout) :: galacticStructure_ - type (treeNode ), intent(inout) :: node - type (enumerationComponentTypeType), intent(in ), optional :: componentType - type (enumerationMassTypeType ), intent(in ), optional :: massType - double precision , intent(in ), optional :: radius - - select type (galacticStructure_) - class is (galacticStructureClass) - galacticStructureVelocityRotationGradient=galacticStructure_%velocityRotationGradient(node,radius,componentType,massType) - class default - galacticStructureVelocityRotationGradient=0.0d0 - call Error_Report('unexpected class'//{introspection:location}) - end select - return - end function galacticStructureVelocityRotationGradient - - !![ - - galacticStructureDestruct - void - class(*), intent(inout), pointer :: galacticStructure_ - - !!] - subroutine galacticStructureDestruct(galacticStructure_) - !!{ - Destruct a {\normalfont \ttfamily taskEvolveForests} object passed to us as an unlimited polymorphic object. - !!} - use :: Error , only : Error_Report - use :: Galactic_Structure, only : galacticStructureClass - implicit none - class(*), intent(inout), pointer :: galacticStructure_ - - galacticStructure__ => galacticStructure_ - select type (galacticStructure__) - class is (galacticStructureClass) - !![ - - !!] - class default - call Error_Report('unexpected class'//{introspection:location}) - end select - return - end subroutine galacticStructureDestruct - - !![ - - galacticStructureStateRestore - void - ISO_C_Binding, only : c_ptr, c_size_t - class (* ), intent(inout) :: self - integer , intent(in ) :: stateFile - type (c_ptr ), intent(in ) :: gslStateFile - integer(c_size_t), intent(in ) :: stateOperationID - - !!] - subroutine galacticStructureStateRestore(self,stateFile,gslStateFile,stateOperationID) - !!{ - Perform a deep copy of galactic structure objects. - !!} - use, intrinsic :: ISO_C_Binding , only : c_ptr , c_size_t - use :: Error , only : Error_Report - use :: Galactic_Structure, only : galacticStructureClass - implicit none - class (* ), intent(inout) :: self - integer , intent(in ) :: stateFile - type (c_ptr ), intent(in ) :: gslStateFile - integer(c_size_t), intent(in ) :: stateOperationID - - select type (self) - class is (galacticStructureClass) - call self%stateRestore(stateFile,gslStateFile,stateOperationID) - class default - call Error_Report("unexpected class"//{introspection:location}) - end select - return - end subroutine galacticStructureStateRestore - - !![ - - galacticStructureStateStore - void - ISO_C_Binding, only : c_ptr, c_size_t - class (* ), intent(inout) :: self - integer , intent(in ) :: stateFile - type (c_ptr ), intent(in ) :: gslStateFile - integer(c_size_t), intent(in ) :: stateOperationID - - !!] - subroutine galacticStructureStateStore(self,stateFile,gslStateFile,stateOperationID) - !!{ - Perform a deep copy of galactic structure objects. - !!} - use, intrinsic :: ISO_C_Binding , only : c_ptr , c_size_t - use :: Error , only : Error_Report - use :: Galactic_Structure, only : galacticStructureClass - implicit none - class (* ), intent(inout) :: self - integer , intent(in ) :: stateFile - type (c_ptr ), intent(in ) :: gslStateFile - integer(c_size_t), intent(in ) :: stateOperationID - - select type (self) - class is (galacticStructureClass) - call self%stateStore(stateFile,gslStateFile,stateOperationID) - class default - call Error_Report("unexpected class"//{introspection:location}) - end select - return - end subroutine galacticStructureStateStore - - !![ - - galacticStructureDeepCopyReset - void - class(*), intent(inout) :: self - - !!] - subroutine galacticStructureDeepCopyReset(self) - !!{ - Perform a deep copy of galactic structure objects. - !!} - use :: Error , only : Error_Report - use :: Galactic_Structure, only : galacticStructureClass - implicit none - class(*), intent(inout) :: self - - select type (self) - class is (galacticStructureClass) - call self%deepCopyReset() - class default - call Error_Report("unexpected class"//{introspection:location}) - end select - return - end subroutine galacticStructureDeepCopyReset - - !![ - - galacticStructureDeepCopyFinalize - void - class(*), intent(inout) :: self - - !!] - subroutine galacticStructureDeepCopyFinalize(self) - !!{ - Finalize a deep copy of galactic structure objects. - !!} - use :: Error , only : Error_Report - use :: Galactic_Structure, only : galacticStructureClass - implicit none - class(*), intent(inout) :: self - - select type (self) - class is (galacticStructureClass) - call self%deepCopyFinalize() - class default - call Error_Report("unexpected class"//{introspection:location}) - end select - return - end subroutine galacticStructureDeepCopyFinalize - - !![ - - galacticStructureDeepCopy - void - class(*), intent(inout) :: self, destination - - !!] - subroutine galacticStructureDeepCopy(self,destination) - !!{ - Perform a deep copy of galactic structure objects. - !!} - use :: Error , only : Error_Report - use :: Galactic_Structure, only : galacticStructureClass - implicit none - class(*), intent(inout) :: self, destination - - select type (self) - class is (galacticStructureClass) - select type (destination) - class is (galacticStructureClass) - call self%deepCopy(destination) - class default - call Error_Report("unexpected class"//{introspection:location}) - end select - class default - call Error_Report("unexpected class"//{introspection:location}) - end select - return - end subroutine galacticStructureDeepCopy - -end module Galactic_Structure_Utilities diff --git a/source/galactic_structure.options.F90 b/source/galactic_structure.options.F90 index e7f543a3bd..3489fd994c 100644 --- a/source/galactic_structure.options.F90 +++ b/source/galactic_structure.options.F90 @@ -51,14 +51,15 @@ module Galactic_Structure_Options yes yes yes - - - - - - - - + + + + + + + + + !!] @@ -85,8 +86,9 @@ module Galactic_Structure_Options structureErrorCode Error codes for galactic structure functions. - - + + + !!] diff --git a/source/galactic_structure.radius_solver.equilibrium.F90 b/source/galactic_structure.radius_solver.equilibrium.F90 index 6c7585b46d..4fdc83d551 100644 --- a/source/galactic_structure.radius_solver.equilibrium.F90 +++ b/source/galactic_structure.radius_solver.equilibrium.F90 @@ -21,10 +21,8 @@ Implementation of an ``equilibrium'' solver for galactic structure. !!} - use :: Dark_Matter_Profiles , only : darkMatterProfileClass use :: Dark_Matter_Profiles_DMO, only : darkMatterProfileDMOClass use :: Dark_Matter_Halo_Scales , only : darkMatterHaloScaleClass - use :: Galactic_Structure , only : galacticStructureClass !![ @@ -40,9 +38,7 @@ & solveForInactiveProperties , convergenceFailureIsFatal double precision :: solutionTolerance class (darkMatterHaloScaleClass ), pointer :: darkMatterHaloScale_ => null() - class (darkMatterProfileClass ), pointer :: darkMatterProfile_ => null() class (darkMatterProfileDMOClass), pointer :: darkMatterProfileDMO_ => null() - class (galacticStructureClass ), pointer :: galacticStructure_ => null() contains final :: equilibriumDestructor procedure :: solve => equilibriumSolve @@ -78,9 +74,7 @@ function equilibriumConstructorParameters(parameters) result(self) type (galacticStructureSolverEquilibrium) :: self type (inputParameters ), intent(inout) :: parameters class (darkMatterHaloScaleClass ), pointer :: darkMatterHaloScale_ - class (darkMatterProfileClass ), pointer :: darkMatterProfile_ class (darkMatterProfileDMOClass ), pointer :: darkMatterProfileDMO_ - class (galacticStructureClass ), pointer :: galacticStructure_ logical :: useFormationHalo , includeBaryonGravity , & & solveForInactiveProperties, convergenceFailureIsFatal double precision :: solutionTolerance @@ -117,22 +111,18 @@ function equilibriumConstructorParameters(parameters) result(self) parameters - - !!] - self=galacticStructureSolverEquilibrium(convergenceFailureIsFatal,useFormationHalo,includeBaryonGravity,solutionTolerance,solveForInactiveProperties,darkMatterHaloScale_,darkMatterProfile_,darkMatterProfileDMO_,galacticStructure_) + self=galacticStructureSolverEquilibrium(convergenceFailureIsFatal,useFormationHalo,includeBaryonGravity,solutionTolerance,solveForInactiveProperties,darkMatterHaloScale_,darkMatterProfileDMO_) !![ - - !!] return end function equilibriumConstructorParameters - function equilibriumConstructorInternal(convergenceFailureIsFatal,useFormationHalo,includeBaryonGravity,solutionTolerance,solveForInactiveProperties,darkMatterHaloScale_,darkMatterProfile_,darkMatterProfileDMO_,galacticStructure_) result(self) + function equilibriumConstructorInternal(convergenceFailureIsFatal,useFormationHalo,includeBaryonGravity,solutionTolerance,solveForInactiveProperties,darkMatterHaloScale_,darkMatterProfileDMO_) result(self) !!{ Internal constructor for the {\normalfont \ttfamily equilibrium} galactic structure solver class. !!} @@ -142,11 +132,9 @@ function equilibriumConstructorInternal(convergenceFailureIsFatal,useFormationHa & solveForInactiveProperties, convergenceFailureIsFatal double precision , intent(in ) :: solutionTolerance class (darkMatterHaloScaleClass ), intent(in ), target :: darkMatterHaloScale_ - class (darkMatterProfileClass ), intent(in ), target :: darkMatterProfile_ class (darkMatterProfileDMOClass ), intent(in ), target :: darkMatterProfileDMO_ - class (galacticStructureClass ), intent(in ), target :: galacticStructure_ !![ - + !!] return @@ -180,9 +168,7 @@ subroutine equilibriumDestructor(self) !![ - - !!] if ( preDerivativeEvent%isAttached(self,equilibriumSolvePreDeriativeHook)) call preDerivativeEvent%detach(self,equilibriumSolvePreDeriativeHook) if ( postEvolveEvent%isAttached(self,equilibriumSolveHook )) call postEvolveEvent%detach(self,equilibriumSolveHook ) @@ -304,31 +290,33 @@ subroutine radiusSolve(node,specificAngularMomentum,radiusGet,radiusSet,velocity Solve for the equilibrium radius of the given component. !!} use :: Display , only : displayVerbosity , displayVerbositySet, verbosityLevelStandard - use :: Galactic_Structure_Options , only : massTypeBaryonic , radiusLarge + use :: Galactic_Structure_Options , only : massTypeBaryonic , radiusLarge , massTypeDark , componentTypeDarkHalo + use :: Mass_Distributions , only : massDistributionClass use :: Error , only : Error_Report use :: ISO_Varying_String , only : varying_string use :: Numerical_Constants_Astronomical, only : gravitationalConstantGalacticus use :: String_Handling , only : operator(//) implicit none - type (treeNode ), intent(inout) :: node - double precision , intent(in ) :: specificAngularMomentum - procedure (solverGet ), intent(in ) , pointer :: radiusGet , velocityGet - procedure (solverSet ), intent(in ) , pointer :: radiusSet , velocitySet - double precision , dimension(:,:), allocatable, save :: radiusHistory + type (treeNode ), intent(inout) :: node + double precision , intent(in ) :: specificAngularMomentum + procedure (solverGet ), intent(in ) , pointer :: radiusGet , velocityGet + procedure (solverSet ), intent(in ) , pointer :: radiusSet , velocitySet + double precision , dimension(:,:), allocatable, save :: radiusHistory !$omp threadprivate(radiusHistory) - double precision , dimension(:,:), allocatable :: radiusHistoryTemporary - double precision , dimension(: ), allocatable :: radiusStoredTmp , velocityStoredTmp - integer , parameter :: storeIncrement =10 - integer , parameter :: iterationsForBisectionMinimum =10 - integer , parameter :: activeComponentMaximumIncrement= 2 - integer :: activeComponentMaximumCurrent - character (len=14 ) :: label - type (varying_string ), save :: message + double precision , dimension(:,:), allocatable :: radiusHistoryTemporary + double precision , dimension(: ), allocatable :: radiusStoredTmp , velocityStoredTmp + class (massDistributionClass), pointer :: massDistribution_ + integer , parameter :: storeIncrement =10 + integer , parameter :: iterationsForBisectionMinimum =10 + integer , parameter :: activeComponentMaximumIncrement= 2 + integer :: activeComponentMaximumCurrent + character (len=14 ) :: label + type (varying_string ), save :: message !$omp threadprivate(message) - double precision :: baryonicVelocitySquared , darkMatterMassFinal, & - & darkMatterVelocitySquared , velocity , & - & radius , radiusNew , & - & specificAngularMomentumMaximum + double precision :: baryonicVelocitySquared , darkMatterMassFinal, & + & darkMatterVelocitySquared , velocity , & + & radius , radiusNew , & + & specificAngularMomentumMaximum ! Count the number of active components. countComponentsActive=countComponentsActive+1 @@ -343,18 +331,22 @@ subroutine radiusSolve(node,specificAngularMomentum,radiusGet,radiusSet,velocity radius=radiusGet(node) if (radius <= 0.0d0) then ! No previous radius was set, so make a simple estimate of sizes of all components ignoring equilibrium contraction and self-gravity. + massDistribution_ => self%darkMatterProfileDMO_%get(node) ! First check that there is a solution within a reasonable radius. - specificAngularMomentumMaximum=+self%darkMatterProfileDMO_%circularVelocity(node_,radiusLarge) & - & * radiusLarge + specificAngularMomentumMaximum=+massDistribution_%rotationCurve(radiusLarge) & + & * radiusLarge if (specificAngularMomentumMaximum < specificAngularMomentum) then ! No solution exists even within a very large radius. Use a simple estimate of the virial radius. radius=self%darkMatterHaloScale_%radiusVirial (node_ ) else - ! Find the radius in the dark matter profile with the required specific angular momentum - radius=self%darkMatterProfileDMO_%radiusFromSpecificAngularMomentum(node_,specificAngularMomentum) + ! Find the radius in the dark matter profile with the required specific angular momentum. + radius=massDistribution_%radiusFromSpecificAngularMomentum(specificAngularMomentum) end if ! Find the velocity at this radius. - velocity=self%darkMatterProfileDMO_%circularVelocity (node_,radius ) + velocity=massDistribution_%rotationCurve(radius) + !![ + + !!] else ! A previous radius was set, so use it, and the previous circular velocity, as the initial guess. velocity=velocityGet(node) @@ -386,14 +378,22 @@ subroutine radiusSolve(node,specificAngularMomentum,radiusGet,radiusSet,velocity ! On subsequent iterations do the full calculation providing component has non-zero specific angular momentum. if (specificAngularMomentum <= 0.0d0) return ! Get current radius of the component. - radius =radiusGet(node) + radius=radiusGet(node) ! Find the enclosed mass in the dark matter halo. - darkMatterMassFinal =self%darkMatterProfile_%enclosedMass(node_,radius) + massDistribution_ => node %massDistribution (componentTypeDarkHalo,massTypeDark) + darkMatterMassFinal = massDistribution_%massEnclosedBySphere(radius ) + !![ + + !!] ! Compute dark matter contribution to rotation curve. darkMatterVelocitySquared=gravitationalConstantGalacticus*darkMatterMassFinal/radius ! Compute baryonic contribution to rotation curve. if (self%includeBaryonGravity) then - baryonicVelocitySquared=self%galacticStructure_%velocityRotation(node,radius,massType=massTypeBaryonic)**2 + massDistribution_ => node %massDistribution(massType=massTypeBaryonic) + baryonicVelocitySquared = massDistribution_%rotationCurve ( radius )**2 + !![ + + !!] else baryonicVelocitySquared=0.0d0 end if diff --git a/source/galactic_structure.radius_solver.fixed.F90 b/source/galactic_structure.radius_solver.fixed.F90 index 3959fdc0e3..19255db895 100644 --- a/source/galactic_structure.radius_solver.fixed.F90 +++ b/source/galactic_structure.radius_solver.fixed.F90 @@ -22,9 +22,8 @@ proportion to specific angular momentum). !!} - use :: Dark_Matter_Halo_Scales , only : darkMatterHaloScaleClass - use :: Dark_Matter_Profiles_DMO, only : darkMatterProfileDMOClass - use :: Virial_Density_Contrast , only : virialDensityContrastClass + use :: Dark_Matter_Halo_Scales, only : darkMatterHaloScaleClass + use :: Virial_Density_Contrast, only : virialDensityContrastClass !![ @@ -60,7 +59,6 @@ double precision :: factor type (enumerationRadiusFixedType) :: radiusFixed class (darkMatterHaloScaleClass ), pointer :: darkMatterHaloScale_ => null() - class (darkMatterProfileDMOClass ), pointer :: darkMatterProfileDMO_ => null() class (virialDensityContrastClass), pointer :: virialDensityContrast_ => null() contains final :: fixedDestructor @@ -89,7 +87,6 @@ function fixedConstructorParameters(parameters) result(self) type (galacticStructureSolverFixed) :: self type (inputParameters ), intent(inout) :: parameters class (darkMatterHaloScaleClass ), pointer :: darkMatterHaloScale_ - class (darkMatterProfileDMOClass ), pointer :: darkMatterProfileDMO_ class (virialDensityContrastClass ), pointer :: virialDensityContrast_ double precision :: factor type (varying_string ) :: radiusFixed @@ -109,20 +106,18 @@ function fixedConstructorParameters(parameters) result(self) parameters - !!] - self=galacticStructureSolverFixed(factor,enumerationRadiusFixedEncode(char(radiusFixed),includesPrefix=.false.),darkMatterHaloScale_,darkMatterProfileDMO_,virialDensityContrast_) + self=galacticStructureSolverFixed(factor,enumerationRadiusFixedEncode(char(radiusFixed),includesPrefix=.false.),darkMatterHaloScale_,virialDensityContrast_) !![ - !!] return end function fixedConstructorParameters - function fixedConstructorInternal(factor,radiusFixed,darkMatterHaloScale_,darkMatterProfileDMO_,virialDensityContrast_) result(self) + function fixedConstructorInternal(factor,radiusFixed,darkMatterHaloScale_,virialDensityContrast_) result(self) !!{ Internal constructor for the {\normalfont \ttfamily fixed} galactic structure solver class. !!} @@ -132,10 +127,9 @@ function fixedConstructorInternal(factor,radiusFixed,darkMatterHaloScale_,darkMa double precision , intent(in ) :: factor type (enumerationRadiusFixedType ), intent(in ) :: radiusFixed class (darkMatterHaloScaleClass ), intent(in ), target :: darkMatterHaloScale_ - class (darkMatterProfileDMOClass ), intent(in ), target :: darkMatterProfileDMO_ class (virialDensityContrastClass ), intent(in ), target :: virialDensityContrast_ !![ - + !!] if (.not.enumerationRadiusFixedIsValid(radiusFixed)) call Error_Report('invalid radiusFixed'//{introspection:location}) @@ -170,7 +164,6 @@ subroutine fixedDestructor(self) !![ - !!] if ( preDerivativeEvent%isAttached(self,fixedSolvePreDeriativeHook)) call preDerivativeEvent%detach(self,fixedSolvePreDeriativeHook) @@ -254,35 +247,42 @@ subroutine radiusSolve(node,specificAngularMomentum,radiusGet,radiusSet,velocity !!{ Solve for the equilibrium radius of the given component. !!} - use :: Dark_Matter_Halo_Spins, only : Dark_Matter_Halo_Angular_Momentum_Scale - use :: Galacticus_Nodes , only : nodeComponentBasic , nodeComponentSpin, treeNode + use :: Dark_Matter_Halo_Spins , only : Dark_Matter_Halo_Angular_Momentum_Scale + use :: Galacticus_Nodes , only : nodeComponentBasic , nodeComponentSpin, treeNode + use :: Mass_Distributions , only : massDistributionClass + use :: Galactic_Structure_Options, only : componentTypeDarkMatterOnly , massTypeDark implicit none - type (treeNode ), intent(inout) :: node - double precision , intent(in ) :: specificAngularMomentum - procedure (solverGet ), intent(in ), pointer :: radiusGet , velocityGet - procedure (solverSet ), intent(in ), pointer :: radiusSet , velocitySet - class (nodeComponentSpin ) , pointer :: spin - class (nodeComponentBasic) , pointer :: basic - double precision :: radius , velocity + type (treeNode ), intent(inout) :: node + double precision , intent(in ) :: specificAngularMomentum + procedure (solverGet ), intent(in ), pointer :: radiusGet , velocityGet + procedure (solverSet ), intent(in ), pointer :: radiusSet , velocitySet + class (nodeComponentSpin ) , pointer :: spin + class (nodeComponentBasic ) , pointer :: basic + class (massDistributionClass) , pointer :: massDistribution_ + double precision :: radius , velocity !$GLC attributes unused :: radiusGet, velocityGet, specificAngularMomentum ! Find the radius of the component, assuming radius is a fixed fraction of radius times spin parameter. spin => node%spin() select case (self%radiusFixed%ID) case (radiusFixedVirial %ID) - velocity = +self %darkMatterHaloScale_ %velocityVirial (node ) - radius = +self %darkMatterHaloScale_ %radiusVirial ( node ) & - & *self %factor & - & *spin %angularMomentum ( ) & - & /Dark_Matter_Halo_Angular_Momentum_Scale ( node , self %darkMatterProfileDMO_ ) + velocity = +self %darkMatterHaloScale_ %velocityVirial ( node ) + radius = +self %darkMatterHaloScale_ %radiusVirial ( node ) & + & *self %factor & + & *spin %angularMomentum ( ) & + & /Dark_Matter_Halo_Angular_Momentum_Scale ( node , self %darkMatterHaloScale_ ) case (radiusFixedTurnaround%ID) - basic => node %basic ( ) - velocity = +self%darkMatterProfileDMO_ %circularVelocityMaximum ( node ) - radius = +self%darkMatterHaloScale_ %radiusVirial ( node ) & - & *self%virialDensityContrast_%turnAroundOverVirialRadii(mass=basic%mass(),time=basic%timeLastIsolated ()) & - & *self %factor & - & *spin %angularMomentum ( ) & - & /Dark_Matter_Halo_Angular_Momentum_Scale ( node , self %darkMatterProfileDMO_ ) + massDistribution_ => node %massDistribution ( componentTypeDarkMatterOnly, massTypeDark ) + basic => node %basic ( ) + velocity = +massDistribution_ %velocityRotationCurveMaximum( ) + radius = +self %darkMatterHaloScale_ %radiusVirial ( node ) & + & *self %virialDensityContrast_%turnAroundOverVirialRadii (mass=basic%mass() ,time=basic%timeLastIsolated ()) & + & *self %factor & + & *spin %angularMomentum ( ) & + & /Dark_Matter_Halo_Angular_Momentum_Scale ( node , self %darkMatterHaloScale_ ) + !![ + + !!] end select ! Set the component size to new radius and velocity. call radiusSet (node,radius ) diff --git a/source/galactic_structure.radius_solver.simple.F90 b/source/galactic_structure.radius_solver.simple.F90 index 759abc1dc4..4dc68b5a2d 100644 --- a/source/galactic_structure.radius_solver.simple.F90 +++ b/source/galactic_structure.radius_solver.simple.F90 @@ -260,23 +260,29 @@ subroutine radiusSolve(node,specificAngularMomentum,radiusGet,radiusSet,velocity !!{ Solve for the equilibrium radius of the given component. !!} + use :: Mass_Distributions, only : massDistributionClass implicit none - type (treeNode ), intent(inout) :: node - double precision , intent(in ) :: specificAngularMomentum - procedure (solverGet), intent(in ), pointer :: radiusGet , velocityGet - procedure (solverSet), intent(in ), pointer :: radiusSet , velocitySet - double precision :: radius , velocity + type (treeNode ), intent(inout) :: node + double precision , intent(in ) :: specificAngularMomentum + procedure (solverGet ), intent(in ), pointer :: radiusGet , velocityGet + procedure (solverSet ), intent(in ), pointer :: radiusSet , velocitySet + class (massDistributionClass) , pointer :: massDistribution_ + double precision :: radius , velocity !$GLC attributes unused :: radiusGet, velocityGet ! Return immediately if the specific angular momentum is zero. if (specificAngularMomentum <= 0.0d0) return + massDistribution_ => self%darkMatterProfileDMO_%get(haloNode) ! Find the radius in the dark matter profile with the required specific angular momentum - radius=self%darkMatterProfileDMO_%radiusFromSpecificAngularMomentum(haloNode,specificAngularMomentum) + radius =massDistribution_%radiusFromSpecificAngularMomentum(specificAngularMomentum) ! Find the velocity at this radius. - velocity=self%darkMatterProfileDMO_%circularVelocity(haloNode,radius) + velocity=massDistribution_%rotationCurve (radius ) ! Set the component size to new radius and velocity. call radiusSet (node,radius ) call velocitySet(node,velocity) + !![ + + !!] return end subroutine radiusSolve diff --git a/source/halo_model.projected_correlation_function.F90 b/source/halo_model.projected_correlation_function.F90 index 4ab7dc4dc1..29430c4aa6 100644 --- a/source/halo_model.projected_correlation_function.F90 +++ b/source/halo_model.projected_correlation_function.F90 @@ -305,10 +305,13 @@ double precision function powerSpectrumOneHaloIntegrand(massHalo) !!} use :: Conditional_Mass_Functions, only : haloModelGalaxyTypeCentral, haloModelGalaxyTypeSatellite use :: Calculations_Resets , only : Calculations_Reset - implicit none - double precision, intent(in ) :: massHalo - double precision :: darkMatterProfileKSpace, numberCentrals , & - & numberSatellites , wavenumberMaximum + use :: Mass_Distributions , only : massDistributionClass + implicit none + double precision , intent(in ) :: massHalo + class (massDistributionClass), pointer :: massDistribution_ + double precision :: darkMatterProfileKSpace, numberCentrals , & + & numberSatellites , wavenumberMaximum, & + & radiusVirial call Calculations_Reset(node) call basic % massSet(massHalo ) @@ -320,17 +323,22 @@ double precision function powerSpectrumOneHaloIntegrand(massHalo) if (waveNumber(iWavenumber) > wavenumberMaximum) then powerSpectrumOneHaloIntegrand=0.0d0 else - darkMatterProfileKSpace=darkMatterProfileDMO_%kSpace(node,waveNumber(iWavenumber)/expansionFactor) - numberCentrals =max( & - & +0.0d0 , & - & +conditionalMassFunction_%massFunction(massHalo,projectedCorrelationFunctionMassMinimum,haloModelGalaxyTypeCentral ) & - & -conditionalMassFunction_%massFunction(massHalo,projectedCorrelationFunctionMassMaximum,haloModelGalaxyTypeCentral ) & - & ) - numberSatellites =max( & - & +0.0d0 , & - & +conditionalMassFunction_%massFunction(massHalo,projectedCorrelationFunctionMassMinimum,haloModelGalaxyTypeSatellite) & - & -conditionalMassFunction_%massFunction(massHalo,projectedCorrelationFunctionMassMaximum,haloModelGalaxyTypeSatellite) & - & ) + massDistribution_ => darkMatterProfileDMO_%get (node ) + radiusVirial = darkMatterHaloScale_ %radiusVirial (node ) + darkMatterProfileKSpace = massDistribution_ %fourierTransform(radiusVirial,waveNumber(iWavenumber)/expansionFactor) + numberCentrals = max( & + & +0.0d0 , & + & +conditionalMassFunction_%massFunction(massHalo,projectedCorrelationFunctionMassMinimum,haloModelGalaxyTypeCentral ) & + & -conditionalMassFunction_%massFunction(massHalo,projectedCorrelationFunctionMassMaximum,haloModelGalaxyTypeCentral ) & + & ) + numberSatellites = max( & + & +0.0d0 , & + & +conditionalMassFunction_%massFunction(massHalo,projectedCorrelationFunctionMassMinimum,haloModelGalaxyTypeSatellite) & + & -conditionalMassFunction_%massFunction(massHalo,projectedCorrelationFunctionMassMaximum,haloModelGalaxyTypeSatellite) & + & ) + !![ + + !!] ! Note that we include 2 times the central-satellite term since we want to count each pair twice (i.e. central-satellite and ! then satellite-central). This is consistent with the N(N-1) counting for the satellite-satellite term, and with the ! counting in the two-halo term. @@ -352,8 +360,8 @@ double precision function powerSpectrumTwoHaloTimeIntegrand(timePrime) !!{ Time integrand for the two-halo term in the power spectrum. !!} - use :: Display , only : displayMessage , verbosityLevelWarn, displayMagenta, displayReset - use :: Error, only : errorStatusSuccess + use :: Display, only : displayMessage , verbosityLevelWarn, displayMagenta, displayReset + use :: Error , only : errorStatusSuccess implicit none double precision , intent(in ) :: timePrime type (integrator) :: integratorTime @@ -387,9 +395,11 @@ double precision function powerSpectrumTwoHaloIntegrand(massHalo) Integrand for the two-halo term in the power spectrum. !!} use :: Calculations_Resets, only : Calculations_Reset - implicit none - double precision, intent(in ) :: massHalo - double precision :: wavenumberMaximum + use :: Mass_Distributions , only : massDistributionClass + implicit none + double precision , intent(in ) :: massHalo + class (massDistributionClass), pointer :: massDistribution_ + double precision :: wavenumberMaximum, radiusVirial call Calculations_Reset(node) call basic % massSet(massHalo ) @@ -401,15 +411,19 @@ double precision function powerSpectrumTwoHaloIntegrand(massHalo) if (waveNumber(iWavenumber) > wavenumberMaximum) then powerSpectrumTwoHaloIntegrand=0.0d0 else - powerSpectrumTwoHaloIntegrand= & - & +haloMassFunction_ %differential(time,massHalo ) & - & *darkMatterHaloBias_ %bias (node ) & - & *darkMatterProfileDMO_%kSpace (node,waveNumber(iWavenumber)/expansionFactor) & - & *max( & - & +0.0d0 , & - & +conditionalMassFunction_%massFunction(massHalo,projectedCorrelationFunctionMassMinimum) & - & -conditionalMassFunction_%massFunction(massHalo,projectedCorrelationFunctionMassMaximum) & - & ) + massDistribution_ => darkMatterProfileDMO_%get (node ) + radiusVirial = darkMatterHaloScale_ %radiusVirial (node ) + powerSpectrumTwoHaloIntegrand = +haloMassFunction_ %differential (time,massHalo ) & + & *darkMatterHaloBias_ %bias (node ) & + & *massDistribution_ %fourierTransform(radiusVirial,waveNumber(iWavenumber)/expansionFactor) & + & *max( & + & +0.0d0 , & + & +conditionalMassFunction_%massFunction(massHalo,projectedCorrelationFunctionMassMinimum) & + & -conditionalMassFunction_%massFunction(massHalo,projectedCorrelationFunctionMassMaximum) & + & ) + !![ + + !!] end if return end function powerSpectrumTwoHaloIntegrand diff --git a/source/hot_halo.cold_mode.mass_distribution.F90 b/source/hot_halo.cold_mode.mass_distribution.F90 new file mode 100644 index 0000000000..3b60ec8a75 --- /dev/null +++ b/source/hot_halo.cold_mode.mass_distribution.F90 @@ -0,0 +1,52 @@ +!! Copyright 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, +!! 2019, 2020, 2021, 2022, 2023, 2024 +!! Andrew Benson +!! +!! This file is part of Galacticus. +!! +!! Galacticus is free software: you can redistribute it and/or modify +!! it under the terms of the GNU General Public License as published by +!! the Free Software Foundation, either version 3 of the License, or +!! (at your option) any later version. +!! +!! Galacticus is distributed in the hope that it will be useful, +!! but WITHOUT ANY WARRANTY; without even the implied warranty of +!! MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +!! GNU General Public License for more details. +!! +!! You should have received a copy of the GNU General Public License +!! along with Galacticus. If not, see . + +!!{ +Contains a module which provides a hot halo cold mode mass distribution class. +!!} + +module Hot_Halo_Cold_Mode_Mass_Distributions + !!{ + Provides an object which provides a hot halo cold mode mass distribution class. + !!} + use :: Galacticus_Nodes , only : treeNode + use :: Mass_Distributions , only : massDistributionClass + use :: Galactic_Structure_Options, only : enumerationWeightByType + private + + !![ + + hotHaloColdModeMassDistribution + Hot Halo Cold Mode Mass Distributions + + Object implementing hot halo cold mode mass distributions. + + betaProfile + + Return the mass distribution of the hot halo cold mode component. + class(massDistributionClass) + yes + type (treeNode ), intent(inout) :: node + type (enumerationWeightByType), intent(in ), optional :: weightBy + integer , intent(in ), optional :: weightIndex + + + !!] + +end module Hot_Halo_Cold_Mode_Mass_Distributions diff --git a/source/hot_halo.cold_mode.mass_distribution.beta_profile.F90 b/source/hot_halo.cold_mode.mass_distribution.beta_profile.F90 new file mode 100644 index 0000000000..e6d04520ff --- /dev/null +++ b/source/hot_halo.cold_mode.mass_distribution.beta_profile.F90 @@ -0,0 +1,202 @@ +!! Copyright 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, +!! 2019, 2020, 2021, 2022, 2023, 2024 +!! Andrew Benson +!! +!! This file is part of Galacticus. +!! +!! Galacticus is free software: you can redistribute it and/or modify +!! it under the terms of the GNU General Public License as published by +!! the Free Software Foundation, either version 3 of the License, or +!! (at your option) any later version. +!! +!! Galacticus is distributed in the hope that it will be useful, +!! but WITHOUT ANY WARRANTY; without even the implied warranty of +!! MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +!! GNU General Public License for more details. +!! +!! You should have received a copy of the GNU General Public License +!! along with Galacticus. If not, see . + +!!{ +An implementation of the hot halo mass distribution class for $\beta$-profile distributions. +!!} + + use :: Hot_Halo_Cold_Mode_Density_Core_Radii, only : hotHaloColdModeCoreRadiiClass + use :: Mass_Distributions , only : massDistributionBetaProfile + + !![ + + + A hot halo cold mod mass distribution class which adopts a spherically symmetric $\beta$-profile density profile for the hot + halo. Specifically, + \begin{equation} + \rho_\mathrm{hot halo}(r) \propto \left[ r^2 + r_\mathrm{core}^2 \right]^{3\beta/2}, + \end{equation} + where the core radius, $r_\mathrm{core}$, is set using the selected cored profile core radius method (see + \refPhysics{hotHaloColdModeMassDistributionCoreRadius}). The value of $\beta$ is specified by the {\normalfont + \ttfamily [beta]} parameter. The profile is normalized such that the current mass in the hot gas profile is contained + within the outer radius of the hot halo, $r_\mathrm{hot, outer}$. + + + !!] + type, extends(hotHaloColdModeMassDistributionClass) :: hotHaloColdModeMassDistributionBetaProfile + !!{ + A $\beta$-profile implementation of the hot halo mass distribution class. + !!} + private + double precision :: beta + class (hotHaloColdModeCoreRadiiClass), pointer :: hotHaloColdModeCoreRadii_ => null() + contains + final :: betaProfileDestructor + procedure :: get => betaProfileGet + end type hotHaloColdModeMassDistributionBetaProfile + + interface hotHaloColdModeMassDistributionBetaProfile + !!{ + Constructors for the $\beta$-profile hot halo cold mode mass distribution class. + !!} + module procedure betaProfileConstructorParameters + module procedure betaProfileConstructorInternal + end interface hotHaloColdModeMassDistributionBetaProfile + +contains + + function betaProfileConstructorParameters(parameters) result(self) + !!{ + Constructor for the {\normalfont \ttfamily betaProfile} hot halo cold mode mass distributionclass which builds the object from a + parameter set. + !!} + use :: Array_Utilities , only : operator(.intersection.) + use :: Error , only : Component_List , Error_Report + use :: Galacticus_Nodes, only : defaultHotHaloComponent + use :: Input_Parameters, only : inputParameter , inputParameters + implicit none + type (hotHaloColdModeMassDistributionBetaProfile) :: self + type (inputParameters ), intent(inout) :: parameters + logical , save :: initialized =.false. + class (hotHaloColdModeCoreRadiiClass ), pointer :: hotHaloColdModeCoreRadii_ + double precision :: beta + + if (.not.initialized) then + !$omp critical(betaProfileColdModeInitialized) + if (.not.initialized) then + ! Check that required property is gettable. + if ( & + & .not.( & + & defaultHotHaloComponent% massColdIsGettable() & + & .and. & + & defaultHotHaloComponent%outerRadiusIsGettable() & + & ) & + & ) call Error_Report & + & ( & + & 'This method requires that the "massCold" property of the hot halo is gettable.'// & + & Component_List( & + & 'hotHalo' , & + & defaultHotHaloComponent% massColdAttributeMatch(requireGettable=.true.) & + & .intersection. & + & defaultHotHaloComponent%outerRadiusAttributeMatch(requireGettable=.true.) & + & ) // & + & {introspection:location} & + & ) + initialized=.true. + end if + !$omp end critical(betaProfileColdModeInitialized) + end if + + !![ + + beta + 2.0d0/3.0d0 + The value of $\beta$ in $\beta$-profile hot halo cold mode mass distributions. + parameters + + + !!] + self=hotHaloColdModeMassDistributionBetaProfile(beta,hotHaloColdModeCoreRadii_) + !![ + + + !!] + return + end function betaProfileConstructorParameters + + function betaProfileConstructorInternal(beta,hotHaloColdModeCoreRadii_) result(self) + !!{ + Internal constructor for the {\normalfont \ttfamily betaProfile} hot halo mass distribution class. + !!} + implicit none + type (hotHaloColdModeMassDistributionBetaProfile) :: self + double precision , intent(in ) :: beta + class (hotHaloColdModeCoreRadiiClass ), intent(in ), target :: hotHaloColdModeCoreRadii_ + !![ + + !!] + + return + end function betaProfileConstructorInternal + + subroutine betaProfileDestructor(self) + !!{ + Destructor for the {\normalfont \ttfamily betaProfile} hot halo mass distribution class. + !!} + implicit none + type(hotHaloColdModeMassDistributionBetaProfile), intent(inout) :: self + + !![ + + !!] + return + end subroutine betaProfileDestructor + + function betaProfileGet(self,node,weightBy,weightIndex) result(massDistribution_) + !!{ + Return the $\beta$-profile hot halo mass distribution for the given {\normalfont \ttfamily node}. + !!} + use :: Galacticus_Nodes , only : nodeComponentHotHalo , treeNode + use :: Galactic_Structure_Options, only : componentTypeColdHalo, massTypeGaseous, weightByMass + implicit none + class (massDistributionClass ), pointer :: massDistribution_ + class (hotHaloColdModeMassDistributionBetaProfile), intent(inout) :: self + type (treeNode ), intent(inout) :: node + type (enumerationWeightByType ), intent(in ), optional :: weightBy + integer , intent(in ), optional :: weightIndex + class (nodeComponentHotHalo ), pointer :: hotHalo + double precision :: radiusScale , radiusOuter, & + & mass + !![ + + !!] + + ! Assume a null distribution by default. + massDistribution_ => null() + ! If weighting is not by mass, return a null profile. + if (weightBy_ /= weightByMass) return + ! Get properties of the hot halo. + radiusScale = self %hotHaloColdModeCoreRadii_%radius (node) + hotHalo => node %hotHalo ( ) + radiusOuter = hotHalo %outerRadius( ) + mass = hotHalo %massCold ( ) + ! If outer radius is non-positive return a null profile. + if (radiusOuter <= 0.0d0 .or. mass <= 0.0d0) return + ! Create the beta-profile distribution. + allocate(massDistributionBetaProfile :: massDistribution_) + select type(massDistribution_) + type is (massDistributionBetaProfile) + !![ + + + massDistributionBetaProfile( & + & beta =self%beta , & + & coreRadius = radiusScale , & + & mass = mass , & + & outerRadius = radiusOuter , & + & truncateAtOuterRadius= .true. , & + & componentType = componentTypeColdHalo, & + & massType = massTypeGaseous & + & ) + + + !!] + end select + return + end function betaProfileGet diff --git a/source/hot_halo.mass_distribution.Enzo_hydrostatic.F90 b/source/hot_halo.mass_distribution.Enzo_hydrostatic.F90 index 3e6ceeb97b..c921135390 100644 --- a/source/hot_halo.mass_distribution.Enzo_hydrostatic.F90 +++ b/source/hot_halo.mass_distribution.Enzo_hydrostatic.F90 @@ -49,18 +49,8 @@ class(hotHaloTemperatureProfileClass ), pointer :: hotHaloTemperatureProfile_ => null() class(hotHaloMassDistributionCoreRadiusClass), pointer :: hotHaloMassDistributionCoreRadius_ => null() contains - !![ - - - - !!] - final :: enzoHydrostaticDestructor - procedure :: densityNormalization => enzoHydrostaticDensityNormalization - procedure :: density => enzoHydrostaticDensity - procedure :: densityLogSlope => enzoHydrostaticDensityLogSlope - procedure :: enclosedMass => enzoHydrostaticEnclosedMass - procedure :: radialMoment => enzoHydrostaticRadialMoment - procedure :: rotationNormalization => enzoHydrostaticRotationNormalization + final :: enzoHydrostaticDestructor + procedure :: get => enzoHydrostaticGet end type hotHaloMassDistributionEnzoHydrostatic interface hotHaloMassDistributionEnzoHydrostatic @@ -71,11 +61,6 @@ module procedure enzoHydrostaticConstructorInternal end interface hotHaloMassDistributionEnzoHydrostatic - type (treeNode ), pointer :: node_ - double precision :: radiusScale - class (hotHaloTemperatureProfileClass), pointer :: hotHaloTemperatureProfile_ - !$omp threadprivate(node_,radiusScale,hotHaloTemperatureProfile_) - contains function enzoHydrostaticConstructorParameters(parameters) result(self) @@ -131,202 +116,55 @@ subroutine enzoHydrostaticDestructor(self) return end subroutine enzoHydrostaticDestructor - double precision function enzoHydrostaticDensityNormalization(self,node) - !!{ - Return the density normalization in a {\normalfont \ttfamily enzoHydrostatic} hot halo mass distribution. - !!} - use :: Galacticus_Nodes , only : nodeComponentHotHalo, treeNode - use :: Numerical_Integration, only : integrator - implicit none - class (hotHaloMassDistributionEnzoHydrostatic), intent(inout) :: self - type (treeNode ), intent(inout), target :: node - class (nodeComponentHotHalo ) , pointer :: hotHalo - double precision , parameter :: toleranceRelative=1.0d-3 - type (integrator ) :: integrator_ - double precision :: radiusInner , radiusOuter - - radiusScale = self%hotHaloMassDistributionCoreRadius_%radius (node) - hotHaloTemperatureProfile_ => self %hotHaloTemperatureProfile_ - node_ => node - hotHalo => node %hotHalo ( ) - if ( & - & hotHalo%mass () <= 0.0d0 & - & .or. & - & hotHalo%outerRadius() <= 0.0d0 & - & ) then - enzoHydrostaticDensityNormalization=0.0d0 - else - integrator_ =integrator (enzoHydrostaticEnclosedMassIntegrand,toleranceRelative=toleranceRelative) - radiusInner =+0.0d0 - radiusOuter =+hotHalo %outerRadius( ) - enzoHydrostaticDensityNormalization=+hotHalo %mass ( ) & - & /integrator_%integrate (radiusInner ,radiusOuter ) - end if - return - end function enzoHydrostaticDensityNormalization - - double precision function enzoHydrostaticEnclosedMassIntegrand(radius) - !!{ - Integrand used in finding the normalization of the {\normalfont \ttfamily enzoHydrostatic} hot halo mass distribution. - !!} - use :: Numerical_Constants_Math, only : Pi - implicit none - double precision, intent(in ) :: radius - double precision :: temperature, radiusEffective - - if (radius <= 0.0d0) then - enzoHydrostaticEnclosedMassIntegrand=0.0d0 - else - radiusEffective =max(radius,radiusScale) - temperature =hotHaloTemperatureProfile_%temperature(node_,radiusEffective) - enzoHydrostaticEnclosedMassIntegrand=+4.0d0 & - & *Pi & - & /temperature & - & *radius **2 & - & /radiusEffective**3 - end if - return - end function enzoHydrostaticEnclosedMassIntegrand - - double precision function enzoHydrostaticDensity(self,node,radius) + function enzoHydrostaticGet(self,node,weightBy,weightIndex) result(massDistribution_) !!{ - Return the density in a {\normalfont \ttfamily enzoHydrostatic} hot halo mass distribution. + Return the Enzo hydrostatic hot halo mass distribution for the given {\normalfont \ttfamily node}. !!} + use :: Galacticus_Nodes , only : nodeComponentHotHalo , treeNode + use :: Galactic_Structure_Options, only : componentTypeHotHalo , massTypeGaseous, weightByMass + use :: Mass_Distributions , only : massDistributionEnzoHydrostatic implicit none - class (hotHaloMassDistributionEnzoHydrostatic), intent(inout) :: self - type (treeNode ), intent(inout) :: node - double precision , intent(in ) :: radius - double precision :: temperature , radiusScale, & - & radiusEffective - - radiusScale = self%hotHaloMassDistributionCoreRadius_%radius (node ) - radiusEffective = max(radius,radiusScale) - temperature = +self%hotHaloTemperatureProfile_ %temperature(node,radiusEffective) - enzoHydrostaticDensity = +self%densityNormalization (node ) & - & /temperature & - & /radiusEffective **3 - return - end function enzoHydrostaticDensity - - double precision function enzoHydrostaticDensityLogSlope(self,node,radius) - !!{ - Return the logarithmic slope of the density profile in a {\normalfont \ttfamily enzoHydrostatic} hot halo mass - distribution. - !!} - implicit none - class (hotHaloMassDistributionEnzoHydrostatic), intent(inout) :: self - type (treeNode ), intent(inout) :: node - double precision , intent(in ) :: radius - double precision :: radiusScale - - radiusScale = self%hotHaloMassDistributionCoreRadius_%radius (node ) - if (radius > radiusScale) then - enzoHydrostaticDensityLogSlope = -self%hotHaloTemperatureProfile_ %temperatureLogSlope(node,radius) & - & -3.0d0 - else - enzoHydrostaticDensityLogSlope = +0.0d0 - end if - return - end function enzoHydrostaticDensityLogSlope - - double precision function enzoHydrostaticEnclosedMass(self,node,radius) - !!{ - Return the enclosed mass in a {\normalfont \ttfamily enzoHydrostatic} hot halo mass distribution. - !!} - use :: Galacticus_Nodes , only : nodeComponentHotHalo, treeNode - use :: Numerical_Integration, only : integrator - implicit none - class (hotHaloMassDistributionEnzoHydrostatic), intent(inout) :: self - type (treeNode ), intent(inout), target :: node - double precision , intent(in ) :: radius - double precision , parameter :: toleranceRelative=1.0d-3 - class (nodeComponentHotHalo ) , pointer :: hotHalo - type (integrator ) :: integrator_ - double precision :: radiusInner , radiusOuter - - hotHalo => node%hotHalo() - if (radius > hotHalo%outerRadius()) then - enzoHydrostaticEnclosedMass=hotHalo%mass() - else - radiusScale = self%hotHaloMassDistributionCoreRadius_%radius(node) - hotHaloTemperatureProfile_ => self%hotHaloTemperatureProfile_ - node_ => node - radiusInner = +0.0d0 - radiusOuter = +radius - integrator_ = integrator (enzoHydrostaticEnclosedMassIntegrand,toleranceRelative=toleranceRelative) - enzoHydrostaticEnclosedMass = +self %densityNormalization(node ) & - & *integrator_%integrate (radiusInner ,radiusOuter ) - end if - return - end function enzoHydrostaticEnclosedMass - - double precision function enzoHydrostaticRadialMoment(self,node,moment,radius) - !!{ - Return a radial moment of an {\normalfont \ttfamily enzoHydrostatic} hot halo mass distribution. - !!} - use :: Galacticus_Nodes , only : nodeComponentHotHalo, treeNode - use :: Numerical_Integration, only : integrator - implicit none - class (hotHaloMassDistributionEnzoHydrostatic), intent(inout) :: self - type (treeNode ), intent(inout) :: node - double precision , intent(in ) :: moment , radius - class (nodeComponentHotHalo ), pointer :: hotHalo - double precision , parameter :: toleranceRelative=1.0d-3 - type (integrator ) :: integrator_ - double precision :: radiusInner , radiusOuter, & - & radiusScale - - radiusScale = self%hotHaloMassDistributionCoreRadius_%radius (node) - hotHalo => node %hotHalo( ) - radiusInner = 0.0d0 - radiusOuter = min( & - & radius , & - & hotHalo%outerRadius() & - & ) - integrator_ = integrator (enzoHydrostaticRadialMomentIntegrand,toleranceRelative=toleranceRelative) - enzoHydrostaticRadialMoment = +self %densityNormalization(node ) & - & *integrator_%integrate (radiusInner ,radiusOuter ) - return - - contains - - double precision function enzoHydrostaticRadialMomentIntegrand(radius) - !!{ - Integrand used in finding the normalization of the {\normalfont \ttfamily enzoHydrostatic} hot halo mass distribution. - !!} - implicit none - double precision, intent(in ) :: radius - double precision :: temperature, radiusEffective - - if (radius <= 0.0d0) then - enzoHydrostaticRadialMomentIntegrand=0.0d0 - else - radiusEffective =max(radius,radiusScale) - temperature =self%hotHaloTemperatureProfile_%temperature(node_,radiusEffective) - enzoHydrostaticRadialMomentIntegrand=+radius **moment & - & /radiusEffective**3 & - & /temperature - end if - return - end function enzoHydrostaticRadialMomentIntegrand - - end function enzoHydrostaticRadialMoment - - double precision function enzoHydrostaticRotationNormalization(self,node) - !!{ - Return the relation between specific angular momentum and rotation velocity (assuming a - rotation velocity that is constant in radius) for {\normalfont \ttfamily node}. Specifically, the - normalization, $A$, returned is such that $V_\mathrm{rot} = A J/M$. - !!} - use :: Galacticus_Nodes, only : nodeComponentHotHalo, treeNode - implicit none - class(hotHaloMassDistributionEnzoHydrostatic), intent(inout) :: self - type (treeNode ), intent(inout) :: node - class(nodeComponentHotHalo ), pointer :: hotHalo + class (massDistributionClass ), pointer :: massDistribution_ + class (hotHaloMassDistributionEnzoHydrostatic), intent(inout) :: self + type (treeNode ), intent(inout) :: node + type (enumerationWeightByType ), intent(in ), optional :: weightBy + integer , intent(in ), optional :: weightIndex + class (nodeComponentHotHalo ), pointer :: hotHalo + double precision :: radiusScale , radiusOuter, & + & mass + !![ + + !!] - hotHalo => node%hotHalo() - enzoHydrostaticRotationNormalization= & - & self%radialMoment(node,2.0d0,hotHalo%outerRadius()) & - & /self%radialMoment(node,3.0d0,hotHalo%outerRadius()) + ! Assume a null distribution by default. + massDistribution_ => null() + ! If weighting is not by mass, return a null profile. + if (weightBy_ /= weightByMass) return + ! Get properties of the hot halo. + radiusScale = self %hotHaloMassDistributionCoreRadius_%radius (node) + hotHalo => node %hotHalo ( ) + radiusOuter = hotHalo %outerRadius( ) + mass = hotHalo %mass ( ) + ! If outer radius is non-positive return a null profile. + if (radiusOuter <= 0.0d0 .or. mass <= 0.0d0) return + ! Create the mass distribution. + allocate(massDistributionEnzoHydrostatic :: massDistribution_) + select type(massDistribution_) + type is (massDistributionEnzoHydrostatic) + !![ + + + massDistributionEnzoHydrostatic( & + & mass = mass , & + & radiusOuter = radiusOuter , & + & radiusScale = radiusScale , & + & truncateAtOuterRadius= .true. , & + & componentType = componentTypeHotHalo, & + & massType = massTypeGaseous & + & ) + + + !!] + end select return - end function enzoHydrostaticRotationNormalization + end function enzoHydrostaticGet diff --git a/source/hot_halo.mass_distribution.F90 b/source/hot_halo.mass_distribution.F90 index 06fdb8647a..43dfcafb68 100644 --- a/source/hot_halo.mass_distribution.F90 +++ b/source/hot_halo.mass_distribution.F90 @@ -25,15 +25,10 @@ module Hot_Halo_Mass_Distributions !!{ Provides an object which provides a hot halo mass distribution class. !!} - use :: Galacticus_Nodes , only : treeNode - use :: Hot_Halo_Temperature_Profiles, only : hotHaloTemperatureProfileClass + use :: Galacticus_Nodes , only : treeNode + use :: Mass_Distributions , only : massDistributionClass + use :: Galactic_Structure_Options, only : enumerationWeightByType private - public :: hotHaloMassDistributionDensity , hotHaloMassDistributionRotationCurve , & - & hotHaloMassDistributionEnclosedMass , hotHaloMassDistributionRotationCurveGradient , & - & hotHaloMassDistributionAcceleration , hotHaloMassDistributionAccelerationTidalTensor, & - & hotHaloMassDistributionChandrasekharIntegral, hotHaloMassDistributionThreadInitialize , & - & hotHaloMassDistributionThreadUninitialize , hotHaloMassDistributionDefaultStateStore , & - & hotHaloMassDistributionDefaultStateRestore , hotHaloMassDistributionDensitySphericalAverage !![ @@ -43,431 +38,15 @@ module Hot_Halo_Mass_Distributions Object implementing hot halo mass distributions. betaProfile - - Return the density of the hot halo at the given {\normalfont \ttfamily radius}. - double precision + + Return the mass distribution of the hot halo. + class(massDistributionClass) yes - type (treeNode), intent(inout) :: node - double precision , intent(in ) :: radius - - - Return the logarithmic slope of the density of the hot halo at the given {\normalfont \ttfamily radius}. - double precision - yes - type (treeNode), intent(inout) :: node - double precision , intent(in ) :: radius - - - Return the mass enclosed in the hot halo at the given {\normalfont \ttfamily radius}. - double precision - yes - type (treeNode), intent(inout), target :: node - double precision , intent(in ) :: radius - - - Return the specified radial{\normalfont \ttfamily moment} of the density of the hot halo at the given {\normalfont \ttfamily radius}. - double precision - yes - type (treeNode), intent(inout) :: node - double precision , intent(in ) :: moment, radius - - - Return the integral of the square of the density of the hot halo from zero to the given {\normalfont \ttfamily radius}. - double precision - yes - type (treeNode), intent(inout) :: node - double precision , intent(in ) :: radius - - - Returns the relation between specific angular momentum and rotation velocity (assuming a rotation velocity that is constant in radius) for {\normalfont \ttfamily node}. Specifically, the normalization, $A$, returned is such that $V_\mathrm{rot} = A J/M$. - double precision - yes - type(treeNode), intent(inout) :: node + type (treeNode ), intent(inout) :: node + type (enumerationWeightByType), intent(in ), optional :: weightBy + integer , intent(in ), optional :: weightIndex !!] - class(hotHaloMassDistributionClass ), pointer :: hotHaloMassDistribution_ - class(hotHaloTemperatureProfileClass), pointer :: hotHaloTemperatureProfile_ - !$omp threadprivate(hotHaloMassDistribution_,hotHaloTemperatureProfile_) - - contains - - !![ - - hotHaloMassDistributionThreadInitialize - - !!] - subroutine hotHaloMassDistributionThreadInitialize(parameters_) - !!{ - Initializes the hot halo profile structure tasks module. - !!} - use :: Input_Parameters, only : inputParameters - implicit none - type(inputParameters), intent(inout) :: parameters_ - - !![ - - - !!] - return - end subroutine hotHaloMassDistributionThreadInitialize - - !![ - - hotHaloMassDistributionThreadUninitialize - - !!] - subroutine hotHaloMassDistributionThreadUninitialize() - !!{ - Uninitializes the hot halo profile structure tasks module. - !!} - implicit none - - !![ - - - !!] - return - end subroutine hotHaloMassDistributionThreadUninitialize - - !![ - - hotHaloMassDistributionEnclosedMass - - !!] - double precision function hotHaloMassDistributionEnclosedMass(node,radius,componentType,massType,weightBy,weightIndex) - !!{ - Computes the mass within a given radius for a hot halo profile. - !!} - use :: Galactic_Structure_Options, only : componentTypeAll , componentTypeHotHalo , massTypeAll , massTypeBaryonic , & - & massTypeGaseous , radiusLarge , weightByMass, enumerationComponentTypeType, & - & enumerationMassTypeType, enumerationWeightByType - use :: Galacticus_Nodes , only : nodeComponentHotHalo , treeNode - implicit none - type (treeNode ), intent(inout) :: node - type (enumerationComponentTypeType), intent(in ) :: componentType - type (enumerationMassTypeType ), intent(in ) :: massType - type (enumerationWeightByType ), intent(in ) :: weightBy - integer , intent(in ) :: weightIndex - double precision , intent(in ) :: radius - class (nodeComponentHotHalo ), pointer :: hotHalo - !$GLC attributes unused :: weightIndex - - ! Return zero mass if the requested mass type or component is not matched. - hotHaloMassDistributionEnclosedMass=0.0d0 - if (.not.(componentType == componentTypeAll .or. componentType == componentTypeHotHalo )) return - if (.not.(massType == massTypeAll .or. massType == massTypeBaryonic .or. massType == massTypeGaseous)) return - if (.not.(weightBy == weightByMass )) return - ! Return the enclosed mass. - if (radius >= radiusLarge) then - hotHalo => node %hotHalo() - hotHaloMassDistributionEnclosedMass = hotHalo%mass () - else - hotHaloMassDistributionEnclosedMass = max(hotHaloMassDistribution_%enclosedMass(node,radius),0.0d0) - end if - return - end function hotHaloMassDistributionEnclosedMass - - !![ - - hotHaloMassDistributionAcceleration - - !!] - function hotHaloMassDistributionAcceleration(node,positionCartesian,componentType,massType) - !!{ - Computes the acceleration due to a hot halo profile. - !!} - use :: Galactic_Structure_Options , only : weightByMass , weightIndexNull, enumerationComponentTypeType, enumerationMassTypeType - use :: Galacticus_Nodes , only : treeNode - use :: Numerical_Constants_Astronomical, only : gigaYear , megaParsec - use :: Numerical_Constants_Astronomical, only : gravitationalConstantGalacticus - use :: Numerical_Constants_Prefixes , only : kilo - implicit none - double precision , dimension(3) :: hotHaloMassDistributionAcceleration - type (treeNode ), intent(inout) :: node - type (enumerationComponentTypeType), intent(in ) :: componentType - type (enumerationMassTypeType ), intent(in ) :: massType - double precision , intent(in ), dimension(3) :: positionCartesian - double precision :: radius - - radius =+sqrt(sum(positionCartesian**2)) - hotHaloMassDistributionAcceleration=-kilo & - & *gigaYear & - & /megaParsec & - & *gravitationalConstantGalacticus & - & *hotHaloMassDistributionEnclosedMass(node,radius,componentType,massType,weightByMass,weightIndexNull) & - & *positionCartesian & - & /radius**3 - return - end function hotHaloMassDistributionAcceleration - - !![ - - hotHaloMassDistributionAccelerationTidalTensor - - !!] - function hotHaloMassDistributionAccelerationTidalTensor(node,positionCartesian,componentType,massType) - !!{ - Computes the tidalTensor due to the cold mode halo. - !!} - use :: Galactic_Structure_Options , only : weightByMass , weightIndexNull , enumerationComponentTypeType, enumerationMassTypeType - use :: Galacticus_Nodes , only : treeNode - use :: Numerical_Constants_Math , only : Pi - use :: Numerical_Constants_Astronomical, only : gravitationalConstantGalacticus - use :: Tensors , only : tensorRank2Dimension3Symmetric , tensorIdentityR2D3Sym, assignment(=) , operator(*) - use :: Vectors , only : Vector_Outer_Product - implicit none - type (tensorRank2Dimension3Symmetric) :: hotHaloMassDistributionAccelerationTidalTensor - type (treeNode ), intent(inout) :: node - type (enumerationComponentTypeType ), intent(in ) :: componentType - type (enumerationMassTypeType ), intent(in ) :: massType - double precision , intent(in ), dimension(3) :: positionCartesian - double precision , dimension(3) :: positionSpherical - double precision :: radius , massEnclosed, & - & density - type (tensorRank2Dimension3Symmetric) :: positionTensor - - radius =sqrt(sum(positionCartesian**2)) - positionSpherical=[radius,0.0d0,0.0d0] - massEnclosed =hotHaloMassDistributionEnclosedMass(node,radius ,componentType,massType,weightByMass,weightIndexNull) - density =hotHaloMassDistributionDensity (node,positionSpherical,componentType,massType,weightByMass,weightIndexNull) - positionTensor =Vector_Outer_Product ( positionCartesian,symmetrize=.true. ) - hotHaloMassDistributionAccelerationTidalTensor=+gravitationalConstantGalacticus & - & *( & - & -(massEnclosed /radius**3)*tensorIdentityR2D3Sym & - & +(massEnclosed*3.0d0 /radius**5)*positionTensor & - & -(density *4.0d0*Pi/radius**2)*positionTensor & - & ) - return - end function hotHaloMassDistributionAccelerationTidalTensor - - !![ - - hotHaloMassDistributionChandrasekharIntegral - - !!] - function hotHaloMassDistributionChandrasekharIntegral(node,nodeSatellite,positionCartesian,velocityCartesian,componentType,massType) - !!{ - Computes the Chandrasekhar integral due to the hot halo. - !!} - use :: Galactic_Structure_Options, only : weightByMass , weightIndexNull, enumerationComponentTypeType, enumerationMassTypeType - use :: Galacticus_Nodes , only : treeNode - use :: Ideal_Gases_Thermodynamics, only : Ideal_Gas_Sound_Speed - use :: Numerical_Constants_Math , only : Pi - implicit none - double precision , dimension(3) :: hotHaloMassDistributionChandrasekharIntegral - type (treeNode ), intent(inout) :: node , nodeSatellite - type (enumerationComponentTypeType), intent(in ) :: componentType - type (enumerationMassTypeType ), intent(in ) :: massType - double precision , intent(in ), dimension(3) :: positionCartesian , velocityCartesian - double precision , dimension(3) :: positionSpherical - double precision , parameter :: XvMaximum =10.0d0 - double precision :: radius , velocity , & - & density , xV - !$GLC attributes unused :: radiusHalfMass, nodeSatellite - - hotHaloMassDistributionChandrasekharIntegral=0.0d0 - radius = sqrt(sum(positionCartesian**2)) - velocity = sqrt(sum(velocityCartesian**2)) - if (velocity <= 0.0d0) return - positionSpherical = [radius,0.0d0,0.0d0] - density = hotHaloMassDistributionDensity(node,positionSpherical,componentType,massType,weightByMass,weightIndexNull) - if (density <= 0.0d0) return - xV =+velocity & - & /Ideal_Gas_Sound_Speed(hotHaloTemperatureProfile_%temperature(node,radius)) & - & /sqrt(2.0d0) - hotHaloMassDistributionChandrasekharIntegral=-density & - & *velocityCartesian & - & /velocity **3 - if (Xv <= XvMaximum) & - & hotHaloMassDistributionChandrasekharIntegral=+hotHaloMassDistributionChandrasekharIntegral & - & *( & - & +erf ( xV ) & - & -2.0d0 & - & * xV & - & *exp (-xV**2) & - & /sqrt( Pi ) & - & ) - return - end function hotHaloMassDistributionChandrasekharIntegral - - !![ - - hotHaloMassDistributionRotationCurve - - !!] - double precision function hotHaloMassDistributionRotationCurve(node,radius,componentType,massType) - !!{ - Computes the rotation curve at a given radius for the hot halo density profile. - !!} - use :: Galactic_Structure_Options , only : weightByMass , weightIndexNull, enumerationComponentTypeType, enumerationMassTypeType - use :: Numerical_Constants_Astronomical, only : gravitationalConstantGalacticus - implicit none - type (treeNode ), intent(inout) :: node - type (enumerationComponentTypeType), intent(in ) :: componentType - type (enumerationMassTypeType ), intent(in ) :: massType - double precision , intent(in ) :: radius - double precision :: componentMass - - ! Compute rotation curve if radius is non-zero. - hotHaloMassDistributionRotationCurve=0.0d0 - if (radius > 0.0d0) then - componentMass=hotHaloMassDistributionEnclosedMass(node,radius,componentType,massType,weightByMass,weightIndexNull) - if (componentMass > 0.0d0) & - & hotHaloMassDistributionRotationCurve=+sqrt( & - & +gravitationalConstantGalacticus & - & *componentMass & - & /radius & - & ) - end if - return - end function hotHaloMassDistributionRotationCurve - - !![ - - hotHaloMassDistributionRotationCurveGradient - - !!] - double precision function hotHaloMassDistributionRotationCurveGradient(node,radius,componentType,massType) - !!{ - Computes the rotation curve gradient at a given radius for the hot halo density profile. - !!} - use :: Galactic_Structure_Options , only : weightByMass , weightIndexNull, enumerationComponentTypeType, enumerationMassTypeType - use :: Numerical_Constants_Math , only : Pi - use :: Numerical_Constants_Astronomical, only : gravitationalConstantGalacticus - implicit none - type (treeNode ), intent(inout) :: node - type (enumerationComponentTypeType), intent(in ) :: componentType - type (enumerationMassTypeType ), intent(in ) :: massType - double precision , intent(in ) :: radius - double precision :: componentDensity, componentMass - - ! Set to zero by default. - hotHaloMassDistributionRotationCurveGradient=0.0d0 - ! Compute if a spheroid is present. - if (radius > 0.0d0) then - componentMass=hotHaloMassDistributionEnclosedMass(node,radius,componentType,massType,weightByMass,weightIndexNull) - if (componentMass > 0.0d0) then - componentDensity = hotHaloMassDistribution_%density(node,radius) - hotHaloMassDistributionRotationCurveGradient=+gravitationalConstantGalacticus & - & *( & - & -componentMass & - & /radius **2 & - & +4.0d0 & - & *Pi & - & *radius & - & *componentDensity & - & ) - end if - end if - return - end function hotHaloMassDistributionRotationCurveGradient - - !![ - - hotHaloMassDistributionDensity - - !!] - double precision function hotHaloMassDistributionDensity(node,positionSpherical,componentType,massType,weightBy,weightIndex) - !!{ - Computes the density at a given position for a hot halo profile. - !!} - use :: Galactic_Structure_Options, only : componentTypeAll , componentTypeHotHalo, massTypeAll , massTypeBaryonic , & - & massTypeGaseous , weightByMass , enumerationComponentTypeType, enumerationMassTypeType, & - & enumerationWeightByType - implicit none - type (treeNode ), intent(inout) :: node - type (enumerationComponentTypeType), intent(in ) :: componentType - type (enumerationMassTypeType ), intent(in ) :: massType - type (enumerationWeightByType ), intent(in ) :: weightBy - integer , intent(in ) :: weightIndex - double precision , intent(in ) :: positionSpherical(3) - !$GLC attributes unused :: weightIndex - - hotHaloMassDistributionDensity=0.0d0 - if (.not.(componentType == componentTypeAll .or. componentType == componentTypeHotHalo )) return - if (.not.(massType == massTypeAll .or. massType == massTypeBaryonic .or. massType == massTypeGaseous)) return - if (.not.(weightBy == weightByMass )) return - - hotHaloMassDistributionDensity=max(hotHaloMassDistribution_%density(node,positionSpherical(1)),0.0d0) - return - end function hotHaloMassDistributionDensity - - !![ - - hotHaloMassDistributionDensitySphericalAverage - - !!] - double precision function hotHaloMassDistributionDensitySphericalAverage(node,radius,componentType,massType,weightBy,weightIndex) - !!{ - Computes the spherically-averaged density at a given radius for a hot halo profile. - !!} - use :: Galactic_Structure_Options, only : componentTypeAll , componentTypeHotHalo, massTypeAll , massTypeBaryonic , & - & massTypeGaseous , weightByMass , enumerationComponentTypeType, enumerationMassTypeType, & - & enumerationWeightByType - implicit none - type (treeNode ), intent(inout) :: node - type (enumerationComponentTypeType), intent(in ) :: componentType - type (enumerationMassTypeType ), intent(in ) :: massType - type (enumerationWeightByType ), intent(in ) :: weightBy - integer , intent(in ) :: weightIndex - double precision , intent(in ) :: radius - !$GLC attributes unused :: weightIndex - - hotHaloMassDistributionDensitySphericalAverage=0.0d0 - if (.not.(componentType == componentTypeAll .or. componentType == componentTypeHotHalo )) return - if (.not.(massType == massTypeAll .or. massType == massTypeBaryonic .or. massType == massTypeGaseous)) return - if (.not.(weightBy == weightByMass )) return - - hotHaloMassDistributionDensitySphericalAverage=max(hotHaloMassDistribution_%density(node,radius),0.0d0) - return - end function hotHaloMassDistributionDensitySphericalAverage - - !![ - - hotHaloMassDistributionDefaultStateStore - - !!] - subroutine hotHaloMassDistributionDefaultStateStore(stateFile,gslStateFile,stateOperationID) - !!{ - Store object state, - !!} - use :: Display , only : displayMessage, verbosityLevelInfo - use, intrinsic :: ISO_C_Binding, only : c_ptr , c_size_t - implicit none - integer , intent(in ) :: stateFile - integer(c_size_t), intent(in ) :: stateOperationID - type (c_ptr ), intent(in ) :: gslStateFile - - call displayMessage('Storing state for: hot halo mass distribution',verbosity=verbosityLevelInfo) - !![ - - !!] - return - end subroutine hotHaloMassDistributionDefaultStateStore - - !![ - - hotHaloMassDistributionDefaultStateRestore - - !!] - subroutine hotHaloMassDistributionDefaultStateRestore(stateFile,gslStateFile,stateOperationID) - !!{ - Retrieve object state. - !!} - use :: Display , only : displayMessage, verbosityLevelInfo - use, intrinsic :: ISO_C_Binding, only : c_ptr , c_size_t - implicit none - integer , intent(in ) :: stateFile - integer(c_size_t), intent(in ) :: stateOperationID - type (c_ptr ), intent(in ) :: gslStateFile - - call displayMessage('Retrieving state for: hot halo mass distribution',verbosity=verbosityLevelInfo) - !![ - - !!] - return - end subroutine hotHaloMassDistributionDefaultStateRestore - end module Hot_Halo_Mass_Distributions diff --git a/source/hot_halo.mass_distribution.PatejLoeb2015.F90 b/source/hot_halo.mass_distribution.PatejLoeb2015.F90 index 9384ac6587..87b3cc2fba 100644 --- a/source/hot_halo.mass_distribution.PatejLoeb2015.F90 +++ b/source/hot_halo.mass_distribution.PatejLoeb2015.F90 @@ -38,12 +38,8 @@ class (darkMatterHaloScaleClass ), pointer :: darkMatterHaloScale_ => null() double precision :: gamma , radiusShock contains - final :: patejLoeb2015Destructor - procedure :: density => patejLoeb2015Density - procedure :: densityLogSlope => patejLoeb2015DensityLogSlope - procedure :: enclosedMass => patejLoeb2015EnclosedMass - procedure :: radialMoment => patejLoeb2015RadialMoment - procedure :: rotationNormalization => patejLoeb2015RotationNormalization + final :: patejLoeb2015Destructor + procedure :: get => patejLoeb2015Get end type hotHaloMassDistributionPatejLoeb2015 interface hotHaloMassDistributionPatejLoeb2015 @@ -165,193 +161,60 @@ subroutine patejLoeb2015Destructor(self) return end subroutine patejLoeb2015Destructor - double precision function patejLoeb2015Density(self,node,radius) + function patejLoeb2015Get(self,node,weightBy,weightIndex) result(massDistribution_) !!{ - Return the density in a {\normalfont \ttfamily patejLoeb2015} hot halo mass distribution. + Return the \cite{patej_simple_2015} hot halo mass distribution for the given {\normalfont \ttfamily node}. !!} - use :: Galacticus_Nodes, only : nodeComponentDarkMatterProfile, nodeComponentHotHalo, treeNode + use :: Mass_Distributions , only : massDistributionPatejLoeb2015 + use :: Galacticus_Nodes , only : nodeComponentHotHalo, treeNode + use :: Galactic_Structure_Options, only : componentTypeHotHalo, massTypeGaseous, weightByMass implicit none - class (hotHaloMassDistributionPatejLoeb2015), intent(inout) :: self - type (treeNode ), intent(inout) :: node - double precision , intent(in ) :: radius - class (nodeComponentHotHalo ), pointer :: hotHalo - class (nodeComponentDarkMatterProfile ), pointer :: darkMatterHaloProfile - double precision :: radiusShock , densityNormalization, & - & radiusOuter , radiusDarkMatter - - ! Get the hot halo and dark matter profile components. - hotHalo => node%hotHalo () - darkMatterHaloProfile => node%darkMatterProfile() - ! Find the shock and outer radii. - radiusShock =+self %radiusShock & - & *self %darkMatterHaloScale_%radiusVirial(node ) - radiusOuter =+hotHalo %outerRadius ( ) - ! Find the density normalization. - radiusDarkMatter =+radiusShock & - & *(radiusOuter/radiusShock)**self%gamma - densityNormalization=+hotHalo %mass ( ) & - & /self %darkMatterProfileDMO_ %enclosedMass(node,radiusDarkMatter) - ! Compute the density. - patejLoeb2015Density=+self%gamma & - & *densityNormalization & - & *( & - & +radius & - & /radiusShock & - & )**(3.0d0*self%gamma-3.0d0) & - & *self%darkMatterProfileDMO_%density( & - & node , & - & +radiusShock & - & *( & - & +radius & - & /radiusShock & - & )**self%gamma & - & ) - return - end function patejLoeb2015Density - - double precision function patejLoeb2015DensityLogSlope(self,node,radius) - !!{ - Return the density in a {\normalfont \ttfamily patejLoeb2015} hot halo mass distribution. - !!} - use :: Galacticus_Nodes, only : nodeComponentDarkMatterProfile, treeNode - implicit none - class (hotHaloMassDistributionPatejLoeb2015), intent(inout) :: self - type (treeNode ), intent(inout) :: node - double precision , intent(in ) :: radius - class (nodeComponentDarkMatterProfile ), pointer :: darkMatterHaloProfile - double precision :: radiusShock - - ! Get the dark matter profile component. - darkMatterHaloProfile => node%darkMatterProfile() - ! Find the shock radius. - radiusShock =+self %radiusShock & - & *self%darkMatterHaloScale_%radiusVirial(node ) - ! Compute the log slope of density. - patejLoeb2015DensityLogSlope=+3.0d0 & - & *(self%gamma-1.0d0) & - & + self%gamma & - & * self%darkMatterProfileDMO_%densityLogSlope( & - & node , & - & +radiusShock & - & *( & - & +radius & - & /radiusShock & - & )**self%gamma & - & ) - return - end function patejLoeb2015DensityLogSlope - - double precision function patejLoeb2015EnclosedMass(self,node,radius) - !!{ - Return the enclosed mass in a {\normalfont \ttfamily patejLoeb2015} hot halo mass distribution. - !!} - use :: Galacticus_Nodes, only : nodeComponentDarkMatterProfile, nodeComponentHotHalo, treeNode - implicit none - class (hotHaloMassDistributionPatejLoeb2015), intent(inout) :: self - type (treeNode ), intent(inout), target :: node - double precision , intent(in ) :: radius - class (nodeComponentHotHalo ) , pointer :: hotHalo - class (nodeComponentDarkMatterProfile ) , pointer :: darkMatterHaloProfile - double precision :: radiusShock , densityNormalization, & - & radiusOuter , radiusScale , & - & radiusDarkMatter - - ! Get the hot halo and dark matter profile components. - hotHalo => node%hotHalo () - darkMatterHaloProfile => node%darkMatterProfile() - ! Find the shock, outer, and scale radii. - radiusShock =+self %radiusShock & - & *self%darkMatterHaloScale_%radiusVirial(node ) - radiusOuter = hotHalo %outerRadius ( ) - radiusScale = darkMatterHaloProfile%scale ( ) - ! Find the density normalization. - radiusDarkMatter =+radiusShock & - & *(radiusOuter/radiusShock)**self%gamma - densityNormalization =+ hotHalo %mass ( ) & - & /self%darkMatterProfileDMO_ %enclosedMass(node,radiusDarkMatter) - ! Compute the corresponding radius in the dark matter halo. - radiusDarkMatter =+radiusShock & - & *(radius /radiusShock)**self%gamma - ! Compute the enclosed mass (eqn. 4 of Patej & Loeb 2015). - patejLoeb2015EnclosedMass=+densityNormalization & - & *self%darkMatterProfileDMO_%enclosedMass( & - & node , & - & radiusDarkMatter & - & ) - return - end function patejLoeb2015EnclosedMass - - double precision function patejLoeb2015RadialMoment(self,node,moment,radius) - !!{ - Compute a radial moment in a {\normalfont \ttfamily patejLoeb2015} hot halo mass distribution. - For this profile we have: - \begin{equation} - \rho_\mathrm{g}(r) = f \Gamma (r/s)^{3 \Gamma - 3} \rho_\mathrm{DM}(s[r/s]^\Gamma). - \end{equation} - Defining $R=s[r/s]^\Gamma$, such that $r/s = (R/s)^{1/\Gamma}$, and $\mathrm{d}r = \Gamma^{-1} (R/s)^{1/\Gamma-1} \mathrm{d}R$, then - \begin{equation} - \int r^m \rho_\mathrm{g}(r) \mathrm{d}r = f s^{(m-2)(\Gamma-1)/\Gamma} \int R^{(2\Gamma-2+m)/\Gamma} \rho_\mathrm{DM}(R) \mathrm{d}R, - \end{equation} - or - \begin{equation} - \mathcal{R}_\mathrm{g}(r;m) = f s^{(m-2)(\Gamma-1)/\Gamma} \mathcal{R}_\mathrm{DM}(r;(2\Gamma-2+m)/\Gamma), - \end{equation} - where $\mathcal{R}(r;m)$ is the $m^\mathrm{th}$ radial moment of the density profile. - !!} - use :: Galacticus_Nodes, only : nodeComponentDarkMatterProfile, nodeComponentHotHalo, treeNode - implicit none - class (hotHaloMassDistributionPatejLoeb2015), intent(inout) :: self - type (treeNode ), intent(inout) :: node - double precision , intent(in ) :: moment , radius - class (nodeComponentHotHalo ), pointer :: hotHalo - class (nodeComponentDarkMatterProfile ), pointer :: darkMatterHaloProfile - double precision :: radiusShock , densityNormalization, & - & radiusOuter , radiusScale , & - & radiusDarkMatter - - ! Get the hot halo and dark matter profile components. - hotHalo => node%hotHalo () - darkMatterHaloProfile => node%darkMatterProfile() - ! Find the shock, outer, and scale radii. - radiusShock =+self %radiusShock & - & *self%darkMatterHaloScale_%radiusVirial(node ) - radiusOuter = hotHalo %outerRadius ( ) - radiusScale = darkMatterHaloProfile%scale ( ) - ! Find the density normalization. - radiusDarkMatter =+radiusShock & - & *(radiusOuter/radiusShock)**self%gamma - densityNormalization=+ hotHalo %mass ( ) & - & /self%darkMatterProfileDMO_ %enclosedMass(node,radiusDarkMatter) - ! Compute the corresponding radius in the dark matter halo. - radiusDarkMatter=+( & - & +radiusShock & - & /radiusScale & - & )**(1.0d0-self%gamma) & - & *( & - & +min(radius,hotHalo%outerRadius()) & - & /radiusScale & - & )** self%gamma - ! Compute the radial moment. - patejLoeb2015RadialMoment=+densityNormalization & - & *radiusShock**((self%gamma-1.0d0)*(moment-2.0d0)/self%gamma) & - & *self%darkMatterProfileDMO_%radialMoment(node,moment,radiusDarkMatter) - return - end function patejLoeb2015RadialMoment - - double precision function patejLoeb2015RotationNormalization(self,node) - !!{ - Returns the relation between specific angular momentum and rotation velocity (assuming a - rotation velocity that is constant in radius) for {\normalfont \ttfamily node}. Specifically, the - normalization, $A$, returned is such that $V_\mathrm{rot} = A J/M$. - !!} - use :: Galacticus_Nodes, only : nodeComponentHotHalo, treeNode - implicit none - class(hotHaloMassDistributionPatejLoeb2015), intent(inout) :: self - type (treeNode ), intent(inout) :: node - class(nodeComponentHotHalo ), pointer :: hotHalo + class (massDistributionClass ), pointer :: massDistribution_ + class (hotHaloMassDistributionPatejLoeb2015), intent(inout) :: self + type (treeNode ), intent(inout) :: node + type (enumerationWeightByType ), intent(in ), optional :: weightBy + integer , intent(in ), optional :: weightIndex + class (nodeComponentHotHalo ), pointer :: hotHalo + class (massDistributionClass ), pointer :: massDistributionDMO + double precision :: radiusOuter , mass, & + & radiusShock + !![ + + !!] - hotHalo => node%hotHalo ( ) - patejLoeb2015RotationNormalization = +self%radialMoment(node,2.0d0,hotHalo%outerRadius()) & - & /self%radialMoment(node,3.0d0,hotHalo%outerRadius()) + ! Assume a null distribution by default. + massDistribution_ => null() + ! If weighting is not by mass, return a null profile. + if (weightBy_ /= weightByMass) return + ! Get properties of the hot halo. + hotHalo => node %hotHalo () + radiusOuter = hotHalo%outerRadius() + mass = hotHalo%mass () + ! If outer radius is non-positive return a null profile. + if (radiusOuter <= 0.0d0 .or. mass <= 0.0d0) return + radiusShock = +self %radiusShock & + & *self%darkMatterHaloScale_ %radiusVirial(node) + massDistributionDMO => self%darkMatterProfileDMO_%get (node) + ! Create the mass distribution. + allocate(massDistributionPatejLoeb2015 :: massDistribution_) + select type(massDistribution_) + type is (massDistributionPatejLoeb2015) + !![ + + + massDistributionPatejLoeb2015( & + & mass = mass , & + & gamma =self%gamma , & + & radiusShock = radiusShock , & + & radiusOuter = radiusOuter , & + & massDistribution_= massDistributionDMO , & + & componentType = componentTypeHotHalo, & + & massType = massTypeGaseous & + & ) + + + + !!] + end select return - end function patejLoeb2015RotationNormalization + end function patejLoeb2015Get diff --git a/source/hot_halo.mass_distribution.Ricotti2000.F90 b/source/hot_halo.mass_distribution.Ricotti2000.F90 index 85925b3be0..a5957d475a 100644 --- a/source/hot_halo.mass_distribution.Ricotti2000.F90 +++ b/source/hot_halo.mass_distribution.Ricotti2000.F90 @@ -48,8 +48,8 @@ with parameters selected using the fitting function of \cite{ricotti_feedback_20 class(darkMatterProfileDMOClass), pointer :: darkMatterProfileDMO_ => null() class(darkMatterHaloScaleClass ), pointer :: darkMatterHaloScale_ => null() contains - final :: ricotti2000Destructor - procedure :: initialize => ricotti2000Initialize + final :: ricotti2000Destructor + procedure :: get => ricotti2000Get end type hotHaloMassDistributionRicotti2000 interface hotHaloMassDistributionRicotti2000 @@ -161,28 +161,40 @@ subroutine ricotti2000Destructor(self) return end subroutine ricotti2000Destructor - subroutine ricotti2000Initialize(self,node) + function ricotti2000Get(self,node,weightBy,weightIndex) result(massDistribution_) !!{ - Initialize the {\normalfont \ttfamily ricotti2000} hot halo density profile for the given {\normalfont \ttfamily - node}. Parameterizations of $\beta$ and core radius are taken from section 2.1 of \cite{ricotti_feedback_2000}. + Return the {\normalfont \ttfamily ricotti2000} hot halo mass distribution for the given {\normalfont \ttfamily node}. !!} - use :: Galacticus_Nodes, only : nodeComponentDarkMatterProfile, nodeComponentHotHalo, treeNode + use :: Galacticus_Nodes , only : nodeComponentHotHalo, nodeComponentDarkMatterProfile + use :: Galactic_Structure_Options, only : componentTypeHotHalo, massTypeGaseous , weightByMass implicit none - class (hotHaloMassDistributionRicotti2000 ), intent(inout) :: self - type (treeNode ), intent(inout) :: node - class (nodeComponentHotHalo ), pointer :: hotHalo - class (nodeComponentDarkMatterProfile ), pointer :: darkMatterProfile - double precision , parameter :: virialToGasTemperatureRatio=1.0d0 - double precision :: mass , radiusOuter , & - & radiusScale , radiusVirial , & - & radiusCore , concentration, & - & b , beta + class (massDistributionClass ), pointer :: massDistribution_ + class (hotHaloMassDistributionRicotti2000), intent(inout) :: self + type (treeNode ), intent(inout) :: node + type (enumerationWeightByType ), intent(in ), optional :: weightBy + integer , intent(in ), optional :: weightIndex + class (nodeComponentHotHalo ), pointer :: hotHalo + class (nodeComponentDarkMatterProfile ), pointer :: darkMatterProfile + double precision , parameter :: virialToGasTemperatureRatio=1.0d0 + double precision :: mass , radiusOuter , & + & radiusScale , radiusVirial , & + & radiusCore , concentration, & + & b , beta + !![ + + !!] + ! Assume a null distribution by default. + massDistribution_ => null() + ! If weighting is not by mass, return a null profile. + if (weightBy_ /= weightByMass) return ! Compute parameters of the profile. hotHalo => node %hotHalo ( ) darkMatterProfile => node %darkMatterProfile( ) radiusOuter = hotHalo %outerRadius ( ) mass = hotHalo %mass ( ) + ! If outer radius is non-positive return a null profile. + if (radiusOuter <= 0.0d0 .or. mass <= 0.0d0) return radiusScale = darkMatterProfile %scale ( ) radiusVirial = self %darkMatterHaloScale_%radiusVirial (node) concentration = +radiusVirial & @@ -211,11 +223,24 @@ subroutine ricotti2000Initialize(self,node) mass=0.0d0 radiusOuter=1.0d0 end if - self%distribution=massDistributionBetaProfile( & - & beta =beta , & - & coreRadius =radiusCore , & - & mass =mass , & - & outerRadius=radiusOuter & - & ) + allocate(massDistributionBetaProfile :: massDistribution_) + select type(massDistribution_) + type is (massDistributionBetaProfile) + !![ + + + massDistributionBetaProfile( & + & beta =beta , & + & coreRadius =radiusCore , & + & mass =mass , & + & outerRadius =radiusOuter , & + & truncateAtOuterRadius=.true. , & + & componentType =componentTypeHotHalo, & + & massType =massTypeGaseous & + & ) + + + !!] + end select return - end subroutine ricotti2000Initialize + end function ricotti2000Get diff --git a/source/hot_halo.mass_distribution.beta_profile.F90 b/source/hot_halo.mass_distribution.beta_profile.F90 index 3f172ee3d6..3bda9487b3 100644 --- a/source/hot_halo.mass_distribution.beta_profile.F90 +++ b/source/hot_halo.mass_distribution.beta_profile.F90 @@ -45,22 +45,10 @@ !!} private double precision :: beta - type (massDistributionBetaProfile ) :: distribution class (hotHaloMassDistributionCoreRadiusClass), pointer :: hotHaloMassDistributionCoreRadius_ => null() contains - !![ - - - - !!] - final :: betaProfileDestructor - procedure :: initialize => betaProfileInitialize - procedure :: density => betaProfileDensity - procedure :: densityLogSlope => betaProfileDensityLogSlope - procedure :: enclosedMass => betaProfileEnclosedMass - procedure :: radialMoment => betaProfileRadialMoment - procedure :: densitySquaredIntegral => betaProfileDensitySquaredIntegral - procedure :: rotationNormalization => betaProfileRotationNormalization + final :: betaProfileDestructor + procedure :: get => betaProfileGet end type hotHaloMassDistributionBetaProfile interface hotHaloMassDistributionBetaProfile @@ -160,159 +148,55 @@ subroutine betaProfileDestructor(self) return end subroutine betaProfileDestructor - subroutine betaProfileInitialize(self,node) + function betaProfileGet(self,node,weightBy,weightIndex) result(massDistribution_) !!{ - Initialize the $\beta$-profile hot halo density profile for the given {\normalfont \ttfamily node}. + Return the $\beta$-profile hot halo mass distribution for the given {\normalfont \ttfamily node}. !!} - use :: Galacticus_Nodes, only : nodeComponentHotHalo, treeNode + use :: Galacticus_Nodes , only : nodeComponentHotHalo, treeNode + use :: Galactic_Structure_Options, only : componentTypeHotHalo, massTypeGaseous, weightByMass implicit none - class (hotHaloMassDistributionBetaProfile ), intent(inout) :: self - type (treeNode ), intent(inout) :: node - class (nodeComponentHotHalo ), pointer :: hotHalo - double precision :: radiusScale, radiusOuter, & - & mass - - radiusScale = self%hotHaloMassDistributionCoreRadius_%radius (node) - hotHalo => node %hotHalo ( ) - radiusOuter = hotHalo %outerRadius( ) - mass = hotHalo %mass ( ) - if (radiusOuter <= 0.0d0) then - ! If outer radius is non-positive, set mass to zero and outer radius to an arbitrary value. - mass=0.0d0 - radiusOuter=1.0d0 - end if - self%distribution=massDistributionBetaProfile( & - & beta =self%beta , & - & coreRadius =radiusScale, & - & mass =mass , & - & outerRadius=radiusOuter & - & ) - return - end subroutine betaProfileInitialize - - double precision function betaProfileDensity(self,node,radius) - !!{ - Return the density in a single-betaProfile hot halo mass distribution. - !!} - use :: Coordinates, only : assignment(=), coordinateSpherical - implicit none - class (hotHaloMassDistributionBetaProfile), intent(inout) :: self - type (treeNode ), intent(inout) :: node - double precision , intent(in ) :: radius - type (coordinateSpherical ) :: position - - call self%initialize(node) - position =[radius,0.0d0,0.0d0] - betaProfileDensity=self%distribution%density(position) - return - end function betaProfileDensity - - double precision function betaProfileDensityLogSlope(self,node,radius) - !!{ - Return the logarithmic slope of the density of the hot halo at the given {\normalfont \ttfamily radius}. - !!} - use :: Coordinates, only : assignment(=), coordinateSpherical - implicit none - class (hotHaloMassDistributionBetaProfile), intent(inout) :: self - type (treeNode ), intent(inout) :: node - double precision , intent(in ) :: radius - type (coordinateSpherical ) :: position - - call self%initialize(node) - position =[radius,0.0d0,0.0d0] - betaProfileDensityLogSlope=self%distribution%densityGradientRadial(position,logarithmic=.true.) - return - end function betaProfileDensityLogSlope - - double precision function betaProfileEnclosedMass(self,node,radius) - !!{ - Return the mass enclosed in the hot halo at the given {\normalfont \ttfamily radius}. - !!} - use :: Galacticus_Nodes, only : nodeComponentHotHalo, treeNode - implicit none - class (hotHaloMassDistributionBetaProfile), intent(inout) :: self - type (treeNode ), intent(inout), target :: node - double precision , intent(in ) :: radius - class (nodeComponentHotHalo ) , pointer :: hotHalo - - hotHalo => node%hotHalo() - if (radius > hotHalo%outerRadius()) then - betaProfileEnclosedMass=hotHalo%mass() - else - call self%initialize(node) - betaProfileEnclosedMass=self%distribution%massEnclosedBySphere(radius) - end if - return - end function betaProfileEnclosedMass - - double precision function betaProfileRadialMoment(self,node,moment,radius) - !!{ - Return the radial moment of the density profile of the hot halo to the given {\normalfont \ttfamily radius}. - !!} - use :: Galacticus_Nodes, only : nodeComponentHotHalo, treeNode - implicit none - class (hotHaloMassDistributionBetaProfile), intent(inout) :: self - type (treeNode ), intent(inout) :: node - double precision , intent(in ) :: moment , radius - class (nodeComponentHotHalo ), pointer :: hotHalo - - call self%initialize(node) - hotHalo => node%hotHalo() - betaProfileRadialMoment= & - & self% & - & distribution% & - & densityRadialMoment( & - & moment , & - & radiusMinimum=0.0d0 , & - & radiusMaximum=min( & - & radius , & - & hotHalo%outerRadius() & - & ) & - & ) - return - end function betaProfileRadialMoment - - double precision function betaProfileDensitySquaredIntegral(self,node,radius) - !!{ - Return the integral of the square of the density profile of the hot halo to the given {\normalfont \ttfamily radius}. - !!} - use :: Galacticus_Nodes, only : nodeComponentHotHalo, treeNode - implicit none - class (hotHaloMassDistributionBetaProfile), intent(inout) :: self - type (treeNode ), intent(inout) :: node - double precision , intent(in ) :: radius - class (nodeComponentHotHalo ), pointer :: hotHalo - - call self%initialize(node) - hotHalo => node%hotHalo() - betaProfileDensitySquaredIntegral= & - & self% & - & distribution% & - & densitySquareIntegral( & - & radiusMinimum=0.0d0 , & - & radiusMaximum=min( & - & radius , & - & hotHalo%outerRadius() & - & ) & - & ) - return - end function betaProfileDensitySquaredIntegral - - double precision function betaProfileRotationNormalization(self,node) - !!{ - Returns the relation between specific angular momentum and rotation velocity (assuming a - rotation velocity that is constant in radius) for {\normalfont \ttfamily node}. Specifically, the - normalization, $A$, returned is such that $V_\mathrm{rot} = A J/M$. - !!} - use :: Galacticus_Nodes, only : nodeComponentHotHalo, treeNode - implicit none - class(hotHaloMassDistributionBetaProfile), intent(inout) :: self - type (treeNode ), intent(inout) :: node - class(nodeComponentHotHalo ), pointer :: hotHalo + class (massDistributionClass ), pointer :: massDistribution_ + class (hotHaloMassDistributionBetaProfile), intent(inout) :: self + type (treeNode ), intent(inout) :: node + type (enumerationWeightByType ), intent(in ), optional :: weightBy + integer , intent(in ), optional :: weightIndex + class (nodeComponentHotHalo ), pointer :: hotHalo + double precision :: radiusScale , radiusOuter, & + & mass + !![ + + !!] - hotHalo => node%hotHalo() - betaProfileRotationNormalization= & - & self%radialMoment(node,2.0d0,hotHalo%outerRadius()) & - & /self%radialMoment(node,3.0d0,hotHalo%outerRadius()) + ! Assume a null distribution by default. + massDistribution_ => null() + ! If weighting is not by mass, return a null profile. + if (weightBy_ /= weightByMass) return + ! Get properties of the hot halo. + radiusScale = self %hotHaloMassDistributionCoreRadius_%radius (node) + hotHalo => node %hotHalo ( ) + radiusOuter = hotHalo %outerRadius( ) + mass = hotHalo %mass ( ) + ! If outer radius is non-positive return a null profile. + if (radiusOuter <= 0.0d0 .or. mass <= 0.0d0) return + ! Create the beta-profile distribution. + allocate(massDistributionBetaProfile :: massDistribution_) + select type(massDistribution_) + type is (massDistributionBetaProfile) + !![ + + + massDistributionBetaProfile( & + & beta =self%beta , & + & coreRadius = radiusScale , & + & mass = mass , & + & outerRadius = radiusOuter , & + & truncateAtOuterRadius= .true. , & + & componentType = componentTypeHotHalo, & + & massType = massTypeGaseous & + & ) + + + !!] + end select return - end function betaProfileRotationNormalization + end function betaProfileGet diff --git a/source/hot_halo.mass_distribution.null.F90 b/source/hot_halo.mass_distribution.null.F90 index de915f85a5..fbafd14b59 100644 --- a/source/hot_halo.mass_distribution.null.F90 +++ b/source/hot_halo.mass_distribution.null.F90 @@ -35,11 +35,7 @@ !!} private contains - procedure :: density => nullDensity - procedure :: densityLogSlope => nullDensityLogSlope - procedure :: enclosedMass => nullEnclosedMass - procedure :: radialMoment => nullRadialMoment - procedure :: rotationNormalization => nullRotationNormalization + procedure :: get => nullGet end type hotHaloMassDistributionNull interface hotHaloMassDistributionNull @@ -67,74 +63,17 @@ function nullConstructorParameters(parameters) result(self) return end function nullConstructorParameters - double precision function nullDensity(self,node,radius) + function nullGet(self,node,weightBy,weightIndex) result(massDistribution_) !!{ - Return the density in a null hot halo mass distribution. + Return a null hot halo mass distribution. !!} implicit none - class (hotHaloMassDistributionNull), intent(inout) :: self - type (treeNode ), intent(inout) :: node - double precision , intent(in ) :: radius - !$GLC attributes unused :: self, node, radius + class (massDistributionClass ), pointer :: massDistribution_ + class (hotHaloMassDistributionNull), intent(inout) :: self + type (treeNode ), intent(inout) :: node + type (enumerationWeightByType ), intent(in ), optional :: weightBy + integer , intent(in ), optional :: weightIndex - nullDensity=0.0d0 + massDistribution_ => null() return - end function nullDensity - - double precision function nullDensityLogSlope(self,node,radius) - !!{ - Return the logarithmic slope of the density of the hot halo at the given {\normalfont \ttfamily radius}. - !!} - implicit none - class (hotHaloMassDistributionNull), intent(inout) :: self - type (treeNode ), intent(inout) :: node - double precision , intent(in ) :: radius - !$GLC attributes unused :: self, node, radius - - nullDensityLogSlope=0.0d0 - return - end function nullDensityLogSlope - - double precision function nullEnclosedMass(self,node,radius) - !!{ - Return the mass enclosed in the hot halo at the given {\normalfont \ttfamily radius}. - !!} - implicit none - class (hotHaloMassDistributionNull), intent(inout) :: self - type (treeNode ), intent(inout), target :: node - double precision , intent(in ) :: radius - !$GLC attributes unused :: self, node, radius - - nullEnclosedMass=0.0d0 - return - end function nullEnclosedMass - - double precision function nullRadialMoment(self,node,moment,radius) - !!{ - Return the density of the hot halo at the given {\normalfont \ttfamily radius}. - !!} - implicit none - class (hotHaloMassDistributionNull), intent(inout) :: self - type (treeNode ), intent(inout) :: node - double precision , intent(in ) :: moment, radius - !$GLC attributes unused :: self, node, radius, moment - - nullRadialMoment=0.0d0 - return - end function nullRadialMoment - - double precision function nullRotationNormalization(self,node) - !!{ - Returns the relation between specific angular momentum and rotation velocity (assuming a rotation velocity that is constant - in radius) for {\normalfont \ttfamily node}. Specifically, the normalization, $A$, returned is such that $V_\mathrm{rot} = - A J/M$. - !!} - implicit none - class(hotHaloMassDistributionNull), intent(inout) :: self - type (treeNode ), intent(inout) :: node - !$GLC attributes unused :: self, node - - nullRotationNormalization=0.0d0 - return - end function nullRotationNormalization - + end function nullGet diff --git a/source/hot_halo.outflow_reincorporation.velocity_maximum_scaling.F90 b/source/hot_halo.outflow_reincorporation.velocity_maximum_scaling.F90 index 970838c9d1..6254c583ca 100644 --- a/source/hot_halo.outflow_reincorporation.velocity_maximum_scaling.F90 +++ b/source/hot_halo.outflow_reincorporation.velocity_maximum_scaling.F90 @@ -229,12 +229,14 @@ double precision function velocityMaximumScalingRate(self,node) !!{ Return the rate of mass reincorporation for outflowed gas in the hot halo. !!} - use :: Galacticus_Nodes, only : nodeComponentBasic, nodeComponentHotHalo, treeNode + use :: Galacticus_Nodes , only : nodeComponentBasic , nodeComponentHotHalo, treeNode + use :: Mass_Distributions, only : massDistributionClass implicit none class (hotHaloOutflowReincorporationVelocityMaximumScaling), intent(inout) :: self type (treeNode ), intent(inout) :: node class (nodeComponentBasic ), pointer :: basic class (nodeComponentHotHalo ), pointer :: hotHalo + class (massDistributionClass ), pointer :: massDistribution_ double precision :: timeScale ! Check if node differs from previous one for which we performed calculations. @@ -242,13 +244,17 @@ double precision function velocityMaximumScalingRate(self,node) ! Get required components. ! Compute velocity maximum factor. if (.not.self%velocityMaximumComputed) then - self%velocityMaximumFactor = self%velocityExponentiator %exponentiate(self%darkMatterProfileDMO_ %circularVelocityMaximum(node )) + massDistribution_ => self%darkMatterProfileDMO_ %get (node ) + self%velocityMaximumFactor = self%velocityExponentiator %exponentiate(massDistribution_%velocityRotationCurveMaximum ( )) self%velocityMaximumComputed = .true. + !![ + + !!] end if ! Compute expansion factor factor. if (.not.self%expansionFactorComputed) then - basic => node %basic ( ) - self%expansionFactorFactor = 1.0d0/self%expansionFactorExponentiator%exponentiate(self%cosmologyFunctions_ %expansionFactor (basic%time())) + basic => node %basic ( ) + self%expansionFactorFactor = 1.0d0/self%expansionFactorExponentiator%exponentiate(self %cosmologyFunctions_ %expansionFactor(basic%time())) self%expansionFactorComputed = .true. end if ! Compute the rate. diff --git a/source/hot_halo.ram_pressure_force.Font2008.F90 b/source/hot_halo.ram_pressure_force.Font2008.F90 index 085a82214b..6f04f5aa1c 100644 --- a/source/hot_halo.ram_pressure_force.Font2008.F90 +++ b/source/hot_halo.ram_pressure_force.Font2008.F90 @@ -21,8 +21,7 @@ Implements a model of ram pressure stripping of hot halos based on the methods of \cite{font_colours_2008}. !!} - use :: Hot_Halo_Mass_Distributions, only : hotHaloMassDistributionClass - use :: Galactic_Structure , only : galacticStructureClass + use :: Dark_Matter_Halo_Scales, only : darkMatterHaloScaleClass !![ @@ -42,8 +41,7 @@ Implementation of a hot halo ram pressure force class which follows the model of \cite{font_colours_2008}. !!} private - class(hotHaloMassDistributionClass), pointer :: hotHaloMassDistribution_ => null() - class(galacticStructureClass ), pointer :: galacticStructure_ => null() + class(darkMatterHaloScaleClass), pointer :: darkMatterHaloScale_ => null() contains final :: font2008Destructor procedure :: force => font2008Force @@ -67,32 +65,28 @@ function font2008ConstructorParameters(parameters) result(self) implicit none type (hotHaloRamPressureForceFont2008) :: self type (inputParameters ), intent(inout) :: parameters - class(hotHaloMassDistributionClass ), pointer :: hotHaloMassDistribution_ - class(galacticStructureClass ), pointer :: galacticStructure_ + class(darkMatterHaloScaleClass ), pointer :: darkMatterHaloScale_ !![ - - + !!] - self=hotHaloRamPressureForceFont2008(hotHaloMassDistribution_,galacticStructure_) + self=hotHaloRamPressureForceFont2008(darkMatterHaloScale_) !![ - - + !!] return end function font2008ConstructorParameters - function font2008ConstructorInternal(hotHaloMassDistribution_,galacticStructure_) result(self) + function font2008ConstructorInternal(darkMatterHaloScale_) result(self) !!{ Internal constructor for the {\normalfont \ttfamily font2008} hot halo ram pressure force class. !!} implicit none type (hotHaloRamPressureForceFont2008) :: self - class(hotHaloMassDistributionClass ), intent(in ), target :: hotHaloMassDistribution_ - class(galacticStructureClass ), intent(in ), target :: galacticStructure_ + class(darkMatterHaloScaleClass ), intent(in ), target :: darkMatterHaloScale_ !![ - + !!] return @@ -106,8 +100,7 @@ subroutine font2008Destructor(self) type(hotHaloRamPressureForceFont2008), intent(inout) :: self !![ - - + !!] return end subroutine font2008Destructor @@ -116,16 +109,21 @@ double precision function font2008Force(self,node) !!{ Return a ram pressure force due to the hot halo using the model of \cite{font_colours_2008}. !!} - use :: Galacticus_Nodes, only : nodeComponentSatellite , treeNode - use :: Kepler_Orbits , only : keplerOrbit - use :: Satellite_Orbits, only : Satellite_Orbit_Extremum_Phase_Space_Coordinates, extremumPericenter + use :: Galacticus_Nodes , only : nodeComponentSatellite , treeNode + use :: Kepler_Orbits , only : keplerOrbit + use :: Satellite_Orbits , only : Satellite_Orbit_Extremum_Phase_Space_Coordinates, extremumPericenter + use :: Mass_Distributions , only : massDistributionClass + use :: Coordinates , only : coordinateSpherical , assignment(=) + use :: Galactic_Structure_Options, only : componentTypeHotHalo , massTypeGaseous implicit none class (hotHaloRamPressureForceFont2008), intent(inout) :: self type (treeNode ), intent(inout) :: node class (nodeComponentSatellite ), pointer :: satellite type (treeNode ), pointer :: nodeHost + class (massDistributionClass ), pointer :: massDistribution_ + type (coordinateSpherical ) :: coordinates type (keplerOrbit ) :: orbit - double precision :: radiusOrbital, velocityOrbital + double precision :: radiusOrbital , velocityOrbital ! Find the host node. nodeHost => node %parent @@ -134,9 +132,14 @@ double precision function font2008Force(self,node) ! Get the orbit for this node. orbit = satellite%virialOrbit() ! Get the orbital radius and velocity at pericenter. - call Satellite_Orbit_Extremum_Phase_Space_Coordinates(nodeHost,orbit,extremumPericenter,radiusOrbital,velocityOrbital,self%galacticStructure_) + call Satellite_Orbit_Extremum_Phase_Space_Coordinates(nodeHost,orbit,extremumPericenter,radiusOrbital,velocityOrbital,self%darkMatterHaloScale_) ! Find the ram pressure force at pericenter. - font2008Force=+self%hotHaloMassDistribution_%density(nodeHost,radiusOrbital) & - & *velocityOrbital**2 + coordinates = [radiusOrbital,0.0d0,0.0d0] + massDistribution_ => nodeHost %massDistribution(componentTypeHotHalo,massTypeGaseous) + font2008Force = +massDistribution_%density (coordinates ) & + & *velocityOrbital **2 + !![ + + !!] return end function font2008Force diff --git a/source/hot_halo.ram_pressure_force.orbital_position.F90 b/source/hot_halo.ram_pressure_force.orbital_position.F90 index d372411a04..ac1e1c78d5 100644 --- a/source/hot_halo.ram_pressure_force.orbital_position.F90 +++ b/source/hot_halo.ram_pressure_force.orbital_position.F90 @@ -21,9 +21,6 @@ Implements a model of the ram pressure stripping force from hot halos based on orbital position within the host halo. !!} - use :: Hot_Halo_Mass_Distributions, only : hotHaloMassDistributionClass - use :: Galactic_Structure , only : galacticStructureClass - !![ @@ -42,10 +39,7 @@ Implementation of a hot halo ram pressure force class based on orbital position within the host halo. !!} private - class(hotHaloMassDistributionClass), pointer :: hotHaloMassDistribution_ => null() - class(galacticStructureClass ), pointer :: galacticStructure_ => null() contains - final :: orbitalPositionDestructor procedure :: force => orbitalPositionForce end type hotHaloRamPressureForceOrbitalPosition @@ -65,25 +59,17 @@ function orbitalPositionConstructorParameters(parameters) result(self) !!} use :: Input_Parameters, only : inputParameter, inputParameters implicit none - type (hotHaloRamPressureForceOrbitalPosition) :: self - type (inputParameters ), intent(inout) :: parameters - class(hotHaloMassDistributionClass ), pointer :: hotHaloMassDistribution_ - class(galacticStructureClass ), pointer :: galacticStructure_ + type(hotHaloRamPressureForceOrbitalPosition) :: self + type(inputParameters ), intent(inout) :: parameters - !![ - - - !!] - self=hotHaloRamPressureForceOrbitalPosition(hotHaloMassDistribution_,galacticStructure_) + self=hotHaloRamPressureForceOrbitalPosition() !![ - - !!] return end function orbitalPositionConstructorParameters - function orbitalPositionConstructorInternal(hotHaloMassDistribution_,galacticStructure_) result(self) + function orbitalPositionConstructorInternal() result(self) !!{ Internal constructor for the {\normalfont \ttfamily orbitalPosition} hot halo ram pressure force class. !!} @@ -91,12 +77,7 @@ function orbitalPositionConstructorInternal(hotHaloMassDistribution_,galacticStr use :: Error , only : Error_Report , Component_List use :: Galacticus_Nodes, only : defaultSatelliteComponent implicit none - type (hotHaloRamPressureForceOrbitalPosition) :: self - class(hotHaloMassDistributionClass ), intent(in ), target :: hotHaloMassDistribution_ - class(galacticStructureClass ), intent(in ), target :: galacticStructure_ - !![ - - !!] + type (hotHaloRamPressureForceOrbitalPosition) :: self ! Ensure that required methods are supported. if ( & @@ -119,31 +100,22 @@ function orbitalPositionConstructorInternal(hotHaloMassDistribution_,galacticStr return end function orbitalPositionConstructorInternal - subroutine orbitalPositionDestructor(self) - !!{ - Destructor for the {\normalfont \ttfamily orbitalPosition} hot halo ram pressure force class. - !!} - implicit none - type(hotHaloRamPressureForceOrbitalPosition), intent(inout) :: self - - !![ - - - !!] - return - end subroutine orbitalPositionDestructor - double precision function orbitalPositionForce(self,node) !!{ Return a ram pressure force due to the hot halo based on orbital position within the host halo. !!} - use :: Galacticus_Nodes, only : nodeComponentSatellite - use :: Vectors , only : Vector_Magnitude + use :: Galacticus_Nodes , only : nodeComponentSatellite + use :: Vectors , only : Vector_Magnitude + use :: Mass_Distributions , only : massDistributionClass + use :: Coordinates , only : coordinateSpherical , assignment(=) + use :: Galactic_Structure_Options, only : componentTypeHotHalo , massTypeGaseous implicit none class (hotHaloRamPressureForceOrbitalPosition), intent(inout) :: self type (treeNode ), intent(inout) :: node class (nodeComponentSatellite ), pointer :: satellite type (treeNode ), pointer :: nodeHost + class (massDistributionClass ), pointer :: massDistribution_ + type (coordinateSpherical ) :: coordinates double precision :: radiusOrbital, velocityOrbital ! Find the host node. @@ -153,8 +125,13 @@ double precision function orbitalPositionForce(self,node) ! Compute orbital position and velocity. radiusOrbital = +Vector_Magnitude(satellite%position()) velocityOrbital = +Vector_Magnitude(satellite%velocity()) - ! Find the ram pressure force at this orbital radius. - orbitalPositionForce = +self%hotHaloMassDistribution_%density (nodeHost,radiusOrbital) & - & * velocityOrbital **2 + ! Find the ram pressure force this orbital radius. + coordinates = [radiusOrbital,0.0d0,0.0d0] + massDistribution_ => nodeHost %massDistribution(componentTypeHotHalo,massTypeGaseous) + orbitalPositionForce = +massDistribution_%density (coordinates ) & + & *velocityOrbital **2 + !![ + + !!] return end function orbitalPositionForce diff --git a/source/hot_halo.ram_pressure_force.relative_position.F90 b/source/hot_halo.ram_pressure_force.relative_position.F90 index e54fbaf8d6..6888a6006c 100644 --- a/source/hot_halo.ram_pressure_force.relative_position.F90 +++ b/source/hot_halo.ram_pressure_force.relative_position.F90 @@ -22,7 +22,6 @@ !!} use :: Hot_Halo_Mass_Distributions, only : hotHaloMassDistributionClass - use :: Galactic_Structure , only : galacticStructureClass !![ @@ -43,7 +42,6 @@ !!} private class(hotHaloMassDistributionClass), pointer :: hotHaloMassDistribution_ => null() - class(galacticStructureClass ), pointer :: galacticStructure_ => null() contains final :: relativePositionDestructor procedure :: force => relativePositionForce @@ -68,22 +66,19 @@ function relativePositionConstructorParameters(parameters) result(self) type (hotHaloRamPressureForceRelativePosition) :: self type (inputParameters ), intent(inout) :: parameters class(hotHaloMassDistributionClass ), pointer :: hotHaloMassDistribution_ - class(galacticStructureClass ), pointer :: galacticStructure_ !![ - !!] - self=hotHaloRamPressureForceRelativePosition(hotHaloMassDistribution_,galacticStructure_) + self=hotHaloRamPressureForceRelativePosition(hotHaloMassDistribution_) !![ - !!] return end function relativePositionConstructorParameters - function relativePositionConstructorInternal(hotHaloMassDistribution_,galacticStructure_) result(self) + function relativePositionConstructorInternal(hotHaloMassDistribution_) result(self) !!{ Internal constructor for the {\normalfont \ttfamily relativePosition} hot halo ram pressure force class. !!} @@ -93,9 +88,8 @@ function relativePositionConstructorInternal(hotHaloMassDistribution_,galacticSt implicit none type (hotHaloRamPressureForceRelativePosition) :: self class(hotHaloMassDistributionClass ), intent(in ), target :: hotHaloMassDistribution_ - class(galacticStructureClass ), intent(in ), target :: galacticStructure_ !![ - + !!] ! Ensure that required methods are supported. @@ -128,7 +122,6 @@ subroutine relativePositionDestructor(self) !![ - !!] return end subroutine relativePositionDestructor @@ -137,17 +130,22 @@ double precision function relativePositionForce(self,node) result(force) !!{ Return a ram pressure force due to the hot halo based on orbital position within the host halo. !!} - use :: Galacticus_Nodes, only : nodeComponentPosition, nodeComponentBasic - use :: Vectors , only : Vector_Magnitude + use :: Galacticus_Nodes , only : nodeComponentPosition, nodeComponentBasic + use :: Vectors , only : Vector_Magnitude + use :: Mass_Distributions , only : massDistributionClass + use :: Coordinates , only : coordinateSpherical , assignment(=) + use :: Galactic_Structure_Options, only : componentTypeHotHalo , massTypeGaseous implicit none class (hotHaloRamPressureForceRelativePosition), intent(inout) :: self type (treeNode ), intent(inout) :: node - class (nodeComponentPosition ), pointer :: position , positionHost - class (nodeComponentBasic ), pointer :: basic , basicPrevious , & - & basicCurrent , basicHost - type (treeNode ), pointer :: nodeHost , nodeHostPrevious, & + class (nodeComponentPosition ), pointer :: position , positionHost + class (nodeComponentBasic ), pointer :: basic , basicPrevious , & + & basicCurrent , basicHost + type (treeNode ), pointer :: nodeHost , nodeHostPrevious, & & nodeHostCurrent - double precision :: radiusRelative , velocityRelative + class (massDistributionClass ), pointer :: massDistribution_ + type (coordinateSpherical ) :: coordinates + double precision :: radiusRelative , velocityRelative ! Find the host node. Seek the descendant of the node closest in time to our satellite node. This is necessary as satellites ! can evolve ahead of their hosts. @@ -183,8 +181,13 @@ double precision function relativePositionForce(self,node) result(force) ! Compute orbital position and velocity. radiusRelative = +Vector_Magnitude(position%position()-positionHost%position()) velocityRelative = +Vector_Magnitude(position%velocity()-positionHost%velocity()) - ! Find the ram pressure force at this orbital radius. - force = +self%hotHaloMassDistribution_%density (nodeHost,radiusRelative) & - & * velocityRelative **2 + ! Find the ram pressure force this orbital radius. + coordinates = [radiusRelative,0.0d0,0.0d0] + massDistribution_ => nodeHost %massDistribution(componentTypeHotHalo,massTypeGaseous) + force = +massDistribution_%density (coordinates ) & + & *velocityRelative **2 + !![ + + !!] return end function relativePositionForce diff --git a/source/hot_halo.ram_pressure_stripping.Font2008.F90 b/source/hot_halo.ram_pressure_stripping.Font2008.F90 index f360ca90f9..4085d564ff 100644 --- a/source/hot_halo.ram_pressure_stripping.Font2008.F90 +++ b/source/hot_halo.ram_pressure_stripping.Font2008.F90 @@ -22,12 +22,11 @@ !!} use :: Dark_Matter_Halo_Scales , only : darkMatterHaloScaleClass - use :: Hot_Halo_Mass_Distributions , only : hotHaloMassDistributionClass - use :: Hot_Halo_Ram_Pressure_Forces, only : hotHaloRamPressureForce , hotHaloRamPressureForceClass - use :: Galactic_Structure , only : galacticStructureClass + use :: Hot_Halo_Ram_Pressure_Forces, only : hotHaloRamPressureForce , hotHaloRamPressureForceClass use :: Kind_Numbers , only : kind_int8 use :: Root_Finder , only : rootFinder - + use :: Mass_Distributions , only : massDistributionClass + !![ @@ -51,8 +50,6 @@ private class (darkMatterHaloScaleClass ), pointer :: darkMatterHaloScale_ => null() class (hotHaloRamPressureForceClass), pointer :: hotHaloRamPressureForce_ => null() - class (hotHaloMassDistributionClass), pointer :: hotHaloMassDistribution_ => null() - class (galacticStructureClass ), pointer :: galacticStructure_ => null() double precision :: formFactor logical :: solverFailureIsFatal integer (kind_int8 ) :: uniqueIDLast = -1 @@ -74,8 +71,9 @@ ! Global variables used in root finding. class (hotHaloRamPressureStrippingFont2008), pointer :: self_ type (treeNode ), pointer :: node_ + class (massDistributionClass ), pointer :: massDistribution__, massDistributionGas__ double precision :: forceRamPressure - !$omp threadprivate(self_,node_,forceRamPressure) + !$omp threadprivate(self_,node_,forceRamPressure,massDistribution__, massDistributionGas__) contains @@ -89,8 +87,6 @@ function font2008ConstructorParameters(parameters) result(self) type (inputParameters ), intent(inout) :: parameters class (darkMatterHaloScaleClass ), pointer :: darkMatterHaloScale_ class (hotHaloRamPressureForceClass ), pointer :: hotHaloRamPressureForce_ - class (hotHaloMassDistributionClass ), pointer :: hotHaloMassDistribution_ - class (galacticStructureClass ), pointer :: galacticStructure_ double precision :: formFactor logical :: solverFailureIsFatal @@ -110,21 +106,17 @@ function font2008ConstructorParameters(parameters) result(self) - - !!] - self=hotHaloRamPressureStrippingFont2008(formFactor,solverFailureIsFatal,darkMatterHaloScale_,hotHaloRamPressureForce_,hotHaloMassDistribution_,galacticStructure_) + self=hotHaloRamPressureStrippingFont2008(formFactor,solverFailureIsFatal,darkMatterHaloScale_,hotHaloRamPressureForce_) !![ - - !!] return end function font2008ConstructorParameters - function font2008ConstructorInternal(formFactor,solverFailureIsFatal,darkMatterHaloScale_,hotHaloRamPressureForce_,hotHaloMassDistribution_,galacticStructure_) result(self) + function font2008ConstructorInternal(formFactor,solverFailureIsFatal,darkMatterHaloScale_,hotHaloRamPressureForce_) result(self) !!{ Internal constructor for the {\normalfont \ttfamily font2008} hot halo ram pressure stripping class. !!} @@ -132,13 +124,11 @@ function font2008ConstructorInternal(formFactor,solverFailureIsFatal,darkMatterH type (hotHaloRamPressureStrippingFont2008) :: self class (darkMatterHaloScaleClass ), intent(in ), target :: darkMatterHaloScale_ class (hotHaloRamPressureForceClass ), intent(in ), target :: hotHaloRamPressureForce_ - class (hotHaloMassDistributionClass ), intent(in ), target :: hotHaloMassDistribution_ - class (galacticStructureClass ), intent(in ), target :: galacticStructure_ double precision , intent(in ) :: formFactor logical , intent(in ) :: solverFailureIsFatal double precision , parameter :: toleranceAbsolute =0.0d+0, toleranceRelative=1.0d-3 !![ - + !!] ! Solver for the ram pressure stripping radius. @@ -160,8 +150,6 @@ subroutine font2008Destructor(self) !![ - - !!] return end subroutine font2008Destructor @@ -170,11 +158,12 @@ double precision function font2008RadiusStripped(self,node) !!{ Return the ram pressure stripping radius due to the hot halo using the model of \cite{font_colours_2008}. !!} - use :: Display , only : displayMessage , verbosityLevelSilent - use :: Error , only : Error_Report , errorStatusSuccess , GSL_Error_Details - use :: Root_Finder , only : rangeExpandMultiplicative, rangeExpandSignExpectNegative, rangeExpandSignExpectPositive - use :: String_Handling , only : operator(//) - use :: Functions_Global, only : State_Retrieve_ , State_Store_ , mergerTreeStateStore_ , State_Set_ + use :: Display , only : displayMessage , verbosityLevelSilent + use :: Error , only : Error_Report , errorStatusSuccess , GSL_Error_Details + use :: Root_Finder , only : rangeExpandMultiplicative, rangeExpandSignExpectNegative, rangeExpandSignExpectPositive + use :: String_Handling , only : operator(//) + use :: Functions_Global , only : State_Retrieve_ , State_Store_ , mergerTreeStateStore_ , State_Set_ + use :: Galactic_Structure_Options, only : componentTypeAll , massTypeAll , componentTypeHotHalo , massTypeGaseous implicit none class (hotHaloRamPressureStrippingFont2008), intent(inout), target :: self @@ -194,10 +183,13 @@ double precision function font2008RadiusStripped(self,node) ! Test whether node is a satellite. if (node%isSatellite().and.node%isPhysicallyPlausible) then ! Set a pointer to the satellite node. - self_ => self - node_ => node + self_ => self + node_ => node + ! Get the hot halo mass distributions. + massDistribution__ => node%massDistribution(componentTypeAll ,massTypeAll ) + massDistributionGas__ => node%massDistribution(componentTypeHotHalo,massTypeGaseous) ! Get the ram pressure force due to the hot halo. - forceRamPressure = self%hotHaloRamPressureForce_%force(node) + forceRamPressure = self%hotHaloRamPressureForce_%force(node) ! Find the radial range within which the ram pressure radius must lie. radiusVirialRoot=font2008RadiusSolver(radiusVirial) if (radiusVirialRoot >= 0.0d0) then @@ -288,6 +280,10 @@ double precision function font2008RadiusStripped(self,node) self%radiusLast=font2008RadiusStripped end if end if + !![ + + + !!] else ! If node is not a satellite, or is not physically plausible, return a ram pressure stripping radius equal to the virial radius. font2008RadiusStripped=radiusVirial @@ -299,21 +295,28 @@ double precision function font2008RadiusSolver(radius) !!{ Root function used in finding the ram pressure stripping radius. !!} - use :: Galactic_Structure_Options , only : componentTypeAll , massTypeAll use :: Numerical_Constants_Astronomical, only : gravitationalConstantGalacticus + use :: Mass_Distributions , only : massDistributionClass + use :: Coordinates , only : coordinateSpherical , assignment(=) implicit none - double precision, intent(in ) :: radius - double precision :: massEnclosed , forceBindingGravitational, & - & densityHotHalo + double precision , intent(in ) :: radius + type (coordinateSpherical) :: coordinates + double precision :: massEnclosed , forceBindingGravitational, & + & densityHotHalo ! Get the hot halo mass distribution. - massEnclosed =+self_%galacticStructure_ %massEnclosed(node_,radius,massType=massTypeAll,componentType=componentTypeAll) - densityHotHalo =+self_%hotHaloMassDistribution_%density (node_,radius ) - forceBindingGravitational=+self_%formFactor & - & *gravitationalConstantGalacticus & - & *massEnclosed & - & *densityHotHalo & - & /radius + coordinates = [radius,0.0d0,0.0d0] + densityHotHalo = massDistributionGas__%density(coordinates) + if (densityHotHalo > 0.0d0) then + massEnclosed =+massDistribution__%massEnclosedBySphere(radius) + forceBindingGravitational=+self_%formFactor & + & *gravitationalConstantGalacticus & + & *massEnclosed & + & *densityHotHalo & + & /radius + else + forceBindingGravitational=0.0d0 + end if if (forceBindingGravitational >= 0.0d0) then font2008RadiusSolver=forceBindingGravitational-forceRamPressure else diff --git a/source/hot_halo.ram_pressure_stripping.timescale.ram_pressure_acceleration.F90 b/source/hot_halo.ram_pressure_stripping.timescale.ram_pressure_acceleration.F90 index dba81735c7..7237922c8e 100644 --- a/source/hot_halo.ram_pressure_stripping.timescale.ram_pressure_acceleration.F90 +++ b/source/hot_halo.ram_pressure_stripping.timescale.ram_pressure_acceleration.F90 @@ -23,7 +23,6 @@ !!} use :: Dark_Matter_Halo_Scales , only : darkMatterHaloScaleClass - use :: Hot_Halo_Mass_Distributions , only : hotHaloMassDistributionClass use :: Hot_Halo_Ram_Pressure_Forces, only : hotHaloRamPressureForceClass !![ @@ -49,8 +48,7 @@ acceleration. !!} private - class(darkMatterHaloScaleClass ), pointer :: darkMatterHaloScale_ => null() - class(hotHaloMassDistributionClass), pointer :: hotHaloMassDistribution_ => null() + class(darkMatterHaloScaleClass ), pointer :: darkMatterHaloScale_ => null() class(hotHaloRamPressureForceClass), pointer :: hotHaloRamPressureForce_ => null() contains final :: ramPressureAccelerationDestructor @@ -76,35 +74,31 @@ function ramPressureAccelerationConstructorParameters(parameters) result(self) type (hotHaloRamPressureTimescaleRamPressureAcceleration) :: self type (inputParameters ), intent(inout) :: parameters class(darkMatterHaloScaleClass ), pointer :: darkMatterHaloScale_ - class(hotHaloMassDistributionClass ), pointer :: hotHaloMassDistribution_ class(hotHaloRamPressureForceClass ), pointer :: hotHaloRamPressureForce_ !![ - !!] - self=hotHaloRamPressureTimescaleRamPressureAcceleration(darkMatterHaloScale_,hotHaloMassDistribution_,hotHaloRamPressureForce_) + self=hotHaloRamPressureTimescaleRamPressureAcceleration(darkMatterHaloScale_,hotHaloRamPressureForce_) !![ - !!] return end function ramPressureAccelerationConstructorParameters - function ramPressureAccelerationConstructorInternal(darkMatterHaloScale_,hotHaloMassDistribution_,hotHaloRamPressureForce_) result(self) + function ramPressureAccelerationConstructorInternal(darkMatterHaloScale_,hotHaloRamPressureForce_) result(self) !!{ Internal constructor for the {\normalfont \ttfamily ramPressureAcceleration} hot halo ram pressure timescale class. !!} implicit none type (hotHaloRamPressureTimescaleRamPressureAcceleration) :: self class(darkMatterHaloScaleClass ), intent(in ), target :: darkMatterHaloScale_ - class(hotHaloMassDistributionClass ), intent(in ), target :: hotHaloMassDistribution_ class(hotHaloRamPressureForceClass ), intent(in ), target :: hotHaloRamPressureForce_ !![ - + !!] return @@ -119,7 +113,6 @@ subroutine ramPressureAccelerationDestructor(self) !![ - !!] return @@ -133,9 +126,12 @@ double precision function ramPressureAccelerationTimescale(self,node) that radius, and $P_\mathrm{ram}$ is the ram pressure force (per unit area). The surface density is approximated as $\Sigma_\mathrm{outer} \approx r_\mathrm{outer} \rho_\mathrm{outer}$, where $\rho_\mathrm{outer}$ is the density at the outer radius. !!} - use :: Galacticus_Nodes , only : nodeComponentHotHalo, treeNode - use :: Numerical_Constants_Astronomical, only : gigaYear , megaParsec + use :: Galacticus_Nodes , only : nodeComponentHotHalo , treeNode + use :: Numerical_Constants_Astronomical, only : gigaYear , megaParsec use :: Numerical_Constants_Prefixes , only : kilo + use :: Mass_Distributions , only : massDistributionClass + use :: Coordinates , only : coordinateSpherical , assignment(=) + use :: Galactic_Structure_Options , only : componentTypeHotHalo , massTypeGaseous implicit none class (hotHaloRamPressureTimescaleRamPressureAcceleration), intent(inout) :: self type (treeNode ), intent(inout) :: node @@ -143,16 +139,23 @@ double precision function ramPressureAccelerationTimescale(self,node) type (treeNode ), pointer :: nodeHost double precision , parameter :: timescaleInfinite =huge(1.0d0) double precision , parameter :: velocityStrippingMaximum= 1.0d1 + class (massDistributionClass ), pointer :: massDistribution_ + type (coordinateSpherical ) :: coordinates double precision :: radiusOuter , densityOuter , & & forceRamPressure , surfaceDensityOuter ! Evaluate surface density and ram pressure force. - hotHalo => node %hotHalo ( ) - radiusOuter = hotHalo %outerRadius( ) - densityOuter = self %hotHaloMassDistribution_%density (node,radiusOuter) - forceRamPressure = self %hotHaloRamPressureForce_%force (node ) + massDistribution_ => node %massDistribution(componentTypeHotHalo,massTypeGaseous) + hotHalo => node %hotHalo ( ) + radiusOuter = hotHalo %outerRadius ( ) + coordinates = [radiusOuter,0.0d0,0.0d0] + densityOuter = massDistribution_ %density (coordinates ) + forceRamPressure = self %hotHaloRamPressureForce_%force (node ) surfaceDensityOuter = +radiusOuter & & *densityOuter + !![ + + !!] ! Exit with infinite timescale for zero ram pressure force. if (forceRamPressure <= 0.0d0) then ramPressureAccelerationTimescale=timescaleInfinite diff --git a/source/hot_halo.temperature_profile.Enzo_hydrostatic.F90 b/source/hot_halo.temperature_profile.Enzo_hydrostatic.F90 index e8440bbfc8..0d46a7f54d 100644 --- a/source/hot_halo.temperature_profile.Enzo_hydrostatic.F90 +++ b/source/hot_halo.temperature_profile.Enzo_hydrostatic.F90 @@ -26,13 +26,13 @@ !![ - A hot halo temperature profile class wjocj implements the ``hydrostatic'' temperature profile available in the \gls{enzo} + A hot halo temperature profile class that implements the ``hydrostatic'' temperature profile available in the \gls{enzo} code. Specifically, \begin{equation} T(r) = \hbox{max}\left( {\mathrm{G} M(<r) \mu m_\mathrm{H} \over 3 \mathrm{k_B} r} , T_\mathrm{min} \right), \end{equation} where $M(<r)$ is the total mass enclosed within radius $r$, $\mu$ is the primordial mean atomic mass, and - $T_\mathrm{min}=100$~K is a temperature floor introduced so as to avoid the temperature reaching arbitrarily low masses. + $T_\mathrm{min}=100$~K is a temperature floor introduced so as to avoid the temperature reaching arbitrarily low values. !!] @@ -43,9 +43,8 @@ private class(darkMatterProfileDMOClass), pointer :: darkMatterProfileDMO_ => null() contains - final :: enzoHydrostaticDestructor - procedure :: temperature => enzoHydrostaticTemperature - procedure :: temperatureLogSlope => enzoHydrostaticTemperatureLogSlope + final :: enzoHydrostaticDestructor + procedure :: get => enzoHydrostaticGet end type hotHaloTemperatureProfileEnzoHydrostatic interface hotHaloTemperatureProfileEnzoHydrostatic @@ -108,72 +107,30 @@ subroutine enzoHydrostaticDestructor(self) return end subroutine enzoHydrostaticDestructor - double precision function enzoHydrostaticTemperature(self,node,radius) + function enzoHydrostaticGet(self,node) result(kinematicsDistribution_) !!{ - Return the density in a {\normalfont \ttfamily enzoHydrostatic} hot halo mass distribution. + Return the virial hot halo temperature distribution for the given {\normalfont \ttfamily node}. !!} - use :: Numerical_Constants_Astronomical, only : meanAtomicMassPrimordial, gravitationalConstantGalacticus - use :: Numerical_Constants_Atomic , only : massHydrogenAtom - use :: Numerical_Constants_Physical , only : boltzmannsConstant - use :: Numerical_Constants_Prefixes , only : kilo + use :: Mass_Distributions, only : massDistributionClass, kinematicsDistributionEnzoHydrostatic implicit none - class (hotHaloTemperatureProfileEnzoHydrostatic), intent(inout) :: self - type (treeNode ), intent(inout) :: node - double precision , intent(in ) :: radius - double precision :: enclosedMass - - if (radius == 0.0d0) then - enzoHydrostaticTemperature=temperatureMinimum - else - enclosedMass =self%darkMatterProfileDMO_%enclosedMass( & - & node , & - & radius & - & ) - enzoHydrostaticTemperature=max( & - & +kilo **2 & - & *gravitationalConstantGalacticus & - & *enclosedMass & - & *meanAtomicMassPrimordial & - & *massHydrogenAtom & - & /3.0d0 & - & /boltzmannsConstant & - & /radius , & - & temperatureMinimum & - & ) - end if - return - end function enzoHydrostaticTemperature - - double precision function enzoHydrostaticTemperatureLogSlope(self,node,radius) - !!{ - Return the logarithmic slope of the density profile in a {\normalfont \ttfamily enzoHydrostatic} hot halo mass - distribution. - !!} - use :: Numerical_Constants_Math, only : Pi - implicit none - class (hotHaloTemperatureProfileEnzoHydrostatic), intent(inout) :: self - type (treeNode ), intent(inout) :: node - double precision , intent(in ) :: radius - double precision :: enclosedMass, density - - if (self%temperature(node,radius) <= temperatureMinimum) then - enzoHydrostaticTemperatureLogSlope=0.0d0 - else - enclosedMass = self%darkMatterProfileDMO_%enclosedMass( & - & node , & - & radius & - & ) - density = self%darkMatterProfileDMO_%density ( & - & node , & - & radius & - & ) - enzoHydrostaticTemperatureLogSlope=+4.0d0 & - & *Pi & - & *radius **3 & - & *density & - & /enclosedMass & - & -1.0d0 - end if + class(kinematicsDistributionClass ), pointer :: kinematicsDistribution_ + class(hotHaloTemperatureProfileEnzoHydrostatic), intent(inout) :: self + type (treeNode ), intent(inout) :: node + class(massDistributionClass ), pointer :: massDistribution_ + + ! Create an isothermal kinematics distribution. + allocate(kinematicsDistributionEnzoHydrostatic :: kinematicsDistribution_) + select type(kinematicsDistribution_) + type is (kinematicsDistributionEnzoHydrostatic) + massDistribution_ => self%darkMatterProfileDMO_%get(node) + !![ + + + kinematicsDistributionEnzoHydrostatic(massDistribution_=massDistribution_) + + + + !!] + end select return - end function enzoHydrostaticTemperatureLogSlope - + end function enzoHydrostaticGet diff --git a/source/hot_halo.temperature_profile.F90 b/source/hot_halo.temperature_profile.F90 index e6c4c517ac..6e5108fe9b 100644 --- a/source/hot_halo.temperature_profile.F90 +++ b/source/hot_halo.temperature_profile.F90 @@ -25,7 +25,8 @@ module Hot_Halo_Temperature_Profiles !!{ Provides a hot halo temperature profile class. !!} - use :: Galacticus_Nodes, only : treeNode + use :: Galacticus_Nodes , only : treeNode + use :: Mass_Distributions, only : kinematicsDistributionClass private !![ @@ -36,19 +37,11 @@ module Hot_Halo_Temperature_Profiles Class implementing hot halo temperature profiles. virial - - Return the temperature of the hot halo at the given {\normalfont \ttfamily radius}. - double precision + + Return the temperature distribution of the hot halo. + class(kinematicsDistributionClass) yes - type (treeNode), intent(inout) :: node - double precision , intent(in ) :: radius - - - Return the logarithmic slope of the temperature of the hot halo at the given {\normalfont \ttfamily radius}. - double precision - yes - type (treeNode), intent(inout) :: node - double precision , intent(in ) :: radius + type(treeNode), intent(inout) :: node !!] diff --git a/source/hot_halo.temperature_profile.virial.F90 b/source/hot_halo.temperature_profile.virial.F90 index fedf5b0bd6..2fef2b4ca3 100644 --- a/source/hot_halo.temperature_profile.virial.F90 +++ b/source/hot_halo.temperature_profile.virial.F90 @@ -38,9 +38,8 @@ private class(darkMatterHaloScaleClass), pointer :: darkMatterHaloScale_ => null() contains - final :: virialDestructor - procedure :: temperature => virialTemperature - procedure :: temperatureLogSlope => virialTemperatureLogSlope + final :: virialDestructor + procedure :: get => virialGet end type hotHaloTemperatureProfileVirial interface hotHaloTemperatureProfileVirial @@ -101,32 +100,31 @@ subroutine virialDestructor(self) return end subroutine virialDestructor - double precision function virialTemperature(self,node,radius) + function virialGet(self,node) result(kinematicsDistribution_) !!{ - Return the density in a {\normalfont \ttfamily virial} hot halo mass distribution. + Return the virial hot halo temperature distribution for the given {\normalfont \ttfamily node}. !!} + use :: Mass_Distributions , only : kinematicsDistributionIsothermal + use :: Numerical_Constants_Astronomical, only : meanAtomicMassPrimordial implicit none - class (hotHaloTemperatureProfileVirial), intent(inout) :: self - type (treeNode ), intent(inout) :: node - double precision , intent(in ) :: radius - !$GLC attributes unused :: radius - - virialTemperature=self%darkMatterHaloScale_%temperatureVirial(node) - return - end function virialTemperature - - double precision function virialTemperatureLogSlope(self,node,radius) - !!{ - Return the logarithmic slope of the density profile in a {\normalfont \ttfamily virial} hot halo mass - distribution. - !!} - implicit none - class (hotHaloTemperatureProfileVirial), intent(inout) :: self - type (treeNode ), intent(inout) :: node - double precision , intent(in ) :: radius - !$GLC attributes unused :: self, node, radius - - virialTemperatureLogSlope=0.0d0 + class(kinematicsDistributionClass ), pointer :: kinematicsDistribution_ + class(hotHaloTemperatureProfileVirial), intent(inout) :: self + type (treeNode ), intent(inout) :: node + + ! Create an isothermal kinematics distribution. + allocate(kinematicsDistributionIsothermal :: kinematicsDistribution_) + select type(kinematicsDistribution_) + type is (kinematicsDistributionIsothermal) + !![ + + + kinematicsDistributionIsothermal( & + & temperature_ =self%darkMatterHaloScale_%temperatureVirial (node), & + & massAtomicMean= meanAtomicMassPrimordial & + & ) + + + !!] + end select return - end function virialTemperatureLogSlope - + end function virialGet diff --git a/source/interface.CLASS.F90 b/source/interface.CLASS.F90 index a177827411..7bfca8bb55 100644 --- a/source/interface.CLASS.F90 +++ b/source/interface.CLASS.F90 @@ -594,7 +594,7 @@ double precision function Interface_CLASS_Normalization(cosmologyParameters_) re write (classParameterFile,'(a)' ) '' close(classParameterFile) ! Run CLASS. - call System_Command_Do(classPath//"class "//parameterFile//" >& "//workPath//"/class.log") + call System_Command_Do(classPath//"class "//parameterFile//" > "//workPath//"/class.log") ! Read the CLASS output. found=.false. open(newUnit=classLog,file=char(workPath)//"/class.log",form="formatted",status="old",iostat=status) diff --git a/source/kinematic_distributions.Burkert.F90 b/source/kinematic_distributions.Burkert.F90 new file mode 100644 index 0000000000..a43153001c --- /dev/null +++ b/source/kinematic_distributions.Burkert.F90 @@ -0,0 +1,171 @@ +!! Copyright 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, +!! 2019, 2020, 2021, 2022, 2023 +!! Andrew Benson +!! +!! This file is part of Galacticus. +!! +!! Galacticus is free software: you can redistribute it and/or modify +!! it under the terms of the GNU General Public License as published by +!! the Free Software Foundation, either version 3 of the License, or +!! (at your option) any later version. +!! +!! Galacticus is distributed in the hope that it will be useful, +!! but WITHOUT ANY WARRANTY; without even the implied warranty of +!! MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +!! GNU General Public License for more details. +!! +!! You should have received a copy of the GNU General Public License +!! along with Galacticus. If not, see . + + !!{ + Implementation of a kinematic distribution class for Burkert mass distributions. + !!} + + !![ + + A kinematic distribution class for Burkert mass distributions. + + !!] + type, public, extends(kinematicsDistributionClass) :: kinematicsDistributionBurkert + !!{ + A kinematics distribution for Burkert distributions. + !!} + contains + procedure :: isCollisional => burkertIsCollisional + procedure :: velocityDispersion1D => burkertVelocityDispersion1D + end type kinematicsDistributionBurkert + + interface kinematicsDistributionBurkert + !!{ + Constructors for the {\normalfont \ttfamily burkert} kinematic distribution class. + !!} + module procedure burkertConstructorParameters + module procedure burkertConstructorInternal + end interface kinematicsDistributionBurkert + +contains + + function burkertConstructorParameters(parameters) result(self) + !!{ + Constructor for the {\normalfont \ttfamily burkert} kinematic distribution class which builds the object from a parameter + set. + !!} + use :: Input_Parameters, only : inputParameters + implicit none + type (kinematicsDistributionBurkert) :: self + type (inputParameters ), intent(inout) :: parameters + + self=kinematicsDistributionBurkert() + !![ + + !!] + return + end function burkertConstructorParameters + + function burkertConstructorInternal() result(self) + !!{ + Internal constructor for the {\normalfont \ttfamily burkert} kinematic distribution class. + !!} + implicit none + type (kinematicsDistributionBurkert) :: self + + return + end function burkertConstructorInternal + + logical function burkertIsCollisional(self) + !!{ + Return true indicating that the burkert kinematic distribution represents collisional particles. + !!} + implicit none + class(kinematicsDistributionBurkert), intent(inout) :: self + + burkertIsCollisional=.false. + return + end function burkertIsCollisional + + double precision function burkertVelocityDispersion1D(self,coordinates,massDistributionEmbedding) result(velocityDispersion) + !!{ + Return the 1D velocity dispersion at the specified {\normalfont \ttfamily coordinates} in an Burkert kinematic distribution. + !!} + use :: Dilogarithms , only : Dilogarithm + use :: Numerical_Constants_Astronomical, only : gravitationalConstantGalacticus + implicit none + class (kinematicsDistributionBurkert), intent(inout), target :: self + class (coordinate ), intent(in ) :: coordinates + class (massDistributionClass ), intent(inout) :: massDistributionEmbedding + double precision :: radius + + if (associated(massDistributionEmbedding%kinematicsDistribution_,self)) then + ! For the case of a self-gravitating Burkert distribution we have an analytic solution for the velocity dispersion. + select type (massDistributionEmbedding) + class is (massDistributionBurkert) + radius =+coordinates %rSpherical () & + & /massDistributionEmbedding%scaleLength + velocityDispersion=real( & + & +( & + & +sqrt( & + & +Pi & + & /3.0d0 & + & ) & + & *sqrt( & + & +( & + & +(+1.0d0+radius ) & + & *(+1.0d0+radius**2) & + & *( & + & +48.0d0 *Pi *radius & + & -11.0d0 *Pi**2*radius & + & -96.0d0 *atan(radius) & + & -96.0d0 *radius*atan(radius) & + & +12.0d0 *radius*log( +2.0d0 )**2 & + & - 4.0d0 *Pi *radius*log( +8.0d0 ) & + & +dcmplx(+ 6.0d0,- 6.0d0) *radius*log(dcmplx(0.0d0,-1.0d0)- radius)**2 & + & +dcmplx(+ 6.0d0,+ 6.0d0) *radius*log(dcmplx(0.0d0,+1.0d0)- radius)**2 & + & +dcmplx(+12.0d0,-12.0d0) *radius*log(dcmplx(0.0d0,-1.0d0)- radius) *log((+1.0d0+dcmplx(0.0d0,1.0d0)*radius)/2.0d0) & + & -dcmplx(+24.0d0,+24.0d0) *radius*atan(radius)*log(dcmplx(0.0d0,-2.0d0)/(dcmplx(0.0d0,-1.0d0)+radius)) & + & -dcmplx(+24.0d0,-24.0d0) *radius*atan(radius)*log(dcmplx(0.0d0,+2.0d0)/(dcmplx(0.0d0,+1.0d0)+radius)) & + & +dcmplx(+12.0d0,+12.0d0) *radius*log( dcmplx(+0.0d0,+1.0d0)-radius) *log(dcmplx(+0.0d0,-0.5d0)*(dcmplx(+0.0d0,+1.0d0)+radius )) & + & -dcmplx(+ 0.0d0,+24.0d0) *radius*log( +1.0d0 + dcmplx(+0.0d0,+1.0d0)*radius) *log(dcmplx(+0.5d0,-0.5d0)*( +1.0d0 +radius )) & + & +dcmplx(+ 0.0d0,+24.0d0) *radius*log( +1.0d0 - dcmplx(+0.0d0,+1.0d0)*radius) *log(dcmplx(+0.5d0,+0.5d0)*( +1.0d0 +radius )) & + & +96.0d0 *log( +1.0d0 +radius ) & + & +96.0d0 *radius*log( +1.0d0 +radius ) & + & -dcmplx(+ 0.0d0,+24.0d0) *radius*log(dcmplx(-0.5d0,+0.5d0)*(dcmplx(+0.0d0,-1.0d0)+radius)) *log( +1.0d0+radius ) & + & +dcmplx(+ 0.0d0,+24.0d0) *radius*log(dcmplx(-0.5d0,-0.5d0)*(dcmplx(+0.0d0,+1.0d0)+radius)) *log( +1.0d0+radius ) & + & -24.0d0 *radius*log( +1.0d0 +radius )**2+48.0d0*log( +1.0d0+radius**2 ) & + & -48.0d0 *radius *log( +1.0d0+radius**2 ) & + & -dcmplx(+12.0d0,-12.0d0) *radius*log( dcmplx(+0.0d0,-1.0d0)-radius ) * log( +1.0d0+radius**2 ) & + & -dcmplx(+12.0d0,+12.0d0) *radius*log( dcmplx(+0.0d0,+1.0d0)-radius ) * log( +1.0d0+radius**2 ) & + & -24.0d0 *radius*log( +1.0d0 +radius ) * log( +1.0d0+radius**2 ) & + & +dcmplx(+12.0d0,+12.0d0)*radius*Dilogarithm( +0.5d0 + dcmplx(0.0d0,+0.5d0)* radius ) & + & -96.0d0 *radius*Dilogarithm( -radius ) & + & -dcmplx(+ 0.0d0,+48.0d0)*radius*Dilogarithm( dcmplx(+0.0d0,-1.0d0) * radius ) & + & +dcmplx(+ 0.0d0,+48.0d0)*radius*Dilogarithm( dcmplx(+0.0d0,+1.0d0) * radius ) & + & -24.0d0 *radius*Dilogarithm( -radius**2 ) & + & -dcmplx(+ 0.0d0,+24.0d0)*radius*Dilogarithm( dcmplx(-0.5d0,+0.5d0) *(dcmplx(0.0d0,-1.0d0) +radius )) & + & +dcmplx(+12.0d0,+12.0d0)*radius*Dilogarithm((dcmplx(+0.0d0,-1.0d0)+radius)/(dcmplx(0.0d0,+1.0d0) +radius )) & + & +dcmplx(+ 0.0d0,+24.0d0)*radius*Dilogarithm( dcmplx(-0.5d0,-0.5d0) *(dcmplx(0.0d0,+1.0d0) +radius )) & + & +dcmplx(+12.0d0,-12.0d0)*radius*Dilogarithm( dcmplx(+0.0d0,-0.5d0) *(dcmplx(0.0d0,+1.0d0) +radius )) & + & +dcmplx(+12.0d0,-12.0d0)*radius*Dilogarithm((dcmplx(+0.0d0,+1.0d0)+radius)/(dcmplx(0.0d0,-1.0d0) +radius )) & + & -dcmplx(+ 0.0d0,+24.0d0)*radius*Dilogarithm( dcmplx(+0.5d0,-0.5d0) *(+1.0d0+radius )) & + & +dcmplx(+ 0.0d0,+24.0d0)*radius*Dilogarithm( dcmplx(+0.5d0,+0.5d0) *(+1.0d0+radius )) & + & ) & + & ) & + & /radius & + & ) & + & ) & + & /4.0d0 & + & ) & + & *sqrt( & + & +gravitationalConstantGalacticus & + & *massDistributionEmbedding%densityNormalization & + & ) & + & * massDistributionEmbedding%scaleLength + class default + velocityDispersion=0.0d0 + call Error_Report('expecting a Burkert mass distribution, but received '//char(massDistributionEmbedding%objectType())//{introspection:location}) + end select + else + ! Our Burkert distribution is embedded in another distribution. We must compute the velocity dispersion numerically. + velocityDispersion=self%velocityDispersion1DNumerical(coordinates,massDistributionEmbedding) + end if + return + end function burkertVelocityDispersion1D diff --git a/source/kinematic_distributions.Enzo_hydrostatic.F90 b/source/kinematic_distributions.Enzo_hydrostatic.F90 new file mode 100644 index 0000000000..a3c9cf1681 --- /dev/null +++ b/source/kinematic_distributions.Enzo_hydrostatic.F90 @@ -0,0 +1,177 @@ +!! Copyright 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, +!! 2019, 2020, 2021, 2022, 2023 +!! Andrew Benson +!! +!! This file is part of Galacticus. +!! +!! Galacticus is free software: you can redistribute it and/or modify +!! it under the terms of the GNU General Public License as published by +!! the Free Software Foundation, either version 3 of the License, or +!! (at your option) any later version. +!! +!! Galacticus is distributed in the hope that it will be useful, +!! but WITHOUT ANY WARRANTY; without even the implied warranty of +!! MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +!! GNU General Public License for more details. +!! +!! You should have received a copy of the GNU General Public License +!! along with Galacticus. If not, see . + + !!{ + Implementation of a kinematic distribution class that mimics the ``hydrostatic'' solution from the Enzo code. + !!} + + !![ + + + A kinematic class that implements the ``hydrostatic'' temperature profile available in the \gls{enzo} + code. Specifically, + \begin{equation} + T(r) = \hbox{max}\left( {\mathrm{G} M(<r) \mu m_\mathrm{H} \over 3 \mathrm{k_B} r} , T_\mathrm{min} \right), + \end{equation} + where $M(<r)$ is the total mass enclosed within radius $r$, $\mu$ is the primordial mean atomic mass, and + $T_\mathrm{min}=100$~K is a temperature floor introduced so as to avoid the temperature reaching arbitrarily low values. + + + !!] + type, public, extends(kinematicsDistributionClass) :: kinematicsDistributionEnzoHydrostatic + !!{ + A enzoHydrostatic kinematic distribution. + !!} + class(massDistributionClass), pointer :: massDistribution_ => null() + contains + final :: enzoHydrostaticDestructor + procedure :: isCollisional => enzoHydrostaticIsCollisional + procedure :: temperature => enzoHydrostaticTemperature + procedure :: temperatureGradientLogarithmic => enzoHydrostaticTemperature + end type kinematicsDistributionEnzoHydrostatic + + interface kinematicsDistributionEnzoHydrostatic + !!{ + Constructors for the {\normalfont \ttfamily enzoHydrostatic} kinematic distribution class. + !!} + module procedure enzoHydrostaticKinematicsConstructorParameters + module procedure enzoHydrostaticKinematicsConstructorInternal + end interface kinematicsDistributionEnzoHydrostatic + + ! Minimum temperature allowed in this distribution. + double precision, parameter :: temperatureMinimum=1.0d2 + +contains + + function enzoHydrostaticKinematicsConstructorParameters(parameters) result(self) + !!{ + Constructor for the {\normalfont \ttfamily isothermal} kinematic distribution class which builds the object from a parameter + set. + !!} + use :: Input_Parameters, only : inputParameters + implicit none + type (kinematicsDistributionEnzoHydrostatic) :: self + type (inputParameters ), intent(inout) :: parameters + class(massDistributionClass ), pointer :: massDistribution_ + + !![ + + !!] + self=kinematicsDistributionEnzoHydrostatic(massDistribution_) + !![ + + + !!] + return + end function enzoHydrostaticKinematicsConstructorParameters + + function enzoHydrostaticKinematicsConstructorInternal(massDistribution_) result(self) + !!{ + Constructor for {\normalfont \ttfamily enzoHydrostatic} kinematics distribution class. + !!} + implicit none + type (kinematicsDistributionEnzoHydrostatic) :: self + class(massDistributionClass ), intent(in ), target :: massDistribution_ + !![ + + !!] + + return + end function enzoHydrostaticKinematicsConstructorInternal + + subroutine enzoHydrostaticDestructor(self) + !!{ + Destructor for the {\normalfont \ttfamily enzoHydrostatic} kinematic distribution class. + !!} + type(kinematicsDistributionEnzoHydrostatic), intent(inout) :: self + implicit none + + !![ + + !!] + return + end subroutine enzoHydrostaticDestructor + + logical function enzoHydrostaticIsCollisional(self) + !!{ + Return false indicating that the enzoHydrostatic kinematic distribution represents collisionless particles. + !!} + implicit none + class(kinematicsDistributionEnzoHydrostatic), intent(inout) :: self + + enzoHydrostaticIsCollisional=.true. + return + end function enzoHydrostaticIsCollisional + + double precision function enzoHydrostaticTemperature(self,coordinates) result(temperature) + !!{ + Return the temperature at the specified {\normalfont \ttfamily coordinates} in an Enzo hydrostatic kinematic distribution. + !!} + use :: Numerical_Constants_Astronomical, only : meanAtomicMassPrimordial, gravitationalConstantGalacticus + use :: Numerical_Constants_Atomic , only : massHydrogenAtom + use :: Numerical_Constants_Physical , only : boltzmannsConstant + use :: Numerical_Constants_Prefixes , only : kilo + implicit none + class (kinematicsDistributionEnzoHydrostatic), intent(inout) :: self + class (coordinate ), intent(in ) :: coordinates + double precision :: massEnclosed + + if (coordinates%rSpherical() == 0.0d0) then + temperature =temperatureMinimum + else + massEnclosed=self%massDistribution_%massEnclosedBySphere(coordinates%rSpherical()) + temperature =max( & + & +kilo **2 & + & *gravitationalConstantGalacticus & + & *massEnclosed & + & *meanAtomicMassPrimordial & + & *massHydrogenAtom & + & /3.0d0 & + & /boltzmannsConstant & + & /coordinates%rSpherical() , & + & temperatureMinimum & + & ) + end if + return + end function enzoHydrostaticTemperature + + double precision function enzoHydrostaticTemperatureGradientLogarithmic(self,coordinates) result(temperatureGradientLogarithmic) + !!{ + Return the logarithmic gradient of the temperature at the specified {\normalfont \ttfamily coordinates} in an Enzo hydrostatic kinematic distribution. + !!} + use :: Numerical_Constants_Math, only : Pi + implicit none + class (kinematicsDistributionEnzoHydrostatic), intent(inout) :: self + class (coordinate ), intent(in ) :: coordinates + double precision :: massEnclosed, density + + if (self%temperature(coordinates) <= temperatureMinimum) then + temperatureGradientLogarithmic=temperatureMinimum + else + density =self%massDistribution_%density (coordinates ) + massEnclosed =self%massDistribution_%massEnclosedBySphere(coordinates%rSpherical()) + temperatureGradientLogarithmic=+ 4.0d0 & + & * Pi & + & *coordinates%rSpherical ()**3 & + & * density & + & / massEnclosed & + & - 1.0d0 + end if + return + end function enzoHydrostaticTemperatureGradientLogarithmic diff --git a/source/kinematic_distributions.Lam2013.F90 b/source/kinematic_distributions.Lam2013.F90 new file mode 100644 index 0000000000..7d170a668f --- /dev/null +++ b/source/kinematic_distributions.Lam2013.F90 @@ -0,0 +1,208 @@ +!! Copyright 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, +!! 2019, 2020, 2021, 2022, 2023 +!! Andrew Benson +!! +!! This file is part of Galacticus. +!! +!! Galacticus is free software: you can redistribute it and/or modify +!! it under the terms of the GNU General Public License as published by +!! the Free Software Foundation, either version 3 of the License, or +!! (at your option) any later version. +!! +!! Galacticus is distributed in the hope that it will be useful, +!! but WITHOUT ANY WARRANTY; without even the implied warranty of +!! MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +!! GNU General Public License for more details. +!! +!! You should have received a copy of the GNU General Public License +!! along with Galacticus. If not, see . + + !!{ + Implementation of a kinematic distribution class for the \cite{lam_modeling_2013} model of halo accretion flows. + !!} + + use :: Cosmology_Functions , only : cosmologyFunctionsClass + use :: Numerical_Interpolation, only : interpolator + + !![ + + A kinematic distribution class for the \cite{lam_modeling_2013} model of halo accretion flows. + + !!] + type, public, extends(kinematicsDistributionCollisionless) :: kinematicsDistributionLam2013 + !!{ + A kinematics distribution for the \cite{lam_modeling_2013} model of halo accretion flows. + !!} + class (cosmologyFunctionsClass), pointer :: cosmologyFunctions_ => null() + type (interpolator ) :: correlationFunctionVolumeAveraged_ + double precision , allocatable, dimension(:) :: radius , correlationFunctionVolumeAveraged + double precision :: overdensityCritical , radiusVirial , & + & massVirial , scaleFactorVelocity , & + & redshift , time , & + & rateLinearGrowth + contains + final :: lam2013Destructor + procedure :: velocityRadial => lam2013VelocityRadial + end type kinematicsDistributionLam2013 + + interface kinematicsDistributionLam2013 + !!{ + Constructors for the {\normalfont \ttfamily lam2013} kinematic distribution class. + !!} + module procedure lam2013ConstructorParameters + module procedure lam2013ConstructorInternal + end interface kinematicsDistributionLam2013 + +contains + + function lam2013ConstructorParameters(parameters) result(self) + !!{ + Constructor for the {\normalfont \ttfamily lam2013} kinematic distribution class which builds the object from a parameter + set. + !!} + use :: Input_Parameters, only : inputParameters + implicit none + type (kinematicsDistributionLam2013) :: self + type (inputParameters ), intent(inout) :: parameters + class (cosmologyFunctionsClass ), pointer :: cosmologyFunctions_ + double precision , allocatable , dimension(:) :: radius , correlationFunctionVolumeAveraged + double precision :: radiusVirial , massVirial , & + & overdensityCritical, redshift , & + & scaleFactorVelocity, rateLinearGrowth + + !![ + + scaleFactorVelocity + parameters + 1.0d0 + A scale factor to be applied to inflow velocities. + + + redshift + The redshift of the halo. + parameters + + + massVirial + The virial mass of the halo. + parameters + + + radiusVirial + The virial radius of the halo. + parameters + + + overdensityCritical + The critical overdensity. + parameters + + + rateLinearGrowth + The logarithmic derivative of the linear growth factor with respect to expansion factor. + parameters + + + radius + The radius in the tabulated volume-averaged correlation function. + parameters + + + correlationFunctionVolumeAveraged + The correlation in the tabulated volume-averaged correlation function. + parameters + + + !!] + self=kinematicsDistributionLam2013(massVirial,radiusVirial,cosmologyFunctions_%cosmicTime(cosmologyFunctions_%expansionFactorFromRedshift(redshift)),overdensityCritical,rateLinearGrowth,scaleFactorVelocity,radius,correlationFunctionVolumeAveraged,cosmologyFunctions_) + !![ + + + !!] + return + end function lam2013ConstructorParameters + + function lam2013ConstructorInternal(massVirial,radiusVirial,time,overdensityCritical,rateLinearGrowth,scaleFactorVelocity,radius,correlationFunctionVolumeAveraged,cosmologyFunctions_) result(self) + !!{ + Internal constructor for the {\normalfont \ttfamily lam2013} kinematic distribution class. + !!} + implicit none + type (kinematicsDistributionLam2013) :: self + class (cosmologyFunctionsClass ), intent(in ), target :: cosmologyFunctions_ + double precision , intent(in ) :: radiusVirial , massVirial , & + & overdensityCritical, time , & + & scaleFactorVelocity, rateLinearGrowth + double precision , intent(in ), dimension(:) :: radius , correlationFunctionVolumeAveraged + !![ + + !!] + + self%redshift =self%cosmologyFunctions_%redshiftFromExpansionFactor(self%cosmologyFunctions_%expansionFactor(time)) + self%correlationFunctionVolumeAveraged_=interpolator(radius,correlationFunctionVolumeAveraged) + return + end function lam2013ConstructorInternal + + subroutine lam2013Destructor(self) + !!{ + Destructor for the {\normalfont \ttfamily correlationFunction} accretion flow mass distribution class. + !!} + implicit none + type(kinematicsDistributionLam2013), intent(inout) :: self + + !![ + + !!] + return + end subroutine lam2013Destructor + + double precision function lam2013VelocityRadial(self,coordinates,massDistributionEmbedding) result(velocityRadial) + !!{ + Return the radial velocity at the specified {\normalfont \ttfamily coordinates} in the \cite{lam_modeling_2013} model for the accretion flow around a halo. + !!} + implicit none + class (kinematicsDistributionLam2013), intent(inout) :: self + class (coordinate ), intent(in ) :: coordinates + class (massDistributionClass ), intent(inout) :: massDistributionEmbedding + double precision :: massShell , radius, & + & densityContrastNonLinear + + radius=coordinates%rSpherical() + ! Evaluate the mass in the shell outside the halo virial radius using equation (B4) of Lam et al. (2013). + if (radius > self%radiusVirial) then + massShell =+self %cosmologyFunctions_ %matterDensityEpochal (self%time) & + & *4.0d0 & + & *Pi & + & /3.0d0 & + & *( & + & + radius **3*(1.0d0+self%correlationFunctionVolumeAveraged_%interpolate( radius )) & + & -self%radiusVirial**3*(1.0d0+self%correlationFunctionVolumeAveraged_%interpolate(self%radiusVirial)) & + & ) + else + massShell =+0.0d0 + end if + ! Compute the nonlinear density contrast using equation (B1) of Lam et al. (2013). + densityContrastNonlinear=+( & + & +self%massVirial & + & + massShell & + & ) & + & /self%cosmologyFunctions_%matterDensityEpochal (self%time) & + & /( & + & +4.0d0 & + & *Pi & + & /3.0d0 & + & *radius**3 & + & ) + ! Evaluate the inflow velocity in the spherical collapse model using equation (B2) of Lam et al. (2013). + velocityRadial =-self%scaleFactorVelocity & + & *self%cosmologyFunctions_%hubbleParameterEpochal (self%time) & + & *radius & + & *self%cosmologyFunctions_%expansionFactor (self%time) & + & *self%rateLinearGrowth & + & /3.0d0 & + & * self%overdensityCritical & + & *( & + & +densityContrastNonLinear**(1.0d0/self%overdensityCritical) & + & -1.0d0 & + & ) + return + end function lam2013VelocityRadial diff --git a/source/kinematic_distributions.NFW.F90 b/source/kinematic_distributions.NFW.F90 new file mode 100644 index 0000000000..69e8c6a82e --- /dev/null +++ b/source/kinematic_distributions.NFW.F90 @@ -0,0 +1,263 @@ +!! Copyright 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, +!! 2019, 2020, 2021, 2022, 2023 +!! Andrew Benson +!! +!! This file is part of Galacticus. +!! +!! Galacticus is free software: you can redistribute it and/or modify +!! it under the terms of the GNU General Public License as published by +!! the Free Software Foundation, either version 3 of the License, or +!! (at your option) any later version. +!! +!! Galacticus is distributed in the hope that it will be useful, +!! but WITHOUT ANY WARRANTY; without even the implied warranty of +!! MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +!! GNU General Public License for more details. +!! +!! You should have received a copy of the GNU General Public License +!! along with Galacticus. If not, see . + + !!{ + Implementation of a kinematic distribution class for NFW mass distributions. + !!} + + !![ + + A kinematic distribution class for NFW mass distributions. + + !!] + type, public, extends(kinematicsDistributionClass) :: kinematicsDistributionNFW + !!{ + A kinematics distribution for NFW distributions. + !!} + logical :: useSeriesApproximation + contains + procedure :: isCollisional => nfwIsCollisional + procedure :: velocityDispersion1D => nfwVelocityDispersion1D + end type kinematicsDistributionNFW + + interface kinematicsDistributionNFW + !!{ + Constructors for the {\normalfont \ttfamily nfw} kinematic distribution class. + !!} + module procedure nfwConstructorParameters + module procedure nfwConstructorInternal + end interface kinematicsDistributionNFW + +contains + + function nfwConstructorParameters(parameters) result(self) + !!{ + Constructor for the {\normalfont \ttfamily nfw} kinematic distribution class which builds the object from a parameter + set. + !!} + use :: Input_Parameters, only : inputParameters + implicit none + type (kinematicsDistributionNFW) :: self + type (inputParameters ), intent(inout) :: parameters + logical :: useSeriesApproximation + + !![ + + useSeriesApproximation + .false. + If true, use a fast series approximation to the velocity dispersion profile in an NFW mass distribution. + parameters + + !!] + self=kinematicsDistributionNFW(useSeriesApproximation) + !![ + + !!] + return + end function nfwConstructorParameters + + function nfwConstructorInternal(useSeriesApproximation) result(self) + !!{ + Internal constructor for the {\normalfont \ttfamily nfw} kinematic distribution class. + !!} + implicit none + type (kinematicsDistributionNFW) :: self + logical , intent(in ) :: useSeriesApproximation + !![ + + !!] + + return + end function nfwConstructorInternal + + logical function nfwIsCollisional(self) + !!{ + Return true indicating that the nfw kinematic distribution represents collisional particles. + !!} + implicit none + class(kinematicsDistributionNFW), intent(inout) :: self + + nfwIsCollisional=.false. + return + end function nfwIsCollisional + + double precision function nfwVelocityDispersion1D(self,coordinates,massDistributionEmbedding) result(velocityDispersion) + !!{ + Return the 1D velocity dispersion at the specified {\normalfont \ttfamily coordinates} in an NFW kinematic distribution. + !!} + use :: Dilogarithms , only : Dilogarithm + use :: Numerical_Constants_Math , only : Pi + use :: Numerical_Constants_Astronomical, only : gravitationalConstantGalacticus + implicit none + class (kinematicsDistributionNFW), intent(inout) , target :: self + class (coordinate ), intent(in ) :: coordinates + class (massDistributionClass ), intent(inout) :: massDistributionEmbedding + double precision , parameter :: minimumRadiusForExactSolution =1.0d-2 + double precision , parameter :: maximumRadiusForExactSolution =1.0d+2 + double precision , parameter :: nfwNormalizationFactorUnitRadius=-8.5d0+Pi**2-6.0d0*log(2.0d0)+6.0d0*log(2.0d0)**2 ! Precomputed NFW normalization factor for unit radius. + integer , parameter :: maximumExpansionOrder =7 + double precision , dimension(maximumExpansionOrder+1) :: coefficient , radiusPower + double precision :: logRadius , onePlusRadius , & + & logOnePlusRadius , velocityDispersionSquare, & + & radius + integer :: i + + if (associated(massDistributionEmbedding%kinematicsDistribution_,self)) then + ! For the case of a self-gravitating NFW distribution we have an analytic solution for the velocity dispersion. + select type (massDistributionEmbedding) + class is (massDistributionNFW) + radius =+coordinates %rSpherical () & + & /massDistributionEmbedding%scaleLength + if (self%useSeriesApproximation) then + if (radius == 0.0d0) then + velocityDispersionSquare=0.0d0 + else + if (radius < 0.33d0) then + ! Expand around 0. + radiusPower(1)= 1.0d0 + radiusPower(2)= radius + logRadius = log(radius) + coefficient(1)= 0.0d0 + coefficient(2)= 1.0d0/ 4.0d0*(-23.0d0 + 2.0d0*Pi**2- 2.0d0*logRadius) + coefficient(3)= (-59.0d0/6.0d0 + Pi**2- logRadius) + coefficient(4)= 1.0d0/ 24.0d0*(-101.0d0 +12.0d0*Pi**2-12.0d0*logRadius) + coefficient(5)= 11.0d0/ 60.0d0 + coefficient(6)=-13.0d0/ 240.0d0 + coefficient(7)= 37.0d0/1400.0d0 + coefficient(8)=-17.0d0/1050.0d0 + else if (radius < 0.68d0) then + ! Expand around 1/2. + radiusPower(1)= 1.0d0 + radiusPower(2)= radius-0.5d0 + coefficient(1)= 9.2256912491493508d-2 + coefficient(2)= 1.8995942538987498d-2 + coefficient(3)=-6.1247239215578800d-2 + coefficient(4)= 9.7544538830827322d-2 + coefficient(5)=-1.4457663797045428d-1 + coefficient(6)= 2.1545129876370470d-1 + coefficient(7)=-3.2824371986452579d-1 + coefficient(8)= 5.1242111712986012d-1 + else if (radius < 1.35d0) then + ! Expand around 1. + radiusPower(1)= 1.0d0 + radiusPower(2)= radius-1.0d0 + coefficient(1)= 9.3439401238895310d-2 + coefficient(2)=-6.2683780821546887d-3 + coefficient(3)=-8.2007484513808621d-3 + coefficient(4)= 1.0119593363084506d-2 + coefficient(5)=-9.2481085050239271d-3 + coefficient(6)= 7.8754354146912774d-3 + coefficient(7)=-6.5855139302751235d-3 + coefficient(8)= 5.5035102596088475d-3 + else if (radius < 2.66d0) then + ! Expand around 2. + radiusPower(1)= 1.0d0 + radiusPower(2)= radius-2.0d0 + coefficient(1)= 8.4126434467263518d-2 + coefficient(2)=-9.8388986218866523d-3 + coefficient(3)= 6.1288152708705594d-4 + coefficient(4)= 4.3464937545102683d-4 + coefficient(5)=-3.4479664620159904d-4 + coefficient(6)= 1.8815165134120623d-4 + coefficient(7)=-9.2066324234421410d-5 + coefficient(8)= 4.3068151103206337d-5 + else + ! Expand around infinity. + radiusPower(1)= 1.0d0 + radiusPower(2)= 1.0d0/radius + logRadius = log(radius) + coefficient(1)= 0.0d0 + coefficient(2)=(- 3.0d0+ 4.0d0*logRadius)/ 16.0d0 + coefficient(3)=( 69.0d0+ 20.0d0*logRadius)/ 200.0d0 + coefficient(4)=(- 97.0d0- 60.0d0*logRadius)/ 1200.0d0 + coefficient(5)=( 71.0d0+ 105.0d0*logRadius)/ 3675.0d0 + coefficient(6)=(- 1.0d0- 56.0d0*logRadius)/ 3136.0d0 + coefficient(7)=(-1271.0d0+2520.0d0*logRadius)/211680.0d0 + coefficient(8)=( 341.0d0- 360.0d0*logRadius)/ 43200.0d0 + end if + do i=3,maximumExpansionOrder+1 + radiusPower(i)=radiusPower(i-1)*radiusPower(2) + end do + velocityDispersionSquare=sum(coefficient*radiusPower) + end if + else + if (radius == 1.0d0) then + velocityDispersionSquare=nfwNormalizationFactorUnitRadius + else if (radius >= maximumRadiusForExactSolution) then + logRadius = log(radius) + velocityDispersionSquare=+(- 3.0d0+ 4.0d0*logRadius)/( 16.0d0*radius ) & + & +( 69.0d0+ 20.0d0*logRadius)/( 200.0d0*radius**2) & + & +(- 97.0d0- 60.0d0*logRadius)/( 1200.0d0*radius**3) & + & +( 71.0d0+ 105.0d0*logRadius)/( 3675.0d0*radius**4) & + & +(- 1.0d0- 56.0d0*logradius)/( 3136.0d0*radius**5) & + & +(-1271.0d0+2520.0d0*logRadius)/(211680.0d0*radius**6) + else if (radius >= minimumRadiusForExactSolution) then + onePlusRadius = 1.0d0+radius + logRadius = log( radius) + logOnePlusRadius = log(onePlusRadius) + velocityDispersionSquare=+0.5d0 & + & * radius & + & *onePlusRadius**2 & + & *( & + & +Pi**2 & + & -logRadius & + & -1.0d0/ radius & + & -1.0d0/onePlusRadius**2 & + & -6.0d0/onePlusRadius & + & +( & + & +1.0d0+ 1.0d0/radius**2 & + & - 4.0d0/radius & + & -2.0d0/onePlusRadius & + & ) & + & *logOnePlusRadius & + & +3.0d0 & + & *logOnePlusRadius**2 & + & +6.0d0 & + & *Dilogarithm(-radius) & + & ) + else if (radius > 0.0d0) then + logRadius = log(radius) + velocityDispersionSquare=+ 1.0d0/ 4.0d0*(-23.0d0 + 2.0d0*Pi**2- 2.0d0*logRadius)*radius & + & + (-59.0d0/6.0d0 + Pi**2- logRadius)*radius**2 & + & + 1.0d0/ 24.0d0*(-101.0d0 +12.0d0*Pi**2-12.0d0*logRadius)*radius**3 & + & +11.0d0/ 60.0d0 *radius**4 & + & -13.0d0/ 240.0d0 *radius**5 & + & +37.0d0/1400.0d0 *radius**6 + else + velocityDispersionSquare=0.0d0 + end if + end if + velocityDispersion=+sqrt( & + & +4.0d0 & + & *Pi & + & *velocityDispersionSquare & + & *gravitationalConstantGalacticus & + & *massDistributionEmbedding%densityNormalization & + & ) & + & * massDistributionEmbedding%scaleLength + class default + velocityDispersion=0.0d0 + call Error_Report('expecting an NFW mass distribution, but received '//char(massDistributionEmbedding%objectType())//{introspection:location}) + end select + else + ! Our NFW distribution is embedded in another distribution. We must compute the velocity dispersion numerically. + velocityDispersion=self%velocityDispersion1DNumerical(coordinates,massDistributionEmbedding) + end if + return + end function nfwVelocityDispersion1D diff --git a/source/kinematic_distributions.SIDM.isothermal.F90 b/source/kinematic_distributions.SIDM.isothermal.F90 new file mode 100644 index 0000000000..39bbd3b5c0 --- /dev/null +++ b/source/kinematic_distributions.SIDM.isothermal.F90 @@ -0,0 +1,125 @@ +!! Copyright 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, +!! 2019, 2020, 2021, 2022, 2023 +!! Andrew Benson +!! +!! This file is part of Galacticus. +!! +!! Galacticus is free software: you can redistribute it and/or modify +!! it under the terms of the GNU General Public License as published by +!! the Free Software Foundation, either version 3 of the License, or +!! (at your option) any later version. +!! +!! Galacticus is distributed in the hope that it will be useful, +!! but WITHOUT ANY WARRANTY; without even the implied warranty of +!! MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +!! GNU General Public License for more details. +!! +!! You should have received a copy of the GNU General Public License +!! along with Galacticus. If not, see . + + !!{ + Provides a kinematic distribution class implementing the ``isothermal'' approximation to the effects of SIDM based on the model + of \cite{jiang_semi-analytic_2023}. + !!} + + !![ + + + A kinematic distribution class implementing the ``isothermal'' approximation to the effects of SIDM based on the model of + \cite{jiang_semi-analytic_2023}. + + + !!] + type, public, extends(kinematicsDistributionClass) :: kinematicsDistributionSIDMIsothermal + !!{ + A kinematic distribution class implementing the ``isothermal'' approximation to the effects of SIDM based on the model + of \cite{jiang_semi-analytic_2023}. + !!} + contains + procedure :: isCollisional => sidmIsothermalIsCollisional + procedure :: velocityDispersion1D => sidmIsothermalVelocityDispersion1D + end type kinematicsDistributionSIDMIsothermal + + interface kinematicsDistributionSIDMIsothermal + !!{ + Constructors for the {\normalfont \ttfamily sidmIsothermal} kinematic distribution class. + !!} + module procedure sidmIsothermalConstructorParameters + module procedure sidmIsothermalConstructorInternal + end interface kinematicsDistributionSIDMIsothermal + +contains + + function sidmIsothermalConstructorParameters(parameters) result(self) + !!{ + Constructor for the {\normalfont \ttfamily sidmIsothermal} kinematic distribution class which builds the object from a parameter + set. + !!} + use :: Input_Parameters, only : inputParameters + implicit none + type(kinematicsDistributionSIDMIsothermal) :: self + type(inputParameters ), intent(inout) :: parameters + + self=kinematicsDistributionSIDMIsothermal() + !![ + + !!] + return + end function sidmIsothermalConstructorParameters + + function sidmIsothermalConstructorInternal() result(self) + !!{ + Internal constructor for the {\normalfont \ttfamily sidmIsothermal} kinematic distribution class. + !!} + implicit none + type(kinematicsDistributionSIDMIsothermal) :: self + + return + end function sidmIsothermalConstructorInternal + + logical function sidmIsothermalIsCollisional(self) + !!{ + Return true indicating that the sidmIsothermal kinematic distribution represents collisional particles. + !!} + implicit none + class(kinematicsDistributionSIDMIsothermal), intent(inout) :: self + + sidmIsothermalIsCollisional=.false. + return + end function sidmIsothermalIsCollisional + + double precision function sidmIsothermalVelocityDispersion1D(self,coordinates,massDistributionEmbedding) result(velocityDispersion) + !!{ + Return the 1D velocity dispersion at the specified {\normalfont \ttfamily coordinates} in an SIDMIsothermal kinematic distribution. + !!} + use :: ISO_Varying_String, only : char + implicit none + class(kinematicsDistributionSIDMIsothermal), intent(inout), target :: self + class(coordinate ), intent(in ) :: coordinates + class(massDistributionClass ), intent(inout) :: massDistributionEmbedding + + if (associated(massDistributionEmbedding%kinematicsDistribution_,self)) then + ! For the case of a self-gravitating SIDM isothermal distribution we have a constant velocity dispersion in the core region. + select type (massDistributionEmbedding) + class is (massDistributionSphericalSIDMIsothermal ) + if (coordinates%rSpherical() > massDistributionEmbedding%radiusInteraction()) then + velocityDispersion=self %velocityDispersion1DNumerical(coordinates,massDistributionEmbedding) + else + velocityDispersion=massDistributionEmbedding%velocityDispersionCentral + end if + class is (massDistributionSphericalSIDMIsothermalBaryons) + if (coordinates%rSpherical() > massDistributionEmbedding%radiusInteraction()) then + velocityDispersion=self %velocityDispersion1DNumerical(coordinates,massDistributionEmbedding) + else + velocityDispersion=massDistributionEmbedding%velocityDispersionCentral + end if + class default + velocityDispersion=0.0d0 + call Error_Report('expecting an SIDMIsothermal mass distribution, but received '//char(massDistributionEmbedding%objectType())//{introspection:location}) + end select + else + ! Our SIDM isothermal distribution is embedded in another distribution. We must compute the velocity dispersion numerically. + velocityDispersion=self%velocityDispersion1DNumerical(coordinates,massDistributionEmbedding) + end if + return + end function sidmIsothermalVelocityDispersion1D diff --git a/source/kinematic_distributions.Shi2016.F90 b/source/kinematic_distributions.Shi2016.F90 new file mode 100644 index 0000000000..ecbed71109 --- /dev/null +++ b/source/kinematic_distributions.Shi2016.F90 @@ -0,0 +1,103 @@ +!! Copyright 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, +!! 2019, 2020, 2021, 2022, 2023 +!! Andrew Benson +!! +!! This file is part of Galacticus. +!! +!! Galacticus is free software: you can redistribute it and/or modify +!! it under the terms of the GNU General Public License as published by +!! the Free Software Foundation, either version 3 of the License, or +!! (at your option) any later version. +!! +!! Galacticus is distributed in the hope that it will be useful, +!! but WITHOUT ANY WARRANTY; without even the implied warranty of +!! MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +!! GNU General Public License for more details. +!! +!! You should have received a copy of the GNU General Public License +!! along with Galacticus. If not, see . + + !!{ + Implementation of a kinematic distribution class for the \cite{shi_outer_2016} model of halo accretion flows. + !!} + + !![ + + A kinematic distribution class for the \cite{shi_outer_2016} model of halo accretion flows. + + !!] + type, public, extends(kinematicsDistributionCollisionless) :: kinematicsDistributionShi2016 + !!{ + A kinematics distribution for the \cite{shi_outer_2016} model of halo accretion flows. + !!} + contains + procedure :: velocityRadial => shi2016KinematicsVelocityRadial + end type kinematicsDistributionShi2016 + + interface kinematicsDistributionShi2016 + !!{ + Constructors for the {\normalfont \ttfamily shi2016} kinematic distribution class. + !!} + module procedure shi2016KinematicsConstructorParameters + module procedure shi2016KinematicsConstructorInternal + end interface kinematicsDistributionShi2016 + +contains + + function shi2016KinematicsConstructorParameters(parameters) result(self) + !!{ + Constructor for the {\normalfont \ttfamily shi2016} kinematic distribution class which builds the object from a parameter + set. + !!} + use :: Input_Parameters, only : inputParameters + implicit none + type(kinematicsDistributionShi2016) :: self + type(inputParameters ), intent(inout) :: parameters + + self=kinematicsDistributionShi2016() + !![ + + !!] + return + end function shi2016KinematicsConstructorParameters + + function shi2016KinematicsConstructorInternal() result(self) + !!{ + Internal constructor for the {\normalfont \ttfamily shi2016} kinematic distribution class. + !!} + implicit none + type(kinematicsDistributionShi2016) :: self + + return + end function shi2016KinematicsConstructorInternal + + double precision function shi2016KinematicsVelocityRadial(self,coordinates,massDistributionEmbedding) result(velocityRadial) + !!{ + Return the radial velocity at the specified {\normalfont \ttfamily coordinates} in the \cite{shi_outer_2016} model for the accretion flow around a halo. + !!} + use :: Error, only : Error_Report + implicit none + class(kinematicsDistributionShi2016), intent(inout) :: self + class(coordinate ), intent(in ) :: coordinates + class(massDistributionClass ), intent(inout) :: massDistributionEmbedding + + select type (massDistributionEmbedding) + class is (massDistributionShi2016) + if (coordinates%rSpherical() > massDistributionEmbedding%radiusMaximumPhysical) then + ! Beyond the maximum radius for the flow just return the mean matter velocity. + velocityRadial=+massDistributionEmbedding%cosmologyFunctions_ %hubbleParameterEpochal(massDistributionEmbedding%time ) & + & *coordinates %rSpherical ( ) + else if (coordinates%rSpherical() < massDistributionEmbedding%radiusMinimumPhysical) then + velocityRadial=+0.0d0 + call Error_Report('radius is less than minimum tabulated for accretion flow'//{introspection:location}) + else + velocityRadial=+massDistributionEmbedding%interpolatorVelocityPhysical%interpolate (coordinates %rSpherical()) + end if + velocityRadial=+velocityRadial & + & *massDistributionEmbedding%scaleFactorVelocity + class default + velocityRadial=0.0d0 + call Error_Report('expecting a Shi2016 mass distribution, but received '//char(massDistributionEmbedding%objectType())//{introspection:location}) + end select + return + end function shi2016KinematicsVelocityRadial diff --git a/source/kinematic_distributions.Zhao1996.F90 b/source/kinematic_distributions.Zhao1996.F90 new file mode 100644 index 0000000000..a19d6b0945 --- /dev/null +++ b/source/kinematic_distributions.Zhao1996.F90 @@ -0,0 +1,262 @@ +!! Copyright 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, +!! 2019, 2020, 2021, 2022, 2023 +!! Andrew Benson +!! +!! This file is part of Galacticus. +!! +!! Galacticus is free software: you can redistribute it and/or modify +!! it under the terms of the GNU General Public License as published by +!! the Free Software Foundation, either version 3 of the License, or +!! (at your option) any later version. +!! +!! Galacticus is distributed in the hope that it will be useful, +!! but WITHOUT ANY WARRANTY; without even the implied warranty of +!! MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +!! GNU General Public License for more details. +!! +!! You should have received a copy of the GNU General Public License +!! along with Galacticus. If not, see . + +!+ Contributions to this file made by: Andrew Benson, Xiaolong Du. + + !!{ + Implementation of a kinematic distribution class for \cite{zhao_analytical_1996} mass distributions. + !!} + + !![ + + A kinematic distribution class for \cite{zhao_analytical_1996} mass distributions. + + !!] + type, public, extends(kinematicsDistributionClass) :: kinematicsDistributionZhao1996 + !!{ + A kinematics distribution for \cite{zhao_analytical_1996} distributions. + !!} + contains + procedure :: isCollisional => zhao1996IsCollisional + procedure :: velocityDispersion1D => zhao1996VelocityDispersion1D + end type kinematicsDistributionZhao1996 + + interface kinematicsDistributionZhao1996 + !!{ + Constructors for the {\normalfont \ttfamily zhao1996} kinematic distribution class. + !!} + module procedure zhao1996ConstructorParameters + module procedure zhao1996ConstructorInternal + end interface kinematicsDistributionZhao1996 + +contains + + function zhao1996ConstructorParameters(parameters) result(self) + !!{ + Constructor for the {\normalfont \ttfamily zhao1996} kinematic distribution class which builds the object from a parameter + set. + !!} + use :: Input_Parameters, only : inputParameters + implicit none + type(kinematicsDistributionZhao1996) :: self + type(inputParameters ), intent(inout) :: parameters + + self=kinematicsDistributionZhao1996() + !![ + + !!] + return + end function zhao1996ConstructorParameters + + function zhao1996ConstructorInternal() result(self) + !!{ + Internal constructor for the {\normalfont \ttfamily zhao1996} kinematic distribution class. + !!} + implicit none + type(kinematicsDistributionZhao1996) :: self + + return + end function zhao1996ConstructorInternal + + logical function zhao1996IsCollisional(self) + !!{ + Return true indicating that the zhao1996 kinematic distribution represents collisional particles. + !!} + implicit none + class(kinematicsDistributionZhao1996), intent(inout) :: self + + zhao1996IsCollisional=.false. + return + end function zhao1996IsCollisional + + double precision function zhao1996VelocityDispersion1D(self,coordinates,massDistributionEmbedding) result(velocityDispersion) + !!{ + Return the 1D velocity dispersion at the specified {\normalfont \ttfamily coordinates} in an Zhao1996 kinematic distribution. + !!} + use :: Numerical_Constants_Astronomical, only : gravitationalConstantGalacticus + use :: Dilogarithms , only : Dilogarithm + implicit none + class (kinematicsDistributionZhao1996), intent(inout), target :: self + class (coordinate ), intent(in ) :: coordinates + class (massDistributionClass ), intent(inout) :: massDistributionEmbedding + double precision , parameter :: radiusTiny =1.0d-3, radiusLarge=1.0d2 + double precision :: radius + + if (associated(massDistributionEmbedding%kinematicsDistribution_,self)) then + ! For the case of a self-gravitating Zhao1996 distribution we have an analytic solution for the velocity dispersion. + select type (massDistributionEmbedding) + class is (massDistributionZhao1996) + radius =+coordinates %rSpherical () & + & /massDistributionEmbedding%scaleLength + select case (massDistributionEmbedding%specialCase%ID) + case (specialCaseGeneral %ID) + ! No analytic solution is available - fall back to the numerical solution. + velocityDispersion=self%velocityDispersion1DNumerical(coordinates,massDistributionEmbedding) + return + case (specialCaseNFW %ID) + if (radius < radiusTiny ) then + ! Use series solution for small radii. + velocityDispersion=+11.0d0*Pi/15.0d0*radius**4 & + & + Pi/ 6.0d0*radius**3*(-101.0d0+12.0d0*Pi**2-12.0d0*log(radius)) & + & + 2.0d0*Pi/ 3.0d0*radius**2*(- 59.0d0+ 6.0d0*Pi**2- 6.0d0*log(radius)) & + & + Pi *radius *(- 23.0d0+ 2.0d0*Pi**2- 2.0d0*log(radius)) + else if (radius > radiusLarge) then + ! Use series solution for large radii. + velocityDispersion=+Pi*(- 3.0d0+ 4.0d0*log(radius))/( 4.0d0*radius ) & + & +Pi*(+ 69.0d0+ 20.0d0*log(radius))/( 50.0d0*radius**2) & + & +Pi*(- 97.0d0- 60.0d0*log(radius))/( 300.0d0*radius**3) & + & +Pi*(+284.0d0+420.0d0*log(radius))/(3675.0d0*radius**4) + else + velocityDispersion=+2.0d0 & + & *Pi & + & *( & + & +radius & + & *( & + & -1.0d0 & + & +radius & + & *( & + & -9.0d0 & + & -7.0d0 * radius & + & +Pi **2*(+1.0d0+radius)**2 & + & ) & + & ) & + & +radius**4*log(+1.0d0+1.0d0/radius) & + & +log(+1.0d0+radius)+radius*(-(radius*(+1.0d0+2.0d0*radius)*log(radius)) & + & +log(+1.0d0+radius)*(-2.0d0-4.0d0*radius*(2.0d0+radius)+3.0d0*radius*(+1.0d0+radius)**2*log(+1.0d0+radius))) & + & +6.0d0*radius**2*(+1.0d0+radius)**2*Dilogarithm(-radius) & + & ) & + & /radius + end if + case (specialCaseCoredNFW %ID) + if (radius < radiusTiny ) then + ! Use series solution for small radii. + velocityDispersion=+(119.0d0*Pi-12.0d0*Pi**3)/ 6.0d0 & + & +(119.0d0*Pi-12.0d0*Pi**3)/ 2.0d0*radius & + & +(353.0d0*Pi-36.0d0*Pi**3)/ 6.0d0*radius**2 & + & +(121.0d0*Pi-12.0d0*Pi**3)/ 6.0d0*radius**3 & + & - 9.0d0*Pi**4 /20.0d0*radius**4 + else if (radius > radiusLarge) then + ! Use series solution for large radii. + velocityDispersion=+Pi*(- 5.0d0+ 4.0d0*log(radius))/( 4.0d0*radius ) & + & +Pi*(+ 177.0d0+ 60.0d0*log(radius))/( 100.0d0*radius**2) & + & +Pi*(- 157.0d0- 60.0d0*log(radius))/( 300.0d0*radius**3) & + & +Pi*(+5857.0d0+1260.0d0*log(radius))/(14700.0d0*radius**4) + else + ! Use full solution. + velocityDispersion=+(+1.0d0+radius)**3 & + & *( & + & +Pi & + & *( & + & +95.0d0 & + & -12.0d0*Pi**2*(+1.0d0+radius)**4 & + & + 2.0d0 * radius *(130.0d0+9.0d0*radius*(13.0d0+4.0d0*radius)) & + & ) & + & /6.0d0 & + & /(+1.0d0+radius)**4 & + & +(2.0d0*Pi*(2.0d0+9.0d0*radius+6.0d0*radius**2)*log(+1.0d0+radius))/(radius*(+1.0d0+radius)**2) & + & - 6.0d0*Pi*log (+1.0d0+radius)**2 & + & -12.0d0*Pi*Dilogarithm( -radius) & + & ) + end if + case (specialCaseGamma0_5NFW%ID) + if (radius < radiusTiny ) then + ! Use series solution for small radii. + velocityDispersion=+16.0d0*Pi/ 3.0d0*sqrt(radius )*(- 11.0d0+ 16.0d0*log(2.0d0)) & + & +16.0d0*Pi/ 15.0d0* radius**1.5d0*(-139.0d0+ 200.0d0*log(2.0d0)) & + & + 2.0d0*Pi/ 7.0d0* radius**2.5d0*(-387.0d0+ 560.0d0*log(2.0d0)) & + & + 4.0d0*Pi/189.0d0* radius**3.5d0*(-887.0d0+1260.0d0*log(2.0d0)) + else if (radius > radiusLarge) then + ! Use series solution for large radii. + velocityDispersion=+Pi*(- 29.0d0+ 24.0d0*log(2.0d0)+ 12.0d0*log(radius))/( 12.0d0*radius ) & + & +Pi*(+107.0d0+120.0d0*log(2.0d0)+ 60.0d0*log(radius))/( 120.0d0*radius**2) & + & +Pi*(- 11.0d0- 40.0d0*log(2.0d0)- 20.0d0*log(radius))/( 96.0d0*radius**3) & + & +Pi*(+ 57.0d0+280.0d0*log(2.0d0)+140.0d0*log(radius))/(1344.0d0*radius**4) + else + ! Use full solution. + velocityDispersion=+8.0d0 & + & /9.0d0 & + & *Pi & + & *( & + & - 6.0d0* sqrt(radius *(+1.0d0+radius)) & + & -38.0d0* sqrt(radius**3*(+1.0d0+radius)) & + & -57.0d0* sqrt(radius**5*(+1.0d0+radius)) & + & -24.0d0* sqrt(radius**7*(+1.0d0+radius)) & + & +24.0d0*( & + & + sqrt(radius**3*(+1.0d0+radius)) & + & +3.0d0*sqrt(radius**5*(+1.0d0+radius)) & + & +3.0d0*sqrt(radius**7*(+1.0d0+radius)) & + & + sqrt(radius**9*(+1.0d0+radius)) & + & ) & + & * (log(radius) + log(+1.0d0+radius)) & + & - 6.0d0*(+1.0d0+radius)**2 *(+1.0d0+2.0d0*radius) *(-1.0d0+8.0d0*radius*(+1.0d0+radius))*asinh(sqrt(radius)) & + & -24.0d0* radius **1.5d0*(+1.0d0 +radius)**3.5d0*(log(+radius)-log(+16.0d0*(+1.0d0+radius))) & + & ) & + & / radius & + & /(+1.0d0+radius) + end if + case (specialCaseGamma1_5NFW%ID) + if (radius < radiusTiny ) then + ! Use series solution for small radii. + velocityDispersion=+8.0d0*Pi/ 3.0d0*sqrt(radius ) & + & - Pi/3150.0d0* radius**3.5d0 *(-19861.0d0+60480.0d0*log(2.0d0)-7560.0d0*log(radius)) & + & - Pi/ 175.0d0* radius**2.5d0 *(- 8683.0d0+13440.0d0*log(2.0d0)-1680.0d0*log(radius)) & + & -4.0d0*Pi/ 75.0d0* radius**1.5d0 *(- 817.0d0+ 960.0d0*log(2.0d0)- 120.0d0*log(radius)) + else if (radius > radiusLarge) then + ! Use series solution for large radii. + velocityDispersion=+Pi*(- 7.0d0+ 8.0d0*log(2.0d0)+ 4.0d0*log(radius))/( 4.0d0*radius ) & + & +Pi*(+ 147.0d0+ 120.0d0*log(2.0d0)+ 60.0d0*log(radius))/( 200.0d0*radius**2) & + & +Pi*(- 79.0d0- 840.0d0*log(2.0d0)- 420.0d0*log(radius))/( 2400.0d0*radius**3) & + & +Pi*(-3589.0d0+7560.0d0*log(2.0d0)+3780.0d0*log(radius))/(33600.0d0*radius**4) + else + ! Use full solution. + velocityDispersion=+8.0d0 & + & /5.0d0 & + & *Pi & + & * radius **1.5d0 & + & *(+1.0d0+radius)**1.5d0 & + & *( & + & +2.0d0*(+1.0d0+2*radius*(-1.0d0+4.0d0*radius+8.0d0*radius**2))*asinh(sqrt(radius))/sqrt(radius**5*(+1.0d0+radius)) & + & +( & + & -2.0d0+radius *(5.0d0+12.0d0*radius-32.0d0*radius*(+1.0d0+radius)* log(2.0d0 ) ) & + & +4.0d0*radius**2* (+1.0d0+radius)*(log(radius)-5.0d0*log(+1.0d0+radius)) & + & ) & + & /radius**2 & + & /(+1.0d0+radius) & + & ) + end if + case default + velocityDispersion=+0.0d0 + call Error_Report('unknown special case'//{introspection:location}) + end select + velocityDispersion=+sqrt( & + & +velocityDispersion & + & *gravitationalConstantGalacticus & + & *massDistributionEmbedding%densityNormalization & + & ) & + & * massDistributionEmbedding%scaleLength + class default + velocityDispersion=0.0d0 + call Error_Report('expecting a Zhao1996 mass distribution, but received '//char(massDistributionEmbedding%objectType())//{introspection:location}) + end select + else + ! Our Zhao1996 distribution is embedded in another distribution. We must compute the velocity dispersion numerically. + velocityDispersion=self%velocityDispersion1DNumerical(coordinates,massDistributionEmbedding) + end if + return + end function zhao1996VelocityDispersion1D diff --git a/source/kinematic_distributions.collisionless.F90 b/source/kinematic_distributions.collisionless.F90 new file mode 100644 index 0000000000..97fcdb66d4 --- /dev/null +++ b/source/kinematic_distributions.collisionless.F90 @@ -0,0 +1,130 @@ +!! Copyright 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, +!! 2019, 2020, 2021, 2022, 2023 +!! Andrew Benson +!! +!! This file is part of Galacticus. +!! +!! Galacticus is free software: you can redistribute it and/or modify +!! it under the terms of the GNU General Public License as published by +!! the Free Software Foundation, either version 3 of the License, or +!! (at your option) any later version. +!! +!! Galacticus is distributed in the hope that it will be useful, +!! but WITHOUT ANY WARRANTY; without even the implied warranty of +!! MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +!! GNU General Public License for more details. +!! +!! You should have received a copy of the GNU General Public License +!! along with Galacticus. If not, see . + + !!{ + Implementation of a kinematic distribution class for collisionless mass distributions. + !!} + + !![ + + A kinematic distribution class for collisionless mass distributions. + + !!] + type, public, extends(kinematicsDistributionClass) :: kinematicsDistributionCollisionless + !!{ + A kinematics distribution for collisionless distributions. + !!} + contains + procedure :: isCollisional => collisionlessIsCollisional + procedure :: velocityDispersion1D => collisionlessVelocityDispersion1D + end type kinematicsDistributionCollisionless + + interface kinematicsDistributionCollisionless + !!{ + Constructors for the {\normalfont \ttfamily collisionless} kinematic distribution class. + !!} + module procedure collisionlessConstructorParameters + module procedure collisionlessConstructorInternal + module procedure collisionlessConstructorDecorated + end interface kinematicsDistributionCollisionless + +contains + + function collisionlessConstructorParameters(parameters) result(self) + !!{ + Constructor for the {\normalfont \ttfamily collisionless} kinematic distribution class which builds the object from a parameter + set. + !!} + use :: Input_Parameters, only : inputParameters + implicit none + type (kinematicsDistributionCollisionless) :: self + type (inputParameters ), intent(inout) :: parameters + double precision :: toleranceRelativeVelocityDispersion, toleranceRelativeVelocityDispersionMaximum + + !![ + + toleranceRelativeVelocityDispersion + 1.0d-6 + parameters + The relative tolerance to use in numerical solutions for the velocity dispersion in dark-matter-only density profiles. + + + toleranceRelativeVelocityDispersionMaximum + 1.0d-3 + parameters + The maximum relative tolerance to use in numerical solutions for the velocity dispersion in dark-matter-only density profiles. + + !!] + self=kinematicsDistributionCollisionless(toleranceRelativeVelocityDispersion,toleranceRelativeVelocityDispersionMaximum) + !![ + + !!] + return + end function collisionlessConstructorParameters + + function collisionlessConstructorInternal(toleranceRelativeVelocityDispersion,toleranceRelativeVelocityDispersionMaximum) result(self) + !!{ + Internal constructor for the {\normalfont \ttfamily collisionless} kinematic distribution class. + !!} + implicit none + type (kinematicsDistributionCollisionless) :: self + double precision , intent(in ), optional :: toleranceRelativeVelocityDispersion, toleranceRelativeVelocityDispersionMaximum + !![ + + !!] + + return + end function collisionlessConstructorInternal + + function collisionlessConstructorDecorated(kinematicsDistribution_) result(self) + !!{ + Internal constructor for the {\normalfont \ttfamily collisionless} kinematic distribution class. + !!} + implicit none + type (kinematicsDistributionCollisionless) :: self + class(kinematicsDistributionClass ), intent(in ) :: kinematicsDistribution_ + + self%toleranceRelativeVelocityDispersion =kinematicsDistribution_%toleranceRelativeVelocityDispersion + self%toleranceRelativeVelocityDispersionMaximum=kinematicsDistribution_%toleranceRelativeVelocityDispersionMaximum + return + end function collisionlessConstructorDecorated + + logical function collisionlessIsCollisional(self) + !!{ + Return false indicating that the collisionless kinematic distribution represents collisionless particles. + !!} + implicit none + class(kinematicsDistributionCollisionless), intent(inout) :: self + + collisionlessIsCollisional=.false. + return + end function collisionlessIsCollisional + + double precision function collisionlessVelocityDispersion1D(self,coordinates,massDistributionEmbedding) result(velocityDispersion) + !!{ + Return the 1D velocity dispersion at the specified {\normalfont \ttfamily coordinates} in an collisionless kinematic distribution. + !!} + implicit none + class(kinematicsDistributionCollisionless), intent(inout), target :: self + class(coordinate ), intent(in ) :: coordinates + class(massDistributionClass ), intent(inout) :: massDistributionEmbedding + + velocityDispersion=self%velocityDispersion1DNumerical(coordinates,massDistributionEmbedding) + return + end function collisionlessVelocityDispersion1D diff --git a/source/kinematic_distributions.finite-resolution.NFW.F90 b/source/kinematic_distributions.finite-resolution.NFW.F90 new file mode 100644 index 0000000000..aa23a03003 --- /dev/null +++ b/source/kinematic_distributions.finite-resolution.NFW.F90 @@ -0,0 +1,399 @@ +!! Copyright 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, +!! 2019, 2020, 2021, 2022, 2023 +!! Andrew Benson +!! +!! This file is part of Galacticus. +!! +!! Galacticus is free software: you can redistribute it and/or modify +!! it under the terms of the GNU General Public License as published by +!! the Free Software Foundation, either version 3 of the License, or +!! (at your option) any later version. +!! +!! Galacticus is distributed in the hope that it will be useful, +!! but WITHOUT ANY WARRANTY; without even the implied warranty of +!! MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +!! GNU General Public License for more details. +!! +!! You should have received a copy of the GNU General Public License +!! along with Galacticus. If not, see . + + !!{ + Implementation of a kinematic distribution class for finite-resolution NFW mass distributions. + !!} + + use :: Numerical_Interpolation, only : interpolator + + !![ + + A kinematic distribution class for finite-resolution NFW mass distributions. + + !!] + type, public, extends(kinematicsDistributionClass) :: kinematicsDistributionFiniteResolutionNFW + !!{ + A kinematics distribution for finite-resolution NFW distributions. + !!} + double precision :: velocityDispersion1DRadiusPrevious , velocityDispersion1DPrevious + ! Velocity dispersion tabulation. + logical :: velocityDispersion1DTableInitialized + integer :: velocityDispersion1DTableLengthResolutionCount , velocityDispersion1DTableRadiusCount + double precision , allocatable, dimension(: ) :: velocityDispersion1DTableLengthResolution , velocityDispersion1DTableRadius + double precision , allocatable, dimension(:,:) :: velocityDispersion1DTable + type (interpolator), allocatable :: velocityDispersion1DTableLengthResolutionInterpolator, velocityDispersion1DTableRadiusInterpolator + double precision :: velocityDispersion1DRadiusMinimum , velocityDispersion1DRadiusMaximum , & + & velocityDispersion1DLengthResolutionMinimum , velocityDispersion1DLengthResolutionMaximum + contains + !![ + + + + + + !!] + procedure :: isCollisional => finiteResolutionNFWIsCollisional + procedure :: velocityDispersion1D => finiteResolutionNFWVelocityDispersion1D + procedure :: velocityDispersion1DTabulate => finiteResolutionNFWVelocityDispersionRadialTabulate + procedure :: storeVelocityDispersionTable => finiteResolutionNFWStoreVelocityDispersionTable + procedure :: restoreVelocityDispersionTable => finiteResolutionNFWRestoreVelocityDispersionTable + end type kinematicsDistributionFiniteResolutionNFW + + interface kinematicsDistributionFiniteResolutionNFW + !!{ + Constructors for the {\normalfont \ttfamily nfw} kinematic distribution class. + !!} + module procedure finiteResolutionNFWConstructorParameters + module procedure finiteResolutionNFWConstructorInternal + end interface kinematicsDistributionFiniteResolutionNFW + + ! Tabulation resolution parameters. + integer, parameter :: velocityDispersion1DTableRadiusPointsPerDecade =100 + integer, parameter :: velocityDispersion1DTableLengthResolutionPointsPerDecade=100 + + class(kinematicsDistributionFiniteResolutionNFW ), pointer :: self_ + class(massDistributionSphericalFiniteResolutionNFW), pointer :: massDistributionEmbedding_ + integer :: iLengthResolution_ + !$omp threadprivate(iLengthResolution_,self_,massDistributionEmbedding_) + +contains + + function finiteResolutionNFWConstructorParameters(parameters) result(self) + !!{ + Constructor for the {\normalfont \ttfamily finiteResolutionNFW} kinematic distribution class which builds the object from a parameter + set. + !!} + use :: Input_Parameters, only : inputParameters + implicit none + type(kinematicsDistributionFiniteResolutionNFW) :: self + type(inputParameters ), intent(inout) :: parameters + + self=kinematicsDistributionFiniteResolutionNFW() + !![ + + !!] + return + end function finiteResolutionNFWConstructorParameters + + function finiteResolutionNFWConstructorInternal() result(self) + !!{ + Internal constructor for the {\normalfont \ttfamily finiteResolutionNFW} kinematic distribution class. + !!} + implicit none + type(kinematicsDistributionFiniteResolutionNFW) :: self + + self%velocityDispersion1DRadiusMinimum =+huge(0.0d0) + self%velocityDispersion1DRadiusMaximum =-huge(0.0d0) + self%velocityDispersion1DLengthResolutionMinimum=+huge(0.0d0) + self%velocityDispersion1DLengthResolutionMaximum=-huge(0.0d0) + self%velocityDispersion1DRadiusPrevious =-huge(0.0d0) + self%velocityDispersion1DPrevious =-huge(0.0d0) + return + end function finiteResolutionNFWConstructorInternal + + logical function finiteResolutionNFWIsCollisional(self) + !!{ + Return true indicating that the finiteResolutionNFW kinematic distribution represents collisional particles. + !!} + implicit none + class(kinematicsDistributionFiniteResolutionNFW), intent(inout) :: self + + finiteResolutionNFWIsCollisional=.false. + return + end function finiteResolutionNFWIsCollisional + + double precision function finiteResolutionNFWVelocityDispersion1D(self,coordinates,massDistributionEmbedding) result(velocityDispersion) + !!{ + Return the 1D velocity dispersion at the specified {\normalfont \ttfamily coordinates} in an finite-resolution NFW kinematic distribution. + !!} + use :: Numerical_Constants_Astronomical, only : gravitationalConstantGalacticus + implicit none + class (kinematicsDistributionFiniteResolutionNFW), intent(inout) , target :: self + class (coordinate ), intent(in ) :: coordinates + class (massDistributionClass ), intent(inout) :: massDistributionEmbedding + double precision , parameter :: lengthResolutionScaleFreeSmall=1.0d-3 + integer (c_size_t ), dimension(0:1) :: jLengthResolution + double precision , dimension(0:1) :: hLengthResolution + integer :: iLengthResolution + double precision :: radiusScaleFree , radiusScaleFreeEffective + + if (associated(massDistributionEmbedding%kinematicsDistribution_,self)) then + ! For the case of a self-gravitating finite-resolution NFW distribution we have a tabulated solution for the velocity dispersion. + select type (massDistributionEmbedding) + class is (massDistributionSphericalFiniteResolutionNFW) + if (coordinates%rSpherical() /= self%velocityDispersion1DRadiusPrevious) then + self%velocityDispersion1DRadiusPrevious=coordinates%rSpherical() + ! Compute the effective radius. In the core of the profile the velocity dispersion must become constant. Therefore, we + ! limit the smallest radius we consider to a small fraction of the core radius. Below this radius a constant velocity + ! dispersion is assumed. + radiusScaleFree =coordinates%rSpherical()/massDistributionEmbedding%radiusScale + radiusScaleFreeEffective=max(radiusScaleFree,lengthResolutionScaleFreeSmall*massDistributionEmbedding%lengthResolutionScaleFree) + ! Ensure table is sufficiently extensive. + call self%velocityDispersion1DTabulate(massDistributionEmbedding,radiusScaleFreeEffective,massDistributionEmbedding%lengthResolutionScaleFree) + ! Interpolate to get the velocity dispersion. + call self%velocityDispersion1DTableLengthResolutionInterpolator%linearFactors(massDistributionEmbedding%lengthResolutionScaleFree,jLengthResolution(0),hLengthResolution) + jLengthResolution(1)=jLengthResolution(0)+1 + self%velocityDispersion1DPrevious=0.0d0 + do iLengthResolution=0,1 + self%velocityDispersion1DPrevious=+self%velocityDispersion1DPrevious & + & +self%velocityDispersion1DTableRadiusInterpolator%interpolate(radiusScaleFreeEffective,self%velocityDispersion1DTable(:,jLengthResolution(iLengthResolution))) & + & * hLengthResolution(iLengthResolution) + end do + self%velocityDispersion1DPrevious=+self%velocityDispersion1DPrevious & + & *sqrt( & + & +gravitationalConstantGalacticus & + & *massDistributionEmbedding%densityNormalization & + & *massDistributionEmbedding%radiusScale **2 & + & ) + end if + velocityDispersion=self%velocityDispersion1DPrevious + class default + velocityDispersion=0.0d0 + call Error_Report('expecting a finite-resolution NFW mass distribution, but received '//char(massDistributionEmbedding%objectType())//{introspection:location}) + end select + else + ! Our finite resolution NFW distribution is embedded in another distribution. We must compute the velocity dispersion numerically. + velocityDispersion=self%velocityDispersion1DNumerical(coordinates,massDistributionEmbedding) + end if + return + end function finiteResolutionNFWVelocityDispersion1D + + subroutine finiteResolutionNFWVelocityDispersionRadialTabulate(self,massDistributionEmbedding,radius,radiusCore) + !!{ + Tabulates the mass enclosed within a given radius for finite resolution NFW density profiles. + !!} + use :: Numerical_Ranges , only : Make_Range, rangeTypeLogarithmic + use :: Numerical_Integration, only : integrator + implicit none + class (kinematicsDistributionFiniteResolutionNFW ), intent(inout), target :: self + class (massDistributionSphericalFiniteResolutionNFW), intent(inout), target :: massDistributionEmbedding + double precision , intent(in ) :: radius , radiusCore + double precision , parameter :: radiusTiny =1.0d-2 + type (integrator ), save :: integrator_ + logical , save :: initialized =.false. + !$omp threadprivate(integrator_,initialized) + logical :: retabulate + integer :: iLengthResolution , iRadius , & + & i + double precision :: jeansIntegral , jeansIntegralPrevious, & + & radiusLower , radiusUpper , & + & radiusOuter , density + + do i=1,2 + retabulate=.false. + if (.not.self%velocityDispersion1DTableInitialized) then + retabulate=.true. + else if ( & + & radius < self%velocityDispersion1DRadiusMinimum & + & .or. & + & radius > self%velocityDispersion1DRadiusMaximum & + & .or. & + & radiusCore < self%velocityDispersion1DLengthResolutionMinimum & + & .or. & + & radiusCore > self%velocityDispersion1DLengthResolutionMaximum & + & ) then + retabulate=.true. + end if + if (retabulate .and.i==1) call self%restoreVelocityDispersionTable() + if (.not.retabulate ) exit + end do + if (retabulate) then + ! Decide how many points to tabulate and allocate table arrays. + self%velocityDispersion1DRadiusMinimum =min(0.5d0*radius ,self%velocityDispersion1DRadiusMinimum ) + self%velocityDispersion1DRadiusMaximum =max(2.0d0*radius ,self%velocityDispersion1DRadiusMaximum ) + self%velocityDispersion1DLengthResolutionMinimum =min(0.5d0*radiusCore,self%velocityDispersion1DLengthResolutionMinimum) + self%velocityDispersion1DLengthResolutionMaximum =max(2.0d0*radiusCore,self%velocityDispersion1DLengthResolutionMaximum) + self%velocityDispersion1DTableRadiusCount =int(log10(self%velocityDispersion1DRadiusMaximum /self%velocityDispersion1DRadiusMinimum )*dble(velocityDispersion1DTableRadiusPointsPerDecade ))+1 + self%velocityDispersion1DTableLengthResolutionCount=int(log10(self%velocityDispersion1DLengthResolutionMaximum/self%velocityDispersion1DLengthResolutionMinimum)*dble(velocityDispersion1DTableLengthResolutionPointsPerDecade))+1 + if (allocated(self%velocityDispersion1DTableRadius)) then + deallocate(self%velocityDispersion1DTableLengthResolution) + deallocate(self%velocityDispersion1DTableRadius ) + deallocate(self%velocityDispersion1DTable ) + end if + allocate(self%velocityDispersion1DTableLengthResolution( self%velocityDispersion1DTableLengthResolutionCount)) + allocate(self%velocityDispersion1DTableRadius (self%velocityDispersion1DTableRadiusCount )) + allocate(self%velocityDispersion1DTable (self%velocityDispersion1DTableRadiusCount,self%velocityDispersion1DTableLengthResolutionCount)) + ! Create a range of radii and core radii. + self%velocityDispersion1DTableRadius =Make_Range(self%velocityDispersion1DRadiusMinimum ,self%velocityDispersion1DRadiusMaximum ,self%velocityDispersion1DTableRadiusCount ,rangeType=rangeTypeLogarithmic) + self%velocityDispersion1DTableLengthResolution=Make_Range(self%velocityDispersion1DLengthResolutionMinimum,self%velocityDispersion1DLengthResolutionMaximum,self%velocityDispersion1DTableLengthResolutionCount,rangeType=rangeTypeLogarithmic) + ! Initialize integrator if necessary. + if (.not.initialized) then + integrator_=integrator(jeansEquationIntegrand,toleranceRelative=1.0d-2) + initialized=.true. + end if + ! Loop over radii and α and populate tables. + self_ => self + massDistributionEmbedding_ => massDistributionEmbedding + radiusOuter = max(10.0d0*self%velocityDispersion1DRadiusMaximum,1000.0d0) + do iLengthResolution=1,self%velocityDispersion1DTableLengthResolutionCount + iLengthResolution_ =iLengthResolution + jeansIntegralPrevious=0.0d0 + do iRadius=self%velocityDispersion1DTableRadiusCount,1,-1 + ! For radii that are tiny compared to the core radius the velocity dispersion become almost constant. Simply assume this to avoid floating point errors. + if ( & + & self%velocityDispersion1DTableRadius(iRadius) < radiusTiny & + & .and. & + & self%velocityDispersion1DTableRadius(iRadius) < radiusTiny*self%velocityDispersion1DTableLengthResolution(iLengthResolution) & + & .and. & + & iRadius < self%velocityDispersion1DTableRadiusCount & + & ) then + self%velocityDispersion1DTable(iRadius,iLengthResolution)=self%velocityDispersion1DTable(iRadius+1,iLengthResolution) + else + ! Find the limits for the integral. + if (iRadius == self%velocityDispersion1DTableRadiusCount) then + radiusUpper=radiusOuter + else + radiusUpper=self%velocityDispersion1DTableRadius(iRadius+1) + end if + radiusLower =self %velocityDispersion1DTableRadius( iRadius ) + density =massDistributionEmbedding%densityScaleFree (radiusLower,self%velocityDispersion1DTableLengthResolution(iLengthResolution)) + jeansIntegral =integrator_ %integrate (radiusLower, radiusUpper ) + self%velocityDispersion1DTable(iRadius,iLengthResolution)=+sqrt( & + & +( & + & +jeansIntegral & + & +jeansIntegralPrevious & + & ) & + & /density & + & ) + jeansIntegralPrevious =+jeansIntegralPrevious & + & +jeansIntegral + end if + end do + end do + ! Build interpolators. + if (allocated(self%velocityDispersion1DTableLengthResolutionInterpolator)) deallocate(self%velocityDispersion1DTableLengthResolutionInterpolator) + if (allocated(self%velocityDispersion1DTableRadiusInterpolator )) deallocate(self%velocityDispersion1DTableRadiusInterpolator ) + allocate(self%velocityDispersion1DTableLengthResolutionInterpolator) + allocate(self%velocityDispersion1DTableRadiusInterpolator ) + self%velocityDispersion1DTableLengthResolutionInterpolator=interpolator(self%velocityDispersion1DTableLengthResolution) + self%velocityDispersion1DTableRadiusInterpolator =interpolator(self%velocityDispersion1DTableRadius ) + ! Specify that tabulation has been made. + self%velocityDispersion1DTableInitialized=.true. + call self%storeVelocityDispersionTable() + end if + return + end subroutine finiteResolutionNFWVelocityDispersionRadialTabulate + + double precision function jeansEquationIntegrand(radius) + !!{ + Integrand for dark matter profile Jeans equation. + !!} + implicit none + double precision, intent(in ) :: radius + + if (radius > 0.0d0) then + jeansEquationIntegrand=+massDistributionEmbedding_%massEnclosedScaleFree(radius,self_%velocityDispersion1DTableLengthResolution(iLengthResolution_)) & + & *massDistributionEmbedding_%densityScaleFree (radius,self_%velocityDispersion1DTableLengthResolution(iLengthResolution_)) & + & / radius **2 + else + jeansEquationIntegrand=0.0d0 + end if + return + end function jeansEquationIntegrand + + subroutine finiteResolutionNFWStoreVelocityDispersionTable(self) + !!{ + Store the tabulated velocity dispersion data to file. + !!} + use :: File_Utilities , only : File_Lock , File_Unlock , lockDescriptor, Directory_Make, & + & File_Path + use :: HDF5_Access , only : hdf5Access + use :: IO_HDF5 , only : hdf5Object + use :: Input_Paths , only : inputPath , pathTypeDataDynamic + use :: ISO_Varying_String, only : varying_string, operator(//) , char + implicit none + class(kinematicsDistributionFiniteResolutionNFW), intent(inout) :: self + type (lockDescriptor ) :: fileLock + type (hdf5Object ) :: file + type (varying_string ) :: fileName + + fileName=inputPath(pathTypeDataDynamic) // & + & 'darkMatter/' // & + & self%objectType ( )// & + & 'VelocityDispersion_' // & + & self%hashedDescriptor(includeSourceDigest=.true.)// & + & '.hdf5' + call Directory_Make(char(File_Path(char(fileName)))) + ! Always obtain the file lock before the hdf5Access lock to avoid deadlocks between OpenMP threads. + call File_Lock(char(fileName),fileLock,lockIsShared=.false.) + !$ call hdf5Access%set() + call file%openFile(char(fileName),overWrite=.true.,objectsOverwritable=.true.,readOnly=.false.) + call file%writeDataset(self%velocityDispersion1DTableLengthResolution,'radiusCore' ) + call file%writeDataset(self%velocityDispersion1DTableRadius ,'radius' ) + call file%writeDataset(self%velocityDispersion1DTable ,'velocityDispersion') + call file%close() + !$ call hdf5Access%unset() + call File_Unlock(fileLock) + return + end subroutine finiteResolutionNFWStoreVelocityDispersionTable + + subroutine finiteResolutionNFWRestoreVelocityDispersionTable(self) + !!{ + Restore the tabulated velocity dispersion data from file. + !!} + use :: File_Utilities , only : File_Exists , File_Lock , File_Unlock, lockDescriptor + use :: HDF5_Access , only : hdf5Access + use :: IO_HDF5 , only : hdf5Object + use :: Input_Paths , only : inputPath , pathTypeDataDynamic + use :: ISO_Varying_String, only : varying_string, operator(//) + implicit none + class(kinematicsDistributionFiniteResolutionNFW), intent(inout) :: self + type (lockDescriptor ) :: fileLock + type (hdf5Object ) :: file + type (varying_string ) :: fileName + + fileName=inputPath(pathTypeDataDynamic) // & + & 'darkMatter/' // & + & self%objectType ( )// & + & 'VelocityDispersion_' // & + & self%hashedDescriptor(includeSourceDigest=.true.)// & + & '.hdf5' + if (File_Exists(fileName)) then + if (allocated(self%velocityDispersion1DTableRadius)) then + deallocate(self%velocityDispersion1DTableLengthResolution) + deallocate(self%velocityDispersion1DTableRadius ) + deallocate(self%velocityDispersion1DTable ) + end if + ! Always obtain the file lock before the hdf5Access lock to avoid deadlocks between OpenMP threads. + call File_Lock(char(fileName),fileLock,lockIsShared=.true.) + !$ call hdf5Access%set() + call file%openFile(char(fileName)) + call file%readDataset('radiusCore' ,self%velocityDispersion1DTableLengthResolution) + call file%readDataset('radius' ,self%velocityDispersion1DTableRadius ) + call file%readDataset('velocityDispersion',self%velocityDispersion1DTable ) + call file%close() + !$ call hdf5Access%unset() + call File_Unlock(fileLock) + self%velocityDispersion1DTableRadiusCount =size(self%velocityDispersion1DTableRadius ) + self%velocityDispersion1DTableLengthResolutionCount=size(self%velocityDispersion1DTableLengthResolution) + self%velocityDispersion1DRadiusMinimum =self%velocityDispersion1DTableRadius ( 1) + self%velocityDispersion1DRadiusMaximum =self%velocityDispersion1DTableRadius (self%velocityDispersion1DTableRadiusCount ) + self%velocityDispersion1DLengthResolutionMinimum =self%velocityDispersion1DTableLengthResolution( 1) + self%velocityDispersion1DLengthResolutionMaximum =self%velocityDispersion1DTableLengthResolution(self%velocityDispersion1DTableLengthResolutionCount) + if (allocated(self%velocityDispersion1DTableLengthResolutionInterpolator)) deallocate(self%velocityDispersion1DTableLengthResolutionInterpolator) + if (allocated(self%velocityDispersion1DTableRadiusInterpolator )) deallocate(self%velocityDispersion1DTableRadiusInterpolator ) + allocate(self%velocityDispersion1DTableLengthResolutionInterpolator) + allocate(self%velocityDispersion1DTableRadiusInterpolator ) + self%velocityDispersion1DTableLengthResolutionInterpolator=interpolator(self%velocityDispersion1DTableLengthResolution) + self%velocityDispersion1DTableRadiusInterpolator =interpolator(self%velocityDispersion1DTableRadius ) + self%velocityDispersion1DTableInitialized =.true. + end if + return + end subroutine finiteResolutionNFWRestoreVelocityDispersionTable diff --git a/source/kinematic_distributions.heated.F90 b/source/kinematic_distributions.heated.F90 new file mode 100644 index 0000000000..39e755e8c4 --- /dev/null +++ b/source/kinematic_distributions.heated.F90 @@ -0,0 +1,261 @@ +!! Copyright 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, +!! 2019, 2020, 2021, 2022, 2023 +!! Andrew Benson +!! +!! This file is part of Galacticus. +!! +!! Galacticus is free software: you can redistribute it and/or modify +!! it under the terms of the GNU General Public License as published by +!! the Free Software Foundation, either version 3 of the License, or +!! (at your option) any later version. +!! +!! Galacticus is distributed in the hope that it will be useful, +!! but WITHOUT ANY WARRANTY; without even the implied warranty of +!! MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +!! GNU General Public License for more details. +!! +!! You should have received a copy of the GNU General Public License +!! along with Galacticus. If not, see . + + !!{ + Implementation of a kinematic distribution class for heated mass distributions. + !!} + + !![ + + An heated kinematic distribution class masses. + + !!] + type, public, extends(kinematicsDistributionClass) :: kinematicsDistributionHeated + !!{ + A heated kinematic distribution. + !!} + logical :: velocityDispersionApproximate + type (enumerationNonAnalyticSolversType) :: nonAnalyticSolver + contains + procedure :: isCollisional => heatedIsCollisional + procedure :: velocityDispersion1D => heatedVelocityDispersion1D + procedure :: jeansEquationIntegrand => heatedJeansEquationIntegrand + procedure :: jeansEquationRadius => heatedJeansEquationRadius + end type kinematicsDistributionHeated + + interface kinematicsDistributionHeated + !!{ + Constructors for the {\normalfont \ttfamily heated} kinematic distribution class. + !!} + module procedure heatedConstructorParameters + module procedure heatedConstructorInternal + end interface kinematicsDistributionHeated + + ! State used to indicate whether we are solving for a self-gravitating heated system or not. + logical :: isSelfGravitating=.true. + !$omp threadprivate(isSelfGravitating) + +contains + + function heatedConstructorParameters(parameters) result(self) + !!{ + Constructor for the {\normalfont \ttfamily isothermal} kinematic distribution class which builds the object from a parameter + set. + !!} + use :: Input_Parameters, only : inputParameters + implicit none + type (kinematicsDistributionHeated) :: self + type (inputParameters ), intent(inout) :: parameters + logical :: velocityDispersionApproximate + type (varying_string ) :: nonAnalyticSolver + double precision :: toleranceRelativeVelocityDispersion, toleranceRelativeVelocityDispersionMaximum + + !![ + + velocityDispersionApproximate + .true. + parameters + + If {\normalfont \ttfamily true}, radial velocity dispersion is computed using an approximate method in which we assume + that $\sigma_\mathrm{r}^2(r) \rightarrow \sigma_\mathrm{r}^2(r) - (2/3) \epsilon(r)$, where $\epsilon(r)$ is the specific + heating energy. If {\normalfont \ttfamily false} then radial velocity dispersion is computed by numerically solving the + Jeans equation. + + + + toleranceRelativeVelocityDispersion + 1.0d-6 + parameters + The relative tolerance to use in numerical solutions for the velocity dispersion in dark-matter-only density profiles. + + + toleranceRelativeVelocityDispersionMaximum + 1.0d-3 + parameters + The maximum relative tolerance to use in numerical solutions for the velocity dispersion in dark-matter-only density profiles. + + + nonAnalyticSolver + var_str('fallThrough') + parameters + + Selects how solutions are computed when no analytic solution is available. If set to ``{\normalfont \ttfamily + fallThrough}'' then the solution ignoring heating is used, while if set to ``{\normalfont \ttfamily numerical}'' then + numerical solvers are used to find solutions. + + + !!] + self=kinematicsDistributionHeated(enumerationNonAnalyticSolversEncode(char(nonAnalyticSolver),includesPrefix=.false.),velocityDispersionApproximate,toleranceRelativeVelocityDispersion,toleranceRelativeVelocityDispersionMaximum) + !![ + + !!] + return + end function heatedConstructorParameters + + function heatedConstructorInternal(nonAnalyticSolver,velocityDispersionApproximate,toleranceRelativeVelocityDispersion,toleranceRelativeVelocityDispersionMaximum) result(self) + !!{ + Constructor for {\normalfont \ttfamily heated} kinematics distribution class. + !!} + implicit none + type (kinematicsDistributionHeated ) :: self + logical , intent(in ) :: velocityDispersionApproximate + double precision , intent(in ) :: toleranceRelativeVelocityDispersion, toleranceRelativeVelocityDispersionMaximum + type (enumerationNonAnalyticSolversType), intent(in ) :: nonAnalyticSolver + !![ + + !!] + + return + end function heatedConstructorInternal + + logical function heatedIsCollisional(self) + !!{ + Return false indicating that the heated kinematic distribution represents collisionless particles. + !!} + implicit none + class(kinematicsDistributionHeated), intent(inout) :: self + + heatedIsCollisional=.false. + return + end function heatedIsCollisional + + double precision function heatedVelocityDispersion1D(self,coordinates,massDistributionEmbedding) result(velocityDispersion) + !!{ + Return the 1D velocity dispersion at the specified {\normalfont \ttfamily coordinates} in an heated kinematic distribution. + !!} + use :: Coordinates, only : coordinateSpherical, assignment(=) + implicit none + class (kinematicsDistributionHeated), intent(inout), target :: self + class (coordinate ), intent(in ) :: coordinates + class (massDistributionClass ), intent(inout) :: massDistributionEmbedding + double precision :: radiusInitial , energySpecific, & + & velocityDispersionSquare + type (coordinateSpherical ) :: coordinatesInitial + + if (associated(massDistributionEmbedding%kinematicsDistribution_,self)) then + ! For the case of a self-gravitating heated distribution we have an optimized numerical solution for the velocity dispersion. + select type (massDistributionEmbedding) + class is (massDistributionSphericalHeated) + if (massDistributionEmbedding%massDistributionHeating_%specificEnergyIsEverywhereZero() .or. self%nonAnalyticSolver == nonAnalyticSolversFallThrough) then + ! Use the original, unheated profile velocity dispersion. + velocityDispersion=massDistributionEmbedding%massDistribution_%kinematicsDistribution_%velocityDispersion1D(coordinates,massDistributionEmbedding%massDistribution_) + else if (self%velocityDispersionApproximate) then + ! Use the approximate solution for velocity dispersion. + radiusInitial =+massDistributionEmbedding%radiusInitial (coordinates%rSpherical () ) + coordinatesInitial = [radiusInitial,0.0d0,0.0d0] + energySpecific =+massDistributionEmbedding%massDistributionHeating_ %specificEnergy ( radiusInitial ,massDistributionEmbedding%massDistribution_) + velocityDispersionSquare =+massDistributionEmbedding%massDistribution_ %kinematicsDistribution_%velocityDispersion1D ( coordinatesInitial ,massDistributionEmbedding%massDistribution_)**2 & + & -2.0d0/3.0d0*energySpecific + velocityDispersion =+sqrt(max(0.0d0,velocityDispersionSquare)) + else + ! Use a numerical solution. + velocityDispersion =+self %velocityDispersion1DNumerical(coordinates ,massDistributionEmbedding ) + end if + class default + velocityDispersion =+0.0d0 + call Error_Report('mass distribution must be of the `massDistributionSphericalHeated` class but found `'//char(massDistributionEmbedding%objectType())//'`'//{introspection:location}) + end select + else + ! Our heated distribution is embedded in another distribution. We must compute the velocity dispersion numerically. We set + ! state to indicate that we must solve for a non-self-gravitating system. + isSelfGravitating =.false. + velocityDispersion=self%velocityDispersion1DNumerical(coordinates,massDistributionEmbedding) + isSelfGravitating =.true. + end if + return + end function heatedVelocityDispersion1D + + double precision function heatedJeansEquationIntegrand(self,radius,massDistributionEmbedding) + !!{ + Integrand for the Jeans equation in a heated mass distribution. Here we do the integration with respect to the initial radius + $r_i$. + \begin{eqnarray} + \sigma_r(r) &=& \frac{1}{\rho(r)}\int_r^{r^{\mathrm{max}}} \rho(r) \frac{\mathrm{G} M(r)}{r^2} \mathrm{d} r \nonumber \\ + &=& \frac{1}{\rho(r)}\int_{r_i}^{r_{i}^{\mathrm{max}}} \rho_i(r_i) \frac{\mathrm{G} M(r_i)}{r_i^2}\left(\frac{r_i}{r}\right)^4 \mathrm{d} r_i. + \end{eqnarray} + Here $r$ can be written as a function of $r_i$ + \begin{equation} + r=\frac{1}{1/r_i-2\epsilon(r_i)/(\mathrm{G}M(r_i))}. + \end{equation} + !!} + use :: Error , only : Error_Report + use :: Coordinates , only : coordinateSpherical , assignment(=) + use :: Numerical_Constants_Astronomical, only : gravitationalConstantGalacticus + implicit none + class (kinematicsDistributionHeated), intent(inout) :: self + double precision , intent(in ) :: radius + class (massDistributionClass ), intent(inout) :: massDistributionEmbedding + double precision :: radiusFinal , energySpecific, & + & massEnclosed + type (coordinateSpherical ) :: coordinates + + if (isSelfGravitating) then + select type (massDistributionEmbedding) + class is (massDistributionSphericalHeated) + massEnclosed =+massDistributionEmbedding %massDistribution_%massEnclosedBySphere(radius ) + energySpecific=+massDistributionEmbedding%massDistributionHeating_ %specificEnergy (radius,massDistributionEmbedding%massDistribution_) + radiusFinal =+1.0d0 & + & /( & + & +1.0d0/radius & + & -2.0d0*energySpecific/gravitationalConstantGalacticus/massEnclosed & + & ) + if (radiusFinal > 0.0d0) then + coordinates = [radius,0.0d0,0.0d0] + heatedJeansEquationIntegrand=+gravitationalConstantGalacticus & + & *massEnclosed & + & *massDistributionEmbedding%massDistribution_%density(coordinates) & + & / radius **2 & + & *(radius/radiusFinal)**4 + else + heatedJeansEquationIntegrand=+0.0d0 + end if + class default + heatedJeansEquationIntegrand =+0.0d0 + call Error_Report('mass distribution must be of the `massDistributionSphericalHeated` class'//{introspection:location}) + end select + else + heatedJeansEquationIntegrand=self%kinematicsDistributionClass%jeansEquationIntegrand(radius,massDistributionEmbedding) + end if + return + end function heatedJeansEquationIntegrand + + double precision function heatedJeansEquationRadius(self,radius,massDistributionEmbedding) + !!{ + Return the radius variable used in solving the Jeans equation that corresponds to a given physical radius. + Here we do the integration with respect to the initial radius, so return the initial radius. + !!} + use :: Error, only : Error_Report + implicit none + class (kinematicsDistributionHeated), intent(inout) :: self + double precision , intent(in ) :: radius + class (massDistributionClass ), intent(inout) :: massDistributionEmbedding + + if (isSelfGravitating) then + select type (massDistributionEmbedding) + class is (massDistributionSphericalHeated) + heatedJeansEquationRadius=massDistributionEmbedding%radiusInitial(radius) + class default + heatedJeansEquationRadius=0.0d0 + call Error_Report('mass distribution must be of the `massDistributionSphericalHeated` class'//{introspection:location}) + end select + else + heatedJeansEquationRadius=self%kinematicsDistributionClass%jeansEquationRadius(radius,massDistributionEmbedding) + end if + return + end function heatedJeansEquationRadius diff --git a/source/kinematic_distributions.isothermal.F90 b/source/kinematic_distributions.isothermal.F90 new file mode 100644 index 0000000000..ebd8e0fd75 --- /dev/null +++ b/source/kinematic_distributions.isothermal.F90 @@ -0,0 +1,190 @@ +!! Copyright 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, +!! 2019, 2020, 2021, 2022, 2023 +!! Andrew Benson +!! +!! This file is part of Galacticus. +!! +!! Galacticus is free software: you can redistribute it and/or modify +!! it under the terms of the GNU General Public License as published by +!! the Free Software Foundation, either version 3 of the License, or +!! (at your option) any later version. +!! +!! Galacticus is distributed in the hope that it will be useful, +!! but WITHOUT ANY WARRANTY; without even the implied warranty of +!! MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +!! GNU General Public License for more details. +!! +!! You should have received a copy of the GNU General Public License +!! along with Galacticus. If not, see . + + !!{ + Implementation of an isothermal kinematic distribution class. + !!} + + !![ + + An isothermal kinematic distribution class masses. + + !!] + type, public, extends(kinematicsDistributionClass) :: kinematicsDistributionIsothermal + !!{ + An isothermal kinematic distribution. + !!} + double precision :: temperature_ , massAtomicMean, & + & velocityDispersion_ + contains + procedure :: isCollisional => isothermalIsCollisional + procedure :: temperature => isothermalTemperature + procedure :: temperatureGradientLogarithmic => isothermalTemperatureGradientLogarithmic + procedure :: velocityDispersion1D => isothermalVelocityDispersion1D + end type kinematicsDistributionIsothermal + + interface kinematicsDistributionIsothermal + !!{ + Constructors for the {\normalfont \ttfamily isothermal} kinematic distribution class. + !!} + module procedure isothermalConstructorParameters + module procedure isothermalConstructorInternal + end interface kinematicsDistributionIsothermal + +contains + + function isothermalConstructorParameters(parameters) result(self) + !!{ + Constructor for the {\normalfont \ttfamily isothermal} kinematic distribution class which builds the object from a parameter + set. + !!} + use :: Input_Parameters, only : inputParameters + implicit none + type (kinematicsDistributionIsothermal) :: self + type (inputParameters ), intent(inout) :: parameters + double precision :: temperature , massAtomicMean, & + & velocityDispersion + + if (parameters%isPresent('temperature')) then + !![ + + temperature + The temperature of the distribution. + parameters + + + massAtomicMean + The mean atomic mass (in atomic mass units) of the distribution. + parameters + + !!] + self=kinematicsDistributionIsothermal(temperature_=temperature,massAtomicMean=massAtomicMean) + else + !![ + + velocityDispersion + The velocity dispersion of the distribution. + parameters + + !!] + self=kinematicsDistributionIsothermal(velocityDispersion_=velocityDispersion) + end if + !![ + + !!] + return + end function isothermalConstructorParameters + + function isothermalConstructorInternal(temperature_,massAtomicMean,velocityDispersion_) result(self) + !!{ + Constructor for {\normalfont \ttfamily isothermal} kinematics distribution class. + !!} + use :: Error , only : Error_Report + use :: Numerical_Constants_Atomic , only : atomicMassUnit + use :: Numerical_Constants_Physical, only : boltzmannsConstant + use :: Numerical_Constants_Prefixes, only : kilo + implicit none + type (kinematicsDistributionIsothermal) :: self + double precision , intent(in ), optional :: temperature_ , massAtomicMean, & + & velocityDispersion_ + !![ + + !!] + + if (present(velocityDispersion_) .and. present(temperature_)) then + call Error_Report('can not provide both [temperature] and [velocityDispersion]'//{introspection:location}) + else if (present(temperature_ )) then + if (.not.present(massAtomicMean)) call Error_Report('[massAtomicMean] must be provided' //{introspection:location}) + self%velocityDispersion_=+sqrt( & + & + boltzmannsConstant & + & *self%temperature_ & + & /self%massAtomicMean & + & / atomicMassUnit & + & ) & + & /kilo + else if (present(velocityDispersion_)) then + if ( present(massAtomicMean)) call Error_Report('[massAtomicMean] must not be provided'//{introspection:location}) + self%massAtomicMean =+1.0d0 + self%temperature_ =+( & + & +velocityDispersion_ & + & *kilo & + & )**2 & + & *atomicMassUnit & + & /boltzmannsConstant + else + call Error_Report('either [temperature] or [velocityDispersion] must be supplied'//{introspection:location}) + end if + return + end function isothermalConstructorInternal + + logical function isothermalIsCollisional(self) + !!{ + Return true indicating that the isothermal kinematic distribution represents collisional particles. + !!} + implicit none + class(kinematicsDistributionIsothermal), intent(inout) :: self + + isothermalIsCollisional=.true. + return + end function isothermalIsCollisional + + double precision function isothermalTemperature(self,coordinates) + !!{ + Return the temperature at the specified {\normalfont \ttfamily coordinates} in an isothermal kinematic distribution. + !!} + implicit none + class(kinematicsDistributionIsothermal), intent(inout) :: self + class(coordinate ), intent(in ) :: coordinates + !$GLC attributes unused :: coordinates + + isothermalTemperature=self%temperature_ + return + end function isothermalTemperature + + double precision function isothermalTemperatureGradientLogarithmic(self,coordinates) + !!{ + Return the logarithmic gradient of temperature at the specified {\normalfont \ttfamily coordinates} in an isothermal kinematic distribution. + !!} + implicit none + class(kinematicsDistributionIsothermal), intent(inout) :: self + class(coordinate ), intent(in ) :: coordinates + !$GLC attributes unused :: coordinates, self + + isothermalTemperatureGradientLogarithmic=0.0d0 + return + end function isothermalTemperatureGradientLogarithmic + + double precision function isothermalVelocityDispersion1D(self,coordinates,massDistributionEmbedding) result(velocityDispersion) + !!{ + Return the 1D velocity dispersion at the specified {\normalfont \ttfamily coordinates} in an isothermal kinematic distribution. + !!} + implicit none + class(kinematicsDistributionIsothermal), intent(inout), target :: self + class(coordinate ), intent(in ) :: coordinates + class(massDistributionClass ), intent(inout) :: massDistributionEmbedding + + if (associated(massDistributionEmbedding%kinematicsDistribution_,self)) then + ! For the case of a self-gravitating isothermal distribution we have an analytic solution for the velocity dispersion. + velocityDispersion=self%velocityDispersion_ + else + ! Our isothermal distribution is embedded in another distribution. We must compute the velocity dispersion numerically. + velocityDispersion=self%velocityDispersion1DNumerical(coordinates,massDistributionEmbedding) + end if + return + end function isothermalVelocityDispersion1D diff --git a/source/kinematic_distributions.local.F90 b/source/kinematic_distributions.local.F90 new file mode 100644 index 0000000000..b24a013c52 --- /dev/null +++ b/source/kinematic_distributions.local.F90 @@ -0,0 +1,119 @@ +!! Copyright 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, +!! 2019, 2020, 2021, 2022, 2023 +!! Andrew Benson +!! +!! This file is part of Galacticus. +!! +!! Galacticus is free software: you can redistribute it and/or modify +!! it under the terms of the GNU General Public License as published by +!! the Free Software Foundation, either version 3 of the License, or +!! (at your option) any later version. +!! +!! Galacticus is distributed in the hope that it will be useful, +!! but WITHOUT ANY WARRANTY; without even the implied warranty of +!! MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +!! GNU General Public License for more details. +!! +!! You should have received a copy of the GNU General Public License +!! along with Galacticus. If not, see . + + !!{ + Implementation of a local kinematic distribution class. + !!} + + !![ + + + A local kinematic distribution class in which the 1D velocity dispersion is given by + \begin{equation} + \sigma_\mathrm{1D}(\mathbf{r}) = \alpha V_\mathrm{c}(r), + \end{equation} + where $r = |\mathbf{r}|$ and $V_\mathrm{c}(r)$ is the rotation curve. Here, $\alpha=${\normalfont \ttfamily []} is a + parameter. + + + !!] + type, public, extends(kinematicsDistributionClass) :: kinematicsDistributionLocal + !!{ + A local kinematic distribution. + !!} + double precision :: alpha + contains + procedure :: isCollisional => localIsCollisional + procedure :: velocityDispersion1D => localVelocityDispersion1D + end type kinematicsDistributionLocal + + interface kinematicsDistributionLocal + !!{ + Constructors for the {\normalfont \ttfamily local} kinematic distribution class. + !!} + module procedure localConstructorParameters + module procedure localConstructorInternal + end interface kinematicsDistributionLocal + +contains + + function localConstructorParameters(parameters) result(self) + !!{ + Constructor for the {\normalfont \ttfamily isothermal} kinematic distribution class which builds the object from a parameter + set. + !!} + use :: Input_Parameters, only : inputParameters + implicit none + type (kinematicsDistributionLocal) :: self + type (inputParameters ), intent(inout) :: parameters + double precision :: alpha + + !![ + + alpha + The parameter $\alpha$ in the relation $\sigma_\mathrm{1D}(\mathbf{r}) = \alpha V_\mathrm{c}(r)$. + 1.0d0/sqrt(2.0d0) + parameters + + !!] + self=kinematicsDistributionLocal(alpha) + !![ + + !!] + return + end function localConstructorParameters + + function localConstructorInternal(alpha) result(self) + !!{ + Constructor for {\normalfont \ttfamily local} kinematics distribution class. + !!} + implicit none + type (kinematicsDistributionLocal) :: self + double precision , intent(in ) :: alpha + !![ + + !!] + + return + end function localConstructorInternal + + logical function localIsCollisional(self) + !!{ + Return false indicating that the local kinematic distribution represents collisionless particles. + !!} + implicit none + class(kinematicsDistributionLocal), intent(inout) :: self + + localIsCollisional=.false. + return + end function localIsCollisional + + double precision function localVelocityDispersion1D(self,coordinates,massDistributionEmbedding) + !!{ + Return the 1D velocity dispersion at the specified {\normalfont \ttfamily coordinates} in an local kinematic distribution. + !!} + implicit none + class(kinematicsDistributionLocal), intent(inout), target :: self + class(coordinate ), intent(in ) :: coordinates + class(massDistributionClass ), intent(inout) :: massDistributionEmbedding + + localVelocityDispersion1D=+self %alpha & + & *massDistributionEmbedding%rotationCurve(coordinates%rSpherical()) + return + end function localVelocityDispersion1D diff --git a/source/kinematic_distributions.spherical.scaler.F90 b/source/kinematic_distributions.spherical.scaler.F90 new file mode 100644 index 0000000000..c20343b410 --- /dev/null +++ b/source/kinematic_distributions.spherical.scaler.F90 @@ -0,0 +1,150 @@ +!! Copyright 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, +!! 2019, 2020, 2021, 2022, 2023 +!! Andrew Benson +!! +!! This file is part of Galacticus. +!! +!! Galacticus is free software: you can redistribute it and/or modify +!! it under the terms of the GNU General Public License as published by +!! the Free Software Foundation, either version 3 of the License, or +!! (at your option) any later version. +!! +!! Galacticus is distributed in the hope that it will be useful, +!! but WITHOUT ANY WARRANTY; without even the implied warranty of +!! MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +!! GNU General Public License for more details. +!! +!! You should have received a copy of the GNU General Public License +!! along with Galacticus. If not, see . + + !!{ + Implementation of a spherical scaler kinematic distribution class. + !!} + + !![ + + A spherical scaler kinematic distribution class masses. + + !!] + type, public, extends(kinematicsDistributionClass) :: kinematicsDistributionSphericalScaler + !!{ + An spherical scaler kinematic distribution. + !!} + class (kinematicsDistributionClass), pointer :: kinematicsDistribution_ => null() + double precision :: factorScalingLength , factorScalingMass + contains + final :: kinematicsSphericalScalerDestructor + procedure :: isCollisional => kinematicsSphericalScalerIsCollisional + procedure :: velocityDispersion1D => kinematicsSphericalScalerVelocityDispersion1D + end type kinematicsDistributionSphericalScaler + + interface kinematicsDistributionSphericalScaler + !!{ + Constructors for the {\normalfont \ttfamily sphericalScaler} kinematic distribution class. + !!} + module procedure kinematicsSphericalScalerConstructorParameters + module procedure kinematicsSphericalScalerConstructorInternal + end interface kinematicsDistributionSphericalScaler + +contains + + function kinematicsSphericalScalerConstructorParameters(parameters) result(self) + !!{ + Constructor for the {\normalfont \ttfamily sphericalScaler} kinematic distribution class which builds the object from a parameter + set. + !!} + use :: Input_Parameters, only : inputParameters + implicit none + type (kinematicsDistributionSphericalScaler) :: self + type (inputParameters ), intent(inout) :: parameters + class (kinematicsDistributionClass ), pointer :: kinematicsDistribution_ + double precision :: factorScalingLength , factorScalingMass + + !![ + + factorScalingLength + The factor by which to scale lengths. + parameters + + + factorScalingMass + The factor by which to scale the mass. + parameters + + + !!] + self=kinematicsDistributionSphericalScaler(factorScalingLength,factorScalingMass,kinematicsDistribution_) + !![ + + + !!] + return + end function kinematicsSphericalScalerConstructorParameters + + function kinematicsSphericalScalerConstructorInternal(factorScalingLength,factorScalingMass,kinematicsDistribution_) result(self) + !!{ + Constructor for ``sphericalScaler'' convergence class. + !!} + implicit none + type (kinematicsDistributionSphericalScaler) :: self + class (kinematicsDistributionClass ), intent(in ), target :: kinematicsDistribution_ + double precision , intent(in ) :: factorScalingLength , factorScalingMass + !![ + + !!] + + return + end function kinematicsSphericalScalerConstructorInternal + + subroutine kinematicsSphericalScalerDestructor(self) + !!{ + Destructor for the ``sphericalScaler'' mass distribution class. + !!} + implicit none + type(kinematicsDistributionSphericalScaler), intent(inout) :: self + + !![ + + !!] + return + end subroutine kinematicsSphericalScalerDestructor + + logical function kinematicsSphericalScalerIsCollisional(self) + !!{ + Return true indicating that the spherical scaler kinematic distribution represents collisional particles. + !!} + implicit none + class(kinematicsDistributionSphericalScaler), intent(inout) :: self + + kinematicsSphericalScalerIsCollisional=.false. + return + end function kinematicsSphericalScalerIsCollisional + + double precision function kinematicsSphericalScalerVelocityDispersion1D(self,coordinates,massDistributionEmbedding) result(velocityDispersion) + !!{ + Return the 1D velocity dispersion at the specified {\normalfont \ttfamily coordinates} in a spherical scaler kinematic distribution. + !!} + implicit none + class(kinematicsDistributionSphericalScaler), intent(inout), target :: self + class(coordinate ), intent(in ) :: coordinates + class(massDistributionClass ), intent(inout) :: massDistributionEmbedding + + if (associated(massDistributionEmbedding%kinematicsDistribution_,self)) then + ! For the case of a self-gravitating scaled distribution we have an analytic solution for the velocity dispersion. + select type (massDistributionEmbedding) + class is (massDistributionSphericalScaler) + velocityDispersion=+sqrt( & + & +self%factorScalingMass & + & /self%factorScalingLength & + & ) & + & *self%kinematicsDistribution_%velocityDispersion1D(coordinates,massDistributionEmbedding%massDistribution_) + class default + velocityDispersion=0.0d0 + call Error_Report('expecting a spherical scaler mass distribution, but received '//char(massDistributionEmbedding%objectType())//{introspection:location}) + end select + else + ! Our scaled distribution is embedded in another distribution. We must compute the velocity dispersion numerically. + velocityDispersion=self%velocityDispersion1DNumerical(coordinates,massDistributionEmbedding) + end if + return + end function kinematicsSphericalScalerVelocityDispersion1D diff --git a/source/kinematic_distributions.truncated.F90 b/source/kinematic_distributions.truncated.F90 new file mode 100644 index 0000000000..ed95814c83 --- /dev/null +++ b/source/kinematic_distributions.truncated.F90 @@ -0,0 +1,165 @@ +!! Copyright 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, +!! 2019, 2020, 2021, 2022, 2023 +!! Andrew Benson +!! +!! This file is part of Galacticus. +!! +!! Galacticus is free software: you can redistribute it and/or modify +!! it under the terms of the GNU General Public License as published by +!! the Free Software Foundation, either version 3 of the License, or +!! (at your option) any later version. +!! +!! Galacticus is distributed in the hope that it will be useful, +!! but WITHOUT ANY WARRANTY; without even the implied warranty of +!! MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +!! GNU General Public License for more details. +!! +!! You should have received a copy of the GNU General Public License +!! along with Galacticus. If not, see . + + !!{ + Implementation of a kinematic distribution class for truncated mass distributions. + !!} + + !![ + + An truncated kinematic distribution class masses. + + !!] + type, public, extends(kinematicsDistributionClass) :: kinematicsDistributionTruncated + !!{ + A kinematic distribution for truncated mass distributions. + !!} + double precision :: velocityDispersionDecoratedTruncateMinimum, velocityDispersionTruncateMinimum, & + & densityTruncateMinimum + logical :: velocityDispersionTruncateMinimumComputed + contains + procedure :: isCollisional => truncatedIsCollisional + procedure :: velocityDispersion1D => truncatedVelocityDispersion1D + end type kinematicsDistributionTruncated + + interface kinematicsDistributionTruncated + !!{ + Constructors for the {\normalfont \ttfamily truncated} kinematic distribution class. + !!} + module procedure truncatedConstructorParameters + module procedure truncatedConstructorInternal + end interface kinematicsDistributionTruncated + +contains + + function truncatedConstructorParameters(parameters) result(self) + !!{ + Constructor for the {\normalfont \ttfamily isothermal} kinematic distribution class which builds the object from a parameter + set. + !!} + use :: Input_Parameters, only : inputParameters + implicit none + type(kinematicsDistributionTruncated) :: self + type(inputParameters ), intent(inout) :: parameters + + self=kinematicsDistributionTruncated() + !![ + + !!] + return + end function truncatedConstructorParameters + + function truncatedConstructorInternal() result(self) + !!{ + Internal constructor for the {\normalfont \ttfamily isothermal} kinematic distribution class. + !!} + implicit none + type(kinematicsDistributionTruncated) :: self + + self%velocityDispersionTruncateMinimumComputed=.false. + return + end function truncatedConstructorInternal + + logical function truncatedIsCollisional(self) + !!{ + Return false indicating that the truncated kinematic distribution represents collisionless particles. + !!} + implicit none + class(kinematicsDistributionTruncated), intent(inout) :: self + + truncatedIsCollisional=.false. + return + end function truncatedIsCollisional + + double precision function truncatedVelocityDispersion1D(self,coordinates,massDistributionEmbedding) result(velocityDispersion) + !!{ + Return the 1D velocity dispersion at the specified {\normalfont \ttfamily coordinates} in an truncated kinematic distribution. + !!} + use :: Coordinates, only : coordinateSpherical, assignment(=) + implicit none + class (kinematicsDistributionTruncated), intent(inout), target :: self + class (coordinate ), intent(in ) :: coordinates + class (massDistributionClass ), intent(inout) :: massDistributionEmbedding + class (kinematicsDistributionClass ), pointer :: kinematicsDistribution_ + type (coordinateSpherical ) :: coordinatesTruncateMinimum + logical :: analytic + double precision :: density , velocityDispersionDecorated + + analytic=.false. + if (associated(massDistributionEmbedding%kinematicsDistribution_,self)) then + ! For the case of a self-gravitating truncated distribution we have a piecewise solution for the velocity dispersion in some cases. + select type (massDistributionEmbedding) + class is (massDistributionSphericalTruncated) + if (coordinates%rSpherical() < massDistributionEmbedding%radiusTruncateMinimum) then + ! Use the decorated mass distribution solution, adjusted for the outer truncation shell. + analytic = .true. + kinematicsDistribution_ => massDistributionEmbedding%massDistribution_%kinematicsDistribution ( ) + density = massDistributionEmbedding %density (coordinates ) + velocityDispersionDecorated = kinematicsDistribution_ %velocityDispersion1D (coordinates ,massDistributionEmbedding%massDistribution_) + if (.not.self%velocityDispersionTruncateMinimumComputed) then + coordinatesTruncateMinimum = [massDistributionEmbedding %radiusTruncateMinimum,0.0d0,0.0d0] + self%densityTruncateMinimum = massDistributionEmbedding %density (coordinatesTruncateMinimum ) + self%velocityDispersionDecoratedTruncateMinimum = kinematicsDistribution_ %velocityDispersion1D (coordinatesTruncateMinimum,massDistributionEmbedding%massDistribution_) + self%velocityDispersionTruncateMinimum = self %velocityDispersion1DNumerical(coordinatesTruncateMinimum,massDistributionEmbedding ) + self%velocityDispersionTruncateMinimumComputed = .true. + end if + !![ + + !!] + end if + class is (massDistributionSphericalTruncatedExponential) + if (coordinates%rSpherical() < massDistributionEmbedding%radiusTruncateMinimum) then + ! Use the decorated mass distribution solution, adjusted for the outer truncation shell. + analytic = .true. + kinematicsDistribution_ => massDistributionEmbedding%massDistribution_%kinematicsDistribution ( ) + density = massDistributionEmbedding %density (coordinates ) + velocityDispersionDecorated = kinematicsDistribution_ %velocityDispersion1D (coordinates ,massDistributionEmbedding%massDistribution_) + if (.not.self%velocityDispersionTruncateMinimumComputed) then + coordinatesTruncateMinimum = [massDistributionEmbedding %radiusTruncateMinimum,0.0d0,0.0d0] + self%densityTruncateMinimum = massDistributionEmbedding %density (coordinatesTruncateMinimum ) + self%velocityDispersionDecoratedTruncateMinimum = kinematicsDistribution_ %velocityDispersion1D (coordinatesTruncateMinimum,massDistributionEmbedding%massDistribution_) + self%velocityDispersionTruncateMinimum = self %velocityDispersion1DNumerical(coordinatesTruncateMinimum,massDistributionEmbedding ) + self%velocityDispersionTruncateMinimumComputed = .true. + end if + !![ + + !!] + end if + class default + velocityDispersion=+0.0d0 + call Error_Report('mass distribution must be of the `massDistributionSphericalTruncated` or `massDistributionSphericalTruncatedExponential` class'//{introspection:location}) + end select + end if + ! Use a numerical solution if no analytic solution was available. + if (analytic) then + velocityDispersion=+sqrt( & + & + velocityDispersionDecorated **2 & + & +self%densityTruncateMinimum & + & / density & + & *( & + & +self%velocityDispersionTruncateMinimum **2 & + & -self%velocityDispersionDecoratedTruncateMinimum**2 & + & ) & + & ) + else + velocityDispersion=+self%velocityDispersion1DNumerical(coordinates,massDistributionEmbedding) + end if + return + end function truncatedVelocityDispersion1D + diff --git a/source/kinematic_distributions.undecorator.F90 b/source/kinematic_distributions.undecorator.F90 new file mode 100644 index 0000000000..4e54273cea --- /dev/null +++ b/source/kinematic_distributions.undecorator.F90 @@ -0,0 +1,132 @@ +!! Copyright 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, +!! 2019, 2020, 2021, 2022, 2023 +!! Andrew Benson +!! +!! This file is part of Galacticus. +!! +!! Galacticus is free software: you can redistribute it and/or modify +!! it under the terms of the GNU General Public License as published by +!! the Free Software Foundation, either version 3 of the License, or +!! (at your option) any later version. +!! +!! Galacticus is distributed in the hope that it will be useful, +!! but WITHOUT ANY WARRANTY; without even the implied warranty of +!! MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +!! GNU General Public License for more details. +!! +!! You should have received a copy of the GNU General Public License +!! along with Galacticus. If not, see . + + !!{ + Implementation of a kinematic distribution class for decorated mass distributions, that uses the undecorated mass distribution. + !!} + + !![ + + A kinematic distribution class for decorated mass distributions, that uses the undecorated mass distribution. + + !!] + type, public, extends(kinematicsDistributionClass) :: kinematicsDistributionUndecorator + !!{ + A kinematics distribution for decorated mass distributions, that uses the undecorated mass distribution. + !!} + class(kinematicsDistributionClass), pointer :: kinematicsDistribution_ => null() + contains + final :: undecoratorDestructor + procedure :: isCollisional => undecoratorIsCollisional + procedure :: velocityDispersion1D => undecoratorVelocityDispersion1D + end type kinematicsDistributionUndecorator + + interface kinematicsDistributionUndecorator + !!{ + Constructors for the {\normalfont \ttfamily undecorator} kinematic distribution class. + !!} + module procedure undecoratorConstructorParameters + module procedure undecoratorConstructorInternal + end interface kinematicsDistributionUndecorator + +contains + + function undecoratorConstructorParameters(parameters) result(self) + !!{ + Constructor for the {\normalfont \ttfamily undecorator} kinematic distribution class which builds the object from a parameter + set. + !!} + use :: Input_Parameters, only : inputParameters + implicit none + type (kinematicsDistributionUndecorator) :: self + type (inputParameters ), intent(inout) :: parameters + class(kinematicsDistributionClass ), pointer :: kinematicsDistribution_ + + !![ + + !!] + self=kinematicsDistributionUndecorator(kinematicsDistribution_) + !![ + + !!] + return + end function undecoratorConstructorParameters + + function undecoratorConstructorInternal(kinematicsDistribution_) result(self) + !!{ + Internal constructor for the {\normalfont \ttfamily undecorator} kinematic distribution class. + !!} + implicit none + type (kinematicsDistributionUndecorator) :: self + class(kinematicsDistributionClass ), intent(in ), target :: kinematicsDistribution_ + !![ + + !!] + + return + end function undecoratorConstructorInternal + + subroutine undecoratorDestructor(self) + !!{ + Destructor for the {\normalfont \ttfamily undecorator} kinematic distribution class. + !!} + implicit none + type(kinematicsDistributionUndecorator), intent(inout) :: self + + !![ + + !!] + return + end subroutine undecoratorDestructor + + logical function undecoratorIsCollisional(self) + !!{ + Return whether undecorator kinematic distribution represents collisional particles. + !!} + implicit none + class(kinematicsDistributionUndecorator), intent(inout) :: self + + undecoratorIsCollisional=self%kinematicsDistribution_%isCollisional() + return + end function undecoratorIsCollisional + + double precision function undecoratorVelocityDispersion1D(self,coordinates,massDistributionEmbedding) result(velocityDispersion) + !!{ + Return the 1D velocity dispersion at the specified {\normalfont \ttfamily coordinates} in an undecorator kinematic distribution. + !!} + implicit none + class(kinematicsDistributionUndecorator), intent(inout), target :: self + class(coordinate ), intent(in ) :: coordinates + class(massDistributionClass ), intent(inout) :: massDistributionEmbedding + + if (associated(massDistributionEmbedding%kinematicsDistribution_,self)) then + ! For the case of a self-gravitating distribution we can use the undecorated kinematic distribution in its own mass distribution. + select type (massDistributionEmbedding) + class is (massDistributionSphericalDecorator) + velocityDispersion=self%kinematicsDistribution_%velocityDispersion1D(coordinates,massDistributionEmbedding%massDistribution_) + class default + velocityDispersion=+0.0d0 + call Error_Report('mass distribution must be of the `massDistributionSphericalDecorator` class but found `'//char(massDistributionEmbedding%objectType())//'`'//{introspection:location}) + end select + else + ! Our distribution is embedded in another distribution. We must compute the velocity dispersion numerically + velocityDispersion=self%velocityDispersion1DNumerical(coordinates,massDistributionEmbedding) + end if + return + end function undecoratorVelocityDispersion1D diff --git a/source/libraryClasses.xml b/source/libraryClasses.xml index b6c1cf88de..88514a0f92 100644 --- a/source/libraryClasses.xml +++ b/source/libraryClasses.xml @@ -29,13 +29,8 @@ - - - - - - + + diff --git a/source/mass_distributions.F90 b/source/mass_distributions.F90 index e23715ab28..41abb962d9 100644 --- a/source/mass_distributions.F90 +++ b/source/mass_distributions.F90 @@ -26,18 +26,69 @@ module Mass_Distributions Implements a class that provides mass distributions. !!} use :: Coordinates , only : coordinate - use :: Galactic_Structure_Options, only : enumerationComponentTypeType, enumerationMassTypeType, massTypeAll , massTypeDark , & - & massTypeBaryonic , massTypeGalactic , massTypeGaseous , massTypeStellar, & - & massTypeBlackHole , componentTypeAll , componentTypeUnknown, massTypeUnknown + use :: Galactic_Structure_Options, only : enumerationComponentTypeType , enumerationMassTypeType, massTypeAll , massTypeDark , & + & massTypeBaryonic , massTypeGalactic , massTypeGaseous , massTypeStellar , & + & massTypeBlackHole , componentTypeAll , componentTypeUnknown , massTypeUnknown , & + & componentTypeDisk , componentTypeSpheroid , componentTypeBlackHole, enumerationStructureErrorCodeType use :: Numerical_Random_Numbers , only : randomNumberGeneratorClass use :: Tensors , only : tensorRank2Dimension3Symmetric + use :: Numerical_Interpolation , only : interpolator private - + public :: massDistributionMatches_ + !![ massDistribution Mass Distributions Class providing mass distributions. + + + call kinematicsDistributionDestructor(self) + + + + Set the kinematics distribution for this mass distribution. + void + yes + class(kinematicsDistributionClass), intent(in ) :: kinematicsDistribution_ + + call kinematicsDistributionAcquire(self,kinematicsDistribution_) + + + + Get a pointer to the kinematics distribution for this mass distribution. + class(kinematicsDistributionClass) + yes + + massDistributionKinematicsDistribution => self%kinematicsDistribution_ + call kinematicsDistributionIncrement(self) + + + + Set the component and mass types of the mass distribution. + void + yes + type(enumerationComponentTypeType), intent(in ), optional :: componentType + type(enumerationMassTypeType ), intent(in ), optional :: massType + + if (present(componentType)) self%componentType=componentType + if (present( massType)) self% massType= massType + + + + Return the subset of the mass distribution matching the given {\normalfont componentType} and {\normalfont \ttfamily massType}. + class(massDistributionClass) + yes + type(enumerationComponentTypeType), intent(in ), optional :: componentType + type(enumerationMassTypeType ), intent(in ), optional :: massType + + if (self%matches(componentType,massType)) then + call selfAcquire(self,massDistributionSubset) + else + massDistributionSubset => null() + end if + + Return true if this mass distribution matches the specified component and mass type. logical @@ -45,39 +96,7 @@ module Mass_Distributions type(enumerationComponentTypeType), intent(in ), optional :: componentType type(enumerationMassTypeType ), intent(in ), optional :: massType - type(enumerationComponentTypeType) :: componentType_ - type(enumerationMassTypeType ) :: massType_ - if (present(componentType)) then - componentType_=componentType - else - componentType_=componentTypeAll - end if - if (present(massType )) then - massType_ =massType - else - massType_ =massTypeAll - end if - massDistributionMatches= ( & - & massType_ == massTypeAll & - & .or. & - & massType_ == massTypeDark .and. self%massType == massTypeDark & - & .or. & - & massType_ == massTypeBaryonic .and. (self%massType == massTypeGaseous .or. self%massType == massTypeStellar ) & - & .or. & - & massType_ == massTypeGalactic .and. (self%massType == massTypeGaseous .or. self%massType == massTypeStellar .or. self%massType == massTypeBlackHole) & - & .or. & - & massType_ == massTypeGaseous .and. self%massType == massTypeGaseous & - & .or. & - & massType_ == massTypeStellar .and. self%massType == massTypeStellar & - & .or. & - & massType_ == massTypeBlackHole .and. self%massType == massTypeBlackHole & - & ) & - & .and. & - & ( & - & componentType_ == componentTypeAll & - & .or. & - & componentType_ == self%componentType & - & ) + massDistributionMatches=massDistributionMatches_(self%componentType,self%massType,componentType,massType) @@ -89,6 +108,22 @@ module Mass_Distributions massDistributionSymmetry=massDistributionSymmetryNone + + Return true if the distribution is spherically symmetric. + logical + yes + + massDistributionIsSphericallySymmetric=.false. + + + + Return true if the distribution can be assumed to have a monotonically decreasing surface density. + logical + yes + + massDistributionAssumeMonotonicDecreasingSurfaceDensity=.false. + + Return true if the distribution is dimensionless. logical @@ -101,68 +136,382 @@ module Mass_Distributions Return the total mass of the distribution. double precision yes - type(enumerationComponentTypeType), intent(in ), optional :: componentType - type(enumerationMassTypeType ), intent(in ), optional :: massType Return the gravitational acceleration due to the distribution at the given coordinates. double precision, dimension(3) yes - class(coordinate ), intent(in ) :: coordinates - type (enumerationComponentTypeType), intent(in ), optional :: componentType - type (enumerationMassTypeType ), intent(in ), optional :: massType + class(coordinate), intent(in ) :: coordinates Return the gravitational tidal tensor due to the distribution at the given coordinates. type(tensorRank2Dimension3Symmetric) yes - class(coordinate ), intent(in ) :: coordinates - type (enumerationComponentTypeType), intent(in ), optional :: componentType - type (enumerationMassTypeType ), intent(in ), optional :: massType + class(coordinate), intent(in ) :: coordinates Return the density of the distribution at the given coordinates. double precision yes - class(coordinate ), intent(in ) :: coordinates - type (enumerationComponentTypeType), intent(in ), optional :: componentType - type (enumerationMassTypeType ), intent(in ), optional :: massType + class(coordinate), intent(in ) :: coordinates + + + Return the average density on a spherical shell of the gievn radius. + double precision + yes + double precision, intent(in ) :: radius Return the radial gradient of density of the distribution at the given coordinates. double precision yes - class (coordinate ), intent(in ) :: coordinates - logical , intent(in ), optional :: logarithmic - type (enumerationComponentTypeType), intent(in ), optional :: componentType - type (enumerationMassTypeType ), intent(in ), optional :: massType + yes + class (coordinate), intent(in ) :: coordinates + logical , intent(in ), optional :: logarithmic Return the gravitational potential of the distribution at the given coordinates. double precision yes - class(coordinate ), intent(in ) :: coordinates - type (enumerationComponentTypeType), intent(in ), optional :: componentType - type (enumerationMassTypeType ), intent(in ), optional :: massType + yes + class(coordinate ), intent(in ) :: coordinates + type (enumerationStructureErrorCodeType), intent( out), optional :: status + + + Return true if the gravitational potential for this distribution has an analytic form. + logical + yes + + massDistributionPotentialIsAnalytic=.false. + + + + Return the difference in that gravitational potential of the distribution between the given coordinates. + double precision + yes + yes + class(coordinate ), intent(in ) :: coordinates1, coordinates2 + type (enumerationStructureErrorCodeType), intent( out), optional :: status + Galactic_Structure_Options + + double precision :: potential1, potential2 + + massDistributionPotentialDifference=0.0d0 + if (self%potentialIsAnalytic()) then + potential1=self%potential(coordinates1,status) + if (present(status)) then + if (status /= structureErrorCodeSuccess) return + end if + potential2=self%potential(coordinates2,status) + if (present(status)) then + if (status /= structureErrorCodeSuccess) return + end if + massDistributionPotentialDifference=potential1-potential2 + else + massDistributionPotentialDifference=self%potentialDifferenceNumerical(coordinates1,coordinates2,status) + end if + + + + Return the difference in that gravitational potential of the distribution between the given coordinates using a numerical calculation. + double precision + yes + yes + class(coordinate ), intent(in ) :: coordinates1, coordinates2 + type (enumerationStructureErrorCodeType), intent( out), optional :: status + Galactic_Structure_Options + + massDistributionPotentialDifferenceNumerical=massDistributionPotentialDifferenceNumerical_(self,coordinates1,coordinates2,status) + Return the mass enclosed in the distribution by a sphere of given radius. double precision yes yes - double precision , intent(in ) :: radius - type (enumerationComponentTypeType), intent(in ), optional :: componentType - type (enumerationMassTypeType ), intent(in ), optional :: massType + double precision, intent(in ) :: radius + + + Return the radius enclosing a specified mass. + double precision + yes + yes + double precision, intent(in ), optional :: mass, massFractional + + massDistributionRadiusEnclosingMass=self%radiusEnclosingMassNumerical(mass,massFractional) + + + + Return the radius enclosing a specified mass using a numerical calculation. + double precision + yes + yes + double precision, intent(in ), optional :: mass, massFractional + Root_Finder + + type (rootFinder), save :: finder + logical , save :: finderConstructed=.false. + !$omp threadprivate(finder,finderConstructed) + double precision , parameter :: toleranceAbsolute=0.0d0 , toleranceRelative=1.0d-6 + + if (present(mass )) then + massTarget= mass + else if (present(massFractional)) then + massTarget=self%massTotal()*massFractional + else + call Error_Report('either "mass" or "massFractional" must be provided'//{introspection:location}) + end if + if (massTarget <= 0.0d0) then + massDistributionRadiusEnclosingMassNumerical=0.0d0 + return + end if + if (.not.finderConstructed) then + finder =rootFinder( & + & rootFunction =massEnclosedRoot , & + & toleranceAbsolute =toleranceAbsolute , & + & toleranceRelative =toleranceRelative , & + & solverType =GSL_Root_fSolver_Brent , & + & rangeExpandUpward =2.0d0 , & + & rangeExpandDownward =0.5d0 , & + & rangeExpandType =rangeExpandMultiplicative , & + & rangeExpandDownwardSignExpect=rangeExpandSignExpectNegative, & + & rangeExpandUpwardSignExpect =rangeExpandSignExpectPositive & + & ) + finderConstructed=.true. + end if + self_ => self + massDistributionRadiusEnclosingMassNumerical = finder%find(rootGuess=1.0d0) + + + + Return the radius enclosing a specified density. + double precision + yes + yes + double precision, intent(in ) :: density + double precision, intent(in ), optional :: radiusGuess + + massDistributionRadiusEnclosingDensity=self%radiusEnclosingDensityNumerical(density,radiusGuess) + + + + Return the radius enclosing a specified density using a numerical calculation. + double precision + yes + yes + double precision, intent(in ) :: density + double precision, intent(in ), optional :: radiusGuess + Root_Finder + + type (rootFinder), save :: finder + logical , save :: finderConstructed=.false. + !$omp threadprivate(finder,finderConstructed) + double precision , parameter :: toleranceAbsolute=0.0d0 , toleranceRelative=1.0d-3 + double precision :: radiusGuess_ + + if (.not.finderConstructed) then + finder =rootFinder( & + & rootFunction =densityEnclosedRoot , & + & toleranceAbsolute =toleranceAbsolute , & + & toleranceRelative =toleranceRelative , & + & solverType =GSL_Root_fSolver_Brent , & + & rangeExpandUpward =2.0d0 , & + & rangeExpandDownward =0.5d0 , & + & rangeExpandType =rangeExpandMultiplicative , & + & rangeExpandDownwardSignExpect=rangeExpandSignExpectPositive, & + & rangeExpandUpwardSignExpect =rangeExpandSignExpectNegative & + & ) + finderConstructed=.true. + end if + self_ => self + densityTarget = density + radiusGuess_ = self%radiusEnclosingDensityPrevious__ + if (present(radiusGuess)) radiusGuess_=radiusGuess + massDistributionRadiusEnclosingDensityNumerical = finder%find(rootGuess=radiusGuess_) + self%radiusEnclosingDensityPrevious__ = massDistributionRadiusEnclosingDensityNumerical + + + + Return the radius enclosing a specified surface density. + double precision + yes + yes + double precision, intent(in ) :: densitySurface + double precision, intent(in ), optional :: radiusGuess + + massDistributionRadiusEnclosingSurfaceDensity=self%radiusEnclosingSurfaceDensityNumerical(densitySurface,radiusGuess) + + + + Return the radius enclosing a specified surface density using a numerical calculation. + double precision + yes + yes + double precision, intent(in ) :: densitySurface + double precision, intent(in ), optional :: radiusGuess + Root_Finder + + type (rootFinder), save :: finder + logical , save :: finderConstructed=.false. + !$omp threadprivate(finder,finderConstructed) + double precision , parameter :: toleranceAbsolute=0.0d0 , toleranceRelative=1.0d-3 + double precision :: radiusGuess_ + + if (.not.finderConstructed) then + finder =rootFinder( & + & rootFunction =densitySurfaceEnclosedRoot , & + & toleranceAbsolute =toleranceAbsolute , & + & toleranceRelative =toleranceRelative , & + & solverType =GSL_Root_fSolver_Brent , & + & rangeExpandUpward =2.0d0 , & + & rangeExpandDownward =0.5d0 , & + & rangeExpandType =rangeExpandMultiplicative , & + & rangeExpandDownwardSignExpect=rangeExpandSignExpectPositive, & + & rangeExpandUpwardSignExpect =rangeExpandSignExpectNegative & + & ) + finderConstructed=.true. + end if + self_ => self + densitySurfaceTarget = densitySurface + radiusGuess_ = self%radiusEnclosingDensitySurfacePrevious__ + if (present(radiusGuess)) radiusGuess_=radiusGuess + massDistributionRadiusEnclosingSurfaceDensityNumerical = finder%find(rootGuess=radiusGuess_) + self%radiusEnclosingDensitySurfacePrevious__ = massDistributionRadiusEnclosingSurfaceDensityNumerical + + + + Return the radius corresponding to a given specific angular momentum. + double precision + yes + yes + double precision, intent(in ) :: angularMomentumSpecific + + massDistributionRadiusFromSpecificAngularMomentum=self%radiusFromSpecificAngularMomentumNumerical(angularMomentumSpecific) + + + + Return the radius corresponding to a given specific angular momentum using a numerical calculation. + double precision + yes + yes + double precision, intent(in ) :: angularMomentumSpecific + Root_Finder + + type (rootFinder), save :: finder + logical , save :: finderConstructed=.false. + !$omp threadprivate(finder,finderConstructed) + double precision , parameter :: toleranceAbsolute=0.0d0 , toleranceRelative=1.0d-6 + + if (angularMomentumSpecific <= 0.0d0) then + massDistributionRadiusFromSpecificAngularMomentumNumerical=+0.0d0 + else + if (.not.finderConstructed) then + finder =rootFinder( & + & rootFunction =specificAngularMomentumRoot , & + & toleranceAbsolute =toleranceAbsolute , & + & toleranceRelative =toleranceRelative , & + & solverType =GSL_Root_fSolver_Brent , & + & rangeExpandUpward =2.0d0 , & + & rangeExpandDownward =0.5d0 , & + & rangeExpandType =rangeExpandMultiplicative , & + & rangeExpandDownwardSignExpect=rangeExpandSignExpectNegative, & + & rangeExpandUpwardSignExpect =rangeExpandSignExpectPositive & + & ) + finderConstructed=.true. + end if + self_ => self + angularMomentumSpecificTarget = angularMomentumSpecific + massDistributionRadiusFromSpecificAngularMomentumNumerical = finder%find(rootGuess=1.0d0) + end if + + + + Return the rotation curve at the given radius. + double precision + yes + double precision, intent(in ) :: radius + + + Return the rotation curve gradient, $\mathrm{d}V^2/\mathrm{d}r$, at the given radius. + double precision + yes + double precision, intent(in ) :: radius + + + Return the maximum velocity in the rotation curve. + double precision + yes + + massDistributionVelocityRotationCurveMaximum=self%rotationCurve(self%radiusRotationCurveMaximum()) + + + + Return the radius of the maximum velocity in the rotation curve. + double precision + yes + yes + Root_Finder + + massDistributionRadiusRotationCurveMaximum=self%radiusRotationCurveMaximumNumerical() + + + + Return the radius of the maximum velocity in the rotation curve. + double precision + yes + yes + Root_Finder Error + + type (rootFinder), save :: finder + logical , save :: finderConstructed=.false. + !$omp threadprivate(finder,finderConstructed) + double precision , parameter :: toleranceAbsolute=0.0d0 , toleranceRelative=1.0d-06, & + & radiusTiny =1.0d-9 , radiusHuge =1.0d+30 + integer :: status + + if (.not.finderConstructed) then + finder =rootFinder( & + & rootFunction =rotationCurveMaximumRoot , & + & toleranceAbsolute =toleranceAbsolute , & + & toleranceRelative =toleranceRelative , & + & solverType =GSL_Root_fSolver_Brent , & + & rangeExpandUpward =2.0d0 , & + & rangeExpandDownward =0.5d0 , & + & rangeExpandType =rangeExpandMultiplicative , & + & rangeExpandDownwardSignExpect=rangeExpandSignExpectPositive , & + & rangeExpandUpwardSignExpect =rangeExpandSignExpectNegative , & + & rangeDownwardLimit =radiusTiny , & + & rangeUpwardLimit =radiusHuge & + & ) + finderConstructed=.true. + end if + self_ => self + massDistributionRadiusRotationCurveMaximumNumerical = finder%find(rootGuess=1.0d0,status=status) + if (status /= errorStatusSuccess .and. .not.self%tolerateVelocityMaximumFailure) & + & call Error_Report('failed to find radius of maximum circular velocity'//{introspection:location}) + + + + Return the surface density at the given coordinates. + double precision + yes + class(coordinate), intent(in ) :: coordinates + + + Return the surface density at the given coordinates. + double precision + yes + double precision, intent(in ) :: moment + double precision, intent(in ), optional :: radiusMinimum, radiusMaximum + logical , intent( out), optional :: isInfinite Return the radial moment of the distribution. double precision yes - double precision , intent(in ) :: moment - double precision , intent(in ), optional :: radiusMinimum, radiusMaximum - logical , intent( out), optional :: isInfinite - type (enumerationComponentTypeType), intent(in ), optional :: componentType - type (enumerationMassTypeType ), intent(in ), optional :: massType + double precision, intent(in ) :: moment + double precision, intent(in ), optional :: radiusMinimum, radiusMaximum + logical , intent( out), optional :: isInfinite Return the integral over the square of the density of the distribution. @@ -171,17 +520,264 @@ module Mass_Distributions double precision, intent(in ), optional :: radiusMinimum, radiusMaximum logical , intent( out), optional :: isInfinite + + Return the Chandresekhar integral of the distribution. + double precision, dimension(3) + yes + class (massDistributionClass), intent(inout) :: massDistributionEmbedding, massDistributionPerturber + double precision , intent(in ) :: massPerturber + class (coordinate ), intent(in ) :: coordinates , velocity + + + Return the radius at which the freefall time to the center equals the given {\normalfont \ttfamily time}. + double precision + yes + double precision, intent(in ) :: time + + + Return the rate of increase of the freefall radius corresponding to the given {\normalfont \ttfamily time}. + double precision + yes + double precision, intent(in ) :: time + + + Return the spherically-symmetrized Fourier transform of the density profile at the given wavenumber. + double precision + yes + double precision, intent(in ) :: radiusOuter , wavenumber + + + Return the total energy of the distribution within the given radius. + double precision + yes + yes + double precision , intent(in ) :: radiusOuter + class (massDistributionClass), intent(inout), target :: massDistributionEmbedding + Return a position sampled from the distribution. double precision, dimension(3) yes - class(randomNumberGeneratorClass ), intent(inout) :: randomNumberGenerator_ - type (enumerationComponentTypeType), intent(in ), optional :: componentType - type (enumerationMassTypeType ), intent(in ), optional :: massType + class(randomNumberGeneratorClass ), intent(inout) :: randomNumberGenerator_ + + + Set a sub-module scope pointers on a stack to allow recursive calls to functions. + void + yes + yes + double precision, intent(in ), dimension(3) :: position1, position2, vectorUnit + double precision, intent(in ) :: separation + + integer :: i + type (massSolver), allocatable, dimension(:) :: solvers_ + if (allocated(massSolvers)) then + if (massSolversCount == size(massSolvers)) then + call move_alloc(massSolvers,solvers_) + allocate(massSolvers(size(solvers_)+massSolversIncrement)) + massSolvers(1:size(solvers_))=solvers_ + do i=1,size(solvers_) + nullify(solvers_(i)%self) + end do + deallocate(solvers_) + end if + else + allocate(massSolvers(massSolversIncrement)) + end if + massSolversCount=massSolversCount+1 + massSolvers(massSolversCount)%self => self + massSolvers(massSolversCount)%separation = separation + massSolvers(massSolversCount)%position1 = position1 + massSolvers(massSolversCount)%position2 = position2 + massSolvers(massSolversCount)%vectorUnit = vectorUnit + + + + Unset a sub-module scope pointers on the stack. + void + yes + + !$GLC attributes unused :: self + massSolvers(massSolversCount)%self => null() + massSolversCount=massSolversCount-1 + + + class (kinematicsDistributionClass ), pointer :: kinematicsDistribution_ => null() + logical :: dimensionless + logical :: tolerateVelocityMaximumFailure = .false. + type (enumerationComponentTypeType) :: componentType = componentTypeUnknown + type (enumerationMassTypeType ) :: massType = massTypeUnknown + double precision :: radiusEnclosingDensityPrevious__ = 1.0d0 + double precision :: radiusEnclosingDensitySurfacePrevious__ = 1.0d0 + + !!] + + !![ + + kinematicsDistribution + Kinematics Distributions + Class providing kinematics distributions. + + Return true if the kinematics is collisional. + logical + yes + + + Return the temperature of the distribution at the given coordinates. + double precision + yes + class(coordinate), intent(in ) :: coordinates + + !$GLC attributes unused :: self, coordinates + kinematicsDistributionTemperature=0.0d0 + + + + Return the logarithmic gradient of the temperature of the distribution at the given coordinates. + double precision + yes + class(coordinate), intent(in ) :: coordinates + + !$GLC attributes unused :: self, coordinates + kinematicsDistributionTemperatureGradientLogarithmic=0.0d0 + + + + Return the mean radial velocity at the given coordinate. + double precision + yes + class(coordinate ), intent(in ) :: coordinates + class(massDistributionClass), intent(inout) :: massDistributionEmbedding + + !$GLC attributes unused :: self, coordinates, massDistributionEmbedding + kinematicsDistributionVelocityRadial=0.0d0 + + + + Return the 1D velocity dispersion at the given coordinate. + double precision + yes + yes + class(coordinate ), intent(in ) :: coordinates + class(massDistributionClass), intent(inout) :: massDistributionEmbedding + + !$GLC attributes unused :: self, coordinates, massDistributionEmbedding + kinematicsDistributionVelocityDispersion1D=0.0d0 + + + + Return the 1D velocity dispersion at the given coordinate by numerically solving the Jeans equation. + double precision + yes + class(coordinate ), intent(in ) :: coordinates + class(massDistributionClass), intent(inout) :: massDistributionEmbedding + + call jeansEquationSolver(self,coordinates%rSpherical(),massDistributionEmbedding) + kinematicsDistributionVelocityDispersion1DNumerical=self%velocityDispersion1D__%interpolate(log(coordinates%rSpherical())) + + + + Return the radius variable used in solving the Jeans equation that corresponds to a given physical radius. + double precision + yes + double precision , intent(in ) :: radius + class (massDistributionClass), intent(inout) :: massDistributionEmbedding + + !$GLC attributes unused :: massDistributionEmbedding + kinematicsDistributionJeansEquationRadius=radius + + + + Integrand for Jeans equation. + double precision + yes + double precision , intent(in ) :: radius + class (massDistributionClass), intent(inout) :: massDistributionEmbedding + Numerical_Constants_Astronomical Coordinates + + type(coordinateSpherical) :: coordinates + if (radius > 0.0d0) then + coordinates = [radius,0.0d0,0.0d0] + kinematicsDistributionJeansEquationIntegrand=+gravitationalConstantGalacticus & + & *massDistributionEmbedding%massEnclosedBySphere(radius ) & + & *massDistributionEmbedding%density (coordinates) & + & / radius **2 + else + kinematicsDistributionJeansEquationIntegrand=+0.0d0 + end if + + + + Set a sub-module scope pointers on a stack to allow recursive calls to functions. + void + yes + yes + class (massDistributionClass), intent(in ), target :: massDistributionEmbedding + + integer :: i + type (kinematicsSolver), allocatable, dimension(:) :: solvers_ + if (allocated(solvers)) then + if (solversCount == size(solvers)) then + call move_alloc(solvers,solvers_) + allocate(solvers(size(solvers_)+solversIncrement)) + solvers(1:size(solvers_))=solvers_ + do i=1,size(solvers_) + nullify(solvers_(i)%self ) + nullify(solvers_(i)%massDistributionEmbedding) + end do + deallocate(solvers_) + end if + else + allocate(solvers(solversIncrement)) + end if + solversCount=solversCount+1 + solvers(solversCount)%self => self + solvers(solversCount)%massDistributionEmbedding => massDistributionEmbedding + + + + Unset a sub-module scope pointers on the stack. + void + yes + + !$GLC attributes unused :: self + solvers(solversCount)%self => null() + solvers(solversCount)%massDistributionEmbedding => null() + solversCount=solversCount-1 + + + type (interpolator), allocatable :: velocityDispersion1D__ + double precision , allocatable, dimension(:) :: velocityDispersionRadialVelocity__ , velocityDispersionRadialRadius__ + double precision :: velocityDispersionRadialRadiusMinimum__ =+huge(0.0d0), velocityDispersionRadialRadiusMaximum__=-huge(0.0d0) + double precision :: velocityDispersionRadialRadiusOuter__ + double precision :: toleranceRelativeVelocityDispersion =1.0d-6 + double precision :: toleranceRelativeVelocityDispersionMaximum=1.0d-3 + + !!] + + !![ + + massDistributionHeating + Heating of Mass Distributions + Class providing heating models for mass distributions. + + Return the specific energy at the given radius. + double precision + yes + double precision , intent(in ) :: radius + class (massDistributionClass), intent(inout) :: massDistribution_ + + + Return the radial gradient of the specific energy at the given radius. + double precision + yes + double precision , intent(in ) :: radius + class (massDistributionClass), intent(inout) :: massDistribution_ + + + Return true if the specific energy is zero everywhere (i.e. no heating). + logical + yes - logical :: dimensionless - type (enumerationComponentTypeType) :: componentType=componentTypeUnknown - type (enumerationMassTypeType ) :: massType =massTypeUnknown !!] @@ -197,4 +793,490 @@ module Mass_Distributions !!] + ! Enumeration of non-analytic solver options. + !![ + + nonAnalyticSolvers + Used to specify the type of solution to use when no analytic solution is available. + yes + public + yes + + + + !!] + + ! Module-scope variables used in root finding. + class (massDistributionClass), pointer :: self_ + double precision :: massTarget , densityTarget , & + & angularMomentumSpecificTarget, densitySurfaceTarget + !$omp threadprivate(self_,massTarget,densityTarget,angularMomentumSpecificTarget,densitySurfaceTarget) + + ! Module-scope pointers used in integrand functions and root finding. + type :: kinematicsSolver + class(kinematicsDistributionClass), pointer :: self => null() + class(massDistributionClass ), pointer :: massDistributionEmbedding => null() + end type kinematicsSolver + type (kinematicsSolver), allocatable, dimension(:) :: solvers + integer , parameter :: solversIncrement=10 + integer :: solversCount = 0 + !$omp threadprivate(solvers,solversCount) + + ! Module-scope pointers used in integrand functions. + type :: massSolver + class (massDistributionClass), pointer :: self => null() + double precision , dimension(3) :: position1 , position2, & + & vectorUnit + double precision :: separation + end type massSolver + type (massSolver), allocatable, dimension(:) :: massSolvers + integer , parameter :: massSolversIncrement=10 + integer :: massSolversCount = 0 + !$omp threadprivate(massSolvers,massSolversCount) + +contains + + subroutine kinematicsDistributionDestructor(self) + !!{ + Destroy a kinematics distribution. + !!} + implicit none + type(massDistributionClass ), intent(inout) :: self + + !![ + + !!] + return + end subroutine kinematicsDistributionDestructor + + subroutine selfAcquire(self,self_) + !!{ + Acquire a reference to a mass distribution. + !!} + implicit none + class(massDistributionClass), intent(inout), target :: self + class(massDistributionClass), intent( out), pointer :: self_ + + !![ + + !!] + return + end subroutine selfAcquire + + subroutine kinematicsDistributionAcquire(self,kinematicsDistribution_) + !!{ + Acquire a reference to a kinematics distribution. + !!} + implicit none + class(massDistributionClass ), intent(inout) :: self + class(kinematicsDistributionClass), intent(in ), target :: kinematicsDistribution_ + + !![ + + + !!] + return + end subroutine kinematicsDistributionAcquire + + subroutine kinematicsDistributionIncrement(self) + !!{ + Increment the reference count to a kinematics distribution. + !!} + implicit none + class(massDistributionClass), intent(inout) :: self + + !![ + + !!] + return + end subroutine kinematicsDistributionIncrement + + logical function massDistributionMatches_(componentTypeTarget,massTypeTarget,componentType,massType) + !!{ + Determine if the requested mass and component types match that of the target mass distribution. + !!} + implicit none + type(enumerationComponentTypeType), intent(in ) :: componentTypeTarget + type(enumerationMassTypeType ), intent(in ) :: massTypeTarget + type(enumerationComponentTypeType), intent(in ), optional :: componentType + type(enumerationMassTypeType ), intent(in ), optional :: massType + + if (present(componentType)) then + massDistributionMatches_ = componentType == componentTypeAll & + & .or. & + & componentType == componentTypeTarget + if (massDistributionMatches_.and.present(massType)) then + massDistributionMatches_= (massType == massTypeAll ) & + & .or. & + & (massType == massTypeDark .and. massTypeTarget == massTypeDark ) & + & .or. & + & (massType == massTypeGaseous .and. massTypeTarget == massTypeGaseous ) & + & .or. & + & (massType == massTypeStellar .and. massTypeTarget == massTypeStellar ) & + & .or. & + & (massType == massTypeBlackHole .and. massTypeTarget == massTypeBlackHole ) & + & .or. & + & (massType == massTypeBaryonic .and. (massTypeTarget == massTypeGaseous .or. massTypeTarget == massTypeStellar )) & + & .or. & + & (massType == massTypeGalactic .and. (massTypeTarget == massTypeGaseous .or. massTypeTarget == massTypeStellar .or. massTypeTarget == massTypeBlackHole ) & + & .and. (componentTypeTarget == componentTypeDisk .or. componentTypeTarget == componentTypeSpheroid .or. componentTypeTarget == componentTypeBlackHole)) + end if + else + if (present(massType)) then + ! Match on mass distribution only. + massDistributionMatches_= (massType == massTypeAll ) & + & .or. & + & (massType == massTypeDark .and. massTypeTarget == massTypeDark ) & + & .or. & + & (massType == massTypeGaseous .and. massTypeTarget == massTypeGaseous ) & + & .or. & + & (massType == massTypeStellar .and. massTypeTarget == massTypeStellar ) & + & .or. & + & (massType == massTypeBlackHole .and. massTypeTarget == massTypeBlackHole ) & + & .or. & + & (massType == massTypeBaryonic .and. (massTypeTarget == massTypeGaseous .or. massTypeTarget == massTypeStellar )) & + & .or. & + & (massType == massTypeGalactic .and. (massTypeTarget == massTypeGaseous .or. massTypeTarget == massTypeStellar .or. massTypeTarget == massTypeBlackHole ) & + & .and. (componentTypeTarget == componentTypeDisk .or. componentTypeTarget == componentTypeSpheroid .or. componentTypeTarget == componentTypeBlackHole)) + else + ! No selection was requested, so we always match. + massDistributionMatches_=.true. + end if + end if + return + end function massDistributionMatches_ + + double precision function massEnclosedRoot(radius) + !!{ + Root function used in finding radii enclosing a target mass. + !!} + implicit none + double precision, intent(in ) :: radius + + massEnclosedRoot=+self_%massEnclosedBySphere(radius) & + & - massTarget + return + end function massEnclosedRoot + + double precision function densityEnclosedRoot(radius) + !!{ + Root function used in finding radii enclosing a target density. + !!} + use :: Numerical_Constants_Math, only : Pi + implicit none + double precision, intent(in ) :: radius + + densityEnclosedRoot=+3.0d0 & + & /4.0d0 & + & /Pi & + & *self_%massEnclosedBySphere(radius) & + & / radius **3 & + & - densityTarget + return + end function densityEnclosedRoot + + double precision function densitySurfaceEnclosedRoot(radius) + !!{ + Root function used in finding radii enclosing a target surface density. + !!} + use :: Coordinates, only : coordinateCylindrical, assignment(=) + implicit none + double precision , intent(in ) :: radius + type (coordinateCylindrical) :: coordinates + + coordinates =[radius,0.0d0,0.0d0] + densitySurfaceEnclosedRoot=+self_%surfaceDensity (coordinates) & + & - densitySurfaceTarget + return + end function densitySurfaceEnclosedRoot + + double precision function specificAngularMomentumRoot(radius) + !!{ + Root function used in finding radii corresponding to a target specific angular momentum. + !!} + implicit none + double precision, intent(in ) :: radius + + specificAngularMomentumRoot=+self_%rotationCurve (radius) & + & * radius & + & - angularMomentumSpecificTarget + return + end function specificAngularMomentumRoot + + double precision function rotationCurveMaximumRoot(radius) + !!{ + Root function used in finding the radius corresponding to the peak of the rotation curve. + !!} + implicit none + double precision, intent(in ) :: radius + + rotationCurveMaximumRoot=self_%rotationCurveGradient(radius) + return + end function rotationCurveMaximumRoot + + double precision function massDistributionPotentialDifferenceNumerical_(self,coordinates1,coordinates2,status) result(potentialDifference) + !!{ + Numerically calculate the potential difference between the provided coordinates. + !!} + use :: Coordinates , only : coordinateCartesian , assignment(=) + use :: Vectors , only : Vector_Magnitude + use :: Numerical_Integration , only : integrator + use :: Galactic_Structure_Options, only : structureErrorCodeSuccess, structureErrorCodeIntegration + use :: Error , only : Error_Report , errorStatusSuccess + implicit none + class (massDistributionClass ), intent(inout), target :: self + class (coordinate ), intent(in ) :: coordinates1 , coordinates2 + type (enumerationStructureErrorCodeType), intent( out), optional :: status + type (coordinateCartesian ) :: coordinates1_ , coordinates2_ + double precision , dimension(3) :: position1 , position2 , & + & vectorUnit + double precision :: separation + type (integrator ), save :: integrator_ + logical , save :: initialized =.false. + integer :: status_ + + if (present(status)) status=structureErrorCodeSuccess + coordinates1_=coordinates1 + coordinates2_=coordinates2 + position1 =coordinates1_ + position2 =coordinates2_ + separation =Vector_Magnitude(position1-position2) + if (separation == 0.0d0) then + potentialDifference=0.0d0 + else + vectorUnit =+( & + & +position1 & + & -position2 & + & ) & + & / separation + ! Initialize integrator if necessary. + if (.not.initialized) then + integrator_=integrator(potentialDifferenceIntegrand,toleranceRelative=1.0d-3) + initialized=.true. + end if + call self%solverSet (position1,position2,vectorUnit,separation) + potentialDifference=integrator_%integrate(0.0d0,separation,status=status_) + call self%solverUnset( ) + if (status_ /= errorStatusSuccess) then + if (present(status)) then + status=structureErrorCodeIntegration + else + call Error_Report("integration of potential difference failed"//{introspection:location}) + end if + end if + end if + return + end function massDistributionPotentialDifferenceNumerical_ + + double precision function potentialDifferenceIntegrand(distance) + !!{ + Integrand used in computing potential differences. + !!} + use :: Coordinates , only : coordinateCartesian , assignment(=) + use :: Numerical_Constants_Astronomical, only : Mpc_per_km_per_s_To_Gyr + implicit none + double precision , intent(in ) :: distance + double precision , dimension(3) :: position , acceleration + type (coordinateCartesian) :: coordinates + + position =+massSolvers(massSolversCount) %position2 & + & +massSolvers(massSolversCount) %vectorUnit & + & * distance + coordinates = position + acceleration =massSolvers(massSolversCount)%self%acceleration(coordinates) + potentialDifferenceIntegrand=-Dot_Product(acceleration,massSolvers(massSolversCount)%vectorUnit) & + & *Mpc_per_km_per_s_To_Gyr + return + end function potentialDifferenceIntegrand + + subroutine jeansEquationSolver(self,radius,massDistributionEmbedding) + !!{ + Solve the Jeans equation numerically to find the 1D velocity dispersion. + !!} + use, intrinsic :: ISO_C_Binding , only : c_size_t + use :: Error , only : Error_Report , errorStatusSuccess + use :: Numerical_Integration , only : integrator + use :: Numerical_Ranges , only : Make_Range , rangeTypeLogarithmic + use :: Table_Labels , only : extrapolationTypeFix + use :: Numerical_Interpolation, only : gsl_interp_linear + use :: Coordinates , only : coordinateSpherical , assignment(=) + implicit none + class (kinematicsDistributionClass), intent(inout) :: self + double precision , intent(in ) :: radius + class (massDistributionClass ), intent(inout) :: massDistributionEmbedding + double precision , parameter :: radiusTinyFactor =1.0d-9 , factorDensityLarge =1.0d+5 + double precision , parameter :: countPointsPerOctave =2.0d0 + double precision , parameter :: toleranceFactor =2.0d0 + double precision , dimension(:) , allocatable :: velocityDispersions , radii + double precision :: radiusMinimum , radiusMaximum , & + & toleranceRelative , density , & + & jeansIntegral , radiusOuter_ , & + & radiusLower , radiusUpper , & + & radiusLowerJeansEquation , radiusUpperJeansEquation , & + & densityMaximum , densityOuter_ , & + & jeansIntegralPrevious + integer (c_size_t ) :: countRadii , iMinimum , & + & iMaximum , i + integer :: status + type (coordinateSpherical ) :: coordinates + type (integrator ), save :: integrator_ + logical , save :: initialized =.false. + logical :: remakeTable + !$omp threadprivate(integrator_,initialized) + + ! Determine if the table must be rebuilt. + remakeTable=.false. + if (.not.allocated(self%velocityDispersionRadialVelocity__)) then + remakeTable=.true. + else + remakeTable= radius < self%velocityDispersionRadialRadiusMinimum__ & + & .or. & + & radius > self%velocityDispersionRadialRadiusMaximum__ + end if + if (remakeTable) then + ! Initialize integrator if necessary. + if (.not.initialized) then + integrator_=integrator(jeansEquationIntegrand_,toleranceRelative=self%toleranceRelativeVelocityDispersion) + initialized=.true. + end if + ! Find the range of radii at which to compute the velocity dispersion, and construct the arrays. + call self%solverSet(massDistributionEmbedding) + !! Set an initial range of radii that brackets the requested radius. + radiusMinimum=0.5d0*radius + radiusMaximum=2.0d0*radius + !! Round to the nearest factor of 2. + radiusMinimum=2.0d0**floor (log(radiusMinimum)/log(2.0d0)) + radiusMaximum=2.0d0**ceiling(log(radiusMaximum)/log(2.0d0)) + !! Expand to encompass any pre-existing range. + if (allocated(self%velocityDispersionRadialRadius__)) then + radiusMinimum=min(radiusMinimum,self%velocityDispersionRadialRadiusMinimum__) + radiusMaximum=max(radiusMaximum,self%velocityDispersionRadialRadiusMaximum__) + end if + !! Set a suitable outer radius for integration. We require that the density at the outer radius be much smaller than that + !! at the maximum radius. This should ensure that any constribution to the Jeans integral from beyond this outer radius is + !! negligible. + !!! Start at the maximum radius and gradually increase the outer radius until the density is sufficiently small. + coordinates =[radiusMaximum,0.0d0,0.0d0] + densityMaximum=massDistributionEmbedding%density(coordinates) + radiusOuter_ =radiusMaximum + densityOuter_ =densityMaximum + do while (densityOuter_ > densityMaximum/factorDensityLarge) + radiusOuter_ =radiusOuter_*2.0d0 + coordinates =[radiusOuter_,0.0d0,0.0d0] + densityOuter_=massDistributionEmbedding%density(coordinates) + end do + !! Construct arrays. + countRadii=nint(log(radiusMaximum/radiusMinimum)/log(2.0d0)*countPointsPerOctave+1.0d0) + allocate(radii (countRadii)) + allocate(velocityDispersions(countRadii)) + radii=Make_Range(radiusMinimum,radiusMaximum,int(countRadii),rangeTypeLogarithmic) + ! Copy in any usable results from any previous solution. + !! Assume by default that no previous solutions are usable. + iMinimum=+huge(0_c_size_t) + iMaximum=-huge(0_c_size_t) + !! Check that a pre-existing solution exists. + if (allocated(self%velocityDispersionRadialRadius__)) then + !! Check that the outer radius for integration has not changed - if it has we need to recompute the full solution for + !! consistency. + if (radiusOuter_ == self%velocityDispersionRadialRadiusOuter__) then + iMinimum=nint(log(self%velocityDispersionRadialRadiusMinimum__/radiusMinimum)/log(2.0d0)*countPointsPerOctave)+1_c_size_t + iMaximum=nint(log(self%velocityDispersionRadialRadiusMaximum__/radiusMinimum)/log(2.0d0)*countPointsPerOctave)+1_c_size_t + velocityDispersions(iMinimum:iMaximum)=self%velocityDispersionRadialVelocity__ + end if + end if + ! Solve for the velocity dispersion where old results were unavailable. + jeansIntegralPrevious=0.0d0 + do i=countRadii,1,-1 + ! Skip cases for which we have a pre-existing solution. + if (i >= iMinimum .and. i <= iMaximum) cycle + ! Find the limits for the integral. + if (i == countRadii) then + radiusUpper=radiusOuter_ + else + radiusUpper=radii(i+1) + end if + radiusLower =radii(i ) + ! Reset the accumulated Jeans integral if necessary. + if (i == iMinimum-1) then + coordinates = [radii(iMinimum),0.0d0,0.0d0] + jeansIntegralPrevious=+ velocityDispersions(iMinimum )**2 & + & *massDistributionEmbedding%density (coordinates) + end if + ! If the interval is wholly outside of the outer radius, the integral is zero. + if (radiusLower > radiusOuter_) then + jeansIntegral =0.0d0 + velocityDispersions(i)=0.0d0 + else + ! Evaluate the integral. + coordinates =[radiusLower,0.0d0,0.0d0] + density =massDistributionEmbedding%density (coordinates ) + radiusLowerJeansEquation=self %jeansEquationRadius(radiusLower ,massDistributionEmbedding) + radiusUpperJeansEquation=self %jeansEquationRadius(radiusUpper ,massDistributionEmbedding) + jeansIntegral =integrator_ %integrate (radiusLowerJeansEquation,radiusUpperJeansEquation,status ) + if (status /= errorStatusSuccess) then + ! Integration failed. + toleranceRelative=+ toleranceFactor & + & *self%toleranceRelativeVelocityDispersion + do while (toleranceRelative < self%toleranceRelativeVelocityDispersionMaximum) + call integrator_%toleranceSet(toleranceRelative=toleranceRelative) + jeansIntegral=integrator_%integrate(radiusLowerJeansEquation,radiusUpperJeansEquation,status) + if (status == errorStatusSuccess) then + exit + else + toleranceRelative=+toleranceFactor & + & *toleranceRelative + end if + end do + if (status /= errorStatusSuccess) call Error_Report('integration of Jeans equation failed'//{introspection:location}) + call integrator_%toleranceSet(toleranceRelative=self%toleranceRelativeVelocityDispersion) + end if + if (density <= 0.0d0) then + ! Density is zero - the velocity dispersion is undefined. If the Jeans integral is also zero this is acceptable - we've + ! been asked for the velocity dispersion in a region of zero density, so we simply return zero dispersion as it should have + ! no consequence. If the Jeans integral is non-zero however, then something has gone wrong. + velocityDispersions(i)=0.0d0 + if (jeansIntegral+jeansIntegralPrevious > 0.0d0) call Error_Report('undefined velocity dispersion'//{introspection:location}) + else + velocityDispersions(i)=sqrt( & + & +( & + & +jeansIntegral & + & +jeansIntegralPrevious & + & ) & + & /density & + & ) + end if + end if + jeansIntegralPrevious=+jeansIntegralPrevious & + & +jeansIntegral + end do + call self%solverUnset() + ! Build the interpolator. + if (allocated(self%velocityDispersion1D__)) deallocate(self%velocityDispersion1D__) + allocate(self%velocityDispersion1D__) + self%velocityDispersion1D__=interpolator(log(radii),velocityDispersions,interpolationType=gsl_interp_linear,extrapolationType=extrapolationTypeFix) + ! Store the current results for future re-use. + if (allocated(self%velocityDispersionRadialRadius__ )) deallocate(self%velocityDispersionRadialRadius__ ) + if (allocated(self%velocityDispersionRadialVelocity__)) deallocate(self%velocityDispersionRadialVelocity__) + allocate(self%velocityDispersionRadialRadius__ (countRadii)) + allocate(self%velocityDispersionRadialVelocity__(countRadii)) + self%velocityDispersionRadialRadius__ =radii + self%velocityDispersionRadialVelocity__ =velocityDispersions + self%velocityDispersionRadialRadiusMinimum__=radiusMinimum + self%velocityDispersionRadialRadiusMaximum__=radiusMaximum + self%velocityDispersionRadialRadiusOuter__ =radiusOuter_ + end if + return + end subroutine jeansEquationSolver + + double precision function jeansEquationIntegrand_(radius) + !!{ + Integrand for the Jeans equation. + !!} + implicit none + double precision, intent(in ) :: radius + + jeansEquationIntegrand_=solvers(solversCount)%self%jeansEquationIntegrand(radius,solvers(solversCount)%massDistributionEmbedding) + return + end function jeansEquationIntegrand_ + end module Mass_Distributions diff --git a/source/mass_distributions.Gaussian_ellipsoid.F90 b/source/mass_distributions.Gaussian_ellipsoid.F90 index 8e4955cad1..876104215f 100644 --- a/source/mass_distributions.Gaussian_ellipsoid.F90 +++ b/source/mass_distributions.Gaussian_ellipsoid.F90 @@ -266,26 +266,20 @@ subroutine gaussianEllipsoidInitialize(self,scaleLength,axes,rotation) return end subroutine gaussianEllipsoidInitialize - double precision function gaussianEllipsoidDensity(self,coordinates,componentType,massType) + double precision function gaussianEllipsoidDensity(self,coordinates) !!{ Return the density at the specified {\normalfont \ttfamily coordinates} in a Gaussian ellipsoid mass distribution. !!} use :: Coordinates , only : assignment(=), coordinateCartesian use :: Linear_Algebra, only : assignment(=), operator(*) , vector implicit none - class (massDistributionGaussianEllipsoid), intent(inout) :: self - class (coordinate ), intent(in ) :: coordinates - type (enumerationComponentTypeType ), intent(in ), optional :: componentType - type (enumerationMassTypeType ), intent(in ), optional :: massType - type (coordinateCartesian ) :: position - double precision , dimension(3) :: positionComponents - double precision :: mSquared - type (vector ) :: positionVectorUnrotated, positionVector + class (massDistributionGaussianEllipsoid), intent(inout) :: self + class (coordinate ), intent(in ) :: coordinates + type (coordinateCartesian ) :: position + double precision , dimension(3) :: positionComponents + double precision :: mSquared + type (vector ) :: positionVectorUnrotated, positionVector - if (.not.self%matches(componentType,massType)) then - gaussianEllipsoidDensity=0.0d0 - return - end if ! Rotate the position to the frame where the ellipsoid is aligned with the principle Cartesian axes. position = coordinates positionComponents = position @@ -319,7 +313,7 @@ double precision function gaussianEllipsoidDensityEllipsoidal(self,mSquared) return end function gaussianEllipsoidDensityEllipsoidal - function gaussianEllipsoidAcceleration(self,coordinates,componentType,massType) + function gaussianEllipsoidAcceleration(self,coordinates) !!{ Computes the gravitational acceleration at {\normalfont \ttfamily coordinates} for Gaussian ellipsoid mass distributions. !!} @@ -327,22 +321,16 @@ function gaussianEllipsoidAcceleration(self,coordinates,componentType,massType) use :: Linear_Algebra , only : assignment(=) , operator(*) , vector use :: Numerical_Constants_Astronomical, only : gravitationalConstantGalacticus implicit none - double precision , dimension(3 ) :: gaussianEllipsoidAcceleration - class (massDistributionGaussianEllipsoid), intent(inout) :: self - class (coordinate ), intent(in ) :: coordinates - type (enumerationComponentTypeType ), intent(in ), optional :: componentType - type (enumerationMassTypeType ), intent(in ), optional :: massType - type (coordinateCartesian ) :: coordinatesCartesian - double precision , dimension(3) :: positionCartesian , positionCartesianScaleFree , & - & accelerationScaleFree - integer :: i - type (vector ) :: positionVector , positionVectorUnrotated , & - & accelerationVector , accelerationVectorUnrotated + double precision , dimension(3) :: gaussianEllipsoidAcceleration + class (massDistributionGaussianEllipsoid), intent(inout) :: self + class (coordinate ), intent(in ) :: coordinates + type (coordinateCartesian ) :: coordinatesCartesian + double precision , dimension(3) :: positionCartesian , positionCartesianScaleFree , & + & accelerationScaleFree + integer :: i + type (vector ) :: positionVector , positionVectorUnrotated , & + & accelerationVector , accelerationVectorUnrotated - if (.not.self%matches(componentType,massType)) then - gaussianEllipsoidAcceleration=0.0d0 - return - end if ! Ensure that acceleration is tabulated. call self%accelerationTabulate() ! Construct the scale-free (and rotated) position. diff --git a/source/mass_distributions.cloud_overdensities.F90 b/source/mass_distributions.cloud_overdensities.F90 index a180179099..688838c22b 100644 --- a/source/mass_distributions.cloud_overdensities.F90 +++ b/source/mass_distributions.cloud_overdensities.F90 @@ -210,25 +210,19 @@ subroutine cloudOverdensitiesDestructor(self) return end subroutine cloudOverdensitiesDestructor - double precision function cloudOverdensitiesDensity(self,coordinates,componentType,massType) + double precision function cloudOverdensitiesDensity(self,coordinates) !!{ Return the density at the specified {\normalfont \ttfamily coordinates} in a cloud overdensities mass distribution. !!} use :: Coordinates , only : assignment(=), coordinateCartesian implicit none - class (massDistributionCloudOverdensities), intent(inout) :: self - class (coordinate ), intent(in ) :: coordinates - type (enumerationComponentTypeType ), intent(in ), optional :: componentType - type (enumerationMassTypeType ), intent(in ), optional :: massType - type (coordinateCartesian ) :: position - double precision , dimension(3) :: positionComponents - double precision :: densityContrast - integer :: neighborCount + class (massDistributionCloudOverdensities), intent(inout) :: self + class (coordinate ), intent(in ) :: coordinates + type (coordinateCartesian ) :: position + double precision , dimension(3) :: positionComponents + double precision :: densityContrast + integer :: neighborCount - if (.not.self%matches(componentType,massType)) then - cloudOverdensitiesDensity=0.0d0 - return - end if ! Extract the position. position =coordinates positionComponents=position diff --git a/source/mass_distributions.composite.F90 b/source/mass_distributions.composite.F90 index 8b9ae0362b..9967858d5c 100644 --- a/source/mass_distributions.composite.F90 +++ b/source/mass_distributions.composite.F90 @@ -38,27 +38,47 @@ !!{ A composite mass distribution class. !!} - type(massDistributionList ), pointer :: massDistributions => null() - type(enumerationMassDistributionSymmetryType) :: symmetry_ + type (massDistributionList ), pointer :: massDistributions => null() + type (enumerationMassDistributionSymmetryType) :: symmetry_ + logical :: isSingleComponent , isCollisionless contains !![ - + + + !!] - final :: compositeDestructor - procedure :: initialize => compositeInitialize - procedure :: symmetry => compositeSymmetry - procedure :: isDimensionless => compositeIsDimensionless - procedure :: massTotal => compositeMassTotal - procedure :: acceleration => compositeAcceleration - procedure :: tidalTensor => compositeTidalTensor - procedure :: density => compositeDensity - procedure :: densityGradientRadial => compositeDensityGradientRadial - procedure :: densityRadialMoment => compositeDensityRadialMoment - procedure :: potential => compositePotential - procedure :: massEnclosedBySphere => compositeMassEnclosedBySphere - procedure :: positionSample => compositePositionSample + final :: compositeDestructor + procedure :: initialize => compositeInitialize + procedure :: subset => compositeSubset + procedure :: describe => compositeDescribe + procedure :: matches => compositeMatches + procedure :: symmetry => compositeSymmetry + procedure :: assumeMonotonicDecreasingSurfaceDensity => compositeAssumeMonotonicDecreasingSurfaceDensity + procedure :: isSphericallySymmetric => compositeIsSphericallySymmetric + procedure :: isDimensionless => compositeIsDimensionless + procedure :: massTotal => compositeMassTotal + procedure :: acceleration => compositeAcceleration + procedure :: tidalTensor => compositeTidalTensor + procedure :: density => compositeDensity + procedure :: surfaceDensity => compositeSurfaceDensity + procedure :: densityGradientRadial => compositeDensityGradientRadial + procedure :: densityRadialMoment => compositeDensityRadialMoment + procedure :: densitySphericalAverage => compositeDensitySphericalAverage + procedure :: densitySquareIntegral => compositeDensitySquareIntegral + procedure :: potentialIsAnalytic => compositePotentialIsAnalytic + procedure :: potential => compositePotential + procedure :: potentialDifference => compositePotentialDifference + procedure :: energy => compositeEnergy + procedure :: massEnclosedBySphere => compositeMassEnclosedBySphere + procedure :: radiusEnclosingMass => compositeRadiusEnclosingMass + procedure :: radiusEnclosingDensity => compositeRadiusEnclosingDensity + procedure :: radiusEnclosingSurfaceDensity => compositeRadiusEnclosingSurfaceDensity + procedure :: rotationCurve => compositeRotationCurve + procedure :: rotationCurveGradient => compositeRotationCurveGradient + procedure :: chandrasekharIntegral => compositeChandrasekharIntegral + procedure :: positionSample => compositePositionSample end type massDistributionComposite interface massDistributionComposite @@ -107,9 +127,9 @@ function compositeConstructorInternal(massDistributions) result(self) !!{ Internal constructor for ``composite'' mass distribution class. !!} - type(massDistributionComposite) :: self - type(massDistributionList ), target, intent(in ) :: massDistributions - type(massDistributionList ), pointer :: massDistribution_ + type(massDistributionComposite) :: self + type(massDistributionList ), pointer, intent(in ) :: massDistributions + type(massDistributionList ), pointer :: massDistribution_ self %massDistributions => massDistributions massDistribution_ => massDistributions @@ -147,22 +167,33 @@ end subroutine compositeDestructor subroutine compositeInitialize(self) !!{ - Destructor for composite mass distributions. + Initialize a composite mass distribution. !!} + use :: ISO_Varying_String, only : char implicit none - class(massDistributionComposite ), intent(inout) :: self - type (massDistributionList ), pointer :: massDistribution_ - type (enumerationMassDistributionSymmetryType) :: symmetry_ - + class (massDistributionComposite ), intent(inout) :: self + type (massDistributionList ), pointer :: massDistribution_ + type (enumerationMassDistributionSymmetryType) :: symmetry_ + logical :: firstComponent , haveKinematics + double precision :: toleranceRelativeVelocityDispersion, toleranceRelativeVelocityDispersionMaximum + ! Begin by assuming the highest degree of symmetry. - self%symmetry_=massDistributionSymmetrySpherical + self%symmetry_ =massDistributionSymmetrySpherical + ! Begin by assuming a single component. + self%isSingleComponent=.true. + firstComponent =.true. + ! Begin by assuming a collisionless distribution. + self%isCollisionless =.true. + haveKinematics =.true. ! Examine each distribution. if (associated(self%massDistributions)) then - massDistribution_ => self%massDistributions + massDistribution_ => self%massDistributions + toleranceRelativeVelocityDispersion = 0.0d0 + toleranceRelativeVelocityDispersionMaximum = 0.0d0 do while (associated(massDistribution_)) ! Dimensionless mass distributions are not allowed. - if (massDistribution_%massDistribution_%isDimensionless()) & - & call Error_Report('dimensionless mass distributions can not be part of a composite distribution'//{introspection:location}) + if (massDistribution_%massDistribution_%isDimensionless()) & + & call Error_Report('dimensionless mass distribution (type: '//char(massDistribution_%massDistribution_%objectType())//') can not be part of a composite distribution'//{introspection:location}) ! Determine the symmetry of the composite distribution. symmetry_=massDistribution_%massDistribution_%symmetry() select case (symmetry_%ID) @@ -179,9 +210,42 @@ subroutine compositeInitialize(self) case default call Error_Report('unknown symmetry'//{introspection:location}) end select + ! Record if we have multiple components. + if (firstComponent) then + firstComponent=.false. + else + self%isSingleComponent=.false. + end if + ! Check for collisional components. + if (associated(massDistribution_%massDistribution_%kinematicsDistribution_)) then + if (massDistribution_%massDistribution_%kinematicsDistribution_%isCollisional()) then + self%isCollisionless=.false. + else + toleranceRelativeVelocityDispersion =max(toleranceRelativeVelocityDispersion,massDistribution_%massDistribution_%kinematicsDistribution_%toleranceRelativeVelocityDispersion ) + toleranceRelativeVelocityDispersionMaximum=max(toleranceRelativeVelocityDispersion,massDistribution_%massDistribution_%kinematicsDistribution_%toleranceRelativeVelocityDispersionMaximum) + end if + else + haveKinematics=.false. + end if ! Move to the next mass distribution. massDistribution_ => massDistribution_%next end do + ! Establish a kinematics distribution. + if (haveKinematics.and.self%isSingleComponent) then + ! For a single component, simply use the kinematics distribution from that component + call self%setKinematicsDistribution(self%massDistributions%massDistribution_%kinematicsDistribution_) + else + ! Construct a collisionless mass distribution. Note that we build a collisionless distribution here even if some + ! component is collisional. A better approach might be to find a self-consistent solution for collisional components + ! assuming hydrostatic equilibrium. + allocate(kinematicsDistributionCollisionless :: self%kinematicsDistribution_) + select type (kinematicsDistribution_ => self%kinematicsDistribution_) + type is (kinematicsDistributionCollisionless) + !![ + + !!] + end select + end if end if return end subroutine compositeInitialize @@ -198,6 +262,17 @@ function compositeSymmetry(self) result(symmetry) return end function compositeSymmetry + logical function compositeIsSphericallySymmetric(self) result(isSphericallySymmetric) + !!{ + Return true if the distribution is spherically symmetric. + !!} + implicit none + class(massDistributionComposite), intent(inout) :: self + + isSphericallySymmetric=self%symmetry_ == massDistributionSymmetrySpherical + return + end function compositeIsSphericallySymmetric + logical function compositeIsDimensionless(self) !!{ Return the dimensionless nature of a composite mass distribution. @@ -210,7 +285,31 @@ logical function compositeIsDimensionless(self) return end function compositeIsDimensionless - double precision function compositeMassTotal(self,componentType,massType) + logical function compositeAssumeMonotonicDecreasingSurfaceDensity(self) result(isSphericallySymmetric) + !!{ + Return true if the surface density is monotonically decreasing. + !!} + implicit none + class(massDistributionComposite), intent(inout) :: self + type (massDistributionList ), pointer :: massDistribution_ + + isSphericallySymmetric=.true. + if (associated(self%massDistributions)) then + massDistribution_ => self%massDistributions + do while (associated(massDistribution_)) + ! If any of the components does not have a monotonically decreasing surface density, report that the total distribution + ! also does not. This is overly conservative. + if (.not.massDistribution_ %massDistribution_%assumeMonotonicDecreasingSurfaceDensity()) then + isSphericallySymmetric=.false. + exit + end if + massDistribution_ => massDistribution_%next + end do + end if + return + end function compositeAssumeMonotonicDecreasingSurfaceDensity + + logical function compositeMatches(self,componentType,massType) !!{ Return the total mass of a composite mass distribution. !!} @@ -219,98 +318,308 @@ double precision function compositeMassTotal(self,componentType,massType) type (enumerationComponentTypeType), intent(in ), optional :: componentType type (enumerationMassTypeType ), intent(in ), optional :: massType type (massDistributionList ), pointer :: massDistribution_ + !![ + + + !!] - compositeMassTotal=0.0d0 + ! Assume no match by default. + compositeMatches=.false. if (associated(self%massDistributions)) then massDistribution_ => self%massDistributions do while (associated(massDistribution_)) - if (massDistribution_ %massDistribution_%matches(componentType,massType)) & - & compositeMassTotal = +compositeMassTotal & - & +massDistribution_ %massDistribution_%massTotal() - massDistribution_ => massDistribution_ %next + ! If any component matches, report that as a match. + if (massDistribution_ %massDistribution_%matches(componentType,massType)) then + compositeMatches=.true. + exit + end if + massDistribution_ => massDistribution_%next end do end if return - end function compositeMassTotal + end function compositeMatches - double precision function compositeDensity(self,coordinates,componentType,massType) + subroutine compositeDescribe(self) !!{ - Return the density at the specified {\normalfont \ttfamily coordinates} in a composite mass distribution. + Display a description of a composite mass distribution. + !!} + use :: Display, only : displayMessage, displayIndent, displayUnindent + implicit none + class(massDistributionComposite), intent(inout) :: self + type (massDistributionList ), pointer :: massDistribution_ + + if (associated(self%massDistributions)) then + massDistribution_ => self%massDistributions + do while (associated(massDistribution_)) + select type (massDistribution__ => massDistribution_ %massDistribution_) + class is (massDistributionComposite) + call displayIndent (massDistribution_%massDistribution_%objectType()) + call massDistribution__%describe() + call displayUnindent("") + class default + call displayMessage(massDistribution_%massDistribution_%objectType()) + end select + massDistribution_ => massDistribution_%next + end do + end if + return + end subroutine compositeDescribe + + function compositeSubset(self,componentType,massType) result(subset) + !!{ + Return the subset of the composite distribution that matches. !!} implicit none + class(massDistributionClass ), pointer :: subset class(massDistributionComposite ), intent(inout) :: self - class(coordinate ), intent(in ) :: coordinates type (enumerationComponentTypeType), intent(in ), optional :: componentType type (enumerationMassTypeType ), intent(in ), optional :: massType - type (massDistributionList ), pointer :: massDistribution_ + class(massDistributionClass ), pointer :: massDistribution___ + type (massDistributionList ), pointer :: subsetHead , subsetNext , & + & compositesHead , compositesNext, & + & massDistribution_ + integer :: countComponents + !![ + + + !!] + + subset => null() + if (associated(self%massDistributions)) then + subsetHead => null() + subsetNext => null() + compositesHead => null() + massDistribution_ => self%massDistributions + countComponents = 0 + do while (associated(massDistribution_)) + select type (massDistribution__ => massDistribution_ %massDistribution_) + class is (massDistributionComposite) + massDistribution___ => massDistribution__%subset(componentType_,massType_) + if (associated(massDistribution___)) then + countComponents=countComponents+1 + if (associated(subsetHead)) then + allocate(subsetNext%next) + subsetNext => subsetNext%next + else + allocate(subsetHead ) + subsetNext => subsetHead + end if + subsetNext%massDistribution_ => massDistribution___ + if (associated(compositesHead)) then + allocate(compositesNext%next) + compositesNext => compositesNext%next + else + allocate(compositesHead ) + compositesNext => compositesHead + end if + compositesNext%massDistribution_ => massDistribution___ + end if + class default + if (massDistribution__%matches(componentType,massType)) then + countComponents=countComponents+1 + if (associated(subsetHead)) then + allocate(subsetNext%next) + subsetNext => subsetNext%next + else + allocate(subsetHead ) + subsetNext => subsetHead + end if + subsetNext%massDistribution_ => massDistribution__ + end if + end select + massDistribution_ => massDistribution_%next + end do + if (associated(subsetHead)) then + if (countComponents == 1) then + !![ + + !!] + deallocate(subsetHead) + else + allocate(massDistributionComposite :: subset) + select type(subset) + type is (massDistributionComposite) + !![ + + !!] + end select + end if + end if + do while (associated(compositesHead)) + compositesNext => compositesHead%next + !![ + + !!] + deallocate(compositesHead) + compositesHead => compositesNext + end do + end if + return + end function compositeSubset + + double precision function compositeMassTotal(self) + !!{ + Return the total mass of a composite mass distribution. + !!} + implicit none + class(massDistributionComposite), intent(inout) :: self + type (massDistributionList ), pointer :: massDistribution_ + + compositeMassTotal=0.0d0 + if (associated(self%massDistributions)) then + massDistribution_ => self%massDistributions + do while (associated(massDistribution_)) + compositeMassTotal = +compositeMassTotal & + & +massDistribution_ %massDistribution_%massTotal() + massDistribution_ => massDistribution_ %next + end do + end if + return + end function compositeMassTotal + + double precision function compositeDensity(self,coordinates) + !!{ + Return the density at the specified {\normalfont \ttfamily coordinates} in a composite mass distribution. + !!} + implicit none + class(massDistributionComposite), intent(inout) :: self + class(coordinate ), intent(in ) :: coordinates + type (massDistributionList ), pointer :: massDistribution_ compositeDensity=0.0d0 if (associated(self%massDistributions)) then massDistribution_ => self%massDistributions do while (associated(massDistribution_)) - if (massDistribution_ %massDistribution_%matches(componentType,massType)) & - & compositeDensity = +compositeDensity & - & +massDistribution_ %massDistribution_%density(coordinates) - massDistribution_ => massDistribution_ %next + compositeDensity = +compositeDensity & + & +massDistribution_ %massDistribution_%density(coordinates) + massDistribution_ => massDistribution_ %next end do end if return end function compositeDensity - double precision function compositeDensityGradientRadial(self,coordinates,logarithmic,componentType,massType) + double precision function compositeDensitySphericalAverage(self,radius) + !!{ + Return the spherically-averaged density at the specified {\normalfont \ttfamily radius} in a composite mass distribution. + !!} + implicit none + class (massDistributionComposite), intent(inout) :: self + double precision , intent(in ) :: radius + type (massDistributionList ), pointer :: massDistribution_ + + compositeDensitySphericalAverage=0.0d0 + if (associated(self%massDistributions)) then + massDistribution_ => self%massDistributions + do while (associated(massDistribution_)) + compositeDensitySphericalAverage = +compositeDensitySphericalAverage & + & +massDistribution_ %massDistribution_%densitySphericalAverage(radius) + massDistribution_ => massDistribution_ %next + end do + end if + return + end function compositeDensitySphericalAverage + + double precision function compositeDensitySquareIntegral(self,radiusMinimum,radiusMaximum,isInfinite) + !!{ + Return the integral of the square of the density within the given radial interval. + !!} + use :: Error, only : Error_Report + implicit none + class (massDistributionComposite), intent(inout) :: self + double precision , intent(in ), optional :: radiusMinimum, radiusMaximum + logical , intent( out), optional :: isInfinite + + if (self%isSingleComponent) then + compositeDensitySquareIntegral=self%massDistributions%massDistribution_%densitySquareIntegral(radiusMinimum,radiusMaximum) + else + compositeDensitySquareIntegral=0.0d0 + call Error_Report('support for ∫ dr ρ²(r) of multiple components is not implemented'//{introspection:location}) + end if + return + end function compositeDensitySquareIntegral + + double precision function compositeSurfaceDensity(self,coordinates) + !!{ + Return the surface density at the specified {\normalfont \ttfamily coordinates} in a composite mass distribution. + !!} + use :: Coordinates, only : coordinate + implicit none + class(massDistributionComposite), intent(inout) :: self + class(coordinate ), intent(in ) :: coordinates + type (massDistributionList ), pointer :: massDistribution_ + + + compositeSurfaceDensity=0.0d0 + if (associated(self%massDistributions)) then + massDistribution_ => self%massDistributions + do while (associated(massDistribution_)) + compositeSurfaceDensity = +compositeSurfaceDensity & + & +massDistribution_ %massDistribution_%surfaceDensity(coordinates) + massDistribution_ => massDistribution_ %next + end do + end if + return + end function compositeSurfaceDensity + + double precision function compositeDensityGradientRadial(self,coordinates,logarithmic) !!{ Return the radial density gradient at the specified {\normalfont \ttfamily coordinates} in a composite mass distribution. !!} + use :: Error, only : Error_Report implicit none - class (massDistributionComposite ), intent(inout) :: self - class (coordinate ), intent(in ) :: coordinates - logical , intent(in ), optional :: logarithmic - type (enumerationComponentTypeType), intent(in ), optional :: componentType - type (enumerationMassTypeType ), intent(in ), optional :: massType - type (massDistributionList ), pointer :: massDistribution_ + class (massDistributionComposite), intent(inout), target :: self + class (coordinate ), intent(in ) :: coordinates + logical , intent(in ), optional :: logarithmic + type (massDistributionList ), pointer :: massDistribution_ + double precision :: density !![ !!] - compositeDensityGradientRadial=0.0d0 - if (associated(self%massDistributions)) then - massDistribution_ => self%massDistributions - do while (associated(massDistribution_)) - if (massDistribution_ %massDistribution_%matches(componentType,massType)) & - & compositeDensityGradientRadial = +compositeDensityGradientRadial & - & +massDistribution_ %massDistribution_%densityGradientRadial(coordinates,logarithmic=.false.) - massDistribution_ => massDistribution_ %next - end do - if (logarithmic_) then - compositeDensityGradientRadial=+compositeDensityGradientRadial & - & *coordinates %rSpherical( ) & - & /self %density (coordinates) + if (self%isSingleComponent) then + compositeDensityGradientRadial=self%massDistributions%massDistribution_%densityGradientRadial(coordinates,logarithmic) + else + compositeDensityGradientRadial=0.0d0 + if (associated(self%massDistributions)) then + massDistribution_ => self%massDistributions + do while (associated(massDistribution_)) + compositeDensityGradientRadial = +compositeDensityGradientRadial & + & +massDistribution_ %massDistribution_%densityGradientRadial(coordinates,logarithmic=.false.) + massDistribution_ => massDistribution_ %next + end do + if (logarithmic_) then + density=self%density(coordinates) + if (density <= 0.0d0) then + if (compositeDensityGradientRadial /= 0.0d0) call Error_Report('non-zero gradient, but zero density'//{introspection:location}) + else + compositeDensityGradientRadial=+compositeDensityGradientRadial & + & *coordinates %rSpherical() & + & / density + end if + end if end if end if return end function compositeDensityGradientRadial - double precision function compositeDensityRadialMoment(self,moment,radiusMinimum,radiusMaximum,isInfinite,componentType,massType) + double precision function compositeDensityRadialMoment(self,moment,radiusMinimum,radiusMaximum,isInfinite) !!{ Return the given radial density moment in a composite mass distribution. !!} implicit none - class (massDistributionComposite ), intent(inout) :: self - double precision , intent(in ) :: moment - double precision , intent(in ), optional :: radiusMinimum, radiusMaximum - logical , intent( out), optional :: isInfinite - type (enumerationComponentTypeType), intent(in ), optional :: componentType - type (enumerationMassTypeType ), intent(in ), optional :: massType - type (massDistributionList ), pointer :: massDistribution_ + class (massDistributionComposite), intent(inout) :: self + double precision , intent(in ) :: moment + double precision , intent(in ), optional :: radiusMinimum , radiusMaximum + logical , intent( out), optional :: isInfinite + type (massDistributionList ), pointer :: massDistribution_ compositeDensityRadialMoment=0.0d0 if (present(isInfinite)) isInfinite=.false. if (associated(self%massDistributions)) then massDistribution_ => self%massDistributions do while (associated(massDistribution_)) - if (massDistribution_ %massDistribution_%matches(componentType,massType)) & - & compositeDensityRadialMoment = +compositeDensityRadialMoment & - & +massDistribution_ %massDistribution_%densityRadialMoment(moment,radiusMinimum,radiusMaximum,isInfinite) + compositeDensityRadialMoment = +compositeDensityRadialMoment & + & +massDistribution_ %massDistribution_%densityRadialMoment(moment,radiusMinimum,radiusMaximum,isInfinite) if (present(isInfinite)) then if (isInfinite) return end if @@ -320,116 +629,281 @@ double precision function compositeDensityRadialMoment(self,moment,radiusMinimum return end function compositeDensityRadialMoment - double precision function compositeMassEnclosedBySphere(self,radius,componentType,massType) + double precision function compositeMassEnclosedBySphere(self,radius) !!{ Computes the mass enclosed within a sphere in a composite mass distribution. !!} implicit none - class (massDistributionComposite ), intent(inout), target :: self - double precision , intent(in ) :: radius - type (enumerationComponentTypeType), intent(in ), optional :: componentType - type (enumerationMassTypeType ), intent(in ), optional :: massType - type (massDistributionList ) , pointer :: massDistribution_ + class (massDistributionComposite), intent(inout), target :: self + double precision , intent(in ) :: radius + type (massDistributionList ) , pointer :: massDistribution_ compositeMassEnclosedBySphere=0.0d0 if (associated(self%massDistributions)) then massDistribution_ => self%massDistributions do while (associated(massDistribution_)) - if (massDistribution_ %massDistribution_%matches(componentType,massType)) & - & compositeMassEnclosedBySphere = +compositeMassEnclosedBySphere & - & +massDistribution_ %massDistribution_%massEnclosedBySphere(radius) - massDistribution_ => massDistribution_ %next + compositeMassEnclosedBySphere = +compositeMassEnclosedBySphere & + & +massDistribution_ %massDistribution_%massEnclosedBySphere(radius) + massDistribution_ => massDistribution_ %next end do end if return end function compositeMassEnclosedBySphere - function compositeAcceleration(self,coordinates,componentType,massType) + double precision function compositeRadiusEnclosingMass(self,mass,massFractional) result(radius) + !!{ + Computes the radius enclosing a given mass or mass fraction for composite mass distributions. + !!} + implicit none + class (massDistributionComposite), intent(inout), target :: self + double precision , intent(in ), optional :: mass, massFractional + + if (self%isSingleComponent) then + radius=self%massDistributions%massDistribution_%radiusEnclosingMass (mass,massFractional) + else + radius=self %radiusEnclosingMassNumerical(mass,massFractional) + end if + return + end function compositeRadiusEnclosingMass + + double precision function compositeRadiusEnclosingDensity(self,density,radiusGuess) result(radius) + !!{ + Computes the radius enclosing a given mean density for composite mass distributions. + !!} + implicit none + class (massDistributionComposite), intent(inout), target :: self + double precision , intent(in ) :: density + double precision , intent(in ), optional :: radiusGuess + + if (self%isSingleComponent) then + radius=self%massDistributions%massDistribution_%radiusEnclosingDensity (density,radiusGuess) + else + radius=self %radiusEnclosingDensityNumerical(density,radiusGuess) + end if + return + end function compositeRadiusEnclosingDensity + + double precision function compositeRadiusEnclosingSurfaceDensity(self,densitySurface,radiusGuess) result(radius) + !!{ + Computes the radius enclosing a given surface density for composite mass distributions. + !!} + implicit none + class (massDistributionComposite), intent(inout), target :: self + double precision , intent(in ) :: densitySurface + double precision , intent(in ), optional :: radiusGuess + + if (self%isSingleComponent) then + radius=self%massDistributions%massDistribution_%radiusEnclosingSurfaceDensity (densitySurface,radiusGuess) + else + radius=self %radiusEnclosingSurfaceDensityNumerical(densitySurface,radiusGuess) + end if + return + end function compositeRadiusEnclosingSurfaceDensity + + double precision function compositeRotationCurve(self,radius) + !!{ + Return the rotation curve for a composite mass distribution. + !!} + implicit none + class (massDistributionComposite), intent(inout) :: self + double precision , intent(in ) :: radius + type (massDistributionList ), pointer :: massDistribution_ + + compositeRotationCurve=0.0d0 + if (associated(self%massDistributions)) then + massDistribution_ => self%massDistributions + do while (associated(massDistribution_)) + compositeRotationCurve = +compositeRotationCurve & + & +massDistribution_ %massDistribution_%rotationCurve(radius)**2 + massDistribution_ => massDistribution_ %next + end do + compositeRotationCurve=sqrt(compositeRotationCurve) + end if + return + end function compositeRotationCurve + + double precision function compositeRotationCurveGradient(self,radius) + !!{ + Return the gradient of the rotation curve for a composite mass distribution. + !!} + implicit none + class (massDistributionComposite), intent(inout) :: self + double precision , intent(in ) :: radius + type (massDistributionList ), pointer :: massDistribution_ + + compositeRotationCurveGradient=0.0d0 + if (associated(self%massDistributions)) then + massDistribution_ => self%massDistributions + do while (associated(massDistribution_)) + compositeRotationCurveGradient = +compositeRotationCurveGradient & + & +massDistribution_ %massDistribution_%rotationCurveGradient(radius) + massDistribution_ => massDistribution_ %next + end do + end if + return + end function compositeRotationCurveGradient + + function compositeAcceleration(self,coordinates) !!{ Computes the gravitational acceleration at {\normalfont \ttfamily coordinates} for a composite mass distribution. !!} implicit none - double precision , dimension(3 ) :: compositeAcceleration - class (massDistributionComposite ), intent(inout) :: self - class (coordinate ), intent(in ) :: coordinates - type (enumerationComponentTypeType), intent(in ), optional :: componentType - type (enumerationMassTypeType ), intent(in ), optional :: massType - type (massDistributionList ), pointer :: massDistribution_ + double precision , dimension(3 ) :: compositeAcceleration + class (massDistributionComposite ), intent(inout) :: self + class (coordinate ), intent(in ) :: coordinates + type (massDistributionList ), pointer :: massDistribution_ compositeAcceleration=[0.0d0,0.0d0,0.0d0] if (associated(self%massDistributions)) then massDistribution_ => self%massDistributions do while (associated(massDistribution_)) - if (massDistribution_ %massDistribution_%matches(componentType,massType)) & - & compositeAcceleration = +compositeAcceleration & - & +massDistribution_ %massDistribution_%acceleration(coordinates) - massDistribution_ => massDistribution_ %next + compositeAcceleration = +compositeAcceleration & + & +massDistribution_ %massDistribution_%acceleration(coordinates) + massDistribution_ => massDistribution_ %next end do end if return end function compositeAcceleration - function compositeTidalTensor(self,coordinates,componentType,massType) + function compositeTidalTensor(self,coordinates) !!{ Computes the gravitational tidal tensor at {\normalfont \ttfamily coordinates} for exponential disk mass distributions. !!} implicit none - type (tensorRank2Dimension3Symmetric) :: compositeTidalTensor - class(massDistributionComposite ), intent(inout) :: self - class(coordinate ), intent(in ) :: coordinates - type (enumerationComponentTypeType ), intent(in ), optional :: componentType - type (enumerationMassTypeType ), intent(in ), optional :: massType - type (massDistributionList ), pointer :: massDistribution_ + type (tensorRank2Dimension3Symmetric) :: compositeTidalTensor + class(massDistributionComposite ), intent(inout) :: self + class(coordinate ), intent(in ) :: coordinates + type (massDistributionList ), pointer :: massDistribution_ compositeTidalTensor=tensorRank2Dimension3Symmetric() if (associated(self%massDistributions)) then massDistribution_ => self%massDistributions do while (associated(massDistribution_)) - if (massDistribution_ %massDistribution_%matches(componentType,massType)) & - & compositeTidalTensor = +compositeTidalTensor & - & +massDistribution_ %massDistribution_%tidalTensor(coordinates) - massDistribution_ => massDistribution_ %next + compositeTidalTensor = +compositeTidalTensor & + & +massDistribution_ %massDistribution_%tidalTensor(coordinates) + massDistribution_ => massDistribution_ %next end do end if return end function compositeTidalTensor - double precision function compositePotential(self,coordinates,componentType,massType) + logical function compositePotentialIsAnalytic(self) result(isAnalytic) + !!{ + Specify whether the potential has an analytic form. + !!} + implicit none + class(massDistributionComposite), intent(inout) :: self + + isAnalytic=.false. + return + end function compositePotentialIsAnalytic + + double precision function compositePotential(self,coordinates,status) !!{ Return the gravitational potential for a composite mass distribution. !!} + use :: Galactic_Structure_Options, only : structureErrorCodeSuccess implicit none - class(massDistributionComposite ), intent(inout) :: self - class(coordinate ), intent(in ) :: coordinates - type (enumerationComponentTypeType), intent(in ), optional :: componentType - type (enumerationMassTypeType ), intent(in ), optional :: massType - type (massDistributionList ), pointer :: massDistribution_ + class(massDistributionComposite ), intent(inout), target :: self + class(coordinate ), intent(in ) :: coordinates + type (enumerationStructureErrorCodeType), intent( out), optional :: status + type (massDistributionList ), pointer :: massDistribution_ + if (present(status)) status=structureErrorCodeSuccess compositePotential=0.0d0 if (associated(self%massDistributions)) then massDistribution_ => self%massDistributions do while (associated(massDistribution_)) - if (massDistribution_ %massDistribution_%matches(componentType,massType)) & - & compositePotential = +compositePotential & - & +massDistribution_ %massDistribution_%potential(coordinates) - massDistribution_ => massDistribution_ %next + compositePotential = +compositePotential & + & +massDistribution_ %massDistribution_%potential(coordinates,status) + if (present(status).and.status /= structureErrorCodeSuccess) return + massDistribution_ => massDistribution_ %next end do end if return end function compositePotential + + double precision function compositePotentialDifference(self,coordinates1,coordinates2,status) result(potential) + !!{ + Return the gravitational potential for a composite mass distribution. + !!} + use :: Galactic_Structure_Options, only : structureErrorCodeSuccess + implicit none + class(massDistributionComposite ), intent(inout), target :: self + class(coordinate ), intent(in ) :: coordinates1 , coordinates2 + type (enumerationStructureErrorCodeType), intent( out), optional :: status + type (massDistributionList ), pointer :: massDistribution_ + + if (present(status)) status=structureErrorCodeSuccess + potential=0.0d0 + if (associated(self%massDistributions)) then + massDistribution_ => self%massDistributions + do while (associated(massDistribution_)) + potential=+potential & + & +massDistribution_%massDistribution_%potentialDifference(coordinates1,coordinates2,status) + if (present(status).and.status /= structureErrorCodeSuccess) return + massDistribution_ => massDistribution_%next + end do + end if + return + end function compositePotentialDifference + + double precision function compositeEnergy(self,radiusOuter,massDistributionEmbedding) result(energy) + !!{ + Compute the energy of the mass distribution. + !!} + use :: Error, only : Error_Report + implicit none + class (massDistributionComposite), intent(inout), target :: self + double precision , intent(in ) :: radiusOuter + class (massDistributionClass ), intent(inout), target :: massDistributionEmbedding + class (massDistributionClass ) , pointer :: self_ + + self_ => self + if (self%isSingleComponent .and. associated(self_,massDistributionEmbedding)) then + energy=self%massDistributions%massDistribution_%energy(radiusOuter,self%massDistributions%massDistribution_) + else + energy=0.0d0 + call Error_Report('support for energy of multiple components is not implemented'//{introspection:location}) + end if + return + end function compositeEnergy - function compositePositionSample(self,randomNumberGenerator_,componentType,massType) + function compositeChandrasekharIntegral(self,massDistributionEmbedding,massDistributionPerturber,massPerturber,coordinates,velocity) + !!{ + Compute the Chandrasekhar integral at the specified {\normalfont \ttfamily coordinates} in a composite mass distribution. + !!} + implicit none + double precision , dimension(3) :: compositeChandrasekharIntegral + class (massDistributionComposite ), intent(inout) :: self + class (massDistributionClass ), intent(inout) :: massDistributionEmbedding , massDistributionPerturber + double precision , intent(in ) :: massPerturber + class (coordinate ), intent(in ) :: coordinates , velocity + type (massDistributionList ), pointer :: massDistribution_ + !$GLC attributes unused :: massDistributionEmbedding + + compositeChandrasekharIntegral=0.0d0 + if (associated(self%massDistributions)) then + massDistribution_ => self%massDistributions + do while (associated(massDistribution_)) + compositeChandrasekharIntegral = +compositeChandrasekharIntegral & + & +massDistribution_%massDistribution_%chandrasekharIntegral(massDistribution_%massDistribution_,massDistributionPerturber,massPerturber,coordinates,velocity) + massDistribution_ => massDistribution_%next + end do + end if + return + end function compositeChandrasekharIntegral + + function compositePositionSample(self,randomNumberGenerator_) !!{ Sample a position from a composite distribution. !!} implicit none - double precision , dimension(3) :: compositePositionSample - class (massDistributionComposite ), intent(inout) :: self - class (randomNumberGeneratorClass ), intent(inout) :: randomNumberGenerator_ - type (enumerationComponentTypeType), intent(in ), optional :: componentType - type (enumerationMassTypeType ), intent(in ), optional :: massType - type (massDistributionList ), pointer :: massDistribution_ - double precision :: massCumulative + double precision , dimension(3) :: compositePositionSample + class (massDistributionComposite ), intent(inout) :: self + class (randomNumberGeneratorClass ), intent(inout) :: randomNumberGenerator_ + type (massDistributionList ), pointer :: massDistribution_ + double precision :: massCumulative compositePositionSample=[0.0d0,0.0d0,0.0d0] if (associated(self%massDistributions)) then @@ -437,14 +911,13 @@ function compositePositionSample(self,randomNumberGenerator_,componentType,massT & *randomNumberGenerator_%uniformSample() massDistribution_ => self%massDistributions do while (associated(massDistribution_)) - if (massDistribution_ %massDistribution_%matches(componentType,massType)) & - & massCumulative=+ massCumulative & - & -massDistribution_%massDistribution_%massTotal () + massCumulative=+ massCumulative & + & -massDistribution_%massDistribution_%massTotal () if (massCumulative <= 0.0d0) then compositePositionSample=massDistribution_%massDistribution_%positionSample(randomNumberGenerator_) return end if - massDistribution_ => massDistribution_ %next + massDistribution_ => massDistribution_%next end do end if diff --git a/source/mass_distributions.cylindrical.F90 b/source/mass_distributions.cylindrical.F90 index 566d974454..ec496368b1 100644 --- a/source/mass_distributions.cylindrical.F90 +++ b/source/mass_distributions.cylindrical.F90 @@ -34,92 +34,25 @@ contains !![ - - - - - - + !!] - procedure :: symmetry => cylindricalSymmetry - procedure(cylindricalRadiusHalfMass ), deferred :: radiusHalfMass - procedure(cylindricalSurfaceDensity ), deferred :: surfaceDensity - procedure(cylindricalRotationCurve ), deferred :: rotationCurve - procedure(cylindricalRotationCurveGradient ), deferred :: rotationCurveGradient - procedure(cylindricalSurfaceDensityRadialMoment), deferred :: surfaceDensityRadialMoment - procedure(cylindricalDensitySphericalAverage ), deferred :: densitySphericalAverage + procedure :: symmetry => cylindricalSymmetry + procedure :: chandrasekharIntegral => cylindricalChandrasekharIntegral + procedure :: densityRadialMoment => cylindricalDensityRadialMoment + procedure(cylindricalRadiusHalfMass), deferred :: radiusHalfMass end type massDistributionCylindrical abstract interface - double precision function cylindricalRadiusHalfMass(self,componentType,massType) + double precision function cylindricalRadiusHalfMass(self) !!{ Interface for cylindrically symmetric mass distribution half mass radii functions. !!} - import massDistributionCylindrical, enumerationComponentTypeType, enumerationMassTypeType - class(massDistributionCylindrical ), intent(inout) :: self - type (enumerationComponentTypeType), intent(in ), optional :: componentType - type (enumerationMassTypeType ), intent(in ), optional :: massType + import massDistributionCylindrical + class(massDistributionCylindrical), intent(inout) :: self end function cylindricalRadiusHalfMass - double precision function cylindricalSurfaceDensity(self,coordinates,componentType,massType) - !!{ - Interface for cylindrically symmetric mass distribution surface density functions. - !!} - import massDistributionCylindrical, coordinate, enumerationComponentTypeType, enumerationMassTypeType - class(massDistributionCylindrical ), intent(inout) :: self - class(coordinate ), intent(in ) :: coordinates - type (enumerationComponentTypeType), intent(in ), optional :: componentType - type (enumerationMassTypeType ), intent(in ), optional :: massType - end function cylindricalSurfaceDensity - - double precision function cylindricalRotationCurve(self,radius,componentType,massType) - !!{ - Interface for cylindrically symmetric mass distribution rotation curve functions. - !!} - import massDistributionCylindrical, enumerationComponentTypeType, enumerationMassTypeType - class (massDistributionCylindrical ), intent(inout) :: self - double precision , intent(in ) :: radius - type (enumerationComponentTypeType), intent(in ), optional :: componentType - type (enumerationMassTypeType ), intent(in ), optional :: massType - end function cylindricalRotationCurve - - double precision function cylindricalRotationCurveGradient(self,radius,componentType,massType) - !!{ - Interface for cylindrically symmetric mass distribution rotation curve gradient functions. - !!} - import massDistributionCylindrical, enumerationComponentTypeType, enumerationMassTypeType - class (massDistributionCylindrical ), intent(inout) :: self - double precision , intent(in ) :: radius - type (enumerationComponentTypeType), intent(in ), optional :: componentType - type (enumerationMassTypeType ), intent(in ), optional :: massType - end function cylindricalRotationCurveGradient - - double precision function cylindricalSurfaceDensityRadialMoment(self,moment,radiusMinimum,radiusMaximum,isInfinite,componentType,massType) - !!{ - Interface for cylindrically symmetric mass distribution surface density radial moment functions. - !!} - import massDistributionCylindrical, enumerationComponentTypeType, enumerationMassTypeType - class (massDistributionCylindrical ), intent(inout) :: self - double precision , intent(in ) :: moment - double precision , intent(in ), optional :: radiusMinimum, radiusMaximum - logical , intent( out), optional :: isInfinite - type (enumerationComponentTypeType), intent(in ), optional :: componentType - type (enumerationMassTypeType ), intent(in ), optional :: massType - end function cylindricalSurfaceDensityRadialMoment - - double precision function cylindricalDensitySphericalAverage(self,radius,componentType,massType) - !!{ - Interface for cylindrically symmetric mass distribution spherically-averaged density functions. - !!} - import massDistributionCylindrical, enumerationComponentTypeType, enumerationMassTypeType - class (massDistributionCylindrical ), intent(inout) :: self - double precision , intent(in ) :: radius - type (enumerationComponentTypeType), intent(in ), optional :: componentType - type (enumerationMassTypeType ), intent(in ), optional :: massType - end function cylindricalDensitySphericalAverage - end interface contains @@ -136,3 +69,144 @@ function cylindricalSymmetry(self) cylindricalSymmetry=massDistributionSymmetryCylindrical return end function cylindricalSymmetry + + function cylindricalChandrasekharIntegral(self,massDistributionEmbedding,massDistributionPerturber,massPerturber,coordinates,velocity) + !!{ + Compute the Chandrasekhar integral at the specified {\normalfont \ttfamily coordinates} in a spherical mass distribution. + !!} + use :: Coordinates , only : coordinateCartesian , coordinateSpherical, coordinateCylindrical , assignment(=) + use :: Galactic_Structure_Options , only : componentTypeAll , massTypeAll , enumerationComponentTypeType, enumerationMassTypeType + use :: Numerical_Constants_Math , only : Pi + use :: Numerical_Constants_Astronomical, only : gravitationalConstantGalacticus + use :: Linear_Algebra , only : vector , matrix , assignment(=) + implicit none + double precision , dimension(3) :: cylindricalChandrasekharIntegral + class (massDistributionCylindrical ), intent(inout) :: self + class (massDistributionClass ), intent(inout) :: massDistributionEmbedding , massDistributionPerturber + double precision , intent(in ) :: massPerturber + class (coordinate ), intent(in ) :: coordinates , velocity + double precision , dimension(3) :: velocityCartesian_ + double precision , parameter :: toomreQRadiusHalfMass =1.50d0 ! The Toomre Q-parameter at the disk half-mass radius (Benson et al., + ! 2004 , https://ui.adsabs.harvard.edu/abs/2004MNRAS.351.1215B, Appendix A). + double precision , parameter :: toomreQFactor =3.36d0 ! The factor appearing in the definition of the Toomre Q-parameter for + ! a stellar disk (Binney & Tremaine, eqn. 6.71). + double precision , dimension(3) :: velocityDisk , velocityRelative , & + & positionCartesianMidplane , & + & positionCylindricalHalfMass , positionCartesian + type (massDistributionGaussianEllipsoid), save :: velocityDistribution + logical , save :: velocityDistributionInitialized =.false. + !$omp threadprivate(velocityDistribution,velocityDistributionInitialized) + type (coordinateCartesian ) :: coordinates_ , coordinatesMidplane , & + & coordinatesMidplaneHalfMass , velocityCartesian + double precision :: velocityDispersionRadial , velocityDispersionAzimuthal , & + & velocityDispersionVertical , velocityCircular , & + & velocityCircularHalfMassRadius , velocityCircularSquaredGradient , & + & velocityCircularSquaredGradientHalfMassRadius , density , & + & densityMidPlane , densitySurface , & + & heightScale , radiusMidplane , & + & frequencyCircular , frequencyEpicyclic , & + & frequencyCircularHalfMassRadius , frequencyEpicyclicHalfMassRadius, & + & densitySurfaceRadiusHalfMass , velocityDispersionRadialHalfMass, & + & velocityDispersionMaximum , velocityRelativeMagnitude , & + & factorSuppressionExtendedMass + type (matrix ) :: rotation + + coordinates_ = coordinates + positionCartesian = coordinates_ + positionCartesianMidplane = [ positionCartesian(1),positionCartesian(2),0.0d0] + positionCylindricalHalfMass = [self%radiusHalfMass ( ),0.0d0 ,0.0d0] + coordinatesMidplane = positionCartesianMidplane + coordinatesMidplaneHalfMass = positionCylindricalHalfMass + radiusMidplane = coordinatesMidplane%rSpherical() + velocityCircular = massDistributionEmbedding%rotationCurve ( radiusMidplane ) + velocityCircularSquaredGradient = massDistributionEmbedding%rotationCurveGradient( radiusMidplane ) + velocityCircularHalfMassRadius = massDistributionEmbedding%rotationCurve (self%radiusHalfMass()) + velocityCircularSquaredGradientHalfMassRadius = massDistributionEmbedding%rotationCurveGradient(self%radiusHalfMass()) + velocityDisk = +[positionCartesianMidplane(2),-positionCartesianMidplane(1),0.0d0] & + & /radiusMidplane & + & *velocityCircular + ! Compute epicyclic frequency. + frequencyCircular =velocityCircular / radiusMidplane + frequencyCircularHalfMassRadius =velocityCircularHalfMassRadius/self%radiusHalfMass() + frequencyEpicyclic =sqrt(velocityCircularSquaredGradient / radiusMidplane +2.0d0*frequencyCircular **2) + frequencyEpicyclicHalfMassRadius=sqrt(velocityCircularSquaredGradientHalfMassRadius/self%radiusHalfMass()+2.0d0*frequencyCircularHalfMassRadius**2) + ! Get disk structural properties. + density =+self%density (coordinates ) + densityMidPlane =+self%density (coordinatesMidplane ) + densitySurface =+self%surfaceDensity(coordinatesMidplane ) + densitySurfaceRadiusHalfMass=+self%surfaceDensity(coordinatesMidplaneHalfMass) + if (density <= 0.0d0) then + cylindricalChandrasekharIntegral=0.0d0 + return + end if + heightScale =+0.5d0 & + & *densitySurface & + & /densityMidPlane + ! Compute normalization of the radial velocity dispersion. + velocityDispersionRadialHalfMass=+toomreQFactor & + & *gravitationalConstantGalacticus & + & *densitySurfaceRadiusHalfMass & + & *toomreQRadiusHalfMass & + & /frequencyEpicyclicHalfMassRadius + ! Find the velocity dispersion components of the disk. + velocityDispersionRadial =+velocityDispersionRadialHalfMass & + & *sqrt( & + & +densitySurface & + & /densitySurfaceRadiusHalfMass & + & ) + velocityDispersionAzimuthal=+velocityDispersionRadial*frequencyEpicyclic/2.0d0/frequencyCircular + velocityDispersionVertical =+sqrt(Pi*gravitationalConstantGalacticus*densitySurface*heightScale) + velocityDispersionMaximum =+maxval([velocityDispersionRadial,velocityDispersionAzimuthal,velocityDispersionVertical]) + velocityDispersionRadial =+velocityDispersionRadial /velocityDispersionMaximum + velocityDispersionAzimuthal=+velocityDispersionAzimuthal/velocityDispersionMaximum + velocityDispersionVertical =+velocityDispersionVertical /velocityDispersionMaximum + if (any([velocityDispersionRadial,velocityDispersionAzimuthal,velocityDispersionVertical] <= 0.0d0)) return + ! Find the relative velocity of the perturber and the disk. + velocityCartesian = velocity + velocityCartesian_ = velocityCartesian + velocityRelative =(velocityCartesian_-velocityDisk)/velocityDispersionMaximum + ! Handle limiting case of large relative velocity. + velocityRelativeMagnitude =sqrt(sum(velocityRelative**2)) + ! Initialize the velocity distribution. + rotation=reshape( & + & [ & + & +positionCartesianMidplane(1),-positionCartesianMidplane(2),+0.0d0 , & + & +positionCartesianMidplane(2),+positionCartesianMidplane(1),+0.0d0 , & + & +0.0d0 ,+0.0d0 ,+radiusMidplane & + & ] & + & /radiusMidplane , & + & [3,3] & + & ) + coordinates_=velocityRelative + if (.not.velocityDistributionInitialized) then + velocityDistribution =massDistributionGaussianEllipsoid(scaleLength=[1.0d0,1.0d0,1.0d0],rotation=rotation,mass=1.0d0,dimensionless=.true.) + velocityDistributionInitialized=.true. + end if + call velocityDistribution%initialize(scaleLength=[velocityDispersionRadial,velocityDispersionAzimuthal,velocityDispersionVertical],rotation=rotation) + ! Compute suppression factor due to satellite being an extended mass distribution. This is largely untested - it is meant to + ! simply avoid extremely large accelerations for subhalo close to the disk plane when that subhalo is much more extended than + ! the disk. + factorSuppressionExtendedMass=min(1.0d0,massDistributionPerturber%massEnclosedBySphere(heightScale)/massPerturber) + ! Evaluate the integral. + cylindricalChandrasekharIntegral=+density & + & *velocityDistribution %acceleration(coordinates_) & + & /velocityDispersionMaximum **2 & + & *factorSuppressionExtendedMass + return + end function cylindricalChandrasekharIntegral + + double precision function cylindricalDensityRadialMoment(self,moment,radiusMinimum,radiusMaximum,isInfinite) + !!{ + Computes radial moments of the density in cylindrical mass distributions. + !!} + use :: Error, only : Error_Report + implicit none + class (massDistributionCylindrical), intent(inout) :: self + double precision , intent(in ) :: moment + double precision , intent(in ), optional :: radiusMinimum, radiusMaximum + logical , intent( out), optional :: isInfinite + + cylindricalDensityRadialMoment=0.0d0 + call Error_Report('radial density moments are not defined in cylindrical mass distributions'//{introspection:location}) + return + end function cylindricalDensityRadialMoment diff --git a/source/mass_distributions.cylindrical.Gaussian_slab.F90 b/source/mass_distributions.cylindrical.Gaussian_slab.F90 index c10596ec28..87163dd3bb 100644 --- a/source/mass_distributions.cylindrical.Gaussian_slab.F90 +++ b/source/mass_distributions.cylindrical.Gaussian_slab.F90 @@ -150,24 +150,18 @@ function gaussianSlabConstructorInternal(scaleHeight,densityCentral,dimensionles return end function gaussianSlabConstructorInternal - double precision function gaussianSlabDensity(self,coordinates,componentType,massType) + double precision function gaussianSlabDensity(self,coordinates) !!{ Return the density at the specified {\normalfont \ttfamily coordinates} in a Gaussian slab mass distribution. !!} use :: Coordinates, only : assignment(=), coordinateCylindrical use :: Error , only : Error_Report implicit none - class (massDistributionGaussianSlab), intent(inout) :: self - class (coordinate ), intent(in ) :: coordinates - type (enumerationComponentTypeType), intent(in ), optional :: componentType - type (enumerationMassTypeType ), intent(in ), optional :: massType - type (coordinateCylindrical ) :: position - double precision :: z + class (massDistributionGaussianSlab), intent(inout) :: self + class (coordinate ), intent(in ) :: coordinates + type (coordinateCylindrical ) :: position + double precision :: z - if (.not.self%matches(componentType,massType)) then - gaussianSlabDensity=0.0d0 - return - end if ! If disk is razor thin, density is undefined. if (self%scaleHeight <= 0.0d0) call Error_Report('density undefined for razor-thin slab'//{introspection:location}) ! Get position in cylindrical coordinate system. @@ -178,76 +172,64 @@ double precision function gaussianSlabDensity(self,coordinates,componentType,mas return end function gaussianSlabDensity - double precision function gaussianSlabDensitySphericalAverage(self,radius,componentType,massType) + double precision function gaussianSlabDensitySphericalAverage(self,radius) !!{ Return the spherically-averaged density at the specified {\normalfont \ttfamily radius} in a Gaussian slab mass distribution. !!} implicit none - class (massDistributionGaussianSlab), intent(inout) :: self - double precision , intent(in ) :: radius - type (enumerationComponentTypeType), intent(in ), optional :: componentType - type (enumerationMassTypeType ), intent(in ), optional :: massType - !$GLC attributes unused :: self, radius, componentType, massType + class (massDistributionGaussianSlab), intent(inout) :: self + double precision , intent(in ) :: radius + !$GLC attributes unused :: self, radius gaussianSlabDensitySphericalAverage=0.0d0 call Error_Report('spherically-averaged density profile is not implemented'//{introspection:location}) return end function gaussianSlabDensitySphericalAverage - double precision function gaussianSlabRotationCurve(self,radius,componentType,massType) + double precision function gaussianSlabRotationCurve(self,radius) !!{ Rotation curve for a infinite extent Gaussian slab. !!} implicit none - class (massDistributionGaussianSlab), intent(inout) :: self - double precision , intent(in ) :: radius - type (enumerationComponentTypeType), intent(in ), optional :: componentType - type (enumerationMassTypeType ), intent(in ), optional :: massType - !$GLC attributes unused :: self, radius, componentType, massType + class (massDistributionGaussianSlab), intent(inout) :: self + double precision , intent(in ) :: radius + !$GLC attributes unused :: self, radius gaussianSlabRotationCurve=0.0d0 return end function gaussianSlabRotationCurve - double precision function gaussianSlabRotationCurveGradient(self,radius,componentType,massType) + double precision function gaussianSlabRotationCurveGradient(self,radius) !!{ Rotation curve gradient for a infinite extent Gaussian slab. !!} implicit none - class (massDistributionGaussianSlab), intent(inout) :: self - double precision , intent(in ) :: radius - type (enumerationComponentTypeType), intent(in ), optional :: componentType - type (enumerationMassTypeType ), intent(in ), optional :: massType - !$GLC attributes unused :: self, radius, componentType, massType + class (massDistributionGaussianSlab), intent(inout) :: self + double precision , intent(in ) :: radius + !$GLC attributes unused :: self, radius gaussianSlabRotationCurveGradient=0.0d0 return end function gaussianSlabRotationCurveGradient - double precision function gaussianSlabSurfaceDensity(self,coordinates,componentType,massType) + double precision function gaussianSlabSurfaceDensity(self,coordinates) !!{ Return the surface density at the specified {\normalfont \ttfamily coordinates} in a Gaussian slab mass distribution. !!} use :: Coordinates , only : coordinate use :: Numerical_Constants_Math, only : Pi implicit none - class (massDistributionGaussianSlab), intent(inout) :: self - class (coordinate ), intent(in ) :: coordinates - type (enumerationComponentTypeType), intent(in ), optional :: componentType - type (enumerationMassTypeType ), intent(in ), optional :: massType + class(massDistributionGaussianSlab), intent(inout) :: self + class(coordinate ), intent(in ) :: coordinates !$GLC attributes unused :: coordinates - if (.not.self%matches(componentType,massType)) then - gaussianSlabSurfaceDensity=0.0d0 - return - end if gaussianSlabSurfaceDensity=+sqrt(2.0d0*Pi) & & *self%scaleHeight & & *self%densityCentral return end function gaussianSlabSurfaceDensity - double precision function gaussianSlabSurfaceDensityRadialMoment(self,moment,radiusMinimum,radiusMaximum,isInfinite,componentType,massType) + double precision function gaussianSlabSurfaceDensityRadialMoment(self,moment,radiusMinimum,radiusMaximum,isInfinite) !!{ Compute radial moments of the Gaussian slab mass distribution surface density profile. !!} @@ -257,14 +239,8 @@ double precision function gaussianSlabSurfaceDensityRadialMoment(self,moment,rad double precision , intent(in ) :: moment double precision , intent(in ), optional :: radiusMinimum, radiusMaximum logical , intent( out), optional :: isInfinite - type (enumerationComponentTypeType), intent(in ), optional :: componentType - type (enumerationMassTypeType ), intent(in ), optional :: massType !$GLC attributes unused :: self, moment, radiusMinimum, radiusMaximum - if (.not.self%matches(componentType,massType)) then - gaussianSlabSurfaceDensityRadialMoment=0.0d0 - return - end if ! All moments are infinite. gaussianSlabSurfaceDensityRadialMoment=huge(0.0d0) if (present(isInfinite)) then @@ -275,16 +251,14 @@ double precision function gaussianSlabSurfaceDensityRadialMoment(self,moment,rad return end function gaussianSlabSurfaceDensityRadialMoment - double precision function gaussianSlabRadiusHalfMass(self,componentType,massType) + double precision function gaussianSlabRadiusHalfMass(self) !!{ Return the half-mass radius for an infinite extent Gaussian slab mass distribution. !!} use :: Error, only : Error_Report implicit none - class(massDistributionGaussianSlab), intent(inout) :: self - type (enumerationComponentTypeType), intent(in ), optional :: componentType - type (enumerationMassTypeType ), intent(in ), optional :: massType - !$GLC attributes unused :: self, componentType, massType + class(massDistributionGaussianSlab), intent(inout) :: self + !$GLC attributes unused :: self gaussianSlabRadiusHalfMass=0.0d0 call Error_Report('half mass radius is undefined'//{introspection:location}) diff --git a/source/mass_distributions.cylindrical.Miyamoto_Nagai.F90 b/source/mass_distributions.cylindrical.Miyamoto_Nagai.F90 index ec0ad3a963..8127eebae2 100644 --- a/source/mass_distributions.cylindrical.Miyamoto_Nagai.F90 +++ b/source/mass_distributions.cylindrical.Miyamoto_Nagai.F90 @@ -52,6 +52,7 @@ procedure :: surfaceDensityRadialMoment => miyamotoNagaiSurfaceDensityRadialMoment procedure :: massEnclosedBySphere => miyamotoNagaiMassEnclosedBySphere procedure :: massEnclosedTabulate => miyamotoNagaiMassEnclosedTabulate + procedure :: potentialIsAnalytic => miyamotoNagaiPotentialIsAnalytic procedure :: potential => miyamotoNagaiPotential procedure :: rotationCurve => miyamotoNagaiRotationCurve procedure :: rotationCurveGradient => miyamotoNagaiRotationCurveGradient @@ -189,23 +190,17 @@ function miyamotoNagaiConstructorInternal(a,b,mass,dimensionless,componentType,m return end function miyamotoNagaiConstructorInternal - double precision function miyamotoNagaiDensity(self,coordinates,componentType,massType) + double precision function miyamotoNagaiDensity(self,coordinates) !!{ Return the density at the specified {\normalfont \ttfamily coordinates} in an \citep{miyamoto_three-dimensional_1975} disk mass distribution. !!} use :: Coordinates, only : assignment(=), coordinateCylindrical implicit none - class (massDistributionMiyamotoNagai), intent(inout) :: self - class (coordinate ), intent(in ) :: coordinates - type (enumerationComponentTypeType ), intent(in ), optional :: componentType - type (enumerationMassTypeType ), intent(in ), optional :: massType - type (coordinateCylindrical ) :: position - double precision :: r , z + class (massDistributionMiyamotoNagai), intent(inout) :: self + class (coordinate ), intent(in ) :: coordinates + type (coordinateCylindrical ) :: position + double precision :: r , z - if (.not.self%matches(componentType,massType)) then - miyamotoNagaiDensity=0.0d0 - return - end if ! Get position in cylindrical coordinate system. position=coordinates ! Compute density. @@ -251,35 +246,27 @@ double precision function miyamotoNagaiDensity(self,coordinates,componentType,ma return end function miyamotoNagaiDensity - double precision function miyamotoNagaiDensitySphericalAverage(self,radius,componentType,massType) + double precision function miyamotoNagaiDensitySphericalAverage(self,radius) !!{ Return the spherically-averaged density at the specified {\normalfont \ttfamily radius} in an \citep{miyamoto_three-dimensional_1975} disk mass distribution. !!} implicit none - class (massDistributionMiyamotoNagai), intent(inout) :: self - double precision , intent(in ) :: radius - type (enumerationComponentTypeType ), intent(in ), optional :: componentType - type (enumerationMassTypeType ), intent(in ), optional :: massType - + class (massDistributionMiyamotoNagai), intent(inout) :: self + double precision , intent(in ) :: radius + miyamotoNagaiDensitySphericalAverage=0.0d0 call Error_Report('spherically-averaged density profile is not implemented'//{introspection:location}) return end function miyamotoNagaiDensitySphericalAverage - double precision function miyamotoNagaiMassEnclosedBySphere(self,radius,componentType,massType) + double precision function miyamotoNagaiMassEnclosedBySphere(self,radius) !!{ Computes the mass enclosed within a sphere of given {\normalfont \ttfamily radius} for \citep{miyamoto_three-dimensional_1975} disk mass distributions. !!} implicit none - class (massDistributionMiyamotoNagai), intent(inout), target :: self - double precision , intent(in ) :: radius - type (enumerationComponentTypeType ), intent(in ), optional :: componentType - type (enumerationMassTypeType ), intent(in ), optional :: massType - - if (.not.self%matches(componentType,massType)) then - miyamotoNagaiMassEnclosedBySphere=0.0d0 - return - end if + class (massDistributionMiyamotoNagai), intent(inout), target :: self + double precision , intent(in ) :: radius + ! Ensure mass enclosed profile is tabulated. call self%massEnclosedTabulate() ! Evaluate the mass enclosed. @@ -287,19 +274,13 @@ double precision function miyamotoNagaiMassEnclosedBySphere(self,radius,componen return end function miyamotoNagaiMassEnclosedBySphere - double precision function miyamotoNagaiRadiusHalfMass(self,componentType,massType) + double precision function miyamotoNagaiRadiusHalfMass(self) !!{ Return the half-mass radius in a Miyamoto-Nagai mass distribution. !!} implicit none - class(massDistributionMiyamotoNagai), intent(inout) :: self - type (enumerationComponentTypeType ), intent(in ), optional :: componentType - type (enumerationMassTypeType ), intent(in ), optional :: massType + class(massDistributionMiyamotoNagai), intent(inout) :: self - if (.not.self%matches(componentType,massType)) then - miyamotoNagaiRadiusHalfMass=0.0d0 - return - end if ! Ensure mass enclosed profile is tabulated. call self%massEnclosedTabulate() ! Return the half-mass radius. @@ -462,22 +443,16 @@ end function integrandSurfaceDensity end subroutine miyamotoNagaiSurfaceDensityTabulate - double precision function miyamotoNagaiSurfaceDensity(self,coordinates,componentType,massType) + double precision function miyamotoNagaiSurfaceDensity(self,coordinates) !!{ Return the surface density at the specified {\normalfont \ttfamily coordinates} in a Miyamoto-Nagai mass distribution. !!} use :: Coordinates, only : assignment(=), coordinateCylindrical implicit none - class(massDistributionMiyamotoNagai), intent(inout) :: self - class(coordinate ), intent(in ) :: coordinates - type (coordinateCylindrical ) :: position - type (enumerationComponentTypeType ), intent(in ), optional :: componentType - type (enumerationMassTypeType ), intent(in ), optional :: massType - - if (.not.self%matches(componentType,massType)) then - miyamotoNagaiSurfaceDensity=0.0d0 - return - end if + class(massDistributionMiyamotoNagai), intent(inout) :: self + class(coordinate ), intent(in ) :: coordinates + type (coordinateCylindrical ) :: position + ! Ensure surface density profile is tabulated. call self%surfaceDensityTabulate() ! Evaluate the surface density. @@ -486,7 +461,7 @@ double precision function miyamotoNagaiSurfaceDensity(self,coordinates,component return end function miyamotoNagaiSurfaceDensity - double precision function miyamotoNagaiSurfaceDensityRadialMoment(self,moment,radiusMinimum,radiusMaximum,isInfinite,componentType,massType) + double precision function miyamotoNagaiSurfaceDensityRadialMoment(self,moment,radiusMinimum,radiusMaximum,isInfinite) !!{ Compute radial moments of the Miyamoto-Nagai mass distribution surface density profile. !!} @@ -497,15 +472,9 @@ double precision function miyamotoNagaiSurfaceDensityRadialMoment(self,moment,ra double precision , intent(in ) :: moment double precision , intent(in ), optional :: radiusMinimum , radiusMaximum logical , intent( out), optional :: isInfinite - type (enumerationComponentTypeType ), intent(in ), optional :: componentType - type (enumerationMassTypeType ), intent(in ), optional :: massType procedure (tablesIntegrationWeightFunction), pointer :: integrandWeightFunction double precision :: radiusMinimumActual , radiusMaximumActual - if (.not.self%matches(componentType,massType)) then - miyamotoNagaiSurfaceDensityRadialMoment=0.0d0 - return - end if ! Set infinity status. if (present(isInfinite)) isInfinite=.false. ! Ensure surface density profile is tabulated. @@ -570,22 +539,16 @@ end function momentWeight end function miyamotoNagaiSurfaceDensityRadialMoment - double precision function miyamotoNagaiRotationCurve(self,radius,componentType,massType) + double precision function miyamotoNagaiRotationCurve(self,radius) !!{ Return the mid-plane rotation curve for a Miyamoto-Nagai mass distribution. !!} use :: Numerical_Constants_Astronomical, only : gravitationalConstantGalacticus implicit none - class (massDistributionMiyamotoNagai), intent(inout) :: self - double precision , intent(in ) :: radius - type (enumerationComponentTypeType ), intent(in ), optional :: componentType - type (enumerationMassTypeType ), intent(in ), optional :: massType - double precision :: r + class (massDistributionMiyamotoNagai), intent(inout) :: self + double precision , intent(in ) :: radius + double precision :: r - if (.not.self%matches(componentType,massType)) then - miyamotoNagaiRotationCurve=0.0d0 - return - end if ! Get dimensionless radius. r=radius/self%a ! Evaluate the rotation curve. @@ -608,22 +571,16 @@ double precision function miyamotoNagaiRotationCurve(self,radius,componentType,m return end function miyamotoNagaiRotationCurve - double precision function miyamotoNagaiRotationCurveGradient(self,radius,componentType,massType) + double precision function miyamotoNagaiRotationCurveGradient(self,radius) !!{ Return the mid-plane rotation curve gradient for an \citep{miyamoto_three-dimensional_1975} disk. !!} use :: Numerical_Constants_Astronomical, only : gravitationalConstantGalacticus implicit none - class (massDistributionMiyamotoNagai), intent(inout) :: self - double precision , intent(in ) :: radius - type (enumerationComponentTypeType ), intent(in ), optional :: componentType - type (enumerationMassTypeType ), intent(in ), optional :: massType - double precision :: r + class (massDistributionMiyamotoNagai), intent(inout) :: self + double precision , intent(in ) :: radius + double precision :: r - if (.not.self%matches(componentType,massType)) then - miyamotoNagaiRotationCurveGradient=0.0d0 - return - end if ! Get dimensionless radius. r=radius/self%a ! Evaluate the rotation curve. @@ -656,24 +613,32 @@ double precision function miyamotoNagaiRotationCurveGradient(self,radius,compone return end function miyamotoNagaiRotationCurveGradient - double precision function miyamotoNagaiPotential(self,coordinates,componentType,massType) + logical function miyamotoNagaiPotentialIsAnalytic(self) result(isAnalytic) + !!{ + Return that the potential has an analytic form. + !!} + implicit none + class(massDistributionMiyamotoNagai), intent(inout) :: self + + isAnalytic=.true. + return + end function miyamotoNagaiPotentialIsAnalytic + + double precision function miyamotoNagaiPotential(self,coordinates,status) !!{ Return the gravitational potential for an \citep{miyamoto_three-dimensional_1975} disk. !!} use :: Coordinates , only : assignment(=) , coordinateCylindrical + use :: Galactic_Structure_Options , only : structureErrorCodeSuccess use :: Numerical_Constants_Astronomical, only : gravitationalConstantGalacticus implicit none - class (massDistributionMiyamotoNagai), intent(inout) :: self - class (coordinate ), intent(in ) :: coordinates - type (enumerationComponentTypeType ), intent(in ), optional :: componentType - type (enumerationMassTypeType ), intent(in ), optional :: massType - type (coordinateCylindrical ) :: position - double precision :: r , z + class (massDistributionMiyamotoNagai ), intent(inout), target :: self + class (coordinate ), intent(in ) :: coordinates + type (enumerationStructureErrorCodeType), intent( out), optional :: status + type (coordinateCylindrical ) :: position + double precision :: r , z - if (.not.self%matches(componentType,massType)) then - miyamotoNagaiPotential=0.0d0 - return - end if + if (present(status)) status=structureErrorCodeSuccess ! Get position in cylindrical coordinate system. position=coordinates ! Compute density. diff --git a/source/mass_distributions.cylindrical.exponential_disk.F90 b/source/mass_distributions.cylindrical.exponential_disk.F90 index 97ceb28f8d..7faa662bf1 100644 --- a/source/mass_distributions.cylindrical.exponential_disk.F90 +++ b/source/mass_distributions.cylindrical.exponential_disk.F90 @@ -22,7 +22,7 @@ !!} !$ use :: OMP_Lib, only : omp_lock_kind - use :: Tables , only : table1DLogarithmicLinear + use :: Tables , only : table1DLogarithmicLinear !![ @@ -67,25 +67,30 @@ !!] - final :: exponentialDiskDestructor - procedure :: tabulate => exponentialDiskTabulate - procedure :: besselFactorRotationCurve => exponentialDiskBesselFactorRotationCurve - procedure :: besselFactorRotationCurveGradient => exponentialDiskBesselFactorRotationCurveGradient - procedure :: besselFactorPotential => exponentialDiskBesselFactorPotential - procedure :: density => exponentialDiskDensity - procedure :: densitySphericalAverage => exponentialDiskDensitySphericalAverage - procedure :: surfaceDensity => exponentialDiskSurfaceDensity - procedure :: massEnclosedBySphere => exponentialDiskMassEnclosedBySphere - procedure :: potential => exponentialDiskPotential - procedure :: rotationCurve => exponentialDiskRotationCurve - procedure :: rotationCurveGradient => exponentialDiskRotationCurveGradient - procedure :: radiusHalfMass => exponentialDiskRadiusHalfMass - procedure :: surfaceDensityRadialMoment => exponentialDiskSurfaceDensityRadialMoment - procedure :: acceleration => exponentialDiskAcceleration - procedure :: tidalTensor => exponentialDiskTidalTensor - procedure :: accelerationTabulate => exponentialDiskAccelerationTabulate - procedure :: accelerationInterpolate => exponentialDiskAccelerationInterpolate - procedure :: positionSample => exponentialDiskPositionSample + final :: exponentialDiskDestructor + procedure :: tabulate => exponentialDiskTabulate + procedure :: besselFactorRotationCurve => exponentialDiskBesselFactorRotationCurve + procedure :: besselFactorRotationCurveGradient => exponentialDiskBesselFactorRotationCurveGradient + procedure :: besselFactorPotential => exponentialDiskBesselFactorPotential + procedure :: assumeMonotonicDecreasingSurfaceDensity => exponentialDiskAssumeMonotonicDecreasingSurfaceDensity + procedure :: massTotal => exponentialDiskMassTotal + procedure :: density => exponentialDiskDensity + procedure :: densityGradientRadial => exponentialDensityGradientRadial + procedure :: densitySphericalAverage => exponentialDiskDensitySphericalAverage + procedure :: surfaceDensity => exponentialDiskSurfaceDensity + procedure :: massEnclosedBySphere => exponentialDiskMassEnclosedBySphere + procedure :: radiusEnclosingSurfaceDensity => exponentialDiskRadiusEnclosingSurfaceDensity + procedure :: potentialIsAnalytic => exponentialDiskPotentialIsAnalytic + procedure :: potential => exponentialDiskPotential + procedure :: rotationCurve => exponentialDiskRotationCurve + procedure :: rotationCurveGradient => exponentialDiskRotationCurveGradient + procedure :: radiusHalfMass => exponentialDiskRadiusHalfMass + procedure :: surfaceDensityRadialMoment => exponentialDiskSurfaceDensityRadialMoment + procedure :: acceleration => exponentialDiskAcceleration + procedure :: tidalTensor => exponentialDiskTidalTensor + procedure :: accelerationTabulate => exponentialDiskAccelerationTabulate + procedure :: accelerationInterpolate => exponentialDiskAccelerationInterpolate + procedure :: positionSample => exponentialDiskPositionSample end type massDistributionExponentialDisk interface massDistributionExponentialDisk @@ -299,45 +304,55 @@ subroutine exponentialDiskTabulate(self) return end subroutine exponentialDiskTabulate - double precision function exponentialDiskRadiusHalfMass(self,componentType,massType) + logical function exponentialDiskAssumeMonotonicDecreasingSurfaceDensity(self) result(assumeMonotonicDecreasingSurfaceDensity) + !!{ + Return true indicating that this distribution has a monotonically-decreasing surface density. + !!} + implicit none + class(massDistributionExponentialDisk), intent(inout) :: self + + assumeMonotonicDecreasingSurfaceDensity=.true. + return + end function exponentialDiskAssumeMonotonicDecreasingSurfaceDensity + + double precision function exponentialDiskMassTotal(self) + !!{ + Return the total mass in an exponential disk distribution. + !!} + implicit none + class(massDistributionExponentialDisk), intent(inout) :: self + + exponentialDiskMassTotal=self%mass + return + end function exponentialDiskMassTotal + + double precision function exponentialDiskRadiusHalfMass(self) !!{ Return the half-mass radius in an exponential disk mass distribution. !!} implicit none - class (massDistributionExponentialDisk), intent(inout) :: self - type (enumerationComponentTypeType ), intent(in ), optional :: componentType - type (enumerationMassTypeType ), intent(in ), optional :: massType - double precision , parameter :: radiusHalfMassToScaleRadius=1.678346990d0 + class (massDistributionExponentialDisk), intent(inout) :: self + double precision , parameter :: radiusHalfMassToScaleRadius=1.678346990d0 - if (.not.self%matches(componentType,massType)) then - exponentialDiskRadiusHalfMass=0.0d0 - return - end if exponentialDiskRadiusHalfMass=+radiusHalfMassToScaleRadius & & *self%scaleRadius return end function exponentialDiskRadiusHalfMass - double precision function exponentialDiskDensity(self,coordinates,componentType,massType) + double precision function exponentialDiskDensity(self,coordinates) !!{ Return the density at the specified {\normalfont \ttfamily coordinates} in an exponential disk mass distribution. !!} use :: Coordinates, only : assignment(=), coordinateCylindrical use :: Error , only : Error_Report implicit none - class (massDistributionExponentialDisk), intent(inout) :: self - class (coordinate ), intent(in ) :: coordinates - type (enumerationComponentTypeType ), intent(in ), optional :: componentType - type (enumerationMassTypeType ), intent(in ), optional :: massType - type (coordinateCylindrical ) :: position - double precision , parameter :: coshArgumentMaximum=50.0d0 - double precision :: r , z, & - & coshTerm + class (massDistributionExponentialDisk), intent(inout) :: self + class (coordinate ), intent(in ) :: coordinates + type (coordinateCylindrical ) :: position + double precision , parameter :: coshArgumentMaximum=50.0d0 + double precision :: r , z, & + & coshTerm - if (.not.self%matches(componentType,massType)) then - exponentialDiskDensity=0.0d0 - return - end if ! If disk is razor thin, density is undefined. if (self%scaleHeight <= 0.0d0) call Error_Report('density undefined for razor-thin disk'//{introspection:location}) ! Get position in cylindrical coordinate system. @@ -354,21 +369,53 @@ double precision function exponentialDiskDensity(self,coordinates,componentType, return end function exponentialDiskDensity - double precision function exponentialDiskDensitySphericalAverage(self,radius,componentType,massType) + double precision function exponentialDensityGradientRadial(self,coordinates,logarithmic) + !!{ + Return the density gradient in the radial direction in a scaled spherical mass distribution. + !!} + use :: Coordinates, only : assignment(=), coordinateCylindrical + use :: Error , only : Error_Report + implicit none + class (massDistributionExponentialDisk), intent(inout), target :: self + class (coordinate ), intent(in ) :: coordinates + logical , intent(in ), optional :: logarithmic + double precision , parameter :: coshArgumentMaximum=50.0d0 + type (coordinateCylindrical ) :: position + double precision :: r , z, & + & coshTerm + !![ + + !!] + + ! If disk is razor thin, density is undefined. + if (self%scaleHeight <= 0.0d0) call Error_Report('density undefined for razor-thin disk'//{introspection:location}) + ! Get position in cylindrical coordinate system. + position=coordinates + ! Compute density. + r= position%r() /self%scaleRadius + z=abs(position%z())/self%scaleHeight + if (z > coshArgumentMaximum) then + coshTerm=(2.0d0*exp(-z)/(1.0d0+exp(-2.0d0*z)))**2 + else + coshTerm=1.0d0/cosh(z)**2 + end if + exponentialDensityGradientRadial=-(r+2.0d0*z*tanh(z)) + if (.not.logarithmic_) & + & exponentialDensityGradientRadial=+ exponentialDensityGradientRadial & + & *self %density (coordinates) & + & /position%rSpherical ( ) + return + end function exponentialDensityGradientRadial + + double precision function exponentialDiskDensitySphericalAverage(self,radius) !!{ Return the spherically-averaged density at the specified {\normalfont \ttfamily coordinates} in an exponential disk mass distribution. Note that this assumes the thin-disk approximation. !!} implicit none - class (massDistributionExponentialDisk), intent(inout) :: self - double precision , intent(in ) :: radius - type (enumerationComponentTypeType ), intent(in ), optional :: componentType - type (enumerationMassTypeType ), intent(in ), optional :: massType + class (massDistributionExponentialDisk), intent(inout) :: self + double precision , intent(in ) :: radius - if (.not.self%matches(componentType,massType)) then - exponentialDiskDensitySphericalAverage=0.0d0 - return - end if exponentialDiskDensitySphericalAverage=+0.5d0 & & * self%surfaceDensityNormalization & & / radius & @@ -379,23 +426,17 @@ double precision function exponentialDiskDensitySphericalAverage(self,radius,com return end function exponentialDiskDensitySphericalAverage - double precision function exponentialDiskMassEnclosedBySphere(self,radius,componentType,massType) + double precision function exponentialDiskMassEnclosedBySphere(self,radius) !!{ Computes the mass enclosed within a sphere of given {\normalfont \ttfamily radius} for exponential disk mass distributions. Note that this assumes the thin-disk approximation. !!} use :: Numerical_Constants_Math, only : Pi implicit none - class (massDistributionExponentialDisk), intent(inout), target :: self - double precision , intent(in ) :: radius - type (enumerationComponentTypeType ), intent(in ), optional :: componentType - type (enumerationMassTypeType ), intent(in ), optional :: massType - double precision :: fractionalRadius + class (massDistributionExponentialDisk), intent(inout), target :: self + double precision , intent(in ) :: radius + double precision :: fractionalRadius - if (.not.self%matches(componentType,massType)) then - exponentialDiskMassEnclosedBySphere=0.0d0 - return - end if fractionalRadius =+radius & & /self%scaleRadius exponentialDiskMassEnclosedBySphere=+2.0d0 & @@ -413,22 +454,16 @@ double precision function exponentialDiskMassEnclosedBySphere(self,radius,compon return end function exponentialDiskMassEnclosedBySphere - double precision function exponentialDiskSurfaceDensity(self,coordinates,componentType,massType) + double precision function exponentialDiskSurfaceDensity(self,coordinates) !!{ Return the surface density at the specified {\normalfont \ttfamily coordinates} in an exponential disk mass distribution. !!} use :: Coordinates, only : coordinate implicit none - class (massDistributionExponentialDisk), intent(inout) :: self - class (coordinate ), intent(in ) :: coordinates - type (enumerationComponentTypeType ), intent(in ), optional :: componentType - type (enumerationMassTypeType ), intent(in ), optional :: massType - double precision :: r + class (massDistributionExponentialDisk), intent(inout) :: self + class (coordinate ), intent(in ) :: coordinates + double precision :: r - if (.not.self%matches(componentType,massType)) then - exponentialDiskSurfaceDensity=0.0d0 - return - end if ! Get the radial coordinate. r=coordinates%rCylindrical()/self%scaleRadius ! Compute the density. @@ -436,23 +471,34 @@ double precision function exponentialDiskSurfaceDensity(self,coordinates,compone return end function exponentialDiskSurfaceDensity - double precision function exponentialDiskRotationCurve(self,radius,componentType,massType) + double precision function exponentialDiskRadiusEnclosingSurfaceDensity(self,densitySurface,radiusGuess) result(radius) + !!{ + Computes the radius enclosing a given surface density for exponential disk mass distributions. + !!} + implicit none + class (massDistributionExponentialDisk), intent(inout), target :: self + double precision , intent(in ) :: densitySurface + double precision , intent(in ), optional :: radiusGuess + + radius=- self%scaleRadius & + & *log( & + & + densitySurface & + & /self%surfaceDensityNormalization & + & ) + return + end function exponentialDiskRadiusEnclosingSurfaceDensity + + double precision function exponentialDiskRotationCurve(self,radius) !!{ Return the mid-plane rotation curve for an exponential disk. !!} use :: Numerical_Constants_Astronomical, only : gravitationalConstantGalacticus implicit none - class (massDistributionExponentialDisk), intent(inout) :: self - double precision , intent(in ) :: radius - type (enumerationComponentTypeType ), intent(in ), optional :: componentType - type (enumerationMassTypeType ), intent(in ), optional :: massType - double precision :: r , halfRadius, & - & radiusFactor + class (massDistributionExponentialDisk), intent(inout) :: self + double precision , intent(in ) :: radius + double precision :: r , halfRadius, & + & radiusFactor - if (.not.self%matches(componentType,massType)) then - exponentialDiskRotationCurve=0.0d0 - return - end if ! Get scale-free radius. r=radius/self%scaleRadius ! Compute rotation curve. @@ -486,23 +532,17 @@ double precision function exponentialDiskRotationCurve(self,radius,componentType return end function exponentialDiskRotationCurve - double precision function exponentialDiskRotationCurveGradient(self,radius,componentType,massType) + double precision function exponentialDiskRotationCurveGradient(self,radius) !!{ Return the mid-plane rotation curve gradient for an exponential disk. !!} use :: Numerical_Constants_Astronomical, only : gravitationalConstantGalacticus implicit none - class (massDistributionExponentialDisk), intent(inout) :: self - double precision , intent(in ) :: radius - type (enumerationComponentTypeType ), intent(in ), optional :: componentType - type (enumerationMassTypeType ), intent(in ), optional :: massType - double precision , parameter :: fractionalRadiusMaximum=30.0d0 - double precision :: besselArgument , besselFactor + class (massDistributionExponentialDisk), intent(inout) :: self + double precision , intent(in ) :: radius + double precision , parameter :: fractionalRadiusMaximum=30.0d0 + double precision :: besselArgument , besselFactor - if (.not.self%matches(componentType,massType)) then - exponentialDiskRotationCurveGradient=0.0d0 - return - end if ! Compute Bessel functions argument. besselArgument=+radius & & /2.0d0 & @@ -520,30 +560,38 @@ double precision function exponentialDiskRotationCurveGradient(self,radius,compo end if ! Make dimensionful if necessary. if (.not.self%dimensionless) exponentialDiskRotationCurveGradient= & - & +sqrt(gravitationalConstantGalacticus) & + & +gravitationalConstantGalacticus & & *exponentialDiskRotationCurveGradient return end function exponentialDiskRotationCurveGradient - double precision function exponentialDiskPotential(self,coordinates,componentType,massType) + logical function exponentialDiskPotentialIsAnalytic(self) result(isAnalytic) + !!{ + Return that the potential has an analytic form. + !!} + implicit none + class(massDistributionExponentialDisk), intent(inout) :: self + + isAnalytic=.true. + return + end function exponentialDiskPotentialIsAnalytic + + double precision function exponentialDiskPotential(self,coordinates,status) !!{ Return the gravitational potential for an exponential disk. !!} use :: Coordinates , only : assignment(=) , coordinateCylindrical + use :: Galactic_Structure_Options , only : structureErrorCodeSuccess use :: Numerical_Constants_Astronomical, only : gravitationalConstantGalacticus implicit none - class (massDistributionExponentialDisk), intent(inout) :: self - class (coordinate ), intent(in ) :: coordinates - type (enumerationComponentTypeType ), intent(in ), optional :: componentType - type (enumerationMassTypeType ), intent(in ), optional :: massType - type (coordinateCylindrical ) :: position - double precision :: correctionSmallRadius, halfRadius, & - & radius - - if (.not.self%matches(componentType,massType)) then - exponentialDiskPotential=0.0d0 - return - end if + class (massDistributionExponentialDisk ), intent(inout), target :: self + class (coordinate ), intent(in ) :: coordinates + type (enumerationStructureErrorCodeType), intent( out), optional :: status + type (coordinateCylindrical ) :: position + double precision :: correctionSmallRadius, halfRadius, & + & radius + + if (present(status)) status=structureErrorCodeSuccess ! Get position in cylindrical coordinate system. position=coordinates ! Compute density. @@ -613,8 +661,8 @@ Compute Bessel function factors appearing in the expression for an razor-thin ex implicit none class (massDistributionExponentialDisk), intent(inout) :: self double precision , intent(in ) :: halfRadius - double precision , parameter :: halfRadiusSmall =1.0d-3 - integer , parameter :: rotationCurvePointsPerDecade=10 + double precision , parameter :: halfRadiusSmall =1.0d-3 + integer , parameter :: rotationCurvePointsPerDecade=100 integer :: iPoint , rotationCurvePointsCount double precision :: x logical :: makeTable @@ -676,7 +724,7 @@ Compute Bessel function factors appearing in the expression for a razor-thin exp double precision , intent(in ) :: halfRadius double precision , parameter :: halfRadiusSmall =1.0d-3 double precision , parameter :: halfRadiusLarge =1.0d+2 - integer , parameter :: rotationCurveGradientPointsPerDecade=10 + integer , parameter :: rotationCurveGradientPointsPerDecade=100 integer :: iPoint , rotationCurveGradientPointsCount double precision :: x @@ -713,17 +761,17 @@ Compute Bessel function factors appearing in the expression for a razor-thin exp x=self%rotationCurveGradientTable%x(iPoint) call self%rotationCurveGradientTable%populate & & ( & - & 2.0d0 & + & +2.0d0 & & *x & & *( & - & Bessel_Function_I0(x)*Bessel_Function_K0(x) & - & -Bessel_Function_I1(x)*Bessel_Function_K1(x) & + & + Bessel_Function_I0(x) *Bessel_Function_K0(x) & + & - Bessel_Function_I1(x) *Bessel_Function_K1(x) & & ) & & +x**2 & & *( & - & Bessel_Function_I1(x) *Bessel_Function_K0(x) & + & + Bessel_Function_I1(x) *Bessel_Function_K0(x) & & - Bessel_Function_K1(x) *Bessel_Function_I0(x) & - & -( Bessel_Function_I0(x)-Bessel_Function_I1(x)/x)*Bessel_Function_K1(x) & + & -(+Bessel_Function_I0(x)-Bessel_Function_I1(x)/x)*Bessel_Function_K1(x) & & -(-Bessel_Function_K0(x)-Bessel_Function_K1(x)/x)*Bessel_Function_I1(x) & & ), & & iPoint & @@ -738,7 +786,7 @@ Compute Bessel function factors appearing in the expression for a razor-thin exp return end function exponentialDiskBesselFactorRotationCurveGradient - double precision function exponentialDiskSurfaceDensityRadialMoment(self,moment,radiusMinimum,radiusMaximum,isInfinite,componentType,massType) + double precision function exponentialDiskSurfaceDensityRadialMoment(self,moment,radiusMinimum,radiusMaximum,isInfinite) !!{ Compute radial moments of the exponential disk mass distribution surface density profile. !!} @@ -749,14 +797,8 @@ double precision function exponentialDiskSurfaceDensityRadialMoment(self,moment, double precision , intent(in ) :: moment double precision , intent(in ), optional :: radiusMinimum, radiusMaximum logical , intent( out), optional :: isInfinite - type (enumerationComponentTypeType ), intent(in ), optional :: componentType - type (enumerationMassTypeType ), intent(in ), optional :: massType double precision :: integralLow , integralHigh - if (.not.self%matches(componentType,massType)) then - exponentialDiskSurfaceDensityRadialMoment=0.0d0 - return - end if ! All moments n>-1 are finite. if (present(isInfinite)) isInfinite=(moment <= -1.0d0) if (moment <= -1.0d0) then @@ -779,7 +821,7 @@ double precision function exponentialDiskSurfaceDensityRadialMoment(self,moment, return end function exponentialDiskSurfaceDensityRadialMoment - function exponentialDiskAcceleration(self,coordinates,componentType,massType) + function exponentialDiskAcceleration(self,coordinates) !!{ Computes the gravitational acceleration at {\normalfont \ttfamily coordinates} for exponential disk mass distributions. !!} @@ -787,20 +829,14 @@ function exponentialDiskAcceleration(self,coordinates,componentType,massType) use :: Numerical_Constants_Astronomical, only : gigaYear , gravitationalConstantGalacticus, megaParsec use :: Numerical_Constants_Prefixes , only : kilo implicit none - double precision , dimension(3 ) :: exponentialDiskAcceleration - class (massDistributionExponentialDisk), intent(inout) :: self - class (coordinate ), intent(in ) :: coordinates - type (enumerationComponentTypeType ), intent(in ) , optional :: componentType - type (enumerationMassTypeType ), intent(in ) , optional :: massType - double precision , dimension(3 ) :: positionCartesian - type (coordinateCylindrical ) :: coordinatesCylindrical - type (coordinateCartesian ) :: coordinatesCartesian - double precision :: accelerationRadial , accelerationVertical + double precision , dimension(3) :: exponentialDiskAcceleration + class (massDistributionExponentialDisk), intent(inout) :: self + class (coordinate ), intent(in ) :: coordinates + double precision , dimension(3) :: positionCartesian + type (coordinateCylindrical ) :: coordinatesCylindrical + type (coordinateCartesian ) :: coordinatesCartesian + double precision :: accelerationRadial , accelerationVertical - if (.not.self%matches(componentType,massType)) then - exponentialDiskAcceleration=0.0d0 - return - end if ! Get position in cylindrical and Cartesian coordinate systems. coordinatesCylindrical=coordinates coordinatesCartesian =coordinates @@ -831,31 +867,24 @@ function exponentialDiskAcceleration(self,coordinates,componentType,massType) return end function exponentialDiskAcceleration - function exponentialDiskTidalTensor(self,coordinates,componentType,massType) + function exponentialDiskTidalTensor(self,coordinates) !!{ Computes the gravitational tidal tensor at {\normalfont \ttfamily coordinates} for exponential disk mass distributions. !!} use :: Coordinates , only : assignment(=) , coordinateCartesian, coordinateCylindrical use :: Numerical_Constants_Astronomical, only : gravitationalConstantGalacticus - use :: Tensors , only : tensorNullR2D3Sym implicit none - type (tensorRank2Dimension3Symmetric ) :: exponentialDiskTidalTensor - class (massDistributionExponentialDisk), intent(inout) :: self - class (coordinate ), intent(in ) :: coordinates - type (enumerationComponentTypeType ), intent(in ), optional :: componentType - type (enumerationMassTypeType ), intent(in ), optional :: massType - double precision , dimension(3 ) :: positionCartesian - double precision , parameter :: radiusCylindricalSmall =1.0d-6 - type (coordinateCartesian ) :: coordinatesCartesian - type (coordinateCylindrical ) :: coordinatesCylindrical - double precision :: accelerationRadial , accelerationVertical , & - & tidalTensorRadialRadial , tidalTensorVerticalVertical, & - & tidalTensorCross , radiusCylindrical - - if (.not.self%matches(componentType,massType)) then - exponentialDiskTidalTensor=tensorNullR2D3Sym - return - end if + type (tensorRank2Dimension3Symmetric ) :: exponentialDiskTidalTensor + class (massDistributionExponentialDisk), intent(inout) :: self + class (coordinate ), intent(in ) :: coordinates + double precision , dimension(3) :: positionCartesian + double precision , parameter :: radiusCylindricalSmall =1.0d-6 + type (coordinateCartesian ) :: coordinatesCartesian + type (coordinateCylindrical ) :: coordinatesCylindrical + double precision :: accelerationRadial , accelerationVertical , & + & tidalTensorRadialRadial , tidalTensorVerticalVertical, & + & tidalTensorCross , radiusCylindrical + ! Get position in cylindrical and Cartesian coordinate systems. coordinatesCylindrical=coordinates coordinatesCartesian =coordinatesCylindrical @@ -1091,11 +1120,11 @@ subroutine exponentialDiskAccelerationTabulate(self) ! Compute the vertical inverse scale-height. Note that our definition of β differs slightly from that of Kuijken & Gilmore ! (1989). They assume a density profile in the vertical direction of the form: ! - ! ρ(z) = sech^ξ(βz/ξ) + ! ρ(z) = sech²(βz/ξ) ! ! while we use: ! - ! ρ(z) = sech^ξ(z/h) + ! ρ(z) = sech²(z/h) ! ! where h is the scale-height. Therefore: ! @@ -1575,25 +1604,19 @@ end function d2Izdz2m end subroutine exponentialDiskAccelerationTabulate - function exponentialDiskPositionSample(self,randomNumberGenerator_,componentType,massType) + function exponentialDiskPositionSample(self,randomNumberGenerator_) !!{ Sample a position from an exponential disk distribution. !!} use :: Lambert_Ws , only : Lambert_Wm1 use :: Numerical_Constants_Math, only : Pi implicit none - double precision , dimension(3) :: exponentialDiskPositionSample - class (massDistributionExponentialDisk), intent(inout) :: self - class (randomNumberGeneratorClass ), intent(inout) :: randomNumberGenerator_ - type (enumerationComponentTypeType ), intent(in ), optional :: componentType - type (enumerationMassTypeType ), intent(in ), optional :: massType - double precision :: radius , height, & - & phi + double precision , dimension(3) :: exponentialDiskPositionSample + class (massDistributionExponentialDisk), intent(inout) :: self + class (randomNumberGeneratorClass ), intent(inout) :: randomNumberGenerator_ + double precision :: radius , height, & + & phi - if (.not.self%matches(componentType,massType)) then - exponentialDiskPositionSample=0.0d0 - return - end if ! Select a radial coordinate. radius=(-1.0d0-Lambert_Wm1((-1.0d0+ randomNumberGenerator_%uniformSample())/exp(1.0d0)))*self%scaleRadius ! Select a vertical coordinate. diff --git a/source/mass_distributions.cylindrical.scaler.F90 b/source/mass_distributions.cylindrical.scaler.F90 index fd55d08ec2..71f0e27c1c 100644 --- a/source/mass_distributions.cylindrical.scaler.F90 +++ b/source/mass_distributions.cylindrical.scaler.F90 @@ -27,7 +27,7 @@ A mass distribution class for scaling cylindrical mass distributions. Specifically, the density at position $\mathbf{x}$ is given by \begin{equation} - \rho(\mathbf{x}) = f_\mathrm{M} \rho^\prime(\mathbf{x}/f_\mathrm{r}), + \rho(\mathbf{x}) = \frac{f_\mathrm{M}}{f_\mathrm{r}^3} \rho^\prime(\mathbf{x}/f_\mathrm{r}), \end{equation} where $\rho^\prime(\mathbf{x})$ is the original mass distribution, and $f_\mathrm{r}=${\normalfont \ttfamily [factorScalingLength]}, and $f_\mathrm{M}=${\normalfont \ttfamily [factorScalingMass]}. @@ -42,20 +42,33 @@ class (massDistributionCylindrical), pointer :: massDistribution_ => null() double precision :: factorScalingLength , factorScalingMass contains - final :: cylindricalScalerDestructor - procedure :: density => cylindricalScalerDensity - procedure :: densitySphericalAverage => cylindricalScalerDensitySphericalAverage - procedure :: surfaceDensity => cylindricalScalerSurfaceDensity - procedure :: radiusHalfMass => cylindricalScalerRadiusHalfMass - procedure :: massEnclosedBySphere => cylindricalScalerMassEnclosedBySphere - procedure :: potential => cylindricalScalerPotential - procedure :: rotationCurve => cylindricalScalerRotationCurve - procedure :: rotationCurveGradient => cylindricalScalerRotationCurveGradient - procedure :: surfaceDensityRadialMoment => cylindricalScalerSurfaceDensityRadialMoment - procedure :: acceleration => cylindricalScalerAcceleration - procedure :: tidalTensor => cylindricalScalerTidalTensor - procedure :: positionSample => cylindricalScalerPositionSample - procedure :: isDimensionless => cylindricalScalerIsDimensionless + !![ + + + + !!] + final :: cylindricalScalerDestructor + procedure :: unscaled => cylindricalScalerUnscaled + procedure :: assumeMonotonicDecreasingSurfaceDensity => cylindricalScalerAssumeMonotonicDecreasingSurfaceDensity + procedure :: massTotal => cylindricalScalerMassTotal + procedure :: density => cylindricalScalerDensity + procedure :: densitySphericalAverage => cylindricalScalerDensitySphericalAverage + procedure :: densityGradientRadial => cylindricalScalerDensityGradientRadial + procedure :: surfaceDensity => cylindricalScalerSurfaceDensity + procedure :: radiusHalfMass => cylindricalScalerRadiusHalfMass + procedure :: massEnclosedBySphere => cylindricalScalerMassEnclosedBySphere + procedure :: radiusEnclosingMass => cylindricalScalerRadiusEnclosingMass + procedure :: radiusEnclosingDensity => cylindricalScalerRadiusEnclosingDensity + procedure :: radiusEnclosingSurfaceDensity => cylindricalScalerRadiusEnclosingSurfaceDensity + procedure :: potentialIsAnalytic => cylindricalScalerPotentialIsAnalytic + procedure :: potential => cylindricalScalerPotential + procedure :: rotationCurve => cylindricalScalerRotationCurve + procedure :: rotationCurveGradient => cylindricalScalerRotationCurveGradient + procedure :: surfaceDensityRadialMoment => cylindricalScalerSurfaceDensityRadialMoment + procedure :: acceleration => cylindricalScalerAcceleration + procedure :: tidalTensor => cylindricalScalerTidalTensor + procedure :: positionSample => cylindricalScalerPositionSample + procedure :: isDimensionless => cylindricalScalerIsDimensionless end type massDistributionCylindricalScaler interface massDistributionCylindricalScaler @@ -119,8 +132,8 @@ function cylindricalScalerConstructorInternal(factorScalingLength,factorScalingM !!] - self%componentType=self%massDistribution_%componentType - self% massType=self%massDistribution_% massType + self%componentType=massDistribution_%componentType + self% massType=massDistribution_% massType return end function cylindricalScalerConstructorInternal @@ -137,6 +150,29 @@ subroutine cylindricalScalerDestructor(self) return end subroutine cylindricalScalerDestructor + function cylindricalScalerUnscaled(self) result(massDistribution_) + !!{ + Return a pointer to the unscaled mass distribution. + !!} + implicit none + class(massDistributionClass ), pointer :: massDistribution_ + class(massDistributionCylindricalScaler), intent(inout) :: self + + massDistribution_ => self%massDistribution_ + return + end function cylindricalScalerUnscaled + + logical function cylindricalScalerAssumeMonotonicDecreasingSurfaceDensity(self) result(assumeMonotonicDecreasingSurfaceDensity) + !!{ + Return true indicating that this distribution has a monotonically-decreasing surface density. + !!} + implicit none + class(massDistributionCylindricalScaler), intent(inout) :: self + + assumeMonotonicDecreasingSurfaceDensity=self%massDistribution_%assumeMonotonicDecreasingSurfaceDensity() + return + end function cylindricalScalerAssumeMonotonicDecreasingSurfaceDensity + logical function cylindricalScalerIsDimensionless(self) !!{ Return the dimensional status. @@ -148,64 +184,90 @@ logical function cylindricalScalerIsDimensionless(self) return end function cylindricalScalerIsDimensionless - double precision function cylindricalScalerDensity(self,coordinates,componentType,massType) + double precision function cylindricalScalerMassTotal(self) + !!{ + Return the total mass in a scaled cylindrical distribution. + !!} + implicit none + class(massDistributionCylindricalScaler), intent(inout):: self + + cylindricalScalerMassTotal=+self%massDistribution_%massTotal () & + & *self %factorScalingMass + return + end function cylindricalScalerMassTotal + + double precision function cylindricalScalerDensity(self,coordinates) !!{ Return the density at the specified {\normalfont \ttfamily coordinates} in a scaled cylindrical distribution. !!} implicit none - class(massDistributionCylindricalScaler), intent(inout) :: self - class(coordinate ), intent(in ) :: coordinates - type (enumerationComponentTypeType ), intent(in ), optional :: componentType - type (enumerationMassTypeType ), intent(in ), optional :: massType - - cylindricalScalerDensity=+self%massDistribution_%density ( & - & coordinates & - & /self%factorScalingLength, & - & componentType , & - & massType & - & ) & - & *self %factorScalingMass & + class(massDistributionCylindricalScaler), intent(inout) :: self + class(coordinate ), intent(in ) :: coordinates + class(coordinate ), allocatable :: coordinatesScaled + + call coordinates%scale(1.0d0/self%factorScalingLength,coordinatesScaled) + cylindricalScalerDensity=+self%massDistribution_%density (coordinatesScaled) & + & *self %factorScalingMass & & /self %factorScalingLength**3 return end function cylindricalScalerDensity - double precision function cylindricalScalerDensitySphericalAverage(self,radius,componentType,massType) + double precision function cylindricalScalerDensityGradientRadial(self,coordinates,logarithmic) + !!{ + Return the density gradient in the radial direction in a scaled cylindrical mass distribution. + !!} + implicit none + class (massDistributionCylindricalScaler), intent(inout), target :: self + class (coordinate ), intent(in ) :: coordinates + logical , intent(in ), optional :: logarithmic + class (coordinate ) , allocatable :: coordinatesScaled + !![ + + !!] + + call coordinates%scale(1.0d0/self%factorScalingLength,coordinatesScaled) + cylindricalScalerDensityGradientRadial=+self%massDistribution_%densityGradientRadial( & + & coordinatesScaled, & + & logarithmic & + & ) + if (.not.logarithmic) & + & cylindricalScalerDensityGradientRadial=+cylindricalScalerDensityGradientRadial & + & *self%factorScalingMass & + & /self%factorScalingLength**4 + return + end function cylindricalScalerDensityGradientRadial + + double precision function cylindricalScalerDensitySphericalAverage(self,radius) !!{ Return the spherically-averaged density at the specified {\normalfont \ttfamily coordinates} in a scaled cylindrical mass distribution. !!} implicit none - class (massDistributionCylindricalScaler), intent(inout) :: self - double precision , intent(in ) :: radius - type (enumerationComponentTypeType ), intent(in ), optional :: componentType - type (enumerationMassTypeType ), intent(in ), optional :: massType + class (massDistributionCylindricalScaler), intent(inout) :: self + double precision , intent(in ) :: radius cylindricalScalerDensitySphericalAverage=+self%massDistribution_%densitySphericalAverage( & & + radius & - & /self%factorScalingLength, & - & componentType , & - & massType & + & /self%factorScalingLength & & ) & & *self %factorScalingMass & & /self %factorScalingLength**3 return end function cylindricalScalerDensitySphericalAverage - double precision function cylindricalScalerRadiusHalfMass(self,componentType,massType) + double precision function cylindricalScalerRadiusHalfMass(self) !!{ Interface for cylindrically symmetric mass distribution half mass radii functions. !!} implicit none - class(massDistributionCylindricalScaler), intent(inout) :: self - type (enumerationComponentTypeType ), intent(in ), optional :: componentType - type (enumerationMassTypeType ), intent(in ), optional :: massType + class(massDistributionCylindricalScaler), intent(inout) :: self - cylindricalScalerRadiusHalfMass=+self%massDistribution_%radiusHalfMass (componentType,massType) & + cylindricalScalerRadiusHalfMass=+self%massDistribution_%radiusHalfMass () & & *self %factorScalingLength return end function cylindricalScalerRadiusHalfMass - double precision function cylindricalScalerMassEnclosedBySphere(self,radius,componentType,massType) + double precision function cylindricalScalerMassEnclosedBySphere(self,radius) !!{ Computes the mass enclosed within a sphere of given {\normalfont \ttfamily radius} for a scaled cylindrical mass distribution. @@ -213,110 +275,189 @@ double precision function cylindricalScalerMassEnclosedBySphere(self,radius,comp implicit none class (massDistributionCylindricalScaler), intent(inout), target :: self double precision , intent(in ) :: radius - type (enumerationComponentTypeType ), intent(in ), optional :: componentType - type (enumerationMassTypeType ), intent(in ), optional :: massType cylindricalScalerMassEnclosedBySphere=+self%massDistribution_%massEnclosedBySphere( & & radius & - & /self%factorScalingLength, & - & componentType , & - & massType & + & /self%factorScalingLength & & ) & & *self %factorScalingMass return end function cylindricalScalerMassEnclosedBySphere - double precision function cylindricalScalerSurfaceDensity(self,coordinates,componentType,massType) + double precision function cylindricalScalerRadiusEnclosingMass(self,mass,massFractional) result(radius) + !!{ + Computes the radius enclosing a given mass or mass fraction for cylindrically-scaled mass distributions. + !!} + implicit none + class (massDistributionCylindricalScaler), intent(inout), target :: self + double precision , intent(in ), optional :: mass, massFractional + + if (present(mass)) then + radius=+self%massDistribution_%radiusEnclosingMass(mass/self%factorScalingMass,massFractional) & + & *self %factorScalingLength + else + radius=+self%massDistribution_%radiusEnclosingMass(mass ,massFractional) & + & *self %factorScalingLength + end if + return + end function cylindricalScalerRadiusEnclosingMass + + double precision function cylindricalScalerRadiusEnclosingDensity(self,density,radiusGuess) result(radius) + !!{ + Computes the radius enclosing a given mean density for cylindrically-scaled mass distributions. + !!} + implicit none + class (massDistributionCylindricalScaler), intent(inout), target :: self + double precision , intent(in ) :: density + double precision , intent(in ), optional :: radiusGuess + + if (present(radiusGuess)) then + radius=+self%massDistribution_%radiusEnclosingDensity( & + & + density & + & /self%factorScalingMass & + & *self%factorScalingLength**3, & + & + radiusGuess & + & /self%factorScalingLength & + & ) & + & *self %factorScalingLength + else + radius=+self%massDistribution_%radiusEnclosingDensity( & + & + density & + & /self%factorScalingMass & + & *self%factorScalingLength**3 & + & ) & + & *self %factorScalingLength + end if + return + end function cylindricalScalerRadiusEnclosingDensity + + double precision function cylindricalScalerRadiusEnclosingSurfaceDensity(self,densitySurface,radiusGuess) result(radius) + !!{ + Computes the radius enclosing a given surface density for cylindrically-scaled mass distributions. + !!} + implicit none + class (massDistributionCylindricalScaler), intent(inout), target :: self + double precision , intent(in ) :: densitySurface + double precision , intent(in ), optional :: radiusGuess + + if (present(radiusGuess)) then + radius=+self%massDistribution_%radiusEnclosingSurfaceDensity( & + & + densitySurface & + & /self%factorScalingMass & + & *self%factorScalingLength**2, & + & + radiusGuess & + & /self%factorScalingLength & + & ) & + & *self %factorScalingLength + else + radius=+self%massDistribution_%radiusEnclosingSurfaceDensity( & + & + densitySurface & + & /self%factorScalingMass & + & *self%factorScalingLength**2 & + & ) & + & *self %factorScalingLength + end if + return + end function cylindricalScalerRadiusEnclosingSurfaceDensity + + double precision function cylindricalScalerSurfaceDensity(self,coordinates) !!{ Return the surface density at the specified {\normalfont \ttfamily coordinates} in a scaled cylindrical distribution. !!} use :: Coordinates, only : coordinate implicit none - class(massDistributionCylindricalScaler), intent(inout) :: self - class(coordinate ), intent(in ) :: coordinates - type (enumerationComponentTypeType ), intent(in ), optional :: componentType - type (enumerationMassTypeType ), intent(in ), optional :: massType - - cylindricalScalerSurfaceDensity=+self%massDistribution_%surfaceDensity ( & - & coordinates & - & /self%factorScalingLength, & - & componentType , & - & massType & - & ) & - & *self %factorScalingMass & + class(massDistributionCylindricalScaler), intent(inout) :: self + class(coordinate ), intent(in ) :: coordinates + class(coordinate ), allocatable :: coordinatesScaled + + call coordinates%scale(1.0d0/self%factorScalingLength,coordinatesScaled) + cylindricalScalerSurfaceDensity=+self%massDistribution_%surfaceDensity (coordinatesScaled) & + & *self %factorScalingMass & & /self %factorScalingLength**2 return end function cylindricalScalerSurfaceDensity - double precision function cylindricalScalerRotationCurve(self,radius,componentType,massType) + double precision function cylindricalScalerRotationCurve(self,radius) !!{ Return the mid-plane rotation curve for a scaled cylindrical distribution. !!} + use :: Numerical_Constants_Astronomical, only : gravitationalConstantGalacticus implicit none class (massDistributionCylindricalScaler), intent(inout) :: self double precision , intent(in ) :: radius - type (enumerationComponentTypeType ), intent(in ), optional :: componentType - type (enumerationMassTypeType ), intent(in ), optional :: massType - - cylindricalScalerRotationCurve=+ self%massDistribution_%rotationCurve ( & - & + radius & - & /self%factorScalingLength, & - & componentType , & - & massType & - & ) & - & *sqrt( & - & +self %factorScalingMass & - & /self %factorScalingLength & + + cylindricalScalerRotationCurve=+ self%massDistribution_%rotationCurve ( & + & + radius & + & /self%factorScalingLength & + & ) & + & *sqrt( & + & +self %factorScalingMass & + & /self %factorScalingLength & & ) + if (self%massDistribution_%isDimensionless()) & + & cylindricalScalerRotationCurve=+cylindricalScalerRotationCurve & + & *sqrt(gravitationalConstantGalacticus) return end function cylindricalScalerRotationCurve - double precision function cylindricalScalerRotationCurveGradient(self,radius,componentType,massType) + double precision function cylindricalScalerRotationCurveGradient(self,radius) !!{ - Return the mid-plane rotation curve gradient for a scaled cylindrical distribution. + Return the mid-plane rotation curve gradient (specifically, $\mathrm{d}V^2_\mathrm{c}/\mathrm{d}r$) for a scaled cylindrical distribution. !!} + use :: Numerical_Constants_Astronomical, only : gravitationalConstantGalacticus implicit none - class (massDistributionCylindricalScaler), intent(inout) :: self - double precision , intent(in ) :: radius - type (enumerationComponentTypeType ), intent(in ), optional :: componentType - type (enumerationMassTypeType ), intent(in ), optional :: massType - - cylindricalScalerRotationCurveGradient=+ self%massDistribution_%rotationCurveGradient( & - & + radius & - & /self%factorScalingLength, & - & componentType , & - & massType & - & ) & - & *sqrt( & - & +self %factorScalingMass & - & /self %factorScalingLength & - & ) & - & / self %factorScalingLength + class (massDistributionCylindricalScaler), intent(inout) :: self + double precision , intent(in ) :: radius + + cylindricalScalerRotationCurveGradient=+self%massDistribution_%rotationCurveGradient( & + & + radius & + & /self%factorScalingLength & + & ) & + & *self%factorScalingMass & + & /self%factorScalingLength**2 + if (self%massDistribution_%isDimensionless()) & + & cylindricalScalerRotationCurveGradient=+cylindricalScalerRotationCurveGradient & + & *gravitationalConstantGalacticus return end function cylindricalScalerRotationCurveGradient - double precision function cylindricalScalerPotential(self,coordinates,componentType,massType) + logical function cylindricalScalerPotentialIsAnalytic(self) result(isAnalytic) + !!{ + Return if the potential has an analytic form. + !!} + implicit none + class(massDistributionCylindricalScaler), intent(inout) :: self + + isAnalytic=self%massDistribution_%potentialIsAnalytic() + return + end function cylindricalScalerPotentialIsAnalytic + + double precision function cylindricalScalerPotential(self,coordinates,status) !!{ Return the gravitational potential for a scaled cylindrical distribution. !!} + use :: Galactic_Structure_Options , only : structureErrorCodeSuccess + use :: Numerical_Constants_Astronomical, only : gravitationalConstantGalacticus implicit none - class(massDistributionCylindricalScaler), intent(inout) :: self - class(coordinate ), intent(in ) :: coordinates - type (enumerationComponentTypeType ), intent(in ), optional :: componentType - type (enumerationMassTypeType ), intent(in ), optional :: massType - - cylindricalScalerPotential=+self%massDistribution_%potential ( & - & coordinates & - & /self%factorScalingLength, & - & componentType , & - & massType & - & ) & - & *self %factorScalingMass & + class(massDistributionCylindricalScaler), intent(inout), target :: self + class(coordinate ), intent(in ) :: coordinates + type (enumerationStructureErrorCodeType), intent( out), optional :: status + class(coordinate ) , allocatable :: coordinatesScaled + + call coordinates%scale(1.0d0/self%factorScalingLength,coordinatesScaled) + cylindricalScalerPotential=+self%massDistribution_%potential ( & + & coordinatesScaled, & + & status & + & ) & + & *self %factorScalingMass & & /self %factorScalingLength + if (self%massDistribution_%isDimensionless()) & + & cylindricalScalerPotential=+cylindricalScalerPotential & + & *gravitationalConstantGalacticus return end function cylindricalScalerPotential - double precision function cylindricalScalerSurfaceDensityRadialMoment(self,moment,radiusMinimum,radiusMaximum,isInfinite,componentType,massType) + double precision function cylindricalScalerSurfaceDensityRadialMoment(self,moment,radiusMinimum,radiusMaximum,isInfinite) !!{ Compute radial moments of a scaled cylindrical distribution. !!} @@ -325,13 +466,11 @@ double precision function cylindricalScalerSurfaceDensityRadialMoment(self,momen double precision , intent(in ) :: moment double precision , intent(in ), optional :: radiusMinimum, radiusMaximum logical , intent( out), optional :: isInfinite - type (enumerationComponentTypeType ), intent(in ), optional :: componentType - type (enumerationMassTypeType ), intent(in ), optional :: massType - + cylindricalScalerSurfaceDensityRadialMoment=0.0d0 !![ - cylindricalScalerSurfaceDensityRadialMoment=self%massDistribution_%surfaceDensityRadialMoment(moment=moment,isInfinite=isInfinite,componentType=componentType,massType=massType{conditions}) + cylindricalScalerSurfaceDensityRadialMoment=self%massDistribution_%surfaceDensityRadialMoment(moment=moment,isInfinite=isInfinite{conditions}) @@ -342,62 +481,62 @@ double precision function cylindricalScalerSurfaceDensityRadialMoment(self,momen return end function cylindricalScalerSurfaceDensityRadialMoment - function cylindricalScalerAcceleration(self,coordinates,componentType,massType) + function cylindricalScalerAcceleration(self,coordinates) !!{ Computes the gravitational acceleration at {\normalfont \ttfamily coordinates} for a scaled cylindrical distribution. !!} + use :: Numerical_Constants_Astronomical, only : gigaYear, gravitationalConstantGalacticus, megaParsec + use :: Numerical_Constants_Prefixes , only : kilo implicit none - double precision , dimension(3 ) :: cylindricalScalerAcceleration - class (massDistributionCylindricalScaler), intent(inout) :: self - class (coordinate ), intent(in ) :: coordinates - type (enumerationComponentTypeType ), intent(in ), optional :: componentType - type (enumerationMassTypeType ), intent(in ), optional :: massType - - cylindricalScalerAcceleration=+self%massDistribution_%acceleration ( & - & coordinates & - & /self%factorScalingLength, & - & componentType , & - & massType & - & ) & - & *self %factorScalingMass & + double precision , dimension(3) :: cylindricalScalerAcceleration + class (massDistributionCylindricalScaler), intent(inout) :: self + class (coordinate ), intent(in ) :: coordinates + class (coordinate ), allocatable :: coordinatesScaled + + call coordinates%scale(1.0d0/self%factorScalingLength,coordinatesScaled) + cylindricalScalerAcceleration=+self%massDistribution_%acceleration (coordinatesScaled) & + & *self %factorScalingMass & & /self %factorScalingLength**2 - return + if (self%massDistribution_%isDimensionless()) & + & cylindricalScalerAcceleration=+cylindricalScalerAcceleration & + & *kilo & + & *gigaYear & + & /megaParsec & + & *gravitationalConstantGalacticus + return end function cylindricalScalerAcceleration - function cylindricalScalerTidalTensor(self,coordinates,componentType,massType) + function cylindricalScalerTidalTensor(self,coordinates) !!{ Computes the gravitational tidal tensor at {\normalfont \ttfamily coordinates} for a scaled cylindrical distribution. !!} + use :: Numerical_Constants_Astronomical, only : gravitationalConstantGalacticus implicit none - type (tensorRank2Dimension3Symmetric ) :: cylindricalScalerTidalTensor - class(massDistributionCylindricalScaler), intent(inout) :: self - class(coordinate ), intent(in ) :: coordinates - type (enumerationComponentTypeType ), intent(in ), optional :: componentType - type (enumerationMassTypeType ), intent(in ), optional :: massType - - cylindricalScalerTidalTensor=+self%massDistribution_%tidalTensor ( & - & coordinates & - & /self%factorScalingLength, & - & componentType , & - & massType & - & ) & - & *self %factorScalingMass & + type (tensorRank2Dimension3Symmetric ) :: cylindricalScalerTidalTensor + class(massDistributionCylindricalScaler), intent(inout) :: self + class(coordinate ), intent(in ) :: coordinates + class(coordinate ), allocatable :: coordinatesScaled + + call coordinates%scale(1.0d0/self%factorScalingLength,coordinatesScaled) + cylindricalScalerTidalTensor=+self%massDistribution_%tidalTensor (coordinatesScaled) & + & *self %factorScalingMass & & /self %factorScalingLength**3 - return + if (self%massDistribution_%isDimensionless()) & + & cylindricalScalerTidalTensor=+cylindricalScalerTidalTensor & + & *gravitationalConstantGalacticus + return end function cylindricalScalerTidalTensor - function cylindricalScalerPositionSample(self,randomNumberGenerator_,componentType,massType) + function cylindricalScalerPositionSample(self,randomNumberGenerator_) !!{ Sample a position from a scaled cylindrical distribution. !!} implicit none - double precision , dimension(3) :: cylindricalScalerPositionSample - class (massDistributionCylindricalScaler), intent(inout) :: self - class (randomNumberGeneratorClass ), intent(inout) :: randomNumberGenerator_ - type (enumerationComponentTypeType ), intent(in ), optional :: componentType - type (enumerationMassTypeType ), intent(in ), optional :: massType + double precision , dimension(3) :: cylindricalScalerPositionSample + class (massDistributionCylindricalScaler), intent(inout) :: self + class (randomNumberGeneratorClass ), intent(inout) :: randomNumberGenerator_ - cylindricalScalerPositionSample=+self%massDistribution_%positionSample (randomNumberGenerator_,componentType,massType) & + cylindricalScalerPositionSample=+self%massDistribution_%positionSample (randomNumberGenerator_) & & *self %factorScalingLength return end function cylindricalScalerPositionSample diff --git a/source/mass_distributions.spherical.Burkert.F90 b/source/mass_distributions.spherical.Burkert.F90 new file mode 100644 index 0000000000..56a1a60d62 --- /dev/null +++ b/source/mass_distributions.spherical.Burkert.F90 @@ -0,0 +1,996 @@ +!! Copyright 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, +!! 2019, 2020, 2021, 2022, 2023 +!! Andrew Benson +!! +!! This file is part of Galacticus. +!! +!! Galacticus is free software: you can redistribute it and/or modify +!! it under the terms of the GNU General Public License as published by +!! the Free Software Foundation, either version 3 of the License, or +!! (at your option) any later version. +!! +!! Galacticus is distributed in the hope that it will be useful, +!! but WITHOUT ANY WARRANTY; without even the implied warranty of +!! MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +!! GNU General Public License for more details. +!! +!! You should have received a copy of the GNU General Public License +!! along with Galacticus. If not, see . + + !!{ + Implementation of a Burkert \citep{burkert_structure_1995} mass distribution class. + !!} + + use :: Numerical_Interpolation , only : interpolator + use :: Numerical_Constants_Math, only : Pi + + !![ + + + A mass distribution class which implements the \citep{burkert_structure_1995} density profile: + \begin{equation} + \rho_\mathrm{dark matter}(r) = \rho_0 \left(1+{r\over r_\mathrm{s}}\right)^{-1} \left(1+[{r\over + r_\mathrm{s}}]^2\right)^{-1}. + \end{equation} + The mass enclosed within radius $r$ is given by + \begin{equation} + M(<r) = \pi \rho_0 r_\mathrm{s}^3 \left[ 2 \log(1 + R) + \log(1 + R^2) -2 \tan^{-1}(R) \right] + \end{equation} + where $R=r/r_\mathrm{s}$. The associated gravitational potential is + \begin{equation} + \Phi(r) = - \frac{\mathrm{G} \pi \rho_0 r_\mathrm{s}^2}{R} \left[ (R-1) \log \left(R^2+1\right)-2 (R+1) \log (R+1)-2 (R+1) \cot^{-1}(R)+\pi \right] + \end{equation} + The peak of the rotation curve occurs at $R=3.2446257246042642$ (found by numerical solution) at which point the rotation + curve amplitude is 1.644297750532498, and the Fourier transform of the profile, $F(k) = \int_0^c 4 \pi r^2 \exp(-i k r) + \rho(r) \mathrm{d} r / k r$ (needed in calculations of clustering using the halo model) is given by + \begin{eqnarray} + F(k) &=& (1+i) \frac{\pi}{k m(c) } \left( \right. \nonumber \\ + & & + \exp( k) \left\{ -i \pi -\mathrm{E}_\mathrm{i}[- k]+\mathrm{E}_\mathrm{i}[(-1+ic)k] \right\} \nonumber \\ + & & +(1-i) \exp(-k) \left\{ +\mathrm{E}_\mathrm{i}[-i k]+\mathrm{E}_\mathrm{i}[(+i+ic)k] \right\} \nonumber \\ + & & + i \exp(-k) \left\{ +\mathrm{E}_\mathrm{i}[+ k]+\mathrm{E}_\mathrm{i}[(+1+ic)k] \right\} \nonumber \\ + & & \left. \right). + \end{eqnarray} + + + !!] + type, public, extends(massDistributionSpherical) :: massDistributionBurkert + !!{ + The \citep{burkert_structure_1995} mass distribution. + !!} + private + double precision :: densityNormalization , scaleLength + double precision :: densityScaleFreeRadiusMinimum , densityScaleFreeRadiusMaximum + double precision :: densityScaleFreeMinimum , densityScaleFreeMaximum + type (interpolator), allocatable :: densityScaleFree_ + double precision :: massScaleFreeRadiusMinimum , massScaleFreeRadiusMaximum + double precision :: massScaleFreeMinimum , massScaleFreeMaximum + type (interpolator), allocatable :: massScaleFree_ + double precision :: angularMomentumSpecificScaleFreeRadiusMinimum, angularMomentumSpecificScaleFreeRadiusMaximum + double precision :: angularMomentumSpecificScaleFreeMinimum , angularMomentumSpecificScaleFreeMaximum + type (interpolator), allocatable :: angularMomentumSpecificScaleFree_ + double precision :: timeFreefallScaleFreeRadiusMinimum , timeFreefallScaleFreeRadiusMaximum + double precision :: timeFreefallScaleFreeMinimum , timeFreefallScaleFreeMaximum + type (interpolator), allocatable :: timeFreefallScaleFree_ + contains + !![ + + + + !!] + procedure :: massTotal => burkertMassTotal + procedure :: density => burkertDensity + procedure :: densityGradientRadial => burkertDensityGradientRadial + procedure :: densityRadialMoment => burkertDensityRadialMoment + procedure :: massEnclosedBySphere => burkertMassEnclosedBySphere + procedure :: velocityRotationCurveMaximum => burkertVelocityRotationCurveMaximum + procedure :: radiusRotationCurveMaximum => burkertRadiusRotationCurveMaximum + procedure :: radiusEnclosingMass => burkertRadiusEnclosingMass + procedure :: radiusEnclosingDensity => burkertRadiusEnclosingDensity + procedure :: radiusFromSpecificAngularMomentum => burkertRadiusFromSpecificAngularMomentum + procedure :: fourierTransform => burkertFourierTransform + procedure :: radiusFreefall => burkertRadiusFreefall + procedure :: radiusFreefallIncreaseRate => burkertRadiusFreefallIncreaseRate + procedure :: timeFreefallTabulate => burkertTimeFreefallTabulate + procedure :: potentialIsAnalytic => burkertPotentialIsAnalytic + procedure :: potential => burkertPotential + procedure :: energyPotential => burkertEnergyPotential + procedure :: descriptor => burkertDescriptor + end type massDistributionBurkert + + interface massDistributionBurkert + !!{ + Constructors for the {\normalfont \ttfamily burkert} mass distribution class. + !!} + module procedure massDistributionBurkertConstructorParameters + module procedure massDistributionBurkertConstructorInternal + end interface massDistributionBurkert + + ! The minimum (scale-free) freefall timescale in a Burkert profile. + double precision , parameter :: timeFreefallScaleFreeMinimum=sqrt(3.0d0*Pi)/4.0d0 + +contains + + function massDistributionBurkertConstructorParameters(parameters) result(self) + !!{ + Constructor for the {\normalfont \ttfamily burkert} mass distribution class which builds the object from a parameter + set. + !!} + use :: Input_Parameters , only : inputParameter , inputParameters + use :: Galactic_Structure_Options, only : enumerationComponentTypeEncode, enumerationMassTypeEncode + use :: Numerical_Constants_Math , only : Pi + implicit none + type (massDistributionBurkert) :: self + type (inputParameters ), intent(inout) :: parameters + double precision :: mass , scaleLength , & + & densityNormalization, radiusOuter + logical :: dimensionless + type (varying_string ) :: componentType , massType + + !![ + + densityNormalization + 1.0d0/Pi/(log(8.0d0)-Pi/2.0d0) + The density normalization of the Burkert profile. + parameters + + + scaleLength + 1.0d0 + The scale radius of the Burkert profile. + parameters + + + mass + 1.0d0 + The mass of the Burkert profile. + parameters + + + radiusOuter + The outer radius of the Burkert profile. + parameters + + + dimensionless + .true. + If true the Burkert profile is considered to be dimensionless. + parameters + + + componentType + var_str('unknown') + The component type that this mass distribution represents. + parameters + + + massType + var_str('unknown') + The mass type that this mass distribution represents. + parameters + + + self=massDistributionBurkert(scaleLength=scaleLength,componentType=enumerationComponentTypeEncode(componentType,includesPrefix=.false.),massType=enumerationMassTypeEncode(massType,includesPrefix=.false.){conditions}) + + + + + + + !!] + return + end function massDistributionBurkertConstructorParameters + + function massDistributionBurkertConstructorInternal(scaleLength,densityNormalization,mass,radiusOuter,dimensionless,componentType,massType) result(self) + !!{ + Internal constructor for ``burkert'' mass distribution class. + !!} + use :: Error , only : Error_Report + use :: Numerical_Constants_Math, only : Pi + implicit none + type (massDistributionBurkert ) :: self + double precision , intent(in ) :: scaleLength + double precision , intent(in ), optional :: densityNormalization, radiusOuter, & + & mass + logical , intent(in ), optional :: dimensionless + type (enumerationComponentTypeType), intent(in ), optional :: componentType + type (enumerationMassTypeType ), intent(in ), optional :: massType + double precision :: radiusScaleFree + !![ + + !!] + + ! Determine density normalization. + if ( & + & present(densityNormalization) & + & ) then + self%densityNormalization=densityNormalization + else if ( & + & present(mass ).and. & + & present(radiusOuter ) & + & ) then + radiusScaleFree =+radiusOuter/self%scaleLength + self%densityNormalization=+mass/Pi/self%scaleLength**3/(2.0d0*log(1.0d0+radiusScaleFree)+log(1.0d0+radiusScaleFree**2)-2.0d0*atan(radiusScaleFree)) + else + call Error_Report('either "densityNormalization", or "mass" and "radiusOuter" must be specified'//{introspection:location}) + end if + ! Determine if profile is dimensionless. + if (present(dimensionless )) then + self%dimensionless=dimensionless + else + self%dimensionless=.false. + end if + ! Initialize memoized results. + self%densityScaleFreeMinimum =+huge(0.0d0) + self%densityScaleFreeMaximum =-huge(0.0d0) + self%densityScaleFreeRadiusMinimum =+1.0d0 + self%densityScaleFreeRadiusMaximum =+1.0d0 + self%massScaleFreeMinimum =+huge(0.0d0) + self%massScaleFreeMaximum =-huge(0.0d0) + self%massScaleFreeRadiusMinimum =+1.0d0 + self%massScaleFreeRadiusMaximum =+1.0d0 + self%angularMomentumSpecificScaleFreeMinimum =+huge(0.0d0) + self%angularMomentumSpecificScaleFreeMaximum =-huge(0.0d0) + self%angularMomentumSpecificScaleFreeRadiusMinimum=+1.0d0 + self%angularMomentumSpecificScaleFreeRadiusMaximum=+1.0d0 + self%timeFreefallScaleFreeMinimum =+huge(0.0d0) + self%timeFreefallScaleFreeMaximum =-huge(0.0d0) + self%timeFreefallScaleFreeRadiusMinimum =+1.0d0 + self%timeFreefallScaleFreeRadiusMaximum =+1.0d0 + return + end function massDistributionBurkertConstructorInternal + + double precision function burkertMassTotal(self) + !!{ + Return the total mass in an Burkert mass distribution. + !!} + implicit none + class(massDistributionBurkert), intent(inout) :: self + + burkertMassTotal=huge(0.0d0) + return + end function burkertMassTotal + + double precision function burkertDensity(self,coordinates) + !!{ + Return the density at the specified {\normalfont \ttfamily coordinates} in an Burkert mass distribution. + !!} + implicit none + class (massDistributionBurkert), intent(inout) :: self + class (coordinate ), intent(in ) :: coordinates + double precision :: radiusScaleFree + + ! Compute the density at this position. + radiusScaleFree=+coordinates%rSpherical () & + & /self %scaleLength + burkertDensity =+self %densityNormalization & + & /(+1.0d0+radiusScaleFree ) & + & /(+1.0d0+radiusScaleFree**2) + return + end function burkertDensity + + double precision function burkertDensityGradientRadial(self,coordinates,logarithmic) result(densityGradientRadial) + !!{ + Return the density at the specified {\normalfont \ttfamily coordinates} in an Burkert \citep{burkert_structure_1995} mass distribution. + !!} + implicit none + class (massDistributionBurkert), intent(inout), target :: self + class (coordinate ), intent(in ) :: coordinates + logical , intent(in ), optional :: logarithmic + double precision :: radiusScaleFree + !![ + + !!] + + radiusScaleFree =+coordinates%rSpherical() & + & /self %scaleLength + densityGradientRadial=-3.0d0 & + & +1.0d0/(1.0d0+radiusScaleFree ) & + & +2.0d0/(1.0d0+radiusScaleFree**2) + if (.not.logarithmic_) densityGradientRadial=+ densityGradientRadial & + & *self %density (coordinates) & + & /coordinates%rSpherical ( ) + return + end function burkertDensityGradientRadial + + double precision function burkertDensityRadialMoment(self,moment,radiusMinimum,radiusMaximum,isInfinite) result(densityRadialMoment) + !!{ + Computes radial moments of the density in an Burkert \citep{burkert_structure_1995} mass distribution. + !!} + implicit none + class (massDistributionBurkert), intent(inout) :: self + double precision , intent(in ) :: moment + double precision , intent(in ), optional :: radiusMinimum , radiusMaximum + logical , intent( out), optional :: isInfinite + double precision :: radialMomentMinimum, radialMomentMaximum + + densityRadialMoment=0.0d0 + if (present(isInfinite)) isInfinite=.false. + if (present(radiusMinimum)) then + radialMomentMinimum=radialMomentScaleFree(radiusMinimum/self%scaleLength) + else + radialMomentMinimum=radialMomentScaleFree( 0.0d0) + end if + if (present(radiusMaximum)) then + radialMomentMaximum=radialMomentScaleFree(radiusMaximum/self%scaleLength) + else + radialMomentMaximum=0.0d0 + if (moment >= 3.0d0) then + if (present(isInfinite)) then + isInfinite=.true. + return + else + call Error_Report('moment is infinite'//{introspection:location}) + end if + end if + end if + densityRadialMoment=+self%densityNormalization & + & *self%scaleLength **(moment+1.0d0) & + & *( & + & +radialMomentMaximum & + & -radialMomentMinimum & + & ) + return + + contains + + double precision function radialMomentScaleFree(radius) + !!{ + Provides the scale-free part of the radial moment of the Burkert density profile. + !!} + use :: Hypergeometric_Functions, only : Hypergeometric_2F1 + use :: Numerical_Comparison , only : Values_Agree + implicit none + double precision, intent(in ) :: radius + + if (Values_Agree(moment,0.0d0,absTol=1.0d-6)) then + radialMomentScaleFree=+0.50d0*atan( radius ) & + & +0.50d0*log (+1.0d0+radius ) & + & -0.25d0*log (+1.0d0+radius**2) + else if (Values_Agree(moment,1.0d0,absTol=1.0d-6)) then + radialMomentScaleFree=+0.50d0*atan( radius ) & + & -0.50d0*log (+1.0d0+radius ) & + & +0.25d0*log (+1.0d0+radius**2) + else if (Values_Agree(moment,2.0d0,absTol=1.0d-6)) then + radialMomentScaleFree=-0.50d0*atan( radius ) & + & +0.50d0*log (+1.0d0+radius ) & + & +0.25d0*log (+1.0d0+radius**2) + else if (Values_Agree(moment,3.0d0,absTol=1.0d-6)) then + radialMomentScaleFree=+ radius & + & -0.50d0*atan( radius ) & + & -0.50d0*log (+1.0d0+radius ) & + & -0.25d0*log (+1.0d0+radius**2) + else + radialMomentScaleFree=+radius**(1.0d0+moment) & + & / 2.0d0 & + & / (1.0d0+moment) & + & / (2.0d0+moment) & + & *( & + & +(2.0d0+moment)*Hypergeometric_2F1([1.0d0,0.5d0*(1.0d0+moment)],[0.5d0*(3.0d0+moment)],-radius**2) & + & +(2.0d0+moment)*Hypergeometric_2F1([1.0d0, (1.0d0+moment)],[ (2.0d0+moment)],-radius ) & + & +(1.0d0+moment)*Hypergeometric_2F1([1.0d0,0.5d0*(2.0d0+moment)],[0.5d0*(4.0d0+moment)],-radius**2) & + & ) + end if + return + end function radialMomentScaleFree + + end function burkertDensityRadialMoment + + double precision function burkertMassEnclosedBySphere(self,radius) result(mass) + !!{ + Computes the mass enclosed within a sphere of given {\normalfont \ttfamily radius} for burkert mass distributions. + !!} + implicit none + class (massDistributionBurkert), intent(inout), target :: self + double precision , intent(in ) :: radius + double precision :: radiusScaleFree + + radiusScaleFree=+ radius & + & /self%scaleLength + mass =+self%densityNormalization & + & *self%scaleLength **3 & + & *massEnclosedScaleFree(radiusScaleFree) + return + end function burkertMassEnclosedBySphere + + double precision function burkertRadiusEnclosingMass(self,mass,massFractional) result(radius) + !!{ + Computes the radius enclosing a given mass or mass fraction for burkert mass distributions. + !!} + use :: Numerical_Ranges, only : Make_Range , rangeTypeLogarithmic + use :: Error , only : Error_Report + implicit none + class (massDistributionBurkert), intent(inout), target :: self + double precision , intent(in ), optional :: mass , massFractional + double precision , allocatable , dimension(:) :: radii , masses + double precision , parameter :: countRadiiPerDecade=100.0d0 + double precision :: massScaleFree , mass_ + integer :: countRadii + + mass_=0.0d0 + if (present(mass)) then + mass_=mass + else if (present(massFractional)) then + call Error_Report('mass is unbounded, so mass fraction is undefined'//{introspection:location}) + else + call Error_Report('either mass or massFractional must be supplied'//{introspection:location}) + end if + massScaleFree=+ mass_ & + & /self%densityNormalization & + & /self%scaleLength **3 + if ( & + & massScaleFree <= self%massScaleFreeMinimum & + & .or. & + & massScaleFree > self%massScaleFreeMaximum & + & ) then + do while (massEnclosedScaleFree(self%massScaleFreeRadiusMinimum) >= massScaleFree) + self%massScaleFreeRadiusMinimum=0.5d0*self%massScaleFreeRadiusMinimum + end do + do while (massEnclosedScaleFree(self%massScaleFreeRadiusMaximum) < massScaleFree) + self%massScaleFreeRadiusMaximum=2.0d0*self%massScaleFreeRadiusMaximum + end do + countRadii=int(log10(self%massScaleFreeRadiusMaximum/self%massScaleFreeRadiusMinimum)*countRadiiPerDecade)+1 + if (allocated(self%massScaleFree_)) deallocate(self%massScaleFree_) + allocate( radii (countRadii)) + allocate( masses (countRadii)) + allocate(self%massScaleFree_ ) + radii =Make_Range(self%massScaleFreeRadiusMinimum,self%massScaleFreeRadiusMaximum,countRadii,rangeTypeLogarithmic) + masses =massEnclosedScaleFree( radii) + self%massScaleFreeMinimum=masses ( 1 ) + self%massScaleFreeMaximum=masses (countRadii ) + self%massScaleFree_ =interpolator (masses ,radii) + end if + radius=+self%massScaleFree_%interpolate(massScaleFree) & + & *self%scaleLength + return + end function burkertRadiusEnclosingMass + + double precision function burkertRadiusEnclosingDensity(self,density,radiusGuess) result(radius) + !!{ + Computes the radius enclosing a given mean density for burkert mass distributions. + !!} + use :: Numerical_Ranges, only : Make_Range, rangeTypeLogarithmic + implicit none + class (massDistributionBurkert), intent(inout), target :: self + double precision , intent(in ) :: density + double precision , intent(in ), optional :: radiusGuess + double precision , allocatable , dimension(:) :: radii , densities + double precision , parameter :: countRadiiPerDecade=100.0d0 + double precision :: densityScaleFree + integer :: countRadii + + densityScaleFree=+density & + & /self%densityNormalization + if ( & + & densityScaleFree <= self%densityScaleFreeMinimum & + & .or. & + & densityScaleFree > self%densityScaleFreeMaximum & + & ) then + do while (densityEnclosedScaleFree(self%densityScaleFreeRadiusMinimum) < densityScaleFree) + self%densityScaleFreeRadiusMinimum=0.5d0*self%densityScaleFreeRadiusMinimum + end do + do while (densityEnclosedScaleFree(self%densityScaleFreeRadiusMaximum) >= densityScaleFree) + self%densityScaleFreeRadiusMaximum=2.0d0*self%densityScaleFreeRadiusMaximum + end do + countRadii=int(log10(self%densityScaleFreeRadiusMaximum/self%densityScaleFreeRadiusMinimum)*countRadiiPerDecade)+1 + if (allocated(self%densityScaleFree_)) deallocate(self%densityScaleFree_) + allocate( radii (countRadii)) + allocate( densities (countRadii)) + allocate(self%densityScaleFree_ ) + radii = Make_Range(self%densityScaleFreeRadiusMinimum,self%densityScaleFreeRadiusMaximum,countRadii,rangeTypeLogarithmic) + densities =-densityEnclosedScaleFree( radii) + self%densityScaleFreeMinimum=-densities (countRadii ) + self%densityScaleFreeMaximum=-densities ( 1 ) + self%densityScaleFree_ = interpolator (densities ,radii) + end if + radius=+self%densityScaleFree_%interpolate(-densityScaleFree) & + & *self%scaleLength + return + end function burkertRadiusEnclosingDensity + + elemental double precision function massEnclosedScaleFree(radius) result(mass) + !!{ + Evaluate the mass enclosed by a given radius in a scale-free Burkert mass distribution. + !!} + use :: Numerical_Constants_Math, only : Pi + implicit none + double precision, intent(in ) :: radius + double precision, parameter :: minimumRadiusForExactSolution=1.0d-4 + + if (radius < minimumRadiusForExactSolution) then + ! Use a series solution for small radii. + mass =+4.0d0 & + & /3.0d0 & + & *Pi & + & * radius**3 & + & *(+1.0d0-radius) + else + ! Use the exact solution. + mass =+Pi & + & *( & + & +2.0d0*log (1.0d0+radius ) & + & + log (1.0d0+radius**2) & + & -2.0d0*atan( radius ) & + & ) + end if + return + end function massEnclosedScaleFree + + elemental double precision function densityEnclosedScaleFree(radius) result(density) + !!{ + Evaluate the mean enclosed density at a given radius in a scale-free Burkert mass distribution. + !!} + use :: Numerical_Constants_Math, only : Pi + implicit none + double precision, intent(in ) :: radius + + density=+3.0d0 & + & /4.0d0 & + & /Pi & + & *massEnclosedScaleFree(radius) & + & / radius **3 + return + end function densityEnclosedScaleFree + + double precision function burkertRadiusFromSpecificAngularMomentum(self,angularMomentumSpecific) result(radius) + !!{ + Computes the radius corresponding to a given specific angular momentum for burkert mass distributions. + !!} + use :: Numerical_Constants_Astronomical, only : gravitationalConstantGalacticus + use :: Numerical_Ranges , only : Make_Range , rangeTypeLogarithmic + implicit none + class (massDistributionBurkert), intent(inout), target :: self + double precision , intent(in ) :: angularMomentumSpecific + double precision , allocatable , dimension(:) :: radii , angularMomentaSpecific + double precision , parameter :: countRadiiPerDecade =100.0d0 + double precision :: angularMomentumSpecificScaleFree + integer :: countRadii + + if (angularMomentumSpecific > 0.0d0) then + angularMomentumSpecificScaleFree=+angularMomentumSpecific & + & /sqrt( & + & +gravitationalConstantGalacticus & + & *self%densityNormalization & + & ) & + & / self%scaleLength **2 + if ( & + & angularMomentumSpecificScaleFree <= self%angularMomentumSpecificScaleFreeMinimum & + & .or. & + & angularMomentumSpecificScaleFree > self%angularMomentumSpecificScaleFreeMaximum & + & ) then + do while (angularMomentumSpecificEnclosedScaleFree(self%angularMomentumSpecificScaleFreeRadiusMinimum) >= angularMomentumSpecificScaleFree) + self%angularMomentumSpecificScaleFreeRadiusMinimum=0.5d0*self%angularMomentumSpecificScaleFreeRadiusMinimum + end do + do while (angularMomentumSpecificEnclosedScaleFree(self%angularMomentumSpecificScaleFreeRadiusMaximum) < angularMomentumSpecificScaleFree) + self%angularMomentumSpecificScaleFreeRadiusMaximum=2.0d0*self%angularMomentumSpecificScaleFreeRadiusMaximum + end do + countRadii=int(log10(self%angularMomentumSpecificScaleFreeRadiusMaximum/self%angularMomentumSpecificScaleFreeRadiusMinimum)*countRadiiPerDecade)+1 + if (allocated(self%angularMomentumSpecificScaleFree_)) deallocate(self%angularMomentumSpecificScaleFree_) + allocate( radii (countRadii)) + allocate( angularMomentaSpecific (countRadii)) + allocate(self%angularMomentumSpecificScaleFree_ ) + radii =Make_Range(self%angularMomentumSpecificScaleFreeRadiusMinimum,self%angularMomentumSpecificScaleFreeRadiusMaximum,countRadii,rangeTypeLogarithmic) + angularMomentaSpecific =angularMomentumSpecificEnclosedScaleFree( radii) + self%angularMomentumSpecificScaleFreeMinimum=angularMomentaSpecific ( 1 ) + self%angularMomentumSpecificScaleFreeMaximum=angularMomentaSpecific ( countRadii ) + self%angularMomentumSpecificScaleFree_ =interpolator (angularMomentaSpecific,radii) + end if + radius=+self%angularMomentumSpecificScaleFree_%interpolate(angularMomentumSpecificScaleFree) & + & *self%scaleLength + else + radius=+0.0d0 + end if + return + end function burkertRadiusFromSpecificAngularMomentum + + elemental double precision function angularMomentumSpecificEnclosedScaleFree(radius) result(angularMomentumSpecific) + !!{ + Evaluate the specific angular momentum at a given radius in a scale-free Burkert mass distribution. + !!} + implicit none + double precision, intent(in ) :: radius + + angularMomentumSpecific=+sqrt( & + & +massEnclosedScaleFree(radius) & + & * radius & + & ) + return + end function angularMomentumSpecificEnclosedScaleFree + + double precision function burkertVelocityRotationCurveMaximum(self) result(velocity) + !!{ + Return the peak velocity in the rotation curve for an burkert mass distribution. + !!} + use :: Numerical_Constants_Astronomical, only : gravitationalConstantGalacticus + use :: Numerical_Constants_Math , only : Pi + implicit none + class (massDistributionBurkert ), intent(inout) :: self + double precision , parameter :: circularVelocityMaximumScaleFree=1.644297750532498d0 ! The circular velocity (in scale-free units) at the peak of the Burkert rotation curve. + ! Numerical value found using Mathematica. + + velocity=+circularVelocityMaximumScaleFree & + & *sqrt( & + & +self%densityNormalization & + & ) & + & * self%scaleLength + if (.not.self%isDimensionless()) & + & velocity=+velocity & + & *sqrt(gravitationalConstantGalacticus) + return + end function burkertVelocityRotationCurveMaximum + + double precision function burkertRadiusRotationCurveMaximum(self) result(radius) + !!{ + Return the peak velocity in the rotation curve for an burkert mass distribution. + !!} + implicit none + class (massDistributionBurkert), intent(inout), target :: self + ! The radius (in scale-free units) at the peak of the Burkert rotation curve. Numerical value found using Mathematica. + double precision , parameter :: radiusCircularVelocityMaximumScaleFree=3.244625724604264d0 + + radius=+radiusCircularVelocityMaximumScaleFree & + & *self%scaleLength + return + end function burkertRadiusRotationCurveMaximum + + logical function burkertPotentialIsAnalytic(self) result(isAnalytic) + !!{ + Return that the potential has an analytic form. + !!} + implicit none + class(massDistributionBurkert), intent(inout) :: self + + isAnalytic=.true. + return + end function burkertPotentialIsAnalytic + + double precision function burkertPotential(self,coordinates,status) result(potential) + !!{ + Return the potential at the specified {\normalfont \ttfamily coordinates} in an burkert mass distribution. + !!} + use :: Coordinates , only : assignment(=) + use :: Galactic_Structure_Options , only : structureErrorCodeSuccess , structureErrorCodeInfinite + use :: Numerical_Constants_Astronomical, only : gravitationalConstantGalacticus + use :: Error , only : Error_Report + implicit none + class (massDistributionBurkert ), intent(inout), target :: self + class (coordinate ), intent(in ) :: coordinates + type (enumerationStructureErrorCodeType), intent( out), optional :: status + double precision :: radiusScaleFree + + if (present(status)) status=structureErrorCodeSuccess + radiusScaleFree=+coordinates%rSpherical () & + & /self %scaleLength + potential=+potentialScaleFree (radiusScaleFree) & + & *self%densityNormalization & + & *self%scaleLength **2 + if (.not.self%isDimensionless()) potential=+gravitationalConstantGalacticus & + & *potential + return + end function burkertPotential + + elemental double precision function potentialScaleFree(radius) result(potential) + !!{ + Compute the potential in a scale-free Burkert mass distribution. + !!} + use :: Numerical_Constants_Math, only : Pi + implicit none + double precision, intent(in ) :: radius + double precision, parameter :: radiusSmall=1.0d-3 + + if (radius < radiusSmall) then + ! Use a series solution for very small radii. + potential=- Pi**2 & + & +2.0d0/ 3.0d0*Pi *radius**2 & + & -1.0d0/ 3.0d0*Pi *radius**3 & + & +2.0d0/21.0d0*Pi *radius**6 + else + ! Use the full expression for larger radii. + potential=-Pi & + & /radius & + & *( & + & +2.0d0*(1.0d0+radius)*log (1.0d0+radius ) & + & + (1.0d0-radius)*log (1.0d0+radius**2) & + & -2.0d0 *atan( radius ) & + & +2.0d0* radius *atan(1.0d0/radius ) & + & ) + end if + return + end function potentialScaleFree + + double precision function potentialDifferenceScaleFree(radius1,radius2) result(potential) + !!{ + Compute the potential difference in a scale-free Burkert mass distribution. + !!} + use :: Numerical_Constants_Math, only : Pi + use :: Numerical_Comparison , only : Values_Agree + implicit none + double precision, intent(in ) :: radius1 , radius2 + double precision, parameter :: radiusSmall =1.0d-3 + double precision, parameter :: toleranceRelative =1.0d-3 + double precision :: potentialGradientLogarithmic , radiusDifferenceLogarithmic + + if (Values_Agree(radius1,radius2,relTol=toleranceRelative) .or. max(radius1,radius2) < radiusSmall) then + if (radius1 < radiusSmall) then + potentialGradientLogarithmic=-4.0d0/3.0d0/Pi *radius1**2 & + & +1.0d0 /Pi *radius1**3 & + & -8.0d0/9.0d0/Pi**2*radius1**4 + else + potentialGradientLogarithmic=-( & + & + log (1.0d0+radius1**2) & + & +2.0d0 *log (1.0d0+radius1 ) & + & -2.0d0 *atan( radius1 ) & + & ) & + & /( & + & + (1.0d0-radius1)*log (1.0d0+radius1**2) & + & +2.0d0*(1.0d0+radius1)*log (1.0d0+radius1 ) & + & -2.0d0 *atan( radius1 ) & + & +2.0d0* radius1 *atan(1.0d0/radius1 ) & + & ) + end if + radiusDifferenceLogarithmic=+1.0d0 & + & -radius2 & + & /radius1 + potential =+potentialScaleFree (radius1) & + & *potentialGradientLogarithmic & + & *radiusDifferenceLogarithmic + else + potential=+potentialScaleFree(radius1) & + & -potentialScaleFree(radius2) + end if + return + end function potentialDifferenceScaleFree + + double precision function burkertFourierTransform(self,radiusOuter,wavenumber) result(fourierTransform) + !!{ + Compute the Fourier transform of the density profile at the given {\normalfont \ttfamily wavenumber} in an Burkert mass + distribution. + !!} + use :: Exponential_Integrals , only : Exponential_Integral + use :: Numerical_Constants_Math, only : Pi + implicit none + class (massDistributionBurkert), intent(inout) :: self + double precision , intent(in ) :: radiusOuter , wavenumber + double precision :: wavenumberScaleFree, radiusOuterScaleFree + + waveNumberScaleFree =+waveNumber *self%scaleLength + radiusOuterScaleFree=+radiusOuter/self%scaleLength + fourierTransform =+dimag( & + & +dcmplx(1.0d0,1.0d0) & + & *Pi & + & /wavenumberScaleFree & + & *( & + & + exp(+ wavenumberScaleFree)*(dcmplx(0.0d0,-1.0d0)*Pi-Exponential_Integral(- wavenumberScaleFree)+Exponential_Integral(dcmplx(-1.0d0, +radiusOuterScaleFree)*wavenumberScaleFree)) & + & +dcmplx(1.0d0,-1.0d0)*exp(-dcmplx(0.0d0,1.0d0)*wavenumberScaleFree)*( +Exponential_Integral(+dcmplx(0.0d0,1.0d0)*wavenumberScaleFree)-Exponential_Integral(dcmplx(+0.0d0,+1.0d0+radiusOuterScaleFree)*wavenumberScaleFree)) & + & +dcmplx(0.0d0,+1.0d0)*exp(- wavenumberScaleFree)*( +Exponential_Integral(+ wavenumberScaleFree)-Exponential_Integral(dcmplx(+1.0d0, +radiusOuterScaleFree)*wavenumberScaleFree)) & + & ) & + & ) & + & /massEnclosedScaleFree(radiusOuterScaleFree) + return + end function burkertFourierTransform + + double precision function burkertRadiusFreefall(self,time) result(radius) + !!{ + Compute the freefall radius at the given {\normalfont \ttfamily time} in an Burkert mass distribution. + !!} + use :: Numerical_Constants_Astronomical, only : Mpc_per_km_per_s_To_Gyr, gravitationalConstantGalacticus + implicit none + class (massDistributionBurkert), intent(inout) :: self + double precision , intent(in ) :: time + double precision :: timeScaleFree, timeScale + + timeScale =+1.0d0/sqrt( & + & +gravitationalConstantGalacticus & + & *self%densityNormalization & + & ) & + & *Mpc_per_km_per_s_To_Gyr + timeScaleFree=+time & + & /timeScale + if (timeScaleFree <= timeFreefallScaleFreeMinimum) then + radius=0.0d0 + return + end if + call self%timeFreefallTabulate(timeScaleFree) + radius=+self%timeFreefallScaleFree_%interpolate(timeScaleFree) & + & *self%scaleLength + return + end function burkertRadiusFreefall + + double precision function burkertRadiusFreefallIncreaseRate(self,time) result(radiusIncreaseRate) + !!{ + Compute the rate of increase of the freefall radius at the given {\normalfont \ttfamily time} in an burkert mass + distribution. + !!} + use :: Numerical_Constants_Astronomical, only : Mpc_per_km_per_s_To_Gyr, gravitationalConstantGalacticus + implicit none + class (massDistributionBurkert), intent(inout) :: self + double precision , intent(in ) :: time + double precision :: timeScaleFree, timeScale + + timeScale =+1.0d0/sqrt( & + & +gravitationalConstantGalacticus & + & *self%densityNormalization & + & ) & + & *Mpc_per_km_per_s_To_Gyr + timeScaleFree=+time & + & /timeScale + if (timeScaleFree <= timeFreefallScaleFreeMinimum) then + radiusIncreaseRate=0.0d0 + return + end if + call self%timeFreefallTabulate(timeScaleFree) + radiusIncreaseRate=+self%timeFreefallScaleFree_%derivative(timeScaleFree) & + & *self%scaleLength & + & / timeScale + return + end function burkertRadiusFreefallIncreaseRate + + subroutine burkertTimeFreefallTabulate(self,timeScaleFree) + !!{ + Tabulate the freefall radius at the given {\normalfont \ttfamily time} in an Burkert mass distribution. + !!} + use :: Numerical_Integration, only : integrator + use :: Numerical_Ranges , only : Make_Range, rangeTypeLogarithmic + implicit none + class (massDistributionBurkert), intent(inout) :: self + double precision , intent(in ) :: timeScaleFree + double precision , allocatable , dimension(:) :: radii , timesFreefall + double precision , parameter :: countRadiiPerDecade=100.0d0 + double precision :: radiusStart + integer :: countRadii , i + type (integrator ) :: integrator_ + + if ( & + & timeScaleFree <= self%timeFreefallScaleFreeMinimum & + & .or. & + & timeScaleFree > self%timeFreefallScaleFreeMaximum & + & ) then + integrator_=integrator(timeFreeFallIntegrand,toleranceRelative=1.0d-6) + do while (timeFreefallScaleFree(self%timeFreefallScaleFreeRadiusMinimum) >= timeScaleFree) + self%timeFreefallScaleFreeRadiusMinimum=0.5d0*self%timeFreefallScaleFreeRadiusMinimum + end do + do while (timeFreefallScaleFree(self%timeFreefallScaleFreeRadiusMaximum) < timeScaleFree) + self%timeFreefallScaleFreeRadiusMaximum=2.0d0*self%timeFreefallScaleFreeRadiusMaximum + end do + countRadii=int(log10(self%timeFreefallScaleFreeRadiusMaximum/self%timeFreefallScaleFreeRadiusMinimum)*countRadiiPerDecade)+1 + if (allocated(self%timeFreefallScaleFree_)) deallocate(self%timeFreefallScaleFree_) + allocate( radii (countRadii)) + allocate( timesFreefall (countRadii)) + allocate(self%timeFreefallScaleFree_ ) + radii=Make_Range(self%timeFreefallScaleFreeRadiusMinimum,self%timeFreefallScaleFreeRadiusMaximum,countRadii,rangeTypeLogarithmic) + do i=1,countRadii + timesFreefall(i)=timeFreefallScaleFree(radii(i)) + end do + self%timeFreefallScaleFreeMinimum=timesFreefall( 1 ) + self%timeFreefallScaleFreeMaximum=timesFreefall( countRadii ) + self%timeFreefallScaleFree_ =interpolator (timesFreefall,radii) + end if + return + + contains + + double precision function timeFreefallScaleFree(radius) + !!{ + Evaluate the freefall time from a given radius in a scale-free Burkert mass distribution. + !!} + implicit none + double precision, intent(in ) :: radius + + radiusStart = radius + timeFreefallScaleFree=integrator_%integrate(0.0d0,radius) + return + end function timeFreefallScaleFree + + double precision function timeFreeFallIntegrand(radius) + !!{ + Integrand used to find the freefall time in a scale-free Burkert mass distribution. + !!} + implicit none + double precision, intent(in ) :: radius + double precision :: potentialDifference + + if (radius == 0.0d0) then + timeFreeFallIntegrand=+0.0d0 + else + potentialDifference=+potentialDifferenceScaleFree(radiusStart,radius) + if (potentialDifference > 0.0d0) then + timeFreeFallIntegrand=+1.0d0 & + & /sqrt( & + & +2.0d0 & + & *potentialDifference & + & ) + else + timeFreeFallIntegrand=+0.0d0 + end if + end if + return + end function timeFreeFallIntegrand + + end subroutine burkertTimeFreefallTabulate + + double precision function burkertEnergyPotential(self,radiusOuter) result(energy) + !!{ + Compute the potential energy within a given {\normalfont \ttfamily radius} in a Burkert mass distribution. This is + \begin{eqnarray} + W &=& \frac{1}{24} \pi ^2 \left(48 \mathrm{G}G + 48 i \text{Li}_2\left(\left(\frac{1}{2}+\frac{i}{2}\right) (x+1)\right)-48 i \text{Li}_2\left(\left(\frac{1}{2}-\frac{i}{2}\right) (x+1)\right)+48 i \text{Li}_2\left(\frac{i+x}{-i+x}\right)-96 \text{Li}_2\left(\left(-\frac{1}{2}+\frac{i}{2}\right) (-i+x)\right)-96 \text{Li}_2\left(\left(-\frac{1}{2}-\frac{i}{2}\right) (i+x)\right)-48 i \text{Li}_2\left(i \exp(2 i \tan ^{-1}(x))\right)+12 \left(\log ^2\left(x^2+1\right)+4 \log (x) \log \left(x^2+1\right)-4 \log (x+1) \log \left(x^2+1\right)+(2+4 i) \pi \log \left(x^2+1\right)+\tan ^{-1}(x) \left(4 \log \left(x^2+1\right)+8 \log \left(-\frac{2 i}{x-i}\right)+8 \log \left(1-i \exp(2 i \tan ^{-1}(x))\right)+2 i \pi \right)-4 \log ^2(x+1)-4 \log (x-i) \log ((1-i) (x+1))-4 \log (x+i) \log ((1+i) (x+1))+\log (64) \log (x-i)-4 \log (x) \log (x-i)+4 \log (x+1) \log (x-i)-3 i \pi \log (x-i)+\log (64) \log (x+i)-4 \log (x) \log (x+i)+4 \log (x+1) \log (x+i)-5 i \pi \log (x+i)+4 i \log (x+1) \log ((-1-i) (x+i))-4 \tan ^{-1}(x)^2+4 \pi \log \left(1+\exp(-2 i \tan ^{-1}(x))\right)+2 \pi \log \left(1-i \exp(2 i \tan ^{-1}(x))\right)-2 \pi \log \left(\sin \left(\tan ^{-1}(x)+\frac{\pi }{4}\right)\right)-\log (2) (7 \pi +\log (4))\right)-48 i \log ((1+i)-(1-i) x) \log (x+1)-\pi ^2 (14-9 i)\right) + \end{eqnarray} + where $x=r/r_\mathrm{s}$ and $\mathrm{G}$ is Catalan's constant. + !!} + use :: Numerical_Constants_Astronomical, only : gravitationalConstantGalacticus + use :: Numerical_Constants_Math , only : Pi , catalan + use :: Dilogarithms , only : Dilogarithm + implicit none + class (massDistributionBurkert), intent(inout) :: self + double precision , intent(in ) :: radiusOuter + double precision :: radiusOuterScaleFree + + radiusOuterScaleFree=+ radiusOuter & + & /self%scaleLength + energy =real( & + & -gravitationalConstantGalacticus & + & *self%scaleLength **5 & + & *self%densityNormalization **2 & + & *( & + & +Pi**2 & + & *( & + & + 48.0d0 *catalan & + & -dcmplx(14.0d0,- 9.0d0)*Pi**2 & + & -dcmplx( 0.0d0,+48.0d0)*log(dcmplx(1.0d0,1.0d0)-dcmplx(1.0d0,-1.0d0)*radiusOuterScaleFree)*log(1.0d0+radiusOuterScaleFree) & + & +12.0d0 & + & *( & + & -4.0d0*atan(radiusOuterScaleFree)**2 & + & - log(2.0d0)*(7.0d0*Pi + log(4.0d0)) & + & +4.0d0*Pi*log(1.0d0+ exp(-2.0d0*dcmplx(0.0d0,1.0d0)*atan(radiusOuterScaleFree))) & + & +2.0d0*Pi*log(1.0d0-dcmplx(0.0d0,1.0d0)*exp(+2.0d0*dcmplx(0.0d0,1.0d0)*atan(radiusOuterScaleFree))) & + & - dcmplx(0.0d0,3.0d0)*Pi*log(dcmplx(0.0d0,-1.0d0) +radiusOuterScaleFree) & + & +log (64.0d0 ) *log(dcmplx(+0.0d0,-1.0d0)+radiusOuterScaleFree)-log(dcmplx(+0.0d0,-1.0d0) +radiusOuterScaleFree) * 4.0d0 *log( radiusOuterScaleFree ) & + & -dcmplx( 0.0d0,5.0d0)*Pi*log(dcmplx(+0.0d0,+1.0d0)+radiusOuterScaleFree) & + & +log (64.0d0 ) *log(dcmplx(+0.0d0,+1.0d0)+radiusOuterScaleFree) & + & - 4.0d0 *log( +radiusOuterScaleFree)*log(dcmplx(+0.0d0,+1.0d0) +radiusOuterScaleFree) + 4.0d0 & + & *log(dcmplx(+0.0d0,-1.0d0) +radiusOuterScaleFree) *log(1.0d0+radiusOuterScaleFree ) & + & +dcmplx( 0.0d0,4.0d0) *log(dcmplx(-1.0d0,-1.0d0) * (dcmplx(+0.0d0,+1.0d0) +radiusOuterScaleFree)) *log(1.0d0+radiusOuterScaleFree ) & + & + 4.0d0 *log(dcmplx(+0.0d0,+1.0d0)+radiusOuterScaleFree)*log( 1.0d0+radiusOuterScaleFree )- 4.0d0 *log(1.0d0+radiusOuterScaleFree )**2 & + & - 4.0d0 *log(dcmplx(+0.0d0,-1.0d0)+radiusOuterScaleFree)*log(dcmplx(+1.0d0,-1.0d0)*(1.0d0+radiusOuterScaleFree)) & + & - 4.0d0 *log(dcmplx(+0.0d0,+1.0d0)+radiusOuterScaleFree)*log(dcmplx(+1.0d0,+1.0d0)*(1.0d0+radiusOuterScaleFree))+dcmplx(2.0d0,4.0d0)*Pi*log(1.0d0+radiusOuterScaleFree**2) & + & + 4.0d0 *log( radiusOuterScaleFree) *log(1.0d0+radiusOuterScaleFree**2) & + & - 4.0d0 *log( +1.0d0 +radiusOuterScaleFree) *log(1.0d0+radiusOuterScaleFree**2) & + & + log(1.0d0+radiusOuterScaleFree**2)**2 & + & + atan(radiusOuterScaleFree) & + & *(dcmplx(0.0d0,2.0d0)*Pi + 8.0d0*log(1 - dcmplx(0.0d0,1.0d0)*exp(2.0d0*dcmplx(0.0d0,1.0d0)*atan(radiusOuterScaleFree))) & + & +8.0d0*log(dcmplx(0.0d0,-2.0d0)/(dcmplx(0.0d0,-1.0d0)+radiusOuterScaleFree )) & + & +4.0d0*log( 1.0d0 +radiusOuterScaleFree**2)) & + & -2.0d0*Pi*log(sin(Pi/4.0d0+atan(radiusOuterScaleFree))) & + & ) & + & -dcmplx(0.0d0,48.0d0)*Dilogarithm(dcmplx(0.0d0,1.0d0)*exp(2.0d0*dcmplx(0.0d0,1.0d0)*atan(radiusOuterScaleFree))) & + & - 96.0d0 *Dilogarithm( dcmplx(-0.5d0,+0.5d0) *(dcmplx(0.0d0,-1.0d0)+radiusOuterScaleFree)) & + & - 96.0d0 *Dilogarithm( dcmplx(-0.5d0,-0.5d0) *(dcmplx(0.0d0,+1.0d0)+radiusOuterScaleFree)) & + & +dcmplx(0.0d0,48.0d0)*Dilogarithm((dcmplx(+0.0d0,+1.0d0)+radiusOuterScaleFree)/(dcmplx(0.0d0,-1.0d0)+radiusOuterScaleFree)) & + & -dcmplx(0.0d0,48.0d0)*Dilogarithm( dcmplx(+0.5d0,-0.5d0) *( +1.0d0 +radiusOuterScaleFree)) & + & +dcmplx(0.0d0,48.0d0)*Dilogarithm( dcmplx(+0.5d0,+0.5d0) *( +1.0d0 +radiusOuterScaleFree)) & + & ) & + & ) & + & /24.d0 & + & ) + return + end function burkertEnergyPotential + + subroutine burkertDescriptor(self,descriptor,includeClass,includeFileModificationTimes) + !!{ + Return an input parameter list descriptor which could be used to recreate this object. + !!} + use :: Input_Parameters, only : inputParameters + implicit none + class (massDistributionBurkert), intent(inout) :: self + type (inputParameters ), intent(inout) :: descriptor + logical , intent(in ), optional :: includeClass , includeFileModificationTimes + character(len=18 ) :: parameterLabel + type (inputParameters ) :: parameters + + if (.not.present(includeClass).or.includeClass) call descriptor%addParameter('massDistribution','Burkert') + parameters=descriptor%subparameters('massDistribution') + write (parameterLabel,'(e17.10)') self%densityNormalization + call parameters%addParameter('densityNormalization',trim(adjustl(parameterLabel))) + write (parameterLabel,'(e17.10)') self%scaleLength + call parameters%addParameter('scaleLength' ,trim(adjustl(parameterLabel))) + return + end subroutine burkertDescriptor + diff --git a/source/mass_distributions.spherical.Einasto.F90 b/source/mass_distributions.spherical.Einasto.F90 new file mode 100644 index 0000000000..cadce55ff9 --- /dev/null +++ b/source/mass_distributions.spherical.Einasto.F90 @@ -0,0 +1,868 @@ +!! Copyright 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, +!! 2019, 2020, 2021, 2022, 2023 +!! Andrew Benson +!! +!! This file is part of Galacticus. +!! +!! Galacticus is free software: you can redistribute it and/or modify +!! it under the terms of the GNU General Public License as published by +!! the Free Software Foundation, either version 3 of the License, or +!! (at your option) any later version. +!! +!! Galacticus is distributed in the hope that it will be useful, +!! but WITHOUT ANY WARRANTY; without even the implied warranty of +!! MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +!! GNU General Public License for more details. +!! +!! You should have received a copy of the GNU General Public License +!! along with Galacticus. If not, see . + + !!{ + Implementation of an Einasto (e.g. \citealt{cardone_spherical_2005}) mass distribution class. + !!} + + use :: Numerical_Interpolation, only : interpolator + + !![ + + + An Einasto (e.g. \citealt{cardone_spherical_2005}) mass distribution class. The density profile is given by: + \begin{equation} + \rho_\mathrm{dark matter}(r) = \rho_{-2} \exp \left( - {2 \over \alpha} \left[ \left( {r \over r_{-2}} \right)^\alpha - 1 + \right] \right). + \end{equation} + + + !!] + type, public, extends(massDistributionSpherical) :: massDistributionEinasto + !!{ + The Einasto (e.g. \citealt{cardone_spherical_2005}) mass distribution. + !!} + private + double precision :: densityNormalization , scaleLength , & + & shapeParameter , massTotal_ + double precision :: enclosedMassRadiusPrevious , enclosedMassPrevious + double precision :: massScaleFreeRadiusMinimum , massScaleFreeRadiusMaximum + double precision :: massScaleFreeMinimum , massScaleFreeMaximum + type (interpolator), allocatable :: massScaleFree_ + double precision :: densityScaleFreeRadiusMinimum , densityScaleFreeRadiusMaximum + double precision :: densityScaleFreeMinimum , densityScaleFreeMaximum + type (interpolator), allocatable :: densityScaleFree_ + double precision :: angularMomentumSpecificScaleFreeRadiusMinimum, angularMomentumSpecificScaleFreeRadiusMaximum + double precision :: angularMomentumSpecificScaleFreeMinimum , angularMomentumSpecificScaleFreeMaximum + type (interpolator), allocatable :: angularMomentumSpecificScaleFree_ + double precision :: timeFreefallScaleFreeRadiusMinimum , timeFreefallScaleFreeRadiusMaximum + double precision :: timeFreefallScaleFreeMinimum , timeFreefallScaleFreeMaximum + type (interpolator), allocatable :: timeFreefallScaleFree_ + contains + !![ + + + + + !!] + procedure :: massTotal => einastoMassTotal + procedure :: density => einastoDensity + procedure :: densityGradientRadial => einastoDensityGradientRadial + procedure :: densityRadialMoment => einastoDensityRadialMoment + procedure :: massEnclosedBySphere => einastoMassEnclosedBySphere + procedure :: radiusEnclosingMass => einastoRadiusEnclosingMass + procedure :: radiusEnclosingDensity => einastoRadiusEnclosingDensity + procedure :: radiusFromSpecificAngularMomentum => einastoRadiusFromSpecificAngularMomentum + procedure :: radiusFreefall => einastoRadiusFreefall + procedure :: radiusFreefallIncreaseRate => einastoRadiusFreefallIncreaseRate + procedure :: timeFreefallTabulate => einastoTimeFreefallTabulate + procedure :: timeFreefallMinimum => einastoTimeFreefallMinimum + procedure :: potentialIsAnalytic => einastoPotentialIsAnalytic + procedure :: potential => einastoPotential + procedure :: descriptor => einastoDescriptor + end type massDistributionEinasto + + interface massDistributionEinasto + !!{ + Constructors for the {\normalfont \ttfamily einasto} mass distribution class. + !!} + module procedure massDistributionEinastoConstructorParameters + module procedure massDistributionEinastoConstructorInternal + end interface massDistributionEinasto + +contains + + function massDistributionEinastoConstructorParameters(parameters) result(self) + !!{ + Constructor for the {\normalfont \ttfamily einasto} mass distribution class which builds the object from a parameter + set. + !!} + use :: Input_Parameters , only : inputParameter , inputParameters + use :: Galactic_Structure_Options, only : enumerationComponentTypeEncode, enumerationMassTypeEncode + use :: Numerical_Constants_Math , only : Pi + use :: Gamma_Functions , only : Gamma_Function + implicit none + type (massDistributionEinasto) :: self + type (inputParameters ), intent(inout) :: parameters + double precision :: mass , scaleLength , & + & densityNormalization, concentration, & + & virialRadius , shapeParameter + logical :: dimensionless + type (varying_string ) :: componentType + type (varying_string ) :: massType + + !![ + + shapeParameter + The shape parameter, $\alpha$, of the Einasto profile. + parameters + + + densityNormalization + shapeParameter/4.0d0/Pi*(2.0d0/shapeParameter)**(3.0d0/shapeParameter)*exp(-2.0d0/shapeParameter)/Gamma_Function(3.0d0/shapeParameter) + The density normalization of the Einasto profile. + parameters + + + scaleLength + 1.0d0 + The scale radius of the Einasto profile. + parameters + + + mass + 1.0d0 + The mass of the Einasto profile. + parameters + + + concentration + 1.0d0 + The concentration of the Einasto profile. + parameters + + + virialRadius + 1.0d0 + The virial radius of the Einasto profile. + parameters + + + dimensionless + .true. + If true the Einasto profile is considered to be dimensionless. + parameters + + + componentType + var_str('unknown') + The component type that this mass distribution represents. + parameters + + + massType + var_str('unknown') + The mass type that this mass distribution represents. + parameters + + + self=massDistributionEinasto(shapeParameter=shapeParameter,componentType=enumerationComponentTypeEncode(componentType,includesPrefix=.false.),massType=enumerationMassTypeEncode(massType,includesPrefix=.false.){conditions}) + + + + + + + + + !!] + return + end function massDistributionEinastoConstructorParameters + + function massDistributionEinastoConstructorInternal(shapeParameter,scaleLength,concentration,densityNormalization,mass,virialRadius,dimensionless,componentType,massType) result(self) + !!{ + Internal constructor for ``einasto'' mass distribution class. + !!} + use :: Error , only : Error_Report + use :: Numerical_Constants_Math, only : Pi + use :: Gamma_Functions , only : Gamma_Function + implicit none + type (massDistributionEinasto ) :: self + double precision , intent(in ) :: shapeParameter + double precision , intent(in ), optional :: scaleLength , concentration, & + & densityNormalization, mass , & + & virialRadius + logical , intent(in ), optional :: dimensionless + type (enumerationComponentTypeType), intent(in ), optional :: componentType + type (enumerationMassTypeType ), intent(in ), optional :: massType + double precision :: radiusScaleFree + !![ + + !!] + + ! Determine scale length + if ( & + & present(scaleLength ) & + & ) then + self%scaleLength =scaleLength + else if ( & + & present(concentration).and. & + & present(virialRadius ) & + & ) then + self%scaleLength=virialRadius/concentration + else + call Error_Report('no means to determine scale length'//{introspection:location}) + end if + ! Determine density normalization. + if ( & + & present(densityNormalization) & + & ) then + self%densityNormalization=+densityNormalization + self%massTotal_ =+4.0d0*Pi*densityNormalization*self%scaleLength**3/shapeParameter/(2.0d0/shapeParameter)**(3.0d0/shapeParameter)*Gamma_Function(3.0d0/shapeParameter)*exp(2.0d0/shapeParameter) + else if ( & + & present(mass ).and. & + & present(virialRadius ) & + & ) then + radiusScaleFree =+virialRadius/self%scaleLength + self%densityNormalization=+mass/4.0d0/Pi/self%scaleLength**3*shapeParameter*exp(-2.0d0/shapeParameter)*(2.0d0/shapeParameter)**(3.0d0/shapeParameter)/Gamma_Function(3.0d0/shapeParameter) + self%massTotal_ =+mass + else + call Error_Report('either "densityNormalization", or "mass" and "virialRadius" must be specified'//{introspection:location}) + end if + ! Determine if profile is dimensionless. + if (present(dimensionless )) then + self%dimensionless=dimensionless + else + self%dimensionless=.false. + end if + + ! Initialize memoized results. + self%enclosedMassPrevious =-huge(0.0d0) + self%enclosedMassRadiusPrevious =-huge(0.0d0) + self%densityScaleFreeMinimum =+huge(0.0d0) + self%densityScaleFreeMaximum =-huge(0.0d0) + self%densityScaleFreeRadiusMinimum =+1.0d0 + self%densityScaleFreeRadiusMaximum =+1.0d0 + self%angularMomentumSpecificScaleFreeMinimum =+huge(0.0d0) + self%angularMomentumSpecificScaleFreeMaximum =-huge(0.0d0) + self%angularMomentumSpecificScaleFreeRadiusMinimum=+1.0d0 + self%angularMomentumSpecificScaleFreeRadiusMaximum=+1.0d0 + self%timeFreefallScaleFreeMinimum =+huge(0.0d0) + self%timeFreefallScaleFreeMaximum =-huge(0.0d0) + self%timeFreefallScaleFreeRadiusMinimum =+1.0d0 + self%timeFreefallScaleFreeRadiusMaximum =+1.0d0 + self%massScaleFreeMinimum =+huge(0.0d0) + self%massScaleFreeMaximum =-huge(0.0d0) + self%massScaleFreeRadiusMinimum =+1.0d0 + self%massScaleFreeRadiusMaximum =+1.0d0 + return + end function massDistributionEinastoConstructorInternal + + double precision function einastoMassTotal(self) + !!{ + Return the total mass in an Einasto mass distribution. + !!} + implicit none + class(massDistributionEinasto), intent(inout) :: self + + einastoMassTotal=self%massTotal_ + return + end function einastoMassTotal + + double precision function einastoDensity(self,coordinates) result(density) + !!{ + Return the density at the specified {\normalfont \ttfamily coordinates} in an Einasto mass distribution. + !!} + implicit none + class (massDistributionEinasto ), intent(inout) :: self + class (coordinate ), intent(in ) :: coordinates + double precision :: radiusScaleFree + + ! Compute the density at this position. + radiusScaleFree=+coordinates%rSpherical () & + & /self %scaleLength + density =+self %densityNormalization & + & *exp( & + & -(2.0d0/self%shapeParameter) & + & *( & + & +radiusScaleFree**self%shapeParameter & + & -1.0d0 & + & ) & + & ) + return + end function einastoDensity + + double precision function einastoDensityGradientRadial(self,coordinates,logarithmic) result(densityGradientRadial) + !!{ + Return the density at the specified {\normalfont \ttfamily coordinates} in an Einasto \citep{navarro_structure_1996} mass distribution. + !!} + implicit none + class (massDistributionEinasto), intent(inout), target :: self + class (coordinate ), intent(in ) :: coordinates + logical , intent(in ), optional :: logarithmic + double precision :: radiusScaleFree + !![ + + !!] + + radiusScaleFree =+coordinates%rSpherical() & + & /self %scaleLength + if (radiusScaleFree <= 0.0d0) then + densityGradientRadial=+0.0d0 + else + densityGradientRadial=-2.0d0 & + & *radiusScaleFree**self%shapeParameter + if (.not.logarithmic_) densityGradientRadial=+ densityGradientRadial & + & *self %density (coordinates) & + & /coordinates%rSpherical ( ) + end if + return + end function einastoDensityGradientRadial + + double precision function einastoDensityRadialMoment(self,moment,radiusMinimum,radiusMaximum,isInfinite) result(densityRadialMoment) + !!{ + Computes radial moments of the density in an Einasto \citep{navarro_structure_1996} mass distribution. + !!} + implicit none + class (massDistributionEinasto), intent(inout) :: self + double precision , intent(in ) :: moment + double precision , intent(in ), optional :: radiusMinimum , radiusMaximum + logical , intent( out), optional :: isInfinite + double precision :: radialMomentMinimum, radialMomentMaximum + + densityRadialMoment=0.0d0 + if (present(isInfinite)) isInfinite=.false. + if ((.not.present(radiusMinimum) .or. radiusMinimum <= 0.0d0) .and. moment <= -1) then + if (present(isInfinite)) then + isInfinite=.true. + return + else + call Error_Report('radial moment is infinite'//{introspection:location}) + end if + end if + if (present(radiusMinimum)) then + radialMomentMinimum=radialMomentScaleFree(radiusMinimum/self%scaleLength) + else + radialMomentMinimum=radialMomentScaleFree( 0.0d0) + end if + if (present(radiusMaximum)) then + radialMomentMaximum=radialMomentScaleFree(radiusMaximum/self%scaleLength) + else + radialMomentMaximum=0.0d0 + end if + densityRadialMoment=+self%densityNormalization & + & *self%scaleLength **(moment+1.0d0) & + & *( & + & +radialMomentMaximum & + & -radialMomentMinimum & + & ) + return + + contains + + double precision function radialMomentScaleFree(radius) + !!{ + Provides the scale-free part of the radial moment of the Einasto density profile. + !!} + use :: Gamma_Functions, only : Gamma_Function, Gamma_Function_Incomplete + implicit none + double precision, intent(in ) :: radius + + radialMomentScaleFree=-exp ( 2.0d0 /self%shapeParameter ) & + & *Gamma_Function ((1.0d0+moment)/self%shapeParameter ) & + & *Gamma_Function_Incomplete((1.0d0+moment)/self%shapeParameter,2.0d0*radius**self%shapeParameter/self%shapeParameter) & + & / self%shapeParameter & + & / ( 2.0d0 /self%shapeParameter )**((1.0d0+moment)/self%shapeParameter) + return + end function radialMomentScaleFree + + end function einastoDensityRadialMoment + + double precision function einastoMassEnclosedBySphere(self,radius) result(mass) + !!{ + Computes the mass enclosed within a sphere of given {\normalfont \ttfamily radius} for einasto mass distributions. + !!} + implicit none + class (massDistributionEinasto), intent(inout), target :: self + double precision , intent(in ) :: radius + double precision :: radiusScaleFree + + if (radius /= self%enclosedMassRadiusPrevious) then + self%enclosedMassRadiusPrevious=+ radius + radiusScaleFree =+ radius & + & /self%scaleLength + self%enclosedMassPrevious =+ massEnclosedScaleFree(radiusScaleFree,self%shapeParameter) & + & *self%densityNormalization & + & *self%scaleLength **3 + end if + mass=self%enclosedMassPrevious + return + end function einastoMassEnclosedBySphere + + double precision function einastoRadiusEnclosingMass(self,mass,massFractional) result(radius) + !!{ + Computes the radius enclosing a given mass or mass fraction for einasto mass distributions. + !!} + use :: Numerical_Ranges, only : Make_Range , rangeTypeLogarithmic + use :: Error , only : Error_Report + implicit none + class (massDistributionEinasto), intent(inout), target :: self + double precision , intent(in ), optional :: mass , massFractional + double precision :: mass_ + double precision , allocatable , dimension(:) :: radii , masses + double precision , parameter :: countRadiiPerDecade=100.0d0 + double precision :: massScaleFree , mass_ + integer :: countRadii , i + + mass_=0.0d0 + if (present(mass)) then + mass_= mass + else if (present(massFractional)) then + mass_=massFractional*self%massTotal_ + else + call Error_Report('either mass or massFractional must be supplied'//{introspection:location}) + end if + massScaleFree=+ mass_ & + & /self%densityNormalization & + & /self%scaleLength **3 + if ( & + & massScaleFree <= self%massScaleFreeMinimum & + & .or. & + & massScaleFree > self%massScaleFreeMaximum & + & ) then + do while (massEnclosedScaleFree(self%massScaleFreeRadiusMinimum,self%shapeParameter) >= massScaleFree) + self%massScaleFreeRadiusMinimum=0.5d0*self%massScaleFreeRadiusMinimum + end do + do while (massEnclosedScaleFree(self%massScaleFreeRadiusMaximum,self%shapeParameter) < massScaleFree) + self%massScaleFreeRadiusMaximum=2.0d0*self%massScaleFreeRadiusMaximum + end do + countRadii=int(log10(self%massScaleFreeRadiusMaximum/self%massScaleFreeRadiusMinimum)*countRadiiPerDecade)+1 + if (allocated(self%massScaleFree_)) deallocate(self%massScaleFree_) + allocate( radii (countRadii)) + allocate( masses (countRadii)) + allocate(self%massScaleFree_ ) + radii =Make_Range(self%massScaleFreeRadiusMinimum,self%massScaleFreeRadiusMaximum,countRadii,rangeTypeLogarithmic) + do i=1,countRadii + masses (i)=massEnclosedScaleFree( radii(i),self%shapeParameter) + end do + self%massScaleFreeMinimum =masses ( 1 ) + self%massScaleFreeMaximum =masses (countRadii ) + self%massScaleFree_ =interpolator (masses ,radii ) + end if + radius=+self%massScaleFree_%interpolate(massScaleFree) & + & *self%scaleLength + return + end function einastoRadiusEnclosingMass + + double precision function einastoRadiusEnclosingDensity(self,density,radiusGuess) result(radius) + !!{ + Computes the radius enclosing a given mean density for Einasto mass distributions. + !!} + use :: Numerical_Ranges, only : Make_Range, rangeTypeLogarithmic + implicit none + class (massDistributionEinasto), intent(inout), target :: self + double precision , intent(in ) :: density + double precision , intent(in ), optional :: radiusGuess + double precision , allocatable , dimension(:) :: radii , densities + double precision , parameter :: countRadiiPerDecade=100.0d0 + double precision :: densityScaleFree + integer :: countRadii , i + + densityScaleFree=+density & + & /self%densityNormalization + if ( & + & densityScaleFree <= self%densityScaleFreeMinimum & + & .or. & + & densityScaleFree > self%densityScaleFreeMaximum & + & ) then + do while (densityEnclosedScaleFree(self%densityScaleFreeRadiusMinimum,self%shapeParameter) < densityScaleFree) + self%densityScaleFreeRadiusMinimum=0.5d0*self%densityScaleFreeRadiusMinimum + end do + do while (densityEnclosedScaleFree(self%densityScaleFreeRadiusMaximum,self%shapeParameter) >= densityScaleFree) + self%densityScaleFreeRadiusMaximum=2.0d0*self%densityScaleFreeRadiusMaximum + end do + countRadii=int(log10(self%densityScaleFreeRadiusMaximum/self%densityScaleFreeRadiusMinimum)*countRadiiPerDecade)+1 + if (allocated(self%densityScaleFree_)) deallocate(self%densityScaleFree_) + allocate( radii (countRadii)) + allocate( densities (countRadii)) + allocate(self%densityScaleFree_ ) + radii = Make_Range(self%densityScaleFreeRadiusMinimum,self%densityScaleFreeRadiusMaximum,countRadii,rangeTypeLogarithmic) + do i=1,countRadii + densities (i)=-densityEnclosedScaleFree(radii (i),self%shapeParameter) + end do + self%densityScaleFreeMinimum =-densities (countRadii ) + self%densityScaleFreeMaximum =-densities ( 1 ) + self%densityScaleFree_ = interpolator (densities ,radii ) + end if + radius=+self%densityScaleFree_%interpolate(-densityScaleFree) & + & *self%scaleLength + return + end function einastoRadiusEnclosingDensity + + double precision function massEnclosedScaleFree(radius,shapeParameter) result(mass) + !!{ + Evaluate the mass enclosed by a given radius in a scale-free Einasto mass distribution. + !!} + use :: Numerical_Constants_Math, only : Pi + use :: Gamma_Functions , only : Gamma_Function, Gamma_Function_Incomplete_Complementary + implicit none + double precision, intent(in ) :: radius, shapeParameter + + mass =+4.0d0 & + & *Pi & + & / shapeParameter & + & / (2.0d0/shapeParameter )**(3.0d0/shapeParameter) & + & *exp (2.0d0/shapeParameter ) & + & *Gamma_Function (3.0d0/shapeParameter ) & + & *Gamma_Function_Incomplete_Complementary(3.0d0/shapeParameter,2.0d0*radius**shapeParameter/shapeParameter) + return + end function massEnclosedScaleFree + + double precision function densityEnclosedScaleFree(radius,shapeParameter) result(density) + !!{ + Evaluate the mean enclosed density at a given radius in a scale-free Einasto mass distribution. + !!} + use :: Numerical_Constants_Math, only : Pi + implicit none + double precision, intent(in ) :: radius, shapeParameter + + density=+3.0d0 & + & /4.0d0 & + & /Pi & + & *massEnclosedScaleFree(radius,shapeParameter) & + & / radius **3 + return + end function densityEnclosedScaleFree + + double precision function einastoRadiusFromSpecificAngularMomentum(self,angularMomentumSpecific) result(radius) + !!{ + Computes the radius corresponding to a given specific angular momentum for einasto mass distributions. + !!} + use :: Numerical_Constants_Astronomical, only : gravitationalConstantGalacticus + use :: Numerical_Ranges , only : Make_Range , rangeTypeLogarithmic + implicit none + class (massDistributionEinasto), intent(inout), target :: self + double precision , intent(in ) :: angularMomentumSpecific + double precision , allocatable , dimension(:) :: radii , angularMomentaSpecific + double precision , parameter :: countRadiiPerDecade =100.0d0 + double precision :: angularMomentumSpecificScaleFree + integer :: countRadii , i + + if (angularMomentumSpecific > 0.0d0) then + angularMomentumSpecificScaleFree=+angularMomentumSpecific & + & /sqrt( & + & +gravitationalConstantGalacticus & + & *self%densityNormalization & + & ) & + & / self%scaleLength **2 + if ( & + & angularMomentumSpecificScaleFree <= self%angularMomentumSpecificScaleFreeMinimum & + & .or. & + & angularMomentumSpecificScaleFree > self%angularMomentumSpecificScaleFreeMaximum & + & ) then + do while (angularMomentumSpecificEnclosedScaleFree(self%angularMomentumSpecificScaleFreeRadiusMinimum,self%shapeParameter) >= angularMomentumSpecificScaleFree) + self%angularMomentumSpecificScaleFreeRadiusMinimum=0.5d0*self%angularMomentumSpecificScaleFreeRadiusMinimum + end do + do while (angularMomentumSpecificEnclosedScaleFree(self%angularMomentumSpecificScaleFreeRadiusMaximum,self%shapeParameter) < angularMomentumSpecificScaleFree) + self%angularMomentumSpecificScaleFreeRadiusMaximum=2.0d0*self%angularMomentumSpecificScaleFreeRadiusMaximum + end do + countRadii=int(log10(self%angularMomentumSpecificScaleFreeRadiusMaximum/self%angularMomentumSpecificScaleFreeRadiusMinimum)*countRadiiPerDecade)+1 + if (allocated(self%angularMomentumSpecificScaleFree_)) deallocate(self%angularMomentumSpecificScaleFree_) + allocate( radii (countRadii)) + allocate( angularMomentaSpecific (countRadii)) + allocate(self%angularMomentumSpecificScaleFree_ ) + radii =Make_Range(self%angularMomentumSpecificScaleFreeRadiusMinimum,self%angularMomentumSpecificScaleFreeRadiusMaximum,countRadii,rangeTypeLogarithmic) + do i=1,countRadii + angularMomentaSpecific (i)=angularMomentumSpecificEnclosedScaleFree( radii(i),self%shapeParameter) + end do + self%angularMomentumSpecificScaleFreeMinimum =angularMomentaSpecific ( 1 ) + self%angularMomentumSpecificScaleFreeMaximum =angularMomentaSpecific ( countRadii ) + self%angularMomentumSpecificScaleFree_ =interpolator (angularMomentaSpecific,radii ) + end if + radius=+self%angularMomentumSpecificScaleFree_%interpolate(angularMomentumSpecificScaleFree) & + & *self%scaleLength + else + radius=+0.0d0 + end if + return + end function einastoRadiusFromSpecificAngularMomentum + + double precision function angularMomentumSpecificEnclosedScaleFree(radius,shapeParameter) result(angularMomentumSpecific) + !!{ + Evaluate the specific angular momentum at a given radius in a scale-free Einasto mass distribution. + !!} + implicit none + double precision, intent(in ) :: radius, shapeParameter + + angularMomentumSpecific=+sqrt( & + & +massEnclosedScaleFree(radius,shapeParameter) & + & * radius & + & ) + return + end function angularMomentumSpecificEnclosedScaleFree + + logical function einastoPotentialIsAnalytic(self) result(isAnalytic) + !!{ + Return that the potential has an analytic form. + !!} + implicit none + class(massDistributionEinasto), intent(inout) :: self + + isAnalytic=.true. + return + end function einastoPotentialIsAnalytic + + double precision function einastoPotential(self,coordinates,status) result(potential) + !!{ + Return the potential at the specified {\normalfont \ttfamily coordinates} in an einasto mass distribution. + !!} + use :: Coordinates , only : assignment(=) + use :: Galactic_Structure_Options , only : structureErrorCodeSuccess , structureErrorCodeInfinite + use :: Numerical_Constants_Astronomical, only : gravitationalConstantGalacticus + use :: Error , only : Error_Report + implicit none + class (massDistributionEinasto ), intent(inout), target :: self + class (coordinate ), intent(in ) :: coordinates + type (enumerationStructureErrorCodeType), intent( out), optional :: status + double precision :: radiusScaleFree + + if (present(status)) status=structureErrorCodeSuccess + radiusScaleFree=+coordinates%rSpherical () & + & /self %scaleLength + potential=+potentialScaleFree (radiusScaleFree,self%shapeParameter) & + & *self%densityNormalization & + & *self%scaleLength **2 + if (.not.self%isDimensionless()) potential=+gravitationalConstantGalacticus & + & *potential + return + end function einastoPotential + + double precision function potentialScaleFree(radius,shapeParameter) result(potential) + !!{ + Compute the potential in a scale-free Einasto mass distribution. Uses the results from \cite{retana-montenegro_analytical_2012}, + their equations (19) and (20), but with different normalizations for the density and scale radius. + !!} + use :: Numerical_Constants_Math, only : Pi + use :: Gamma_Functions , only : Gamma_Function, Gamma_Function_Incomplete, Gamma_Function_Incomplete_Complementary + implicit none + double precision, intent(in ) :: radius, shapeParameter + + if (radius <= 0.0d0) then + potential=-2.0d0 **(+1.0d0-2.0d0/shapeParameter ) & + & *shapeParameter **( 2.0d0/shapeParameter ) & + & *exp ( 2.0d0/shapeParameter ) & + & *Pi & + & *Gamma_Function (+1.0d0+2.0d0/shapeParameter ) + else + potential=-4.0d0 & + & *Pi & + & *exp(2.0d0/shapeParameter) & + & / shapeParameter & + & / radius & + & *( & + & + radius & + & *(2.0d0/shapeParameter) **( -2.0d0/shapeParameter ) & + & *Gamma_Function_Incomplete ( +2.0d0/shapeParameter,2.0d0*radius**shapeParameter/shapeParameter) & + & *Gamma_Function ( +2.0d0/shapeParameter ) & + & +shapeParameter **( +3.0d0/shapeParameter ) & + & / 8.0d0 **( +1.0d0/shapeParameter ) & + & *Gamma_Function_Incomplete_Complementary( +3.0d0/shapeParameter,2.0d0*radius**shapeParameter/shapeParameter) & + & *Gamma_Function ( +3.0d0/shapeParameter ) & + & ) + end if + return + end function potentialScaleFree + + double precision function potentialDifferenceScaleFree(radius1,radius2,shapeParameter) result(potential) + !!{ + Compute the potential difference in a scale-free Einasto mass distribution. + !!} + implicit none + double precision, intent(in ) :: radius1 , radius2, & + & shapeParameter + + potential=+potentialScaleFree(radius1,shapeParameter) & + & -potentialScaleFree(radius2,shapeParameter) + return + end function potentialDifferenceScaleFree + + double precision function einastoRadiusFreefall(self,time) result(radius) + !!{ + Compute the freefall radius at the given {\normalfont \ttfamily time} in an Einasto mass distribution. + !!} + use :: Numerical_Constants_Astronomical, only : Mpc_per_km_per_s_To_Gyr, gravitationalConstantGalacticus + implicit none + class (massDistributionEinasto), intent(inout) :: self + double precision , intent(in ) :: time + double precision :: timeScaleFree, timeScale + + timeScale =+1.0d0 & + & /sqrt( & + & +gravitationalConstantGalacticus & + & *self%densityNormalization & + & ) & + & *Mpc_per_km_per_s_To_Gyr + timeScaleFree=+time & + & /timeScale + if (timeScaleFree <= self%timeFreefallMinimum()) then + radius=0.0d0 + return + end if + call self%timeFreefallTabulate(timeScaleFree) + radius=+self%timeFreefallScaleFree_%interpolate(timeScaleFree) & + & *self%scaleLength + return + end function einastoRadiusFreefall + + double precision function einastoRadiusFreefallIncreaseRate(self,time) result(radiusIncreaseRate) + !!{ + Compute the rate of increase of the freefall radius at the given {\normalfont \ttfamily time} in an einasto mass + distribution. + !!} + use :: Numerical_Constants_Astronomical, only : Mpc_per_km_per_s_To_Gyr, gravitationalConstantGalacticus + implicit none + class (massDistributionEinasto), intent(inout) :: self + double precision , intent(in ) :: time + double precision :: timeScaleFree, timeScale + + timeScale =+1.0d0 & + & /sqrt( & + & +gravitationalConstantGalacticus & + & *self%densityNormalization & + & ) & + & *Mpc_per_km_per_s_To_Gyr + timeScaleFree=+time & + & /timeScale + if (timeScaleFree <= self%timeFreefallMinimum()) then + radiusIncreaseRate=0.0d0 + return + end if + call self%timeFreefallTabulate(timeScaleFree) + radiusIncreaseRate=+self%timeFreefallScaleFree_%derivative(timeScaleFree) & + & *self%scaleLength & + & / timeScale + return + end function einastoRadiusFreefallIncreaseRate + + double precision function einastoTimeFreefallMinimum(self) result(timeScaleFreeMinimum) + !!{ + Compute the minimum freefall time in a scale-free Einasto profile. + !!} + use :: Numerical_Constants_Math, only : Pi + implicit none + class(massDistributionEinasto), intent(inout) :: self + + timeScaleFreeMinimum=+sqrt( & + & + 3.0d0 & + & /16.0d0 & + & *Pi & + & ) & + & *exp( & + & -1.0d0 & + & /self%shapeParameter & + & ) + return + end function einastoTimeFreefallMinimum + + subroutine einastoTimeFreefallTabulate(self,timeScaleFree) + !!{ + Tabulate the freefall radius at the given {\normalfont \ttfamily time} in an Einasto mass distribution. + !!} + use :: Numerical_Integration, only : integrator + use :: Numerical_Ranges , only : Make_Range, rangeTypeLogarithmic + implicit none + class (massDistributionEinasto), intent(inout) :: self + double precision , intent(in ) :: timeScaleFree + double precision , allocatable , dimension(:) :: radii , timesFreefall + double precision , parameter :: countRadiiPerDecade=100.0d0 + double precision :: radiusStart + integer :: countRadii , i + type (integrator ) :: integrator_ + + if ( & + & timeScaleFree <= self%timeFreefallScaleFreeMinimum & + & .or. & + & timeScaleFree > self%timeFreefallScaleFreeMaximum & + & ) then + integrator_=integrator(timeFreeFallIntegrand,toleranceRelative=1.0d-6) + do while (timeFreefallScaleFree(self%timeFreefallScaleFreeRadiusMinimum) >= timeScaleFree) + self%timeFreefallScaleFreeRadiusMinimum=0.5d0*self%timeFreefallScaleFreeRadiusMinimum + end do + do while (timeFreefallScaleFree(self%timeFreefallScaleFreeRadiusMaximum) < timeScaleFree) + self%timeFreefallScaleFreeRadiusMaximum=2.0d0*self%timeFreefallScaleFreeRadiusMaximum + end do + countRadii=int(log10(self%timeFreefallScaleFreeRadiusMaximum/self%timeFreefallScaleFreeRadiusMinimum)*countRadiiPerDecade)+1 + if (allocated(self%timeFreefallScaleFree_)) deallocate(self%timeFreefallScaleFree_) + allocate( radii (countRadii)) + allocate( timesFreefall (countRadii)) + allocate(self%timeFreefallScaleFree_ ) + radii=Make_Range(self%timeFreefallScaleFreeRadiusMinimum,self%timeFreefallScaleFreeRadiusMaximum,countRadii,rangeTypeLogarithmic) + do i=1,countRadii + timesFreefall(i)=timeFreefallScaleFree(radii(i)) + end do + self%timeFreefallScaleFreeMinimum=timesFreefall( 1 ) + self%timeFreefallScaleFreeMaximum=timesFreefall( countRadii ) + self%timeFreefallScaleFree_ =interpolator (timesFreefall,radii) + end if + return + + contains + + double precision function timeFreefallScaleFree(radius) + !!{ + Evaluate the freefall time from a given radius in a scale-free Einasto mass distribution. + !!} + implicit none + double precision, intent(in ) :: radius + + radiusStart = radius + timeFreefallScaleFree=integrator_%integrate(0.0d0,radius) + return + end function timeFreefallScaleFree + + double precision function timeFreeFallIntegrand(radius) + !!{ + Integrand used to find the freefall time in a scale-free Einasto mass distribution. + !!} + implicit none + double precision, intent(in ) :: radius + double precision :: potentialDifference + + if (radius == 0.0d0) then + timeFreeFallIntegrand=+0.0d0 + else + potentialDifference=+potentialDifferenceScaleFree(radiusStart,radius,self%shapeParameter) + if (potentialDifference > 0.0d0) then + timeFreeFallIntegrand=+1.0d0 & + & /sqrt( & + & +2.0d0 & + & *potentialDifference & + & ) + else + timeFreeFallIntegrand=+0.0d0 + end if + end if + return + end function timeFreeFallIntegrand + + end subroutine einastoTimeFreefallTabulate + + subroutine einastoDescriptor(self,descriptor,includeClass,includeFileModificationTimes) + !!{ + Return an input parameter list descriptor which could be used to recreate this object. + !!} + use :: Input_Parameters, only : inputParameters + implicit none + class (massDistributionEinasto), intent(inout) :: self + type (inputParameters ), intent(inout) :: descriptor + logical , intent(in ), optional :: includeClass , includeFileModificationTimes + character(len=18 ) :: parameterLabel + type (inputParameters ) :: parameters + + if (.not.present(includeClass).or.includeClass) call descriptor%addParameter('massDistribution','Einasto') + parameters=descriptor%subparameters('massDistribution') + write (parameterLabel,'(e17.10)') self%densityNormalization + call parameters%addParameter('densityNormalization',trim(adjustl(parameterLabel))) + write (parameterLabel,'(e17.10)') self%scaleLength + call parameters%addParameter('scaleLength' ,trim(adjustl(parameterLabel))) + write (parameterLabel,'(e17.10)') self%shapeParameter + call parameters%addParameter('shapeParameter' ,trim(adjustl(parameterLabel))) + return + end subroutine einastoDescriptor + diff --git a/source/mass_distributions.spherical.Enzo_hydrostatic.F90 b/source/mass_distributions.spherical.Enzo_hydrostatic.F90 new file mode 100644 index 0000000000..64a26e51f1 --- /dev/null +++ b/source/mass_distributions.spherical.Enzo_hydrostatic.F90 @@ -0,0 +1,222 @@ +!! Copyright 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, +!! 2019, 2020, 2021, 2022, 2023 +!! Andrew Benson +!! +!! This file is part of Galacticus. +!! +!! Galacticus is free software: you can redistribute it and/or modify +!! it under the terms of the GNU General Public License as published by +!! the Free Software Foundation, either version 3 of the License, or +!! (at your option) any later version. +!! +!! Galacticus is distributed in the hope that it will be useful, +!! but WITHOUT ANY WARRANTY; without even the implied warranty of +!! MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +!! GNU General Public License for more details. +!! +!! You should have received a copy of the GNU General Public License +!! along with Galacticus. If not, see . + + !!{ + Implementation of a mass distribution class that mimics the ``hydrostatic'' profile used by the Enzo simulation code. + !!} + + !![ + + + A hot halo mass distribution class which adopts a spherically symmetric density profile for the hot halo motivated by the + ``hydrostatic'' profile available in the \gls{enzo} code. Specifically, + \begin{equation} + \rho_\mathrm{hot halo}(r) \propto \left\{ \begin{array}{ll} T^{-1} r^{-1} & \hbox{ if } r > r_\mathrm{core} \\ T^{-1} + r_\mathrm{core}^{-1} & \hbox{ if } r \le r_\mathrm{core}, \end{array} \right. + \end{equation} + where the core radius, $r_\mathrm{core}$, is set using the selected cored profile core radius method (see + \refPhysics{hotHaloMassDistributionCoreRadius}). The profile is normalized such that the current mass in the + hot gas profile is contained within the outer radius of the hot halo, $r_\mathrm{hot, outer}$. Note that the \gls{enzo} + hydrostatic profile does not include this core, but without introducing this the profile mass can be divergent at small + radii. + + + !!] + type, public, extends(massDistributionSpherical) :: massDistributionEnzoHydrostatic + !!{ + The Enzo hydrostatic mass distribution. + !!} + private + double precision :: radiusScale , radiusOuter , & + & mass , normalizationDensity_ + logical :: truncateAtOuterRadius, normalizationDensityComputed + contains + !![ + + + + !!] + procedure :: normalizationDensity => enzoHydrostaticNormalizationDensity + procedure :: density => enzoHydrostaticDensity + procedure :: densityGradientRadial => enzoHydrostaticDensityGradientRadial + end type massDistributionEnzoHydrostatic + + interface massDistributionEnzoHydrostatic + !!{ + Constructors for the {\normalfont \ttfamily enzoHydrostatic} mass distribution class. + !!} + module procedure enzoHydrostaticConstructorParameters + module procedure enzoHydrostaticConstructorInternal + end interface massDistributionEnzoHydrostatic + +contains + + function enzoHydrostaticConstructorParameters(parameters) result(self) + !!{ + Constructor for the {\normalfont \ttfamily enzoHydrostatic} mass distribution class which builds the object from a parameter + set. + !!} + use :: Input_Parameters , only : inputParameter , inputParameters + use :: Galactic_Structure_Options, only : enumerationComponentTypeEncode, enumerationMassTypeEncode + implicit none + type (massDistributionEnzoHydrostatic) :: self + type (inputParameters ), intent(inout) :: parameters + type (varying_string ) :: componentType + type (varying_string ) :: massType + double precision :: radiusScale , radiusOuter, & + & mass + logical :: truncateAtOuterRadius + + !![ + + componentType + var_str('unknown') + The component type that this mass distribution represents. + parameters + + + massType + var_str('unknown') + The mass type that this mass distribution represents. + parameters + + + mass + The mass within the outer radius. + parameters + + + radiusOuter + The outer radius of the mass distribution. + parameters + + + radiusScale + The core radius of the mass distribution. + parameters + + + truncateAtOuterRadius + .false. + If true then the mass distribution is truncated beyond the outer radius. + parameters + + !!] + self=massDistributionEnzoHydrostatic(mass,radiusOuter,radiusScale,truncateAtOuterRadius,enumerationComponentTypeEncode(componentType,includesPrefix=.false.),enumerationMassTypeEncode(massType,includesPrefix=.false.)) + !![ + + !!] + return + end function enzoHydrostaticConstructorParameters + + function enzoHydrostaticConstructorInternal(mass,radiusOuter,radiusScale,truncateAtOuterRadius,componentType,massType) result(self) + !!{ + Internal constructor for ``enzoHydrostatic'' mass distribution class. + !!} + implicit none + type (massDistributionEnzoHydrostatic) :: self + double precision , intent(in ) :: radiusScale , radiusOuter, & + & mass + logical , intent(in ), optional :: truncateAtOuterRadius + type (enumerationComponentTypeType ), intent(in ), optional :: componentType + type (enumerationMassTypeType ), intent(in ), optional :: massType + !![ + + !!] + + ! This distribution profile is never dimensionless. + self%dimensionless =.false. + self%normalizationDensityComputed=.false. + return + end function enzoHydrostaticConstructorInternal + + double precision function enzoHydrostaticNormalizationDensity(self) result(normalizationDensity) + !!{ + Return the density normalization in a {\normalfont \ttfamily enzoHydrostatic} mass distribution. + !!} + implicit none + class (massDistributionEnzoHydrostatic), intent(inout) :: self + double precision :: massEnclosed + + if (.not.self%normalizationDensityComputed) then + self%normalizationDensityComputed=.true. + if ( & + & self%mass <= 0.0d0 & + & .or. & + & self%radiusOuter <= 0.0d0 & + & ) then + normalizationDensity=+0.0d0 + else + ! Compute the normalization. Initially set this to 1, then compute the enclosed mass, then compute the multiplicative + ! factor needed to get the required mass. + self%normalizationDensity_=+1.0d0 + massEnclosed =+self%massEnclosedBySphere(self%radiusOuter) + self%normalizationDensity_=+self%mass & + & / massEnclosed + end if + end if + normalizationDensity=self%normalizationDensity_ + return + end function enzoHydrostaticNormalizationDensity + + double precision function enzoHydrostaticDensity(self,coordinates) result(density) + !!{ + Return the density at the specified {\normalfont \ttfamily coordinates} in an Enzo hydrostatic mass distribution. + !!} + use :: Coordinates, only : coordinateSpherical, assignment(=) + implicit none + class (massDistributionEnzoHydrostatic), intent(inout) :: self + class (coordinate ), intent(in ) :: coordinates + type (coordinateSpherical ) :: coordinatesEffective + double precision :: radiusEffective + + radiusEffective = max(coordinates%rSpherical(),self%radiusScale) + coordinatesEffective= coordinates & + & * radiusEffective & + & /coordinates%rSpherical () + density =+self %normalizationDensity( ) & + & /self%kinematicsDistribution_%temperature (coordinatesEffective) & + & /radiusEffective **3 + + return + end function enzoHydrostaticDensity + + double precision function enzoHydrostaticDensityGradientRadial(self,coordinates,logarithmic) result(densityGradientRadial) + !!{ + Return the density at the specified {\normalfont \ttfamily coordinates} in an EnzoHydrostatic \citep{navarro_structure_1996} mass distribution. + !!} + implicit none + class (massDistributionEnzoHydrostatic), intent(inout), target :: self + class (coordinate ), intent(in ) :: coordinates + logical , intent(in ), optional :: logarithmic + !![ + + !!] + + if (coordinates%rSpherical() > self%radiusScale) then + densityGradientRadial =-self %kinematicsDistribution_%temperatureGradientLogarithmic(coordinates) & + & -3.0d0 + if (.not.logarithmic_) densityGradientRadial=+ densityGradientRadial & + & *self %density (coordinates) & + & /coordinates %rSpherical ( ) + else + densityGradientRadial =+0.0d0 + end if + return + end function enzoHydrostaticDensityGradientRadial diff --git a/source/mass_distributions.spherical.F90 b/source/mass_distributions.spherical.F90 index a324e75d20..3fc4059a50 100644 --- a/source/mass_distributions.spherical.F90 +++ b/source/mass_distributions.spherical.F90 @@ -34,23 +34,61 @@ contains !![ - - + + + + + + + + + + + + + + !!] - procedure :: symmetry => sphericalSymmetry - procedure :: massEnclosedBySphere => sphericalMassEnclosedBySphere - procedure :: radiusHalfMass => sphericalRadiusHalfMass - procedure :: acceleration => sphericalAcceleration - procedure :: tidalTensor => sphericalTidalTensor - procedure :: radiusEnclosingMass => sphericalRadiusEnclosingMass - procedure :: positionSample => sphericalPositionSample + procedure :: symmetry => sphericalSymmetry + procedure :: isSphericallySymmetric => sphericalIsSphericallySymmetric + procedure :: densityGradientRadial => sphericalDensityGradientRadial + procedure :: densityGradientRadialNumerical => sphericalDensityGradientRadialNumerical + procedure :: massEnclosedBySphere => sphericalMassEnclosedBySphere + procedure :: massEnclosedBySphereNumerical => sphericalMassEnclosedBySphereNumerical + procedure :: densityRadialMoment => sphericalDensityRadialMoment + procedure :: densityRadialMomentNumerical => sphericalDensityRadialMomentNumerical + procedure :: potential => sphericalPotential + procedure :: potentialNumerical => sphericalPotentialNumerical + procedure :: fourierTransform => sphericalFourierTransform + procedure :: fourierTransformNumerical => sphericalFourierTransformNumerical + procedure :: radiusFreefall => sphericalRadiusFreefall + procedure :: radiusFreefallNumerical => sphericalRadiusFreefallNumerical + procedure :: radiusFreefallIncreaseRate => sphericalRadiusFreefallIncreaseRate + procedure :: radiusFreefallIncreaseRateNumerical => sphericalRadiusFreefallIncreaseRateNumerical + procedure :: energy => sphericalEnergy + procedure :: energyNumerical => sphericalEnergyNumerical + procedure :: energyPotential => sphericalEnergyPotential + procedure :: energyKinetic => sphericalEnergyKinetic + procedure :: energyPotentialNumerical => sphericalEnergyPotentialNumerical + procedure :: energyKineticNumerical => sphericalEnergyKineticNumerical + procedure :: densitySphericalAverage => sphericalDensitySphericalAverage + procedure :: potentialDifferenceNumerical => sphericalPotentialDifferenceNumerical + procedure :: surfaceDensity => sphericalSurfaceDensity + procedure :: chandrasekharIntegral => sphericalChandrasekharIntegral + procedure :: acceleration => sphericalAcceleration + procedure :: tidalTensor => sphericalTidalTensor + procedure :: positionSample => sphericalPositionSample + procedure :: rotationCurve => sphericalRotationCurve + procedure :: rotationCurveNumerical => sphericalRotationCurve + procedure :: rotationCurveGradient => sphericalRotationCurveGradient + procedure :: radiusHalfMass => sphericalRadiusHalfMass end type massDistributionSpherical ! Module scope variables used in integration and root finding. class (massDistributionSpherical), pointer :: self_ - double precision :: massTarget - !$omp threadprivate(self_,massTarget) + double precision :: time_, radiusFreefall_ + !$omp threadprivate(self_,time_,radiusFreefall_) contains @@ -67,7 +105,85 @@ function sphericalSymmetry(self) return end function sphericalSymmetry - double precision function sphericalMassEnclosedBySphere(self,radius,componentType,massType) + logical function sphericalIsSphericallySymmetric(self) result(isSphericallySymmetric) + !!{ + Return true if the distribution is spherically symmetric. + !!} + implicit none + class(massDistributionSpherical), intent(inout) :: self + + isSphericallySymmetric=.true. + return + end function sphericalIsSphericallySymmetric + + double precision function sphericalDensityGradientRadial(self,coordinates,logarithmic) result(densityGradient) + !!{ + Return the radial density gradient at the specified {\normalfont \ttfamily coordinates} in a spherical mass distribution. + !!} + implicit none + class (massDistributionSpherical), intent(inout), target :: self + class (coordinate ), intent(in ) :: coordinates + logical , intent(in ), optional :: logarithmic + + densityGradient=self%densityGradientRadialNUmerical(coordinates,logarithmic) + return + end function sphericalDensityGradientRadial + + double precision function sphericalDensityGradientRadialNumerical(self,coordinates,logarithmic) result(densityGradient) + !!{ + Return the radial density gradient at the specified {\normalfont \ttfamily coordinates} in a spherical mass distribution using a numerical calculation. + !!} + use :: Numerical_Differentiation, only : differentiator + implicit none + class (massDistributionSpherical ), intent(inout), target :: self + class (coordinate ), intent(in ) :: coordinates + logical , intent(in ), optional :: logarithmic + double precision , parameter :: radiusLogarithmicStep=0.1d0 + type (differentiator ) :: differentiator_ + double precision :: radius + !![ + + !!] + + self_ => self + radius = coordinates %rSpherical( ) + differentiator_ = differentiator (densityEvaluate ) + densityGradient = +differentiator_%derivative(log(radius) ,radiusLogarithmicStep) + if (.not.logarithmic_) & + & densityGradient=+ densityGradient & + & *self%density (coordinates) & + & / radius + return + end function sphericalDensityGradientRadialNumerical + + double precision function densityEvaluate(radiusLogarithmic) result(density) + !!{ + GSL-callable function to evaluate the density of the dark matter profile. + !!} + use :: Coordinates, only : coordinateSpherical, assignment(=) + implicit none + double precision , intent(in ), value :: radiusLogarithmic + type (coordinateSpherical) :: coordinates + + coordinates=[exp(radiusLogarithmic),0.0d0,0.0d0] + density =log(self_%density(coordinates)) + return + end function densityEvaluate + + double precision function sphericalMassEnclosedBySphere(self,radius) result(mass) + !!{ + Computes the mass enclosed within a sphere of given {\normalfont \ttfamily radius} for spherically-symmetric mass + distributions. + !!} + implicit none + class (massDistributionSpherical), intent(inout), target :: self + double precision , intent(in ) :: radius + + mass=self%massEnclosedBySphereNumerical(radius) + return + end function sphericalMassEnclosedBySphere + + double precision function sphericalMassEnclosedBySphereNumerical(self,radius) result(mass) !!{ Computes the mass enclosed within a sphere of given {\normalfont \ttfamily radius} for spherically-symmetric mass distributions using numerical integration. @@ -75,25 +191,35 @@ double precision function sphericalMassEnclosedBySphere(self,radius,componentTyp use :: Numerical_Constants_Math, only : Pi use :: Numerical_Integration , only : integrator implicit none - class (massDistributionSpherical ), intent(inout), target :: self - double precision , intent(in ) :: radius - type (enumerationComponentTypeType), intent(in ), optional :: componentType - type (enumerationMassTypeType ), intent(in ), optional :: massType - type (integrator ) :: integrator_ - - if (.not.self%matches(componentType,massType)) then - sphericalMassEnclosedBySphere=0.0d0 - return - end if - self_ => self - integrator_ = integrator(sphericalMassEnclosedBySphereIntegrand,toleranceRelative=1.0d-6) - sphericalMassEnclosedBySphere = +4.0d0 & - & *Pi & - & *integrator_%integrate(0.0d0,radius) + class (massDistributionSpherical), intent(inout), target :: self + double precision , intent(in ) :: radius + type (integrator ) :: integrator_ + + self_ => self + integrator_ = integrator(sphericalMassEnclosedBySphereIntegrand,toleranceRelative=1.0d-6) + mass = +4.0d0 & + & *Pi & + & *integrator_%integrate(0.0d0,radius) return - end function sphericalMassEnclosedBySphere + end function sphericalMassEnclosedBySphereNumerical - double precision function sphericalMassEnclosedBySphereIntegrand(radius) + double precision function sphericalDensitySphericalAverage(self,radius) + !!{ + Computes the density averaged over a spherical shell. + !!} + use :: Coordinates, only : assignment(=), coordinateSpherical + implicit none + class (massDistributionSpherical), intent(inout) :: self + double precision , intent(in ) :: radius + type (coordinateSpherical ) :: position + + ! For a spherical mass distribution, the density averaged over a spherical shell, is just the regular density at that radius. + position =[radius,0.0d0,0.0d0] + sphericalDensitySphericalAverage=self%density(position) + return + end function sphericalDensitySphericalAverage + + double precision function sphericalMassEnclosedBySphereIntegrand(radius) result(integrand) !!{ Enclosed mass integrand for spherical mass distributions. !!} @@ -102,82 +228,140 @@ double precision function sphericalMassEnclosedBySphereIntegrand(radius) double precision , intent(in ) :: radius type (coordinateSpherical) :: position - position =[radius,0.0d0,0.0d0] - sphericalMassEnclosedBySphereIntegrand=+radius **2 & - & *self_%density(position) + if (radius > 0.0d0) then + position =[radius,0.0d0,0.0d0] + integrand=+radius**2 & + & *self_%density(position) + else + integrand=+0.0d0 + end if return end function sphericalMassEnclosedBySphereIntegrand - double precision function sphericalRadiusEnclosingMass(self,mass,componentType,massType) + double precision function sphericalRadiusHalfMass(self) !!{ - Computes the radius enclosing a given mass in a spherically symmetric mass distribution using numerical root finding. + Computes the half-mass radius of a spherically symmetric mass distribution using numerical root finding. !!} - use :: Root_Finder, only : rangeExpandMultiplicative, rangeExpandSignExpectNegative, rangeExpandSignExpectPositive, rootFinder, & - & GSL_Root_fSolver_Brent implicit none - class (massDistributionSpherical ), intent(inout), target :: self - double precision , intent(in ) :: mass - type (enumerationComponentTypeType), intent(in ), optional :: componentType - type (enumerationMassTypeType ), intent(in ), optional :: massType - type (rootFinder ), save :: finder - logical , save :: finderConstructed=.false. - !$omp threadprivate(finder,finderConstructed) - double precision , parameter :: toleranceAbsolute=0.0d0 , toleranceRelative=1.0d-6 - - if (mass <= 0.0d0 .or. .not.self%matches(componentType,massType)) then - sphericalRadiusEnclosingMass=0.0d0 - return - end if - if (.not.finderConstructed) then - finder =rootFinder( & - & rootFunction =sphericalMassRoot , & - & toleranceAbsolute =toleranceAbsolute , & - & toleranceRelative =toleranceRelative , & - & solverType =GSL_Root_fSolver_Brent , & - & rangeExpandUpward =2.0d0 , & - & rangeExpandDownward =0.5d0 , & - & rangeExpandType =rangeExpandMultiplicative , & - & rangeExpandDownwardSignExpect=rangeExpandSignExpectNegative, & - & rangeExpandUpwardSignExpect =rangeExpandSignExpectPositive & - & ) - finderConstructed=.true. - end if - self_ => self - massTarget = mass - sphericalRadiusEnclosingMass = finder%find(rootGuess=1.0d0) + class(massDistributionSpherical), intent(inout) :: self + + sphericalRadiusHalfMass=self%radiusEnclosingMass(0.5d0*self%massTotal()) return - end function sphericalRadiusEnclosingMass + end function sphericalRadiusHalfMass - double precision function sphericalRadiusHalfMass(self,componentType,massType) + double precision function sphericalPotential(self,coordinates,status) result(potential) !!{ - Computes the half-mass radius of a spherically symmetric mass distribution using numerical root finding. + Return the potential at the specified {\normalfont \ttfamily coordinates} in a spherical mass distribution. + !!} + implicit none + class(massDistributionSpherical ), intent(inout), target :: self + class(coordinate ), intent(in ) :: coordinates + type (enumerationStructureErrorCodeType), intent( out), optional :: status + + potential=self%potentialNumerical(coordinates,status) + return + end function sphericalPotential + + double precision function sphericalPotentialNumerical(self,coordinates,status) result(potential) + !!{ + Return the potential at the specified {\normalfont \ttfamily coordinates} in a spherical mass distribution. !!} + use :: Coordinates , only : assignment(=) + use :: Galactic_Structure_Options , only : structureErrorCodeSuccess + use :: Numerical_Constants_Astronomical, only : gravitationalConstantGalacticus + use :: Numerical_Integration , only : integrator + use :: Numerical_Comparison , only : Values_Agree implicit none - class(massDistributionSpherical ), intent(inout) :: self - type (enumerationComponentTypeType), intent(in ), optional :: componentType - type (enumerationMassTypeType ), intent(in ), optional :: massType + class (massDistributionSpherical ), intent(inout), target :: self + class (coordinate ), intent(in ) :: coordinates + type (enumerationStructureErrorCodeType), intent( out), optional :: status + double precision , parameter :: toleranceRelative =1.0d-3 + double precision , parameter :: radiusMaximumFactor=1.0d+1 + type (integrator ), save :: integrator_ + logical , save :: initialized =.false. + !$omp threadprivate(integrator_,initialized) + double precision :: radiusMaximum , potentialPrevious, & + & radius - if (.not.self%matches(componentType,massType)) then - sphericalRadiusHalfMass=0.0d0 - return + if (present(status)) status=structureErrorCodeSuccess + if (.not.initialized) then + integrator_=integrator(integrandPotential,toleranceRelative=toleranceRelative) + initialized=.true. end if - sphericalRadiusHalfMass=self%radiusEnclosingMass(0.5d0*self%massTotal()) + self_ => self + potential = +0.0d0 + potentialPrevious = +1.0d0 + radius = +coordinates%rSpherical() + radiusMaximum = + radius + do while (.not.Values_Agree(potential,potentialPrevious,relTol=toleranceRelative)) + potentialPrevious=+potential + radiusMaximum =+radiusMaximum & + & *radiusMaximumFactor + potential =+integrator_%integrate( & + & radius , & + & radiusMaximum & + & ) + end do + ! Convert to dimensionful units. + if (.not.self%isDimensionless()) potential=+gravitationalConstantGalacticus & + & *potential return - end function sphericalRadiusHalfMass + end function sphericalPotentialNumerical - double precision function sphericalMassRoot(radius) + double precision function sphericalPotentialDifferenceNumerical(self,coordinates1,coordinates2,status) result(potential) !!{ - Root function used in finding half mass radii of spherically symmetric mass distributions. + Return the potential difference between the two specified + {\normalfont \ttfamily coordinates} in a spherical mass + distribution using a numerical calculation. !!} + use :: Coordinates , only : assignment(=) + use :: Galactic_Structure_Options , only : structureErrorCodeSuccess + use :: Numerical_Constants_Astronomical, only : gravitationalConstantGalacticus + use :: Numerical_Integration , only : integrator + use :: Numerical_Comparison , only : Values_Agree implicit none - double precision, intent(in ) :: radius + class (massDistributionSpherical ), intent(inout), target :: self + class (coordinate ), intent(in ) :: coordinates1 , coordinates2 + type (enumerationStructureErrorCodeType), intent( out), optional :: status + double precision , parameter :: toleranceRelative =1.0d-3 + double precision , parameter :: radiusMaximumFactor=1.0d+1 + type (integrator ), save :: integrator_ + logical , save :: initialized =.false. + !$omp threadprivate(integrator_,initialized) - sphericalMassRoot=+self_%massEnclosedBySphere(radius) & - & - massTarget + if (present(status)) status=structureErrorCodeSuccess + if (.not.initialized) then + integrator_=integrator(integrandPotential,toleranceRelative=toleranceRelative) + initialized=.true. + end if + self_ => self + potential = integrator_%integrate( & + & coordinates1%rSpherical(), & + & coordinates2%rSpherical() & + & ) + ! Convert to dimensionful units. + if (.not.self%isDimensionless()) potential=+gravitationalConstantGalacticus & + & *potential return - end function sphericalMassRoot + end function sphericalPotentialDifferenceNumerical - function sphericalAcceleration(self,coordinates,componentType,massType) + double precision function integrandPotential(radius) + !!{ + Integrand for gravitational potential in a spherical mass distribution. + !!} + implicit none + double precision, intent(in ) :: radius + + if (radius > 0.0d0) then + integrandPotential=-self_%massEnclosedBySphere(radius) & + & / radius **2 + else + integrandPotential=0.0d0 + end if + return + end function integrandPotential + + function sphericalAcceleration(self,coordinates) result(acceleration) !!{ Computes the gravitational acceleration at {\normalfont \ttfamily coordinates} for spherically-symmetric mass distributions. @@ -186,39 +370,37 @@ function sphericalAcceleration(self,coordinates,componentType,massType) use :: Numerical_Constants_Astronomical, only : gigaYear , megaParsec , gravitationalConstantGalacticus use :: Numerical_Constants_Prefixes , only : kilo implicit none - double precision , dimension(3) :: sphericalAcceleration - class (massDistributionSpherical ), intent(inout) :: self - class (coordinate ), intent(in ) :: coordinates - type (enumerationComponentTypeType), intent(in ), optional :: componentType - type (enumerationMassTypeType ), intent(in ), optional :: massType - type (coordinateSpherical ) :: coordinatesSpherical - type (coordinateCartesian ) :: coordinatesCartesian - double precision :: radius - double precision , dimension(3) :: positionCartesian + double precision , dimension(3) :: acceleration + class (massDistributionSpherical ), intent(inout) :: self + class (coordinate ), intent(in ) :: coordinates + type (coordinateSpherical ) :: coordinatesSpherical + type (coordinateCartesian ) :: coordinatesCartesian + double precision :: radius + double precision , dimension(3) :: positionCartesian - if (.not.self%matches(componentType,massType)) then - sphericalAcceleration=0.0d0 - return - end if ! Get position in spherical and Cartesian coordinate systems. coordinatesSpherical=coordinates coordinatesCartesian=coordinates ! Compute the density at this position. - positionCartesian = coordinatesCartesian - radius =+coordinatesSpherical%r ( ) - sphericalAcceleration=-self %massEnclosedBySphere(radius ) & - & * positionCartesian & - & / radius **3 - if (.not.self%isDimensionless()) & - & sphericalAcceleration=+sphericalAcceleration & - & *kilo & - & *gigaYear & - & /megaParsec & - & *gravitationalConstantGalacticus + positionCartesian=coordinatesCartesian + radius =coordinatesSpherical%r() + if (radius > 0.0d0) then + acceleration=-self%massEnclosedBySphere(radius ) & + & * positionCartesian & + & / radius **3 + if (.not.self%isDimensionless()) & + & acceleration=+acceleration & + & *kilo & + & *gigaYear & + & /megaParsec & + & *gravitationalConstantGalacticus + else + acceleration=0.0d0 + end if return end function sphericalAcceleration - function sphericalTidalTensor(self,coordinates,componentType,massType) + function sphericalTidalTensor(self,coordinates) !!{ Computes the gravitational tidal tensor at {\normalfont \ttfamily coordinates} for spherically-symmetric mass distributions. @@ -226,25 +408,19 @@ function sphericalTidalTensor(self,coordinates,componentType,massType) use :: Coordinates , only : assignment(=) , coordinateSpherical, coordinateCartesian use :: Numerical_Constants_Astronomical, only : gravitationalConstantGalacticus use :: Numerical_Constants_Math , only : Pi - use :: Tensors , only : tensorIdentityR2D3Sym , tensorNullR2D3Sym ,assignment(=) , operator(*) + use :: Tensors , only : tensorIdentityR2D3Sym , assignment(=) , operator(*) use :: Vectors , only : Vector_Outer_Product implicit none - type (tensorRank2Dimension3Symmetric) :: sphericalTidalTensor - class (massDistributionSpherical ), intent(inout) :: self - class (coordinate ), intent(in ) :: coordinates - type (enumerationComponentTypeType ), intent(in ), optional :: componentType - type (enumerationMassTypeType ), intent(in ), optional :: massType - type (coordinateSpherical ) :: coordinatesSpherical - type (coordinateCartesian ) :: coordinatesCartesian - double precision :: radius , massEnclosed, & - & density - double precision , dimension(3) :: positionCartesian - type (tensorRank2Dimension3Symmetric) :: positionTensor - - if (.not.self%matches(componentType,massType)) then - sphericalTidalTensor=tensorNullR2D3Sym - return - end if + type (tensorRank2Dimension3Symmetric) :: sphericalTidalTensor + class (massDistributionSpherical ), intent(inout) :: self + class (coordinate ), intent(in ) :: coordinates + type (coordinateSpherical ) :: coordinatesSpherical + type (coordinateCartesian ) :: coordinatesCartesian + double precision :: radius , massEnclosed, & + & density + double precision , dimension(3) :: positionCartesian + type (tensorRank2Dimension3Symmetric) :: positionTensor + ! Get position in spherical and Cartesian coordinate systems. coordinatesSpherical=coordinates coordinatesCartesian=coordinates @@ -264,25 +440,69 @@ function sphericalTidalTensor(self,coordinates,componentType,massType) & *gravitationalConstantGalacticus return end function sphericalTidalTensor - - function sphericalPositionSample(self,randomNumberGenerator_,componentType,massType) + + double precision function sphericalRotationCurve(self,radius) + !!{ + Return the rotation curve for a spherical mass distribution. + !!} + use :: Numerical_Constants_Astronomical, only : gravitationalConstantGalacticus + implicit none + class (massDistributionSpherical), intent(inout) :: self + double precision , intent(in ) :: radius + + if (radius <= 0.0d0) then + sphericalRotationCurve=+0.0d0 + else + sphericalRotationCurve=+sqrt( & + & +self%massEnclosedBySphere(radius) & + & / radius & + & ) + ! Make dimensionful if necessary. + if (.not.self%dimensionless) sphericalRotationCurve= & + & +sqrt(gravitationalConstantGalacticus) & + & *sphericalRotationCurve + end if + return + end function sphericalRotationCurve + + double precision function sphericalRotationCurveGradient(self,radius) + !!{ + Return the rotation curve gradient for a spherical mass distribution. + !!} + use :: Coordinates , only : assignment(=) , coordinateSpherical + use :: Numerical_Constants_Astronomical, only : gravitationalConstantGalacticus + use :: Numerical_Constants_Math , only : Pi + implicit none + class (massDistributionSpherical), intent(inout) :: self + double precision , intent(in ) :: radius + type (coordinateSpherical ) :: position + + position =[radius,0.0d0,0.0d0] + sphericalRotationCurveGradient=+4.0d0 & + & *Pi & + & * radius & + & *self%density (position) & + & -self%massEnclosedBySphere(radius ) & + & / radius **2 + ! Make dimensionful if necessary. + if (.not.self%dimensionless) sphericalRotationCurveGradient= & + & +gravitationalConstantGalacticus & + & *sphericalRotationCurveGradient + return + end function sphericalRotationCurveGradient + + function sphericalPositionSample(self,randomNumberGenerator_) !!{ Computes the half-mass radius of a spherically symmetric mass distribution using numerical root finding. !!} use :: Numerical_Constants_Math, only : Pi implicit none - double precision , dimension(3) :: sphericalPositionSample - class (massDistributionSpherical ), intent(inout) :: self - class (randomNumberGeneratorClass ), intent(inout) :: randomNumberGenerator_ - type (enumerationComponentTypeType), intent(in ), optional :: componentType - type (enumerationMassTypeType ), intent(in ), optional :: massType - double precision :: mass , radius, & - & theta , phi - - if (.not.self%matches(componentType,massType)) then - sphericalPositionSample=0.0d0 - return - end if + double precision , dimension(3) :: sphericalPositionSample + class (massDistributionSpherical ), intent(inout) :: self + class (randomNumberGeneratorClass ), intent(inout) :: randomNumberGenerator_ + double precision :: mass , radius, & + & theta , phi + ! Choose an enclosed mass and find the radius enclosing that mass. Choose angular ! coordinates at random and finally convert to Cartesian. mass =+ self %massTotal ( ) & @@ -298,3 +518,480 @@ function sphericalPositionSample(self,randomNumberGenerator_,componentType,massT & ] return end function sphericalPositionSample + + double precision function sphericalSurfaceDensity(self,coordinates) + !!{ + Return the surface density at the specified {\normalfont \ttfamily coordinates} in an exponential disk mass distribution. + !!} + use :: Error, only : Error_Report + implicit none + class(massDistributionSpherical), intent(inout) :: self + class(coordinate ), intent(in ) :: coordinates + + sphericalSurfaceDensity=0.0d0 + call Error_Report('surface density is not defined for spherically-symmetric distributions'//{introspection:location}) + return + end function sphericalSurfaceDensity + + function sphericalChandrasekharIntegral(self,massDistributionEmbedding,massDistributionPerturber,massPerturber,coordinates,velocity) + !!{ + Compute the Chandrasekhar integral at the specified {\normalfont \ttfamily coordinates} in a spherical mass distribution. + !!} + use :: Coordinates , only : coordinateCartesian , assignment(=) + use :: Numerical_Constants_Math , only : Pi + use :: Galactic_Structure_Options, only : componentTypeAll , massTypeAll + use :: Ideal_Gases_Thermodynamics, only : Ideal_Gas_Sound_Speed + use :: Error , only : Error_Report + implicit none + double precision , dimension(3) :: sphericalChandrasekharIntegral + class (massDistributionSpherical ), intent(inout) :: self + class (massDistributionClass ), intent(inout) :: massDistributionEmbedding , massDistributionPerturber + double precision , intent(in ) :: massPerturber + class (coordinate ), intent(in ) :: coordinates , velocity + double precision , dimension(3) :: velocityCartesian_ + double precision , parameter :: XvMaximum =10.0d0 + type (coordinateCartesian ) :: velocityCartesian + double precision :: radius , velocity_ , & + & density , velocityDispersion , & + & xV + !$GLC attributes unused :: massDistributionPerturber, massPerturber + + sphericalChandrasekharIntegral=0.0d0 + velocity_=velocity%rSpherical() + if (velocity_ <= 0.0d0) return + radius =coordinates%rSpherical( ) + density=self %density (coordinates) + if (density <= 0.0d0) return + if (.not.associated(self%kinematicsDistribution_)) call Error_Report('a kinematics distribution is needed to compute the Chandrasekhar integral'//{introspection:location}) + if (self%kinematicsDistribution_%isCollisional()) then + velocityDispersion=Ideal_Gas_Sound_Speed(self%kinematicsDistribution_%temperature (coordinates )) + else + velocityDispersion= self%kinematicsDistribution_%velocityDispersion1D(coordinates,massDistributionEmbedding) + end if + if (velocityDispersion > 0.0d0) then + xV =+velocity_ & + & /velocityDispersion & + & /sqrt(2.0d0) + else + + xV =+huge(0.0d0) + end if + velocityCartesian = velocity + velocityCartesian_ = velocityCartesian + sphericalChandrasekharIntegral=-density & + & *velocityCartesian_ & + & /velocity_ **3 + if (Xv <= XvMaximum) & + & sphericalChandrasekharIntegral=+sphericalChandrasekharIntegral & + & *( & + & +erf ( xV ) & + & -2.0d0 & + & * xV & + & *exp (-xV**2) & + & /sqrt( Pi ) & + & ) + return + end function sphericalChandrasekharIntegral + + double precision function sphericalFourierTransform(self,radiusOuter,wavenumber) result(fourierTransform) + !!{ + Compute the Fourier transform of the density profile at the given {\normalfont \ttfamily wavenumber} in a spherical mass distribution. + !!} + implicit none + class (massDistributionSpherical), intent(inout) :: self + double precision , intent(in ) :: radiusOuter, wavenumber + + fourierTransform=self%fourierTransformNumerical(radiusOuter,wavenumber) + return + end function sphericalFourierTransform + + double precision function sphericalFourierTransformNumerical(self,radiusOuter,wavenumber) result(fourierTransform) + !!{ + Compute the Fourier transform of the density profile at the given {\normalfont \ttfamily wavenumber} in a spherical mass + distribution using a numerical calculation. + !!} + use :: Numerical_Integration, only : integrator + implicit none + class (massDistributionSpherical), intent(inout) :: self + double precision , intent(in ) :: radiusOuter, wavenumber + type (integrator ) :: integrator_ + + fourierTransform=0.0d0 + integrator_ = integrator (integrandFourierTransform,toleranceRelative=1.0d-3) + fourierTransform=+integrator_%integrate (0.0d0 ,radiusOuter ) & + & /self %massEnclosedBySphere( radiusOuter ) + return + + contains + + double precision function integrandFourierTransform(radius) + !!{ + Integrand for Fourier transform of a spherical mass distribution. + !!} + use :: Numerical_Constants_Math, only : Pi + use :: Coordinates , only : assignment(=), coordinateSpherical + implicit none + double precision , intent(in ) :: radius + type (coordinateSpherical) :: coordinates + + if (radius > 0.0d0) then + coordinates =[radius,0.0d0,0.0d0] + integrandFourierTransform=+4.0d0 & + & *Pi & + & * radius **2 & + & *sin(wavenumber*radius) & + & / (wavenumber*radius) & + & *self%density(coordinates) + else + integrandFourierTransform=0.0d0 + end if + return + end function integrandFourierTransform + + end function sphericalFourierTransformNumerical + + double precision function sphericalRadiusFreefall(self,time) result(radius) + !!{ + Compute the freefall radius at the given {\normalfont \ttfamily time} in a spherical mass distribution. + !!} + implicit none + class (massDistributionSpherical), intent(inout) :: self + double precision , intent(in ) :: time + + radius=self%radiusFreefallNumerical(time) + return + end function sphericalRadiusFreefall + + double precision function sphericalRadiusFreefallNumerical(self,time) result(radius) + !!{ + Compute the freefall radius at the given {\normalfont \ttfamily + time} in a spherical mass distribution using a numerical + calculation. + !!} + use :: Root_Finder , only : rangeExpandMultiplicative , rangeExpandSignExpectNegative, rangeExpandSignExpectPositive, rootFinder + use :: Coordinates , only : coordinateSpherical , assignment(=) + use :: Numerical_Constants_Astronomical, only : gravitationalConstantGalacticus, Mpc_per_km_per_s_To_Gyr + use :: Numerical_Constants_Math , only : Pi + implicit none + class (massDistributionSpherical), intent(inout), target :: self + double precision , intent(in ) :: time + double precision , parameter :: toleranceAbsolute =0.0d0, toleranceRelative=1.0d-3 + type (rootFinder ) :: finder + type (coordinateSpherical ) :: coordinates + double precision :: timeFreefallMinimum + + radius=0.0d0 + coordinates=[0.0d0,0.0d0,0.0d0] + if (self%densityGradientRadial(coordinates,logarithmic=.true.) == 0.0d0) then + ! For mass distributions with a constant density core, the potential in the center is harmonic. This means there is a + ! minimum to the freefall time as a function of radius. Compute that minimum here so that we can return a zero radius for + ! times less than this. + timeFreefallMinimum=+sqrt( & + & + 3.0d0 & + & /16.0d0 & + & *Pi & + & /gravitationalConstantGalacticus & + & /self%density(coordinates) & + & ) & + & *Mpc_per_km_per_s_To_Gyr + else + timeFreefallMinimum=+0.0d0 + end if + if (time < timeFreefallMinimum) return + self_ => self + time_ = time + finder = rootFinder( & + & rootFunction =rootRadiusFreefall , & + & toleranceAbsolute =toleranceAbsolute , & + & toleranceRelative =toleranceRelative , & + & rangeExpandDownward =0.5d0 , & + & rangeExpandUpward =2.0d0 , & + & rangeExpandType =rangeExpandMultiplicative , & + & rangeExpandUpwardSignExpect =rangeExpandSignExpectPositive, & + & rangeExpandDownwardSignExpect=rangeExpandSignExpectNegative & + & ) + radius=finder%find(rootGuess=1.0d0) + return + end function sphericalRadiusFreefallNumerical + + double precision function rootRadiusFreefall(radiusFreefall) + !!{ + Root function used in finding the radius corresponding to a given freefall time. + !!} + use :: Numerical_Integration, only : integrator + implicit none + double precision , intent(in ) :: radiusFreefall + type (integrator) :: integrator_ + + radiusFreefall_ =+radiusFreefall + integrator_ = integrator (integrandTimeFreefall,toleranceRelative=1.0d-3) + rootRadiusFreefall=+integrator_ %integrate(0.0d0 ,radiusFreefall ) & + & -time_ + return + end function rootRadiusFreefall + + double precision function integrandTimeFreefall(radius) + !!{ + Integrand for freefall time in a spherical mass distribution. + !!} + use :: Coordinates , only : assignment(=) , coordinateSpherical + use :: Numerical_Constants_Astronomical, only : Mpc_per_km_per_s_To_Gyr + implicit none + double precision , intent(in ) :: radius + double precision :: potentialDifference + type (coordinateSpherical) :: coordinates , coordinatesFreefall + + coordinates =[radius ,0.0d0,0.0d0] + coordinatesFreefall=[radiusFreefall_,0.0d0,0.0d0] + potentialDifference=+self_%potentialDifference(coordinates,coordinatesFreefall) + if (potentialDifference < 0.0d0) then + integrandTimeFreefall=+Mpc_per_km_per_s_To_Gyr & + & /sqrt( & + & -2.0d0 & + & *potentialDifference & + & ) + else + ! Avoid floating point errors arising from rounding errors. + integrandTimeFreefall=0.0d0 + end if + return + end function integrandTimeFreefall + + double precision function sphericalRadiusFreefallIncreaseRate(self,time) result(radiusIncreaseRate) + !!{ + Compute the rate of increase of the freefall radius at the given {\normalfont \ttfamily time} in an spherical mass + distribution. + !!} + implicit none + class (massDistributionSpherical), intent(inout) :: self + double precision , intent(in ) :: time + + radiusIncreaseRate=self%radiusFreefallIncreaseRateNumerical(time) + return + end function sphericalRadiusFreefallIncreaseRate + + double precision function sphericalRadiusFreefallIncreaseRateNumerical(self,time) result(radiusIncreaseRate) + !!{ + Compute the rate of increase of the freefall radius at the given {\normalfont \ttfamily time} in an spherical mass + distribution using a numerical calculation. + !!} + use :: Numerical_Differentiation, only : differentiator + implicit none + class (massDistributionSpherical), intent(inout) :: self + double precision , intent(in ) :: time + double precision , parameter :: timeLogarithmicStep=1.0d-2 + type (differentiator ) :: differentiator_ + + differentiator_ = differentiator (radiusFreefallEvaluate ) + radiusIncreaseRate=+differentiator_%derivative(log(time) ,timeLogarithmicStep) & + & / time + return + end function sphericalRadiusFreefallIncreaseRateNumerical + + double precision function radiusFreefallEvaluate(timeLogarithmic) + !!{ + GSL-callable function to evaluate the freefall radius of the mass distribution. + !!} + implicit none + double precision, intent(in ), value :: timeLogarithmic + + radiusFreefallEvaluate=self_%radiusFreefall(exp(timeLogarithmic)) + return + end function radiusFreefallEvaluate + + double precision function sphericalEnergy(self,radiusOuter,massDistributionEmbedding) result(energy) + !!{ + Compute the energy within a given {\normalfont \ttfamily radius} in a spherical mass distribution. + !!} + implicit none + class (massDistributionSpherical), intent(inout), target :: self + double precision , intent(in ) :: radiusOuter + class (massDistributionClass ), intent(inout), target :: massDistributionEmbedding + + energy=self%energyNumerical(radiusOuter,massDistributionEmbedding) + return + end function sphericalEnergy + + double precision function sphericalEnergyNumerical(self,radiusOuter,massDistributionEmbedding) result(energy) + !!{ + Compute the energy within a given {\normalfont \ttfamily radius} in a spherical mass distribution using a numerical calculation. + !!} + implicit none + class (massDistributionSpherical), intent(inout) :: self + double precision , intent(in ) :: radiusOuter + class (massDistributionClass ), intent(inout) :: massDistributionEmbedding + + energy=+self%energyPotential(radiusOuter ) & + & +self%energyKinetic (radiusOuter,massDistributionEmbedding) + return + end function sphericalEnergyNumerical + + double precision function sphericalEnergyPotential(self,radiusOuter) result(energy) + !!{ + Compute the potential energy within a given {\normalfont \ttfamily radius} in a spherical mass distribution. + !!} + implicit none + class (massDistributionSpherical), intent(inout) :: self + double precision , intent(in ) :: radiusOuter + + energy=self%energyPotentialNumerical(radiusOuter) + return + end function sphericalEnergyPotential + + double precision function sphericalEnergyKinetic(self,radiusOuter,massDistributionEmbedding) result(energy) + !!{ + Compute the kinetic energy within a given {\normalfont \ttfamily radius} in a spherical mass distribution. + !!} + implicit none + class (massDistributionSpherical), intent(inout) :: self + double precision , intent(in ) :: radiusOuter + class (massDistributionClass ), intent(inout) :: massDistributionEmbedding + + energy=self%energyKineticNumerical(radiusOuter,massDistributionEmbedding) + return + end function sphericalEnergyKinetic + + double precision function sphericalEnergyPotentialNumerical(self,radiusOuter) result(energy) + !!{ + Compute (numerically) the potential energy within a given {\normalfont \ttfamily radius} in a spherical mass distribution. + !!} + use :: Numerical_Constants_Astronomical, only : gravitationalConstantGalacticus + use :: Numerical_Integration , only : integrator + implicit none + class (massDistributionSpherical), intent(inout) :: self + double precision , intent(in ) :: radiusOuter + type (integrator ) :: integrator_ + + integrator_= integrator(integrandEnergyPotential,toleranceRelative=1.0d-3) + energy =-0.5d0 & + & *gravitationalConstantGalacticus & + & *( & + & +integrator_%integrate (0.0d0,radiusOuter) & + & +self %massEnclosedBySphere( radiusOuter)**2 & + & / radiusOuter & + & ) + return + + contains + + double precision function integrandEnergyPotential(radius) + !!{ + Integrand for potential energy of a spherical mass distribution. + !!} + implicit none + double precision, intent(in ) :: radius + + if (radius > 0.0d0) then + integrandEnergyPotential=( & + & +self%massEnclosedBySphere(radius) & + & / radius & + & )**2 + else + integrandEnergyPotential=0.0d0 + end if + return + end function integrandEnergyPotential + + end function sphericalEnergyPotentialNumerical + + double precision function sphericalEnergyKineticNumerical(self,radiusOuter,massDistributionEmbedding) result(energy) + !!{ + Compute (numerically) the kinetic energy within a given {\normalfont \ttfamily radius} in a spherical mass distribution. + !!} + use :: Numerical_Constants_Math, only : Pi + use :: Numerical_Integration , only : integrator + implicit none + class (massDistributionSpherical), intent(inout) :: self + double precision , intent(in ) :: radiusOuter + class (massDistributionClass ), intent(inout) :: massDistributionEmbedding + type (integrator ) :: integrator_ + + integrator_= integrator(integrandEnergyKinetic,toleranceRelative=1.0d-3) + energy =+6.0d0 & + & *Pi & + & *integrator_%integrate(0.0d0,radiusOuter) + return + + contains + + double precision function integrandEnergyKinetic(radius) + !!{ + Integrand for kinetic energy of the halo. + !!} + use :: Coordinates, only : coordinateSpherical, assignment(=) + implicit none + double precision , intent(in ) :: radius + type (coordinateSpherical) :: coordinates + + if (radius > 0.0d0) then + coordinates =[radius,0.0d0,0.0d0] + integrandEnergyKinetic=+self %density (coordinates ) & + & *self%kinematicsDistribution_%velocityDispersion1D(coordinates,massDistributionEmbedding)**2 & + & * radius **2 + else + integrandEnergyKinetic=0.0d0 + end if + return + end function integrandEnergyKinetic + + end function sphericalEnergyKineticNumerical + + double precision function sphericalDensityRadialMoment(self,moment,radiusMinimum,radiusMaximum,isInfinite) result(densityRadialMoment) + !!{ + Returns a radial density moment for a spherical mass distribution. + !!} + implicit none + class (massDistributionSpherical), intent(inout) :: self + double precision , intent(in ) :: moment + double precision , intent(in ), optional :: radiusMinimum, radiusMaximum + logical , intent( out), optional :: isInfinite + + densityRadialMoment=self%densityRadialMomentNumerical(moment,radiusMinimum,radiusMaximum,isInfinite) + return + end function sphericalDensityRadialMoment + + double precision function sphericalDensityRadialMomentNumerical(self,moment,radiusMinimum,radiusMaximum,isInfinite) result(densityRadialMoment) + !!{ + Returns a radial density moment for a spherical mass distribution using a numerical calculation. + !!} + use :: Error , only : Error_Report + use :: Numerical_Integration, only : integrator + implicit none + class (massDistributionSpherical), intent(inout) :: self + double precision , intent(in ) :: moment + double precision , intent(in ), optional :: radiusMinimum, radiusMaximum + logical , intent( out), optional :: isInfinite + type (integrator ) :: integrator_ + double precision :: radiusMinimum_ + + densityRadialMoment=0.0d0 + if (.not.present(radiusMaximum)) call Error_Report('a maximum radius must be provided'//{introspection:location}) + if (present(isInfinite)) isInfinite=.false. + radiusMinimum_=0.0d0 + if (present(radiusMinimum)) radiusMinimum_=radiusMinimum + integrator_ = integrator (integrandMoment,toleranceRelative=1.0d-3) + densityRadialMoment=+integrator_%integrate(radiusMinimum_ ,radiusMaximum ) + return + + contains + + double precision function integrandMoment(radius) + !!{ + Integrand for radial density moment in a spherical mass distribution. + !!} + use :: Coordinates, only : assignment(=), coordinateSpherical + implicit none + double precision , intent(in ) :: radius + type (coordinateSpherical) :: coordinates + + if (radius > 0.0d0) then + coordinates = [radius,0.0d0,0.0d0] + integrandMoment=+self%density(coordinates) & + & * radius **moment + else + integrandMoment=+0.0d0 + end if + return + end function integrandMoment + + end function sphericalDensityRadialMomentNumerical diff --git a/source/mass_distributions.spherical.Hernquist.F90 b/source/mass_distributions.spherical.Hernquist.F90 index d01620674b..2735280366 100644 --- a/source/mass_distributions.spherical.Hernquist.F90 +++ b/source/mass_distributions.spherical.Hernquist.F90 @@ -34,11 +34,14 @@ double precision :: densityNormalization, mass, & & scaleLength contains - procedure :: density => hernquistDensity - procedure :: densityRadialMoment => hernquistDensityRadialMoment - procedure :: massEnclosedBySphere => hernquistMassEnclosedBySphere - procedure :: potential => hernquistPotential - procedure :: radiusHalfMass => hernquistRadiusHalfMass + procedure :: massTotal => hernquistMassTotal + procedure :: density => hernquistDensity + procedure :: densityGradientRadial => hernquistDensityGradientRadial + procedure :: densityRadialMoment => hernquistDensityRadialMoment + procedure :: massEnclosedBySphere => hernquistMassEnclosedBySphere + procedure :: potentialIsAnalytic => hernquistPotentialIsAnalytic + procedure :: potential => hernquistPotential + procedure :: radiusHalfMass => hernquistRadiusHalfMass end type massDistributionHernquist interface massDistributionHernquist @@ -171,35 +174,65 @@ function hernquistConstructorInternal(densityNormalization,mass,scaleLength,dime return end function hernquistConstructorInternal - double precision function hernquistDensity(self,coordinates,componentType,massType) + double precision function hernquistMassTotal(self) + !!{ + Return the total mass in an Hernquist mass distribution. + !!} + implicit none + class(massDistributionHernquist), intent(inout) :: self + + hernquistMassTotal=self%mass + return + end function hernquistMassTotal + + double precision function hernquistDensity(self,coordinates) !!{ Return the density at the specified {\normalfont \ttfamily coordinates} in a Hernquist mass distribution. !!} - use :: Coordinates, only : assignment(=), coordinateSpherical implicit none - class (massDistributionHernquist ), intent(inout) :: self - class (coordinate ), intent(in ) :: coordinates - type (enumerationComponentTypeType), intent(in ), optional :: componentType - type (enumerationMassTypeType ), intent(in ), optional :: massType - type (coordinateSpherical ) :: position - double precision :: r + class (massDistributionHernquist), intent(inout) :: self + class (coordinate ), intent(in ) :: coordinates + double precision :: r - if (.not.self%matches(componentType,massType)) then - hernquistDensity=0.0d0 - return - end if - ! Get position in spherical coordinate system. - position = coordinates ! Compute the density at this position. - r =+position%r () & - & /self %scaleLength - hernquistDensity=+self %densityNormalization & - & / r & + r =+coordinates%rSpherical () & + & /self %scaleLength + hernquistDensity=+self %densityNormalization & + & / r & & /(+1.0d0+r)**3 return end function hernquistDensity - double precision function hernquistDensityRadialMoment(self,moment,radiusMinimum,radiusMaximum,isInfinite,componentType,massType) + double precision function hernquistDensityGradientRadial(self,coordinates,logarithmic) + !!{ + Return the density gradient in the radial direction in a scaled spherical mass distribution. + !!} + implicit none + class (massDistributionHernquist), intent(inout), target :: self + class (coordinate ), intent(in ) :: coordinates + logical , intent(in ), optional :: logarithmic + double precision :: r + !![ + + !!] + + ! Compute the density at this position. + r =+coordinates%rSpherical () & + & /self %scaleLength + if (logarithmic_) then + hernquistDensityGradientRadial=-(+1.0d0+4.0d0*r) & + & /(+1.0d0+ r) + else + hernquistDensityGradientRadial=-self %densityNormalization & + & /self %scaleLength & + & *(+1.0d0+4.0d0*r) & + & / r **2 & + & /(+1.0d0+ r)**4 + end if + return + end function hernquistDensityGradientRadial + + double precision function hernquistDensityRadialMoment(self,moment,radiusMinimum,radiusMaximum,isInfinite) !!{ Returns a radial density moment for the Hernquist mass distribution. !!} @@ -207,17 +240,11 @@ double precision function hernquistDensityRadialMoment(self,moment,radiusMinimum use :: Numerical_Comparison , only : Values_Agree use :: Numerical_Constants_Math, only : Pi implicit none - class (massDistributionHernquist ), intent(inout) :: self - double precision , intent(in ) :: moment - double precision , intent(in ), optional :: radiusMinimum, radiusMaximum - logical , intent( out), optional :: isInfinite - type (enumerationComponentTypeType), intent(in ), optional :: componentType - type (enumerationMassTypeType ), intent(in ), optional :: massType + class (massDistributionHernquist), intent(inout) :: self + double precision , intent(in ) :: moment + double precision , intent(in ), optional :: radiusMinimum, radiusMaximum + logical , intent( out), optional :: isInfinite - if (.not.self%matches(componentType,massType)) then - hernquistDensityRadialMoment=0.0d0 - return - end if ! Abort on limited ranges. if (present(radiusMinimum).or.present(radiusMaximum)) call Error_Report('ranges are not supported'//{introspection:location}) if (moment <= 0.0d0 .or. moment >= 3.0d0) then @@ -248,23 +275,17 @@ double precision function hernquistDensityRadialMoment(self,moment,radiusMinimum return end function hernquistDensityRadialMoment - double precision function hernquistMassEnclosedBySphere(self,radius,componentType,massType) + double precision function hernquistMassEnclosedBySphere(self,radius) !!{ Computes the mass enclosed within a sphere of given {\normalfont \ttfamily radius} for Hernquist mass distributions. !!} use :: Numerical_Constants_Math, only : Pi implicit none - class (massDistributionHernquist ), intent(inout), target :: self - double precision , intent(in ) :: radius - type (enumerationComponentTypeType), intent(in ), optional :: componentType - type (enumerationMassTypeType ), intent(in ), optional :: massType - double precision , parameter :: fractionalRadiusLarge=1.0d6 - double precision :: fractionalRadius + class (massDistributionHernquist), intent(inout), target :: self + double precision , intent(in ) :: radius + double precision , parameter :: fractionalRadiusLarge=1.0d6 + double precision :: fractionalRadius - if (.not.self%matches(componentType,massType)) then - hernquistMassEnclosedBySphere=0.0d0 - return - end if fractionalRadius=radius/self%scaleLength if (fractionalRadius > fractionalRadiusLarge) then ! For very large radius approximate the mass enclosed as the total mass. @@ -275,23 +296,31 @@ double precision function hernquistMassEnclosedBySphere(self,radius,componentTyp return end function hernquistMassEnclosedBySphere - double precision function hernquistPotential(self,coordinates,componentType,massType) + logical function hernquistPotentialIsAnalytic(self) result(isAnalytic) + !!{ + Return that the potential has an analytic form. + !!} + implicit none + class(massDistributionHernquist), intent(inout) :: self + + isAnalytic=.true. + return + end function hernquistPotentialIsAnalytic + + double precision function hernquistPotential(self,coordinates,status) !!{ Return the potential at the specified {\normalfont \ttfamily coordinates} in a Hernquist mass distribution. !!} use :: Coordinates , only : assignment(=) , coordinateSpherical + use :: Galactic_Structure_Options , only : structureErrorCodeSuccess use :: Numerical_Constants_Astronomical, only : gravitationalConstantGalacticus implicit none - class(massDistributionHernquist ), intent(inout) :: self - class(coordinate ), intent(in ) :: coordinates - type (enumerationComponentTypeType), intent(in ), optional :: componentType - type (enumerationMassTypeType ), intent(in ), optional :: massType - type (coordinateSpherical ) :: position + class(massDistributionHernquist ), intent(inout), target :: self + class(coordinate ), intent(in ) :: coordinates + type (enumerationStructureErrorCodeType), intent( out), optional :: status + type (coordinateSpherical ) :: position - if (.not.self%matches(componentType,massType)) then - hernquistPotential=0.0d0 - return - end if + if (present(status)) status=structureErrorCodeSuccess ! Get position in spherical coordinate system. position=coordinates ! Compute the potential at this position. @@ -301,20 +330,14 @@ double precision function hernquistPotential(self,coordinates,componentType,mass return end function hernquistPotential - double precision function hernquistRadiusHalfMass(self,componentType,massType) + double precision function hernquistRadiusHalfMass(self) !!{ Return the half-mass radius of a Hernquist mass distribution. !!} implicit none - class (massDistributionHernquist ), intent(inout) :: self - type (enumerationComponentTypeType), intent(in ), optional :: componentType - type (enumerationMassTypeType ), intent(in ), optional :: massType - double precision , parameter :: radiusHalfMassToScaleRadius=1.0d0/(sqrt(2.0d0)-1.0d0) + class (massDistributionHernquist), intent(inout) :: self + double precision , parameter :: radiusHalfMassToScaleRadius=1.0d0/(sqrt(2.0d0)-1.0d0) - if (.not.self%matches(componentType,massType)) then - hernquistRadiusHalfMass=0.0d0 - return - end if hernquistRadiusHalfMass=+radiusHalfMassToScaleRadius & & *self%scaleLength return diff --git a/source/mass_distributions.spherical.NFW.F90 b/source/mass_distributions.spherical.NFW.F90 index bf7300e069..2e647caa27 100644 --- a/source/mass_distributions.spherical.NFW.F90 +++ b/source/mass_distributions.spherical.NFW.F90 @@ -21,9 +21,19 @@ Implementation of an NFW \citep{navarro_structure_1996} mass distribution class. !!} + use :: Numerical_Interpolation, only : interpolator + + public :: massDistributionNFWStateStore, massDistributionNFWStateRestore + !![ - An NFW \citep{navarro_structure_1996} mass distribution class. + + An NFW \citep{navarro_structure_1996} mass distribution class. The density profile is given by: + \begin{equation} + \rho_\mathrm{dark matter}(r) \propto \left({r\over r_\mathrm{s}}\right)^{-1} \left[1 + \left({r\over r_\mathrm{s}}\right) + \right]^{-2}. + \end{equation} + !!] type, public, extends(massDistributionSpherical) :: massDistributionNFW @@ -31,23 +41,66 @@ The NFW \citep{navarro_structure_1996} mass distribution. !!} private - double precision :: densityNormalization, scaleLength + double precision :: densityNormalization , scaleLength + double precision :: enclosedMassRadiusPrevious, enclosedMassPrevious contains - procedure :: density => nfwDensity - procedure :: descriptor => nfwDescriptor + !![ + + + + !!] + procedure :: massTotal => nfwMassTotal + procedure :: density => nfwDensity + procedure :: densityGradientRadial => nfwDensityGradientRadial + procedure :: densityRadialMoment => nfwDensityRadialMoment + procedure :: massEnclosedBySphere => nfwMassEnclosedBySphere + procedure :: velocityRotationCurveMaximum => nfwVelocityRotationCurveMaximum + procedure :: radiusRotationCurveMaximum => nfwRadiusRotationCurveMaximum + procedure :: radiusEnclosingMass => nfwRadiusEnclosingMass + procedure :: radiusEnclosingDensity => nfwRadiusEnclosingDensity + procedure :: radiusFromSpecificAngularMomentum => nfwRadiusFromSpecificAngularMomentum + procedure :: fourierTransform => nfwFourierTransform + procedure :: radiusFreefall => nfwRadiusFreefall + procedure :: radiusFreefallIncreaseRate => nfwRadiusFreefallIncreaseRate + procedure :: timeFreefallTabulate => nfwTimeFreefallTabulate + procedure :: potentialIsAnalytic => nfwPotentialIsAnalytic + procedure :: potential => nfwPotential + procedure :: energyPotential => nfwEnergyPotential + procedure :: energyKinetic => nfwEnergyKinetic + procedure :: descriptor => nfwDescriptor end type massDistributionNFW - + interface massDistributionNFW !!{ Constructors for the {\normalfont \ttfamily nfw} mass distribution class. !!} - module procedure nfwConstructorParameters - module procedure nfwConstructorInternal + module procedure massDistributionNFWConstructorParameters + module procedure massDistributionNFWConstructorInternal end interface massDistributionNFW + ! Tabulated solutions. + double precision :: densityScaleFreeRadiusMinimum =+ 2.0d0 , densityScaleFreeRadiusMaximum =+ 0.5d0 + double precision :: densityScaleFreeMinimum =+huge(0.0d0), densityScaleFreeMaximum =-huge(0.0d0) + type (interpolator), allocatable :: densityScaleFree_ + double precision :: angularMomentumSpecificScaleFreeRadiusMinimum=+ 2.0d0 , angularMomentumSpecificScaleFreeRadiusMaximum=+ 0.5d0 + double precision :: angularMomentumSpecificScaleFreeMinimum =+huge(0.0d0), angularMomentumSpecificScaleFreeMaximum =-huge(0.0d0) + type (interpolator), allocatable :: angularMomentumSpecificScaleFree_ + double precision :: timeFreefallScaleFreeRadiusMinimum =+ 2.0d0 , timeFreefallScaleFreeRadiusMaximum =+ 0.5d0 + double precision :: timeFreefallScaleFreeMinimum =+huge(0.0d0), timeFreefallScaleFreeMaximum =-huge(0.0d0) + type (interpolator), allocatable :: timeFreefallScaleFree_ + !$omp threadprivate(densityScaleFreeRadiusMinimum , densityScaleFreeRadiusMaximum ) + !$omp threadprivate(densityScaleFreeMinimum , densityScaleFreeMaximum ) + !$omp threadprivate(densityScaleFree_ ) + !$omp threadprivate(angularMomentumSpecificScaleFreeRadiusMinimum, angularMomentumSpecificScaleFreeRadiusMaximum) + !$omp threadprivate(angularMomentumSpecificScaleFreeMinimum , angularMomentumSpecificScaleFreeMaximum ) + !$omp threadprivate(angularMomentumSpecificScaleFree_ ) + !$omp threadprivate(timeFreefallScaleFreeRadiusMinimum , timeFreefallScaleFreeRadiusMaximum ) + !$omp threadprivate(timeFreefallScaleFreeMinimum , timeFreefallScaleFreeMaximum ) + !$omp threadprivate(timeFreefallScaleFree_ ) + contains - function nfwConstructorParameters(parameters) result(self) + function massDistributionNFWConstructorParameters(parameters) result(self) !!{ Constructor for the {\normalfont \ttfamily nfw} mass distribution class which builds the object from a parameter set. @@ -58,7 +111,7 @@ function nfwConstructorParameters(parameters) result(self) implicit none type (massDistributionNFW) :: self type (inputParameters ), intent(inout) :: parameters - double precision :: mass , scaleLength, & + double precision :: mass , scaleLength , & & densityNormalization, concentration, & & virialRadius logical :: dimensionless @@ -68,7 +121,7 @@ function nfwConstructorParameters(parameters) result(self) !![ densityNormalization - 0.5d0/Pi + 1.0d0/2.0d0/Pi/(log(4.0d0)-1.0d0) The density normalization of the NFW profile. parameters @@ -126,9 +179,9 @@ function nfwConstructorParameters(parameters) result(self) !!] return - end function nfwConstructorParameters + end function massDistributionNFWConstructorParameters - function nfwConstructorInternal(scaleLength,concentration,densityNormalization,mass,virialRadius,dimensionless,componentType,massType) result(self) + function massDistributionNFWConstructorInternal(scaleLength,concentration,densityNormalization,mass,virialRadius,dimensionless,componentType,massType) result(self) !!{ Internal constructor for ``nfw'' mass distribution class. !!} @@ -142,7 +195,7 @@ function nfwConstructorInternal(scaleLength,concentration,densityNormalization,m logical , intent(in ), optional :: dimensionless type (enumerationComponentTypeType), intent(in ), optional :: componentType type (enumerationMassTypeType ), intent(in ), optional :: massType - double precision :: r + double precision :: radiusScaleFree !![ !!] @@ -169,8 +222,8 @@ function nfwConstructorInternal(scaleLength,concentration,densityNormalization,m & present(mass ).and. & & present(virialRadius ) & & ) then - r=virialRadius/self%scaleLength - self%densityNormalization=mass/4.0d0/Pi/self%scaleLength**3/(log(1.0d0+r)-r/(1.0d0+r)) + radiusScaleFree =+virialRadius/self%scaleLength + self%densityNormalization=+mass/4.0d0/Pi/self%scaleLength**3/(log(1.0d0+radiusScaleFree)-radiusScaleFree/(1.0d0+radiusScaleFree)) else call Error_Report('either "densityNormalization", or "mass" and "virialRadius" must be specified'//{introspection:location}) end if @@ -180,37 +233,788 @@ function nfwConstructorInternal(scaleLength,concentration,densityNormalization,m else self%dimensionless=.false. end if + ! Initialize memoized results. + self%enclosedMassPrevious =-huge(0.0d0) + self%enclosedMassRadiusPrevious=-huge(0.0d0) return - end function nfwConstructorInternal + end function massDistributionNFWConstructorInternal - double precision function nfwDensity(self,coordinates,componentType,massType) + double precision function nfwMassTotal(self) + !!{ + Return the total mass in an NFW mass distribution. + !!} + implicit none + class(massDistributionNFW), intent(inout) :: self + + nfwMassTotal=huge(0.0d0) + return + end function nfwMassTotal + + double precision function nfwDensity(self,coordinates) !!{ Return the density at the specified {\normalfont \ttfamily coordinates} in an NFW mass distribution. !!} - use :: Coordinates, only : assignment(=), coordinate, coordinateSpherical implicit none - class (massDistributionNFW ), intent(inout) :: self - class (coordinate ), intent(in ) :: coordinates - type (coordinateSpherical ) :: position - type (enumerationComponentTypeType), intent(in ), optional :: componentType - type (enumerationMassTypeType ), intent(in ), optional :: massType - double precision :: r + class (massDistributionNFW), intent(inout) :: self + class (coordinate ), intent(in ) :: coordinates + double precision :: radiusScaleFree - if (.not.self%matches(componentType,massType)) then - nfwDensity=0.0d0 - return - end if - ! Get position in spherical coordinate system. - position = coordinates ! Compute the density at this position. - r =+position%r () & - & /self %scaleLength - nfwDensity=+self %densityNormalization & - & / r & - & /(1.0d0+r)**2 + radiusScaleFree=+coordinates%rSpherical () & + & /self %scaleLength + nfwDensity =+self %densityNormalization & + & / radiusScaleFree & + & /(1.0d0+radiusScaleFree)**2 return end function nfwDensity + + double precision function nfwDensityGradientRadial(self,coordinates,logarithmic) result(densityGradientRadial) + !!{ + Return the density at the specified {\normalfont \ttfamily coordinates} in an NFW \citep{navarro_structure_1996} mass distribution. + !!} + use :: Error, only : Error_Report + implicit none + class (massDistributionNFW), intent(inout), target :: self + class (coordinate ), intent(in ) :: coordinates + logical , intent(in ), optional :: logarithmic + double precision :: radiusScaleFree + !![ + + !!] + + densityGradientRadial=0.0d0 + radiusScaleFree =+coordinates%rSpherical() & + & /self %scaleLength + if (radiusScaleFree <= 0.0d0) then + if (logarithmic_) then + densityGradientRadial=-1.0d0 + else + call Error_Report('gradient is divergent at r=0'//{introspection:location}) + end if + else + densityGradientRadial=-self %densityNormalization & + & /self %scaleLength & + & / radiusScaleFree **2 & + & *(1.0d0+3.0d0*radiusScaleFree) & + & /(1.0d0+ radiusScaleFree)**3 + if (logarithmic_) densityGradientRadial=+ densityGradientRadial & + & /self %density (coordinates) & + & *coordinates%rSpherical ( ) + end if + return + end function nfwDensityGradientRadial + + double precision function nfwDensityRadialMoment(self,moment,radiusMinimum,radiusMaximum,isInfinite) result(densityRadialMoment) + !!{ + Computes radial moments of the density in an NFW \citep{navarro_structure_1996} mass distribution. + !!} + implicit none + class (massDistributionNFW), intent(inout) :: self + double precision , intent(in ) :: moment + double precision , intent(in ), optional :: radiusMinimum , radiusMaximum + logical , intent( out), optional :: isInfinite + double precision :: radialMomentMinimum, radialMomentMaximum + + densityRadialMoment=0.0d0 + if (present(isInfinite)) isInfinite=.false. + if (present(radiusMinimum)) then + radialMomentMinimum=radialMomentScaleFree(radiusMinimum/self%scaleLength) + else + radialMomentMinimum=radialMomentScaleFree( 0.0d0) + end if + if (present(radiusMaximum)) then + radialMomentMaximum=radialMomentScaleFree(radiusMaximum/self%scaleLength) + else + radialMomentMaximum=0.0d0 + if (moment >= 3.0d0) then + if (present(isInfinite)) then + isInfinite=.true. + return + else + call Error_Report('moment is infinite'//{introspection:location}) + end if + end if + end if + densityRadialMoment=+self%densityNormalization & + & *self%scaleLength **(moment+1.0d0) & + & *( & + & +radialMomentMaximum & + & -radialMomentMinimum & + & ) + return + + contains + + double precision function radialMomentScaleFree(radius) + !!{ + Provides the scale-free part of the radial moment of the NFW density profile. + !!} + use :: Hypergeometric_Functions, only : Hypergeometric_2F1 + use :: Numerical_Comparison , only : Values_Agree + implicit none + double precision, intent(in ) :: radius + + if (Values_Agree(moment,0.0d0,absTol=1.0d-6)) then + ! Take the real part of this improper integral. The imaginary parts must cancel when taking differences to compute a + ! proper integral. + radialMomentScaleFree=+1.0d0/ (1.0d0+ radius ) & + & -2.0d0*real(atanh(dcmplx(1.0d0+2.0d0*radius,0.0d0))) + else if (Values_Agree(moment,1.0d0,absTol=1.0d-6)) then + radialMomentScaleFree=-1.0d0/ (1.0d0 +radius ) + else if (Values_Agree(moment,2.0d0,absTol=1.0d-6)) then + radialMomentScaleFree=+1.0d0/ (1.0d0 +radius ) & + & + log (1.0d0 +radius ) + else if (Values_Agree(moment,3.0d0,absTol=1.0d-6)) then + radialMomentScaleFree=+ radius & + & -1.0d0/ (1.0d0 +radius ) & + & -2.0d0*log (1.0d0 +radius ) + else + radialMomentScaleFree=+(1.0d0+radius)**(moment-1.0d0) & + & /moment & + & / (moment-1.0d0) & + & *( & + & - moment & + & * Hypergeometric_2F1([1.0d0-moment,-moment],[2.0d0-moment],1.0d0/(1.0d0+radius)) & + & +(1.0d0+radius) & + & *(moment-1.0d0) & + & *( & + & +(radius/(1.0d0+radius))**moment & + & -Hypergeometric_2F1([ -moment,-moment],[1.0d0-moment],1.0d0/(1.0d0+radius)) & + & ) & + & ) + end if + return + end function radialMomentScaleFree + + end function nfwDensityRadialMoment + + double precision function nfwMassEnclosedBySphere(self,radius) result(mass) + !!{ + Computes the mass enclosed within a sphere of given {\normalfont \ttfamily radius} for nfw mass distributions. + !!} + use :: Numerical_Constants_Math, only : Pi + implicit none + class (massDistributionNFW ), intent(inout), target :: self + double precision , intent(in ) :: radius + double precision :: radiusScaleFree + + if (radius /= self%enclosedMassRadiusPrevious) then + self%enclosedMassRadiusPrevious=+ radius + radiusScaleFree =+ radius & + & /self%scaleLength + self%enclosedMassPrevious =+ massEnclosedScaleFree(radiusScaleFree) & + & *self%densityNormalization & + & *self%scaleLength **3 + end if + mass=self%enclosedMassPrevious + return + end function nfwMassEnclosedBySphere + + double precision function nfwRadiusEnclosingMass(self,mass,massFractional) result(radius) + !!{ + Computes the radius enclosing a given mass or mass fraction for nfw mass distributions. + !!} + use :: Numerical_Constants_Math, only : Pi + use :: Lambert_Ws , only : Lambert_W0 + use :: Error , only : Error_Report + implicit none + class (massDistributionNFW ), intent(inout), target :: self + double precision , intent(in ), optional :: mass , massFractional + double precision , parameter :: massScaleFreeSmall=3.0d-4 + double precision :: mass_ , massScaleFree + + mass_=0.0d0 + if (present(mass)) then + mass_=mass + else if (present(massFractional)) then + call Error_Report('mass is unbounded, so mass fraction is undefined'//{introspection:location}) + else + call Error_Report('either mass or massFractional must be supplied' //{introspection:location}) + end if + massScaleFree=+ mass_ & + & / 4.0d0 & + & / Pi & + & /self%densityNormalization & + & /self%scaleLength**3 + if (massScaleFree <= 0.0d0 ) then + radius=+0.0d0 + else if (massScaleFree < massScaleFreeSmall) then + ! Use a series solution for very small radii. + radius=+ sqrt(2.0d0)*massScaleFree**0.5d0 & + & + 4.0d0/ 3.0d0 *massScaleFree & + & + 13.0d0/ 9.0d0/sqrt(2.0d0)*massScaleFree**1.5d0 & + & + 92.0d0/ 135.0d0 *massScaleFree**2 & + & + 313.0d0/ 540.0d0/sqrt(2.0d0)*massScaleFree**2.5d0 & + & + 1928.0d0/ 8505.0d0 *massScaleFree**3 & + & +56201.0d0/340200.0d0/sqrt(2.0d0)*massScaleFree**3.5d0 & + & + 358.0d0/ 1701.0d0 *massScaleFree**4 + else + radius=-1.0d0 & + & /Lambert_W0( & + & -exp( & + & -1.0d0 & + & -massScaleFree & + & ) & + & ) & + & -1.0d0 + end if + radius=+radius & + & *self%scaleLength + return + end function nfwRadiusEnclosingMass + double precision function nfwRadiusEnclosingDensity(self,density,radiusGuess) result(radius) + !!{ + Computes the radius enclosing a given mean density for nfw mass distributions. + !!} + use :: Numerical_Ranges, only : Make_Range, rangeTypeLogarithmic + implicit none + class (massDistributionNFW), intent(inout), target :: self + double precision , intent(in ) :: density + double precision , intent(in ), optional :: radiusGuess + double precision , allocatable , dimension(:) :: radii , densities + double precision , parameter :: countRadiiPerDecade=100.0d0 + double precision :: densityScaleFree + integer :: countRadii + + densityScaleFree=+density & + & /self%densityNormalization + if ( & + & densityScaleFree < densityScaleFreeMinimum & + & .or. & + & densityScaleFree > densityScaleFreeMaximum & + & ) then + do while (densityEnclosedScaleFree(densityScaleFreeRadiusMinimum) < densityScaleFree) + densityScaleFreeRadiusMinimum=0.5d0*densityScaleFreeRadiusMinimum + end do + do while (densityEnclosedScaleFree(densityScaleFreeRadiusMaximum) > densityScaleFree) + densityScaleFreeRadiusMaximum=2.0d0*densityScaleFreeRadiusMaximum + end do + countRadii=int(log10(densityScaleFreeRadiusMaximum/densityScaleFreeRadiusMinimum)*countRadiiPerDecade)+1 + if (allocated(densityScaleFree_)) deallocate(densityScaleFree_) + allocate(radii (countRadii)) + allocate(densities (countRadii)) + allocate(densityScaleFree_ ) + radii = Make_Range(densityScaleFreeRadiusMinimum,densityScaleFreeRadiusMaximum,countRadii,rangeTypeLogarithmic) + densities =-densityEnclosedScaleFree( radii) + densityScaleFreeMinimum=-densities (countRadii ) + densityScaleFreeMaximum=-densities ( 1 ) + densityScaleFree_ = interpolator (densities ,radii) + end if + radius=+densityScaleFree_%interpolate(-densityScaleFree) & + & *self %scaleLength + return + end function nfwRadiusEnclosingDensity + + elemental double precision function massEnclosedScaleFree(radius) result(mass) + !!{ + Evaluate the mass enclosed by a given radius in a scale-free NFW mass distribution. + !!} + use :: Numerical_Constants_Math, only : Pi + implicit none + double precision, intent(in ) :: radius + double precision, parameter :: minimumRadiusForExactSolution =1.0d-6 + double precision, parameter :: nfwNormalizationFactorUnitRadius=log(2.0d0)-0.5d0 ! Precomputed NFW normalization factor for unit radius. + + if (radius == 1.0d0 ) then + mass=nfwNormalizationFactorUnitRadius + else if (radius >= minimumRadiusForExactSolution) then + mass=log(1.0d0+radius)-radius/(1.0d0+radius) + else + mass=radius**2*(0.5d0+radius*(-2.0d0/3.0d0+radius*(0.75d0+radius*(-0.8d0)))) + end if + mass =+4.0d0 & + & *Pi & + & *mass + return + end function massEnclosedScaleFree + + elemental double precision function densityEnclosedScaleFree(radius) result(density) + !!{ + Evaluate the mean enclosed density at a given radius in a scale-free NFW mass distribution. + !!} + use :: Numerical_Constants_Math, only : Pi + implicit none + double precision, intent(in ) :: radius + + density=+3.0d0 & + & /4.0d0 & + & /Pi & + & *massEnclosedScaleFree(radius) & + & / radius **3 + return + end function densityEnclosedScaleFree + + double precision function nfwRadiusFromSpecificAngularMomentum(self,angularMomentumSpecific) result(radius) + !!{ + Computes the radius corresponding to a given specific angular momentum for nfw mass distributions. + !!} + use :: Numerical_Constants_Astronomical, only : gravitationalConstantGalacticus + use :: Numerical_Ranges , only : Make_Range , rangeTypeLogarithmic + implicit none + class (massDistributionNFW), intent(inout), target :: self + double precision , intent(in ) :: angularMomentumSpecific + double precision , allocatable , dimension(:) :: radii , angularMomentaSpecific + double precision , parameter :: countRadiiPerDecade =100.0d0 + double precision :: angularMomentumSpecificScaleFree + integer :: countRadii + + if (angularMomentumSpecific > 0.0d0) then + angularMomentumSpecificScaleFree=+angularMomentumSpecific & + & /sqrt( & + & +gravitationalConstantGalacticus & + & *self%densityNormalization & + & ) & + & / self%scaleLength **2 + if ( & + & angularMomentumSpecificScaleFree < angularMomentumSpecificScaleFreeMinimum & + & .or. & + & angularMomentumSpecificScaleFree > angularMomentumSpecificScaleFreeMaximum & + & ) then + do while (angularMomentumSpecificEnclosedScaleFree(angularMomentumSpecificScaleFreeRadiusMinimum) > angularMomentumSpecificScaleFree) + angularMomentumSpecificScaleFreeRadiusMinimum=0.5d0*angularMomentumSpecificScaleFreeRadiusMinimum + end do + do while (angularMomentumSpecificEnclosedScaleFree(angularMomentumSpecificScaleFreeRadiusMaximum) < angularMomentumSpecificScaleFree) + angularMomentumSpecificScaleFreeRadiusMaximum=2.0d0*angularMomentumSpecificScaleFreeRadiusMaximum + end do + countRadii=int(log10(angularMomentumSpecificScaleFreeRadiusMaximum/angularMomentumSpecificScaleFreeRadiusMinimum)*countRadiiPerDecade)+1 + if (allocated(angularMomentumSpecificScaleFree_)) deallocate(angularMomentumSpecificScaleFree_) + allocate(radii (countRadii)) + allocate(angularMomentaSpecific (countRadii)) + allocate(angularMomentumSpecificScaleFree_ ) + radii =Make_Range(angularMomentumSpecificScaleFreeRadiusMinimum,angularMomentumSpecificScaleFreeRadiusMaximum,countRadii,rangeTypeLogarithmic) + angularMomentaSpecific =angularMomentumSpecificEnclosedScaleFree( radii) + angularMomentumSpecificScaleFreeMinimum=angularMomentaSpecific ( 1) + angularMomentumSpecificScaleFreeMaximum=angularMomentaSpecific (countRadii) + angularMomentumSpecificScaleFree_ =interpolator (angularMomentaSpecific,radii) + end if + radius=+angularMomentumSpecificScaleFree_%interpolate(angularMomentumSpecificScaleFree) & + & *self %scaleLength + else + radius=+0.0d0 + end if + return + end function nfwRadiusFromSpecificAngularMomentum + + elemental double precision function angularMomentumSpecificEnclosedScaleFree(radius) result(angularMomentumSpecific) + !!{ + Evaluate the specific angular momentum at a given radius in a scale-free NFW mass distribution. + !!} + implicit none + double precision, intent(in ) :: radius + + angularMomentumSpecific=+sqrt( & + & +massEnclosedScaleFree(radius) & + & * radius & + & ) + return + end function angularMomentumSpecificEnclosedScaleFree + + double precision function nfwVelocityRotationCurveMaximum(self) result(velocity) + !!{ + Return the peak velocity in the rotation curve for an nfw mass distribution. + !!} + use :: Numerical_Constants_Astronomical, only : gravitationalConstantGalacticus + use :: Numerical_Constants_Math , only : Pi + implicit none + class (massDistributionNFW), intent(inout) :: self + double precision , parameter :: circularVelocityMaximumScaleFree=0.4649909628174221d0 ! The circular velocity (in scale-free units) at the peak of the NFW rotation curve. + ! Numerical value found using Mathematica. + + velocity=+circularVelocityMaximumScaleFree & + & *sqrt( & + & +4.0d0 & + & *Pi & + & *self%densityNormalization & + & ) & + & * self%scaleLength + if (.not.self%isDimensionless()) & + & velocity=+velocity & + & *sqrt(gravitationalConstantGalacticus) + return + end function nfwVelocityRotationCurveMaximum + + double precision function nfwRadiusRotationCurveMaximum(self) result(radius) + !!{ + Return the peak velocity in the rotation curve for an nfw mass distribution. + !!} + implicit none + class (massDistributionNFW ), intent(inout), target :: self + ! The radius (in scale-free units) at the peak of the NFW rotation curve. Numerical value found using Mathematica. + double precision , parameter :: radiusCircularVelocityMaximumScaleFree=2.162581587064612d0 + + radius=+radiusCircularVelocityMaximumScaleFree & + & *self%scaleLength + return + end function nfwRadiusRotationCurveMaximum + + logical function nfwPotentialIsAnalytic(self) result(isAnalytic) + !!{ + Return that the potential has an analytic form. + !!} + implicit none + class(massDistributionNFW), intent(inout) :: self + + isAnalytic=.true. + return + end function nfwPotentialIsAnalytic + + double precision function nfwPotential(self,coordinates,status) result(potential) + !!{ + Return the potential at the specified {\normalfont \ttfamily coordinates} in an nfw mass distribution. + !!} + use :: Coordinates , only : assignment(=) + use :: Galactic_Structure_Options , only : structureErrorCodeSuccess , structureErrorCodeInfinite + use :: Numerical_Constants_Astronomical, only : gravitationalConstantGalacticus + use :: Error , only : Error_Report + implicit none + class (massDistributionNFW ), intent(inout), target :: self + class (coordinate ), intent(in ) :: coordinates + type (enumerationStructureErrorCodeType), intent( out), optional :: status + double precision :: radiusScaleFree + + if (present(status)) status=structureErrorCodeSuccess + radiusScaleFree=+coordinates%rSpherical () & + & /self %scaleLength + potential=+potentialScaleFree (radiusScaleFree) & + & *self%densityNormalization & + & *self%scaleLength **2 + if (.not.self%isDimensionless()) potential=+gravitationalConstantGalacticus & + & *potential + return + end function nfwPotential + + elemental double precision function potentialScaleFree(radius) result(potential) + !!{ + Compute the potential in a scale-free NFW mass distribution. + !!} + use :: Numerical_Constants_Math, only : Pi + implicit none + double precision, intent(in ) :: radius + double precision, parameter :: radiusSmall=1.0d-6 + double precision :: radiusTerm + + if (radius < radiusSmall) then + ! Use a series solution for very small radii. + radiusTerm=+1.0d0-radius/2.0d0+radius**2/3.0d0 + else + ! Use the full expression for larger radii. + radiusTerm=log(1.0d0+radius)/radius + end if + potential=-4.0d0 & + & *Pi & + & *radiusTerm + return + end function potentialScaleFree + + double precision function potentialDifferenceScaleFree(radius1,radius2) result(potential) + !!{ + Compute the potential difference in a scale-free NFW mass distribution. + !!} + use :: Numerical_Constants_Math, only : Pi + use :: Numerical_Comparison , only : Values_Agree + implicit none + double precision, intent(in ) :: radius1 , radius2 + double precision, parameter :: radiusSmall =1.0d-6 + double precision, parameter :: toleranceRelative =1.0d-3 + double precision :: potentialGradientLogarithmic , radiusDifferenceLogarithmic + + if (Values_Agree(radius1,radius2,relTol=toleranceRelative)) then + if (radius1 < radiusSmall) then + potentialGradientLogarithmic=- radius1 / 2.0d0 & + & +5.0d0*radius1**2/12.0d0 + else + potentialGradientLogarithmic=- 1.0d0 & + & + radius1 & + & / (1.0d0+radius1) & + & /log(1.0d0+radius1) + end if + radiusDifferenceLogarithmic=+1.0d0 & + & -radius2 & + & /radius1 + potential =+potentialScaleFree (radius1) & + & *potentialGradientLogarithmic & + & *radiusDifferenceLogarithmic + else + potential=+potentialScaleFree(radius1) & + & -potentialScaleFree(radius2) + end if + return + end function potentialDifferenceScaleFree + + double precision function nfwFourierTransform(self,radiusOuter,wavenumber) result(fourierTransform) + !!{ + Compute the Fourier transform of the density profile at the given {\normalfont \ttfamily wavenumber} in an NFW mass + distribution, using the expression given in \citeauthor{cooray_halo_2002}~(\citeyear{cooray_halo_2002}; eqn.~81). + !!} + use :: Exponential_Integrals, only : Cosine_Integral, Sine_Integral + implicit none + class (massDistributionNFW ), intent(inout) :: self + double precision , intent(in ) :: radiusOuter , wavenumber + double precision :: wavenumberScaleFree, radiusOuterScaleFree + + waveNumberScaleFree =+waveNumber *self%scaleLength + radiusOuterScaleFree=+radiusOuter/self%scaleLength + fourierTransform =+( & + & +sin(+ waveNumberScaleFree)*(Sine_Integral ((1.0d0+radiusOuterScaleFree)*waveNumberScaleFree)-Sine_Integral (waveNumberScaleFree)) & + & -sin(+radiusOuterScaleFree*waveNumberScaleFree)/ (1.0d0+radiusOuterScaleFree)/waveNumberScaleFree & + & +cos(+ waveNumberScaleFree)*(Cosine_Integral((1.0d0+radiusOuterScaleFree)*waveNumberScaleFree)-Cosine_Integral(waveNumberScaleFree)) & + & ) & + & /(log(1.0d0+radiusOuterScaleFree)-radiusOuterScaleFree/(1.0d0+radiusOuterScaleFree)) + return + end function nfwFourierTransform + + double precision function nfwRadiusFreefall(self,time) result(radius) + !!{ + Compute the freefall radius at the given {\normalfont \ttfamily time} in an NFW mass distribution. + !!} + use :: Numerical_Constants_Astronomical, only : Mpc_per_km_per_s_To_Gyr, gravitationalConstantGalacticus + implicit none + class (massDistributionNFW), intent(inout) :: self + double precision , intent(in ) :: time + double precision :: timeScaleFree, timeScale + + if (time > 0.0d0) then + timeScale =+1.0d0/sqrt( & + & +gravitationalConstantGalacticus & + & *self%densityNormalization & + & ) & + & *Mpc_per_km_per_s_To_Gyr + timeScaleFree=+time & + & /timeScale + call self%timeFreefallTabulate(timeScaleFree) + radius=+timeFreefallScaleFree_%interpolate(timeScaleFree) & + & *self %scaleLength + else + ! For non-positive freefall times, return a zero freefall radius. + radius=+0.0d0 + end if + return + end function nfwRadiusFreefall + + double precision function nfwRadiusFreefallIncreaseRate(self,time) result(radiusIncreaseRate) + !!{ + Compute the rate of increase of the freefall radius at the given {\normalfont \ttfamily time} in an nfw mass + distribution. + !!} + use :: Numerical_Constants_Astronomical, only : Mpc_per_km_per_s_To_Gyr, gravitationalConstantGalacticus + implicit none + class (massDistributionNFW), intent(inout) :: self + double precision , intent(in ) :: time + double precision :: timeScaleFree, timeScale + + if (time > 0.0d0) then + timeScale =+1.0d0/sqrt( & + & +gravitationalConstantGalacticus & + & *self%densityNormalization & + & ) & + & *Mpc_per_km_per_s_To_Gyr + timeScaleFree=+time & + & /timeScale + call self%timeFreefallTabulate(timeScaleFree) + radiusIncreaseRate=+timeFreefallScaleFree_%derivative(timeScaleFree) & + & *self %scaleLength & + & / timeScale + else + ! For non-positive freefall times, return the limiting value for small radii. + radiusIncreaseRate=+0.0d0 + end if + return + end function nfwRadiusFreefallIncreaseRate + + subroutine nfwTimeFreefallTabulate(self,timeScaleFree) + !!{ + Tabulate the freefall radius at the given {\normalfont \ttfamily time} in an NFW mass distribution. + !!} + use :: Numerical_Integration, only : integrator + use :: Numerical_Ranges , only : Make_Range, rangeTypeLogarithmic + implicit none + class (massDistributionNFW), intent(inout) :: self + double precision , intent(in ) :: timeScaleFree + double precision , allocatable , dimension(:) :: radii , timesFreefall + double precision , parameter :: countRadiiPerDecade=100.0d0 + double precision :: radiusStart + integer :: countRadii , i + type (integrator ) :: integrator_ + + if ( & + & timeScaleFree < timeFreefallScaleFreeMinimum & + & .or. & + & timeScaleFree > timeFreefallScaleFreeMaximum & + & ) then + integrator_=integrator(timeFreeFallIntegrand,toleranceRelative=1.0d-3) + do while (timeFreefallScaleFree(timeFreefallScaleFreeRadiusMinimum) > timeScaleFree) + timeFreefallScaleFreeRadiusMinimum=0.5d0*timeFreefallScaleFreeRadiusMinimum + end do + do while (timeFreefallScaleFree(timeFreefallScaleFreeRadiusMaximum) < timeScaleFree) + timeFreefallScaleFreeRadiusMaximum=2.0d0*timeFreefallScaleFreeRadiusMaximum + end do + countRadii=int(log10(timeFreefallScaleFreeRadiusMaximum/timeFreefallScaleFreeRadiusMinimum)*countRadiiPerDecade)+1 + if (allocated(timeFreefallScaleFree_)) deallocate(timeFreefallScaleFree_) + allocate(radii (countRadii)) + allocate(timesFreefall (countRadii)) + allocate(timeFreefallScaleFree_ ) + radii=Make_Range(timeFreefallScaleFreeRadiusMinimum,timeFreefallScaleFreeRadiusMaximum,countRadii,rangeTypeLogarithmic) + do i=1,countRadii + timesFreefall(i)=timeFreefallScaleFree(radii(i)) + end do + timeFreefallScaleFreeMinimum=timesFreefall( 1 ) + timeFreefallScaleFreeMaximum=timesFreefall( countRadii ) + timeFreefallScaleFree_ =interpolator (timesFreefall,radii) + end if + return + + contains + + double precision function timeFreefallScaleFree(radius) + !!{ + Evaluate the freefall time from a given radius in a scale-free NFW mass distribution. + !!} + use :: Numerical_Constants_Math, only : Pi + implicit none + double precision, intent(in ) :: radius + double precision, parameter :: radiusSmall=4.0d-6 + + if (radius > radiusSmall) then + radiusStart = radius + timeFreefallScaleFree=integrator_%integrate(0.0d0,radius) + else + ! Use an approximation here, found by taking series expansions of the logarithms in the integrand and keeping only the + ! first order terms. + timeFreefallScaleFree=2.0d0*sqrt(radius/4.0d0/Pi) + end if + return + end function timeFreefallScaleFree + + double precision function timeFreeFallIntegrand(radius) + !!{ + Integrand used to find the freefall time in a scale-free NFW mass distribution. + !!} + implicit none + double precision, intent(in ) :: radius + double precision :: potentialDifference + + if (radius == 0.0d0) then + timeFreeFallIntegrand=+0.0d0 + else + potentialDifference=+potentialDifferenceScaleFree(radiusStart,radius) + if (potentialDifference > 0.0d0) then + timeFreeFallIntegrand=+1.0d0 & + & /sqrt( & + & +2.0d0 & + & *potentialDifference & + & ) + else + timeFreeFallIntegrand=+0.0d0 + end if + end if + return + end function timeFreeFallIntegrand + + end subroutine nfwTimeFreefallTabulate + + double precision function nfwEnergyPotential(self,radiusOuter) result(energy) + !!{ + Compute the potential energy within a given {\normalfont \ttfamily radius} in an NFW mass distribution. This is + \begin{eqnarray} + W &=& - \frac{\mathrm{G}}{2} \rho_0^2 r_\mathrm{s}^5 \int_0^{x_\mathrm{out}} \frac{m^2(x)}{x^2} \mathrm{d} x, \nonumber \\ + &-& - \frac{\mathrm{G}}{2} \rho_0^2 r_\mathrm{s}^5 \left[ \frac{x}{1+x} - \frac{\log^2(1+x)}{x} + \frac{\left\{\log(1+x)-x/(1+x)\right\}^2}{x} \right], + \end{eqnarray} + where $x=r/r_\mathrm{s}$ and $m(x)$ is the scale-free mass distribution. + !!} + use :: Numerical_Constants_Astronomical, only : gravitationalConstantGalacticus + use :: Numerical_Constants_Math , only : Pi + implicit none + class (massDistributionNFW), intent(inout) :: self + double precision , intent(in ) :: radiusOuter + double precision :: radiusOuterScaleFree + + radiusOuterScaleFree=+ radiusOuter & + & /self%scaleLength + energy=-gravitationalConstantGalacticus & + & *self%scaleLength **5 & + & *self%densityNormalization **2 & + & *8.0d0 & + & *Pi**2 & + & *( & + & +radiusOuterScaleFree/(1.0d0+radiusOuterScaleFree) & + & - log(1.0d0+radiusOuterScaleFree)**2 /radiusOuterScaleFree & + & +(log(1.0d0+radiusOuterScaleFree) -radiusOuterScaleFree/(1.0d0+radiusOuterScaleFree))**2/radiusOuterScaleFree & + & ) + return + end function nfwEnergyPotential + + double precision function nfwEnergyKinetic(self,radiusOuter,massDistributionEmbedding) result(energy) + !!{ + Compute the kinetic energy within a given {\normalfont \ttfamily radius} in an NFW mass distribution. This is + \begin{eqnarray} + T &=& 6 \pi \mathrm{G} \rho_0^2 r_\mathrm{s}^5 \int_0^{x_\mathrm{out}} \rho(x) \sigma^2(x) x^2 \mathrm{d} x, \nonumber \\ + &=& \pi \mathrm{G} \rho_0^2 r_\mathrm{s}^5 \left[ 6 x^3 \text{Li}_2(-x)+x^3 (-\log (x))+\log (x+1) \left(3 x^3 \log (x+1)+((x-6) x+3) x-2\right)+\left(x \left(\pi ^2 x-7\right)+5\right) x+\frac{3}{x+1} \right], + \end{eqnarray} + where $x=r/r_\mathrm{s}$, $\rho(x)$ is the scale-free density, and $\sigma^2(x)$ is the scale-free velocity dispersion. + !!} + use :: Dilogarithms , only : Dilogarithm + use :: Numerical_Constants_Math , only : Pi + use :: Numerical_Constants_Astronomical, only : gravitationalConstantGalacticus + implicit none + class (massDistributionNFW ), intent(inout) :: self + double precision , intent(in ) :: radiusOuter + class (massDistributionClass), intent(inout) :: massDistributionEmbedding + logical :: analytic + double precision :: radiusOuterScaleFree + + analytic=.false. + select type (massDistributionEmbedding) + class is (massDistributionNFW) + select type (kinematicsDistribution_ => massDistributionEmbedding%kinematicsDistribution_) + class is (kinematicsDistributionNFW) + analytic =.true. + radiusOuterScaleFree=+ radiusOuter & + & /self%scaleLength + energy =+gravitationalConstantGalacticus & + & *self%scaleLength **5 & + & *self%densityNormalization **2 & + & *4.0d0 & + & *Pi**2 & + & *( & + & +( & + & +2.0d0 & + & + radiusOuterScaleFree & + & *( & + & -2.0d0 & + & + radiusOuterScaleFree & + & *( & + & -7.0d0 & + & +Pi**2 & + & *(1.0d0+radiusOuterScaleFree) & + & ) & + & ) & + & ) & + & * radiusOuterScaleFree & + & + radiusOuterScaleFree**4 *log (+1.0d0+1.0d0/radiusOuterScaleFree) & + & - radiusOuterScaleFree**3 *log (+ radiusOuterScaleFree) & + & +( & + & - 2.0d0 & + & + radiusOuterScaleFree & + & - 3.0d0*radiusOuterScaleFree**2 & + & - 5.0d0*radiusOuterScaleFree**3 & + & + 3.0d0*radiusOuterScaleFree**3 & + & * (1.0d0+radiusOuterScaleFree ) & + & *log(1.0d0+radiusOuterScaleFree ) & + & ) & + & * log (1.0d0+radiusOuterScaleFree) & + & +6.0d0 & + & * radiusOuterScaleFree**3 & + & * (1.0d0+radiusOuterScaleFree ) & + & * Dilogarithm( -radiusOuterScaleFree) & + & ) & + & /(1.0d0+radiusOuterScaleFree) + end select + end select + if (.not.analytic) energy=self%energyKineticNumerical(radiusOuter,massDistributionEmbedding) + return + end function nfwEnergyKinetic + subroutine nfwDescriptor(self,descriptor,includeClass,includeFileModificationTimes) !!{ Return an input parameter list descriptor which could be used to recreate this object. @@ -233,3 +1037,55 @@ subroutine nfwDescriptor(self,descriptor,includeClass,includeFileModificationTim return end subroutine nfwDescriptor + !![ + + massDistributionNFWStateStore + + !!] + subroutine massDistributionNFWStateStore(stateFile,gslStateFile,stateOperationID) + !!{ + Write the tabulation state to file. + !!} + use :: Display , only : displayMessage, verbosityLevelInfo + use, intrinsic :: ISO_C_Binding, only : c_ptr , c_size_t + implicit none + integer , intent(in ) :: stateFile + integer(c_size_t), intent(in ) :: stateOperationID + type (c_ptr ), intent(in ) :: gslStateFile + + call displayMessage('Storing state for: massDistributionNFW',verbosity=verbosityLevelInfo) + write (stateFile) densityScaleFreeRadiusMinimum , densityScaleFreeRadiusMaximum , & + & densityScaleFreeMinimum , densityScaleFreeMaximum , & + & angularMomentumSpecificScaleFreeRadiusMinimum, angularMomentumSpecificScaleFreeRadiusMaximum, & + & angularMomentumSpecificScaleFreeMinimum , angularMomentumSpecificScaleFreeMaximum , & + & timeFreefallScaleFreeRadiusMinimum , timeFreefallScaleFreeRadiusMaximum , & + & timeFreefallScaleFreeMinimum , timeFreefallScaleFreeMaximum + return + end subroutine massDistributionNFWStateStore + + !![ + + massDistributionNFWStateRestore + + !!] + subroutine massDistributionNFWStateRestore(stateFile,gslStateFile,stateOperationID) + !!{ + Retrieve the tabulation state from the file. + !!} + use :: Display , only : displayMessage, verbosityLevelInfo + use, intrinsic :: ISO_C_Binding, only : c_ptr , c_size_t + implicit none + integer , intent(in ) :: stateFile + integer(c_size_t), intent(in ) :: stateOperationID + type (c_ptr ), intent(in ) :: gslStateFile + + call displayMessage('Retrieving state for: massDistributionNFW',verbosity=verbosityLevelInfo) + read (stateFile) densityScaleFreeRadiusMinimum , densityScaleFreeRadiusMaximum , & + & densityScaleFreeMinimum , densityScaleFreeMaximum , & + & angularMomentumSpecificScaleFreeRadiusMinimum, angularMomentumSpecificScaleFreeRadiusMaximum, & + & angularMomentumSpecificScaleFreeMinimum , angularMomentumSpecificScaleFreeMaximum , & + & timeFreefallScaleFreeRadiusMinimum , timeFreefallScaleFreeRadiusMaximum , & + & timeFreefallScaleFreeMinimum , timeFreefallScaleFreeMaximum + return + end subroutine massDistributionNFWStateRestore + diff --git a/source/mass_distributions.spherical.PatejLoeb2015.F90 b/source/mass_distributions.spherical.PatejLoeb2015.F90 new file mode 100644 index 0000000000..0fb4a47294 --- /dev/null +++ b/source/mass_distributions.spherical.PatejLoeb2015.F90 @@ -0,0 +1,446 @@ +!! Copyright 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, +!! 2019, 2020, 2021, 2022, 2023 +!! Andrew Benson +!! +!! This file is part of Galacticus. +!! +!! Galacticus is free software: you can redistribute it and/or modify +!! it under the terms of the GNU General Public License as published by +!! the Free Software Foundation, either version 3 of the License, or +!! (at your option) any later version. +!! +!! Galacticus is distributed in the hope that it will be useful, +!! but WITHOUT ANY WARRANTY; without even the implied warranty of +!! MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +!! GNU General Public License for more details. +!! +!! You should have received a copy of the GNU General Public License +!! along with Galacticus. If not, see . + + !!{ + Implementation of mass distribution for the \cite{patej_simple_2015} model of the circumgalactic medium. + !!} + + !![ + + A mass distribution for the \cite{patej_simple_2015} model of the circumgalactic medium. + + !!] + type, public, extends(massDistributionSpherical) :: massDistributionPatejLoeb2015 + !!{ + The \cite{patej_simple_2015} model of the circumgalactic medium. + !!} + class (massDistributionClass), pointer :: massDistribution_ => null() + logical :: truncateAtOuterRadius + double precision :: radiusShock , radiusOuter, & + & densityNormalization , gamma , & + & mass + contains + !![ + + + + + !!] + final :: patejLoeb2015Destructor + procedure :: density => patejLoeb2015Density + procedure :: densityGradientRadial => patejLoeb2015DensityGradientRadial + procedure :: densityRadialMoment => patejLoeb2015DensityRadialMoment + procedure :: massEnclosedBySphere => patejLoeb2015MassEnclosedBySphere + procedure :: potentialIsAnalytic => patejLoeb2015PotentialIsAnalytic + procedure :: potential => patejLoeb2015Potential + procedure :: radiusDarkMatter => patejLoeb2015RadiusDarkMatter + procedure :: coordinatesDarkMatter => patejLoeb2015CoordinatesDarkMatter + end type massDistributionPatejLoeb2015 + + interface massDistributionPatejLoeb2015 + !!{ + Constructors for the {\normalfont \ttfamily patejLoeb2015} mass distribution class. + !!} + module procedure patejLoeb2015ConstructorParameters + module procedure patejLoeb2015ConstructorInternal + end interface massDistributionPatejLoeb2015 + +contains + + function patejLoeb2015ConstructorParameters(parameters) result(self) + !!{ + Constructor for the {\normalfont \ttfamily patejLoeb2015} mass distribution class which builds the object from a parameter + set. + !!} + use :: Input_Parameters , only : inputParameter , inputParameters + use :: Galactic_Structure_Options, only : enumerationComponentTypeEncode, enumerationMassTypeEncode + implicit none + type (massDistributionPatejLoeb2015) :: self + type (inputParameters ), intent(inout) :: parameters + double precision :: radiusShock , radiusOuter, & + & mass , gamma , & + & densityNormalization + class (massDistributionClass ), pointer :: massDistribution_ + logical :: truncateAtOuterRadius + type (varying_string ) :: componentType + type (varying_string ) :: massType + + !![ + + gamma + 1.15d0 + The parameter $\Gamma$ in the \cite{patej_simple_2015} mass distribution. + parameters + + + densityNormalization + 0.0d0 + The density normalization of the \cite{patej_simple_2015} mass distribution. + parameters + + + mass + 0.0d0 + The mass of the \cite{patej_simple_2015} mass distribution. + parameters + + + radiusOuter + 0.0d0 + The outer radius of the \cite{patej_simple_2015} mass distribution. + parameters + + + truncateAtOuterRadius + .false. + If true then the \cite{patej_simple_2015} mass distribution is truncated beyond the outer radius. + parameters + + + componentType + var_str('unknown') + The component type that this mass distribution represents. + parameters + + + massType + var_str('unknown') + The mass type that this mass distribution represents. + parameters + + + + self=massDistributionPatejLoeb2015(radiusShock=radiusShock,gamma=gamma,massDistribution_=massDistribution_,componentType=enumerationComponentTypeEncode(componentType,includesPrefix=.false.),massType=enumerationMassTypeEncode(massType,includesPrefix=.false.){conditions}) + + + + + + + + !!] + return + end function patejLoeb2015ConstructorParameters + + function patejLoeb2015ConstructorInternal(gamma,massDistribution_,densityNormalization,mass,radiusOuter,radiusShock,truncateAtOuterRadius,componentType,massType) result(self) + !!{ + Constructor for the {\normalfont \ttfamily patejLoeb2015} mass distribution class. + !!} + use :: Error, only : Error_Report + implicit none + type (massDistributionPatejLoeb2015) :: self + double precision , intent(in ) :: gamma , radiusShock + double precision , intent(in ), optional :: densityNormalization , mass , & + & radiusOuter + logical , intent(in ), optional :: truncateAtOuterRadius + type (enumerationComponentTypeType), intent(in ), optional :: componentType + type (enumerationMassTypeType ), intent(in ), optional :: massType + class (massDistributionClass ), intent(in ), target :: massDistribution_ + !![ + + !!] + + ! This mass distribution is never dimensionless. + self%dimensionless=.false. + ! Determine density normalization. + if ( & + & present(densityNormalization) & + & ) then + self%densityNormalization=densityNormalization + else if ( & + & present(mass ) & + & ) then + self%densityNormalization=mass/self%massDistribution_%massEnclosedBySphere(self%radiusDarkMatter(self%radiusShock)) + else + call Error_Report('no way to determine density normalization'//{introspection:location}) + end if + ! Check for truncation. + if (present(truncateAtOuterRadius)) then + self%truncateAtOuterRadius=truncateAtOuterRadius + else + self%truncateAtOuterRadius=.false. + end if + if (self%truncateAtOuterRadius) then + if (.not.present(radiusOuter)) call Error_Report('can not truncate profile without an outer radius'//{introspection:location}) + self%radiusOuter=radiusOuter + end if + return + end function patejLoeb2015ConstructorInternal + + subroutine patejLoeb2015Destructor(self) + !!{ + Destructor for the {\normalfont \ttfamily patejLoeb2015} mass distribution class. + !!} + type(massDistributionPatejLoeb2015), intent(inout) :: self + implicit none + + !![ + + !!] + return + end subroutine patejLoeb2015Destructor + + double precision function patejLoeb2015RadiusDarkMatter(self,radius) result(radiusDarkMatter) + !!{ + Return the corresponding radius in the dark matter distribution the specified {\normalfont \ttfamily radius} in a \cite{patej_simple_2015} mass distribution. + !!} + implicit none + class (massDistributionPatejLoeb2015), intent(inout) :: self + double precision , intent(in ) :: radius + + + radiusDarkMatter=+ self%radiusShock & + & *( & + & + radius & + & /self%radiusShock & + & )**self%gamma + return + end function patejLoeb2015RadiusDarkMatter + + function patejLoeb2015CoordinatesDarkMatter(self,coordinates) result(coordinatesDarkMatter) + !!{ + Return the corresponding coordinates in the dark matter distribution the specified {\normalfont \ttfamily radius} in a \cite{patej_simple_2015} mass distribution. + !!} + use :: Coordinates, only : coordinateSpherical, assignment(=) + implicit none + type (coordinateSpherical ) :: coordinatesDarkMatter + class(massDistributionPatejLoeb2015), intent(inout) :: self + class(coordinate ), intent(in ) :: coordinates + + coordinatesDarkMatter=coordinates & + & *(coordinates%rSpherical()/self%radiusShock)**(self%gamma-1.0d0) + return + end function patejLoeb2015CoordinatesDarkMatter + + double precision function patejLoeb2015Density(self,coordinates) result(density) + !!{ + Return the density at the specified {\normalfont \ttfamily coordinates} in a \cite{patej_simple_2015} mass distribution. + !!} + implicit none + class(massDistributionPatejLoeb2015), intent(inout) :: self + class(coordinate ), intent(in ) :: coordinates + + density=0.0d0 + if (self%truncateAtOuterRadius .and. coordinates%rSpherical() > self%radiusOuter) return + ! Evaluate density using equation 12 of Patej & Loeb (2015). + density=+self%gamma & + & *self%densityNormalization & + & *( & + & +coordinates%rSpherical () & + & /self %radiusShock & + & )**(3.0d0*self%gamma-3.0d0) & + & *self%massDistribution_%density(self%coordinatesDarkMatter(coordinates)) + return + end function patejLoeb2015Density + + double precision function patejLoeb2015DensityGradientRadial(self,coordinates,logarithmic) result(densityGradientRadial) + !!{ + Return the density at the specified {\normalfont \ttfamily coordinates} in a \cite{patej_simple_2015} mass distribution. + !!} + implicit none + class (massDistributionPatejLoeb2015), intent(inout), target :: self + class (coordinate ), intent(in ) :: coordinates + logical , intent(in ), optional :: logarithmic + !![ + + !!] + + densityGradientRadial=0.0d0 + if (self%truncateAtOuterRadius .and. coordinates%rSpherical() > self%radiusOuter) return + densityGradientRadial=+3.0d0 & + & *(self%gamma-1.0d0) & + & + self%gamma & + & * self%massDistribution_%densityGradientRadial( & + & self%coordinatesDarkMatter(coordinates), & + & logarithmic=.true. & + & ) + if (.not.logarithmic_) densityGradientRadial=densityGradientRadial*self%density(coordinates)/coordinates%rSpherical() + return + end function patejLoeb2015DensityGradientRadial + + double precision function patejLoeb2015MassEnclosedBySphere(self,radius) result(massEnclosedBySphere) + !!{ + Computes the mass enclosed within a sphere of given {\normalfont \ttfamily radius} for a \cite{patej_simple_2015} mass distribution. + !!} + implicit none + class (massDistributionPatejLoeb2015), intent(inout), target :: self + double precision , intent(in ) :: radius + double precision :: radius_ + + ! Compute the enclosed mass (eqn. 4 of Patej & Loeb 2015). + radius_=radius + if (self%truncateAtOuterRadius) radius_=min(radius_,self%radiusOuter) + massEnclosedBySphere=+self %densityNormalization & + & *self%massDistribution_%massEnclosedBySphere(self%radiusDarkMatter(radius_)) + return + end function patejLoeb2015MassEnclosedBySphere + + logical function patejLoeb2015PotentialIsAnalytic(self) result(isAnalytic) + !!{ + Return that the potential has an analytic form. + !!} + implicit none + class(massDistributionPatejLoeb2015), intent(inout) :: self + + isAnalytic=.true. + return + end function patejLoeb2015PotentialIsAnalytic + + double precision function patejLoeb2015Potential(self,coordinates,status) result(potential) + !!{ + Return the potential at the specified {\normalfont \ttfamily coordinates} in a \cite{patej_simple_2015} mass distribution. The + potential is given by + \begin{equation} + \phi(r) = - \int^\infty_r \mathrm{d}r \frac{\mathrm{G}M(r)}{r^2}. + \end{equation} + Given that $M(r) = f M^\prime(r^\prime)$ where $M^\prime(r^\prime)$ is the mass profile of the dark matter distribution, + $r^\prime = r_\mathrm{s} (r/r_\mathrm{s})^\Gamma$ (and, therefore, $r = r_\mathrm{s} (r^\prime/r_\mathrm{s})^{1/\Gamma}$), and + $f$ is the normalization factor, we can write this as: + \begin{eqnarray} + \phi(r) &=& - \int^\infty_{r^\prime(r)} \mathrm{d}r^\prime f \frac{\mathrm{d}r}{\mathrm{d}r^\prime} (r^\prime/r_\mathrm{s})^{-2/\Gamma} \frac{\mathrm{G}M^\prime(r^\prime)}{r_\mathrm{s}^2} \nonumber \\ + &=& - \int^\infty_{r^\prime(r)} \mathrm{d}r^\prime \frac{f}{\Gamma} (r^\prime/r_\mathrm{s})^{-1/\Gamma-1} \frac{\mathrm{G}M^\prime(r^\prime)}{r_\mathrm{s}^2} + \end{eqnarray} + which we can then integrate by parts to get: + \begin{equation} + \phi(r) = + f \left(\frac{r^\prime}{r_\mathrm{s}}\right)^{-1/\Gamma} \frac{\mathrm{G}M^\prime(r^\prime)}{r_\mathrm{s}} - 4 \pi f \frac{\mathrm{G} r_\mathrm{s}^{1/\Gamma} \mathcal{R}(r^\prime,\infty,2-1/\Gamma)}{r_\mathrm{s}}, + \end{equation} + where $\mathcal{R}(a,b,m)$ is the $m^\mathrm{th}$ radial density moment between $r^\prime=a$ and $r^\prime=b$ of the dark + matter distribution, and we have assumed that $(r^\prime/r_\mathrm{s})^{-1/\Gamma} M^\prime(r^\prime) \rightarrow 0$ as + $r^\prime \rightarrow \infty$. + !!} + use :: Numerical_Constants_Astronomical, only : gravitationalConstantGalacticus + use :: Numerical_Constants_Math , only : Pi + use :: Galactic_Structure_Options , only : structureErrorCodeSuccess + implicit none + class (massDistributionPatejLoeb2015 ), intent(inout), target :: self + class (coordinate ), intent(in ) :: coordinates + type (enumerationStructureErrorCodeType), intent( out), optional :: status + double precision :: radiusOuter , radiusDarkMatterInner, & + & radiusDarkMatterOuter, potentialTermInner , & + & potentialTermOuter , potentialTermMass + logical :: computePotential + + potential=0.0d0 + if (present(status)) status=structureErrorCodeSuccess + ! Note that we should check that the condition -1/Γ+3+α < 0, where α is the logarithmic slope of the dark matter density + ! profile as r → ∞, holds to ensure that our assumption that the r=∞ term in the first term in the integration by parts is + ! zero is a valid assumption. Currently we do not perform this check. + if (self%truncateAtOuterRadius) then + ! Add the potential due to a point mass outside of the outer radius. + radiusOuter =max( & + & coordinates%rSpherical (), & + & self %radiusOuter & + & ) + potential =- gravitationalConstantGalacticus & + & *self%massEnclosedBySphere (radiusOuter) & + & / radiusOuter + computePotential=radiusOuter < self%radiusOuter + else + radiusOuter =huge(0.0d0) + computePotential=.true. + end if + if (.not.computePotential) return + ! Add on the contribution from radii inside the mass distribution. + radiusDarkMatterInner=self%radiusDarkMatter(coordinates%rSpherical()) + if (self%truncateAtOuterRadius) then + radiusDarkMatterOuter=+self%radiusDarkMatter(radiusOuter) + potentialTermMass =-4.0d0 & + & *Pi & + & *self %radiusShock**(1.0d0/self%gamma) & + & *self%massDistribution_%densityRadialMoment(2.0d0-1.0d0/self%gamma,radiusDarkMatterInner,radiusDarkMatterOuter) + potentialTermOuter =+( & + & + radiusDarkMatterOuter & + & /self%radiusShock & + & )**(-1.0d0/self%gamma) & + & *self%massDistribution_%massEnclosedBySphere(radiusDarkMatterOuter) + else + ! There is no outer truncation, so the outer radius is at infinity. + potentialTermMass =-4.0d0 & + & *Pi & + & *self %radiusShock**(1.0d0/self%gamma) & + & *self%massDistribution_%densityRadialMoment(2.0d0-1.0d0/self%gamma,radiusDarkMatterInner ) + potentialTermOuter =+0.0d0 + end if + potentialTermInner =+( & + & + radiusDarkMatterInner & + & /self%radiusShock & + & )**(-1.0d0/self%gamma) & + & *self%massDistribution_%massEnclosedBySphere(radiusDarkMatterInner) + potential =+ potential & + & + gravitationalConstantGalacticus & + & *self%densityNormalization & + & /self%radiusShock & + & *( & + & +potentialTermOuter & + & -potentialTermInner & + & +potentialTermMass & + & ) + return + end function patejLoeb2015Potential + + double precision function patejLoeb2015DensityRadialMoment(self,moment,radiusMinimum,radiusMaximum,isInfinite) result(densityRadialMoment) + !!{ + Computes radial moments of the density in a \cite{patej_simple_2015} mass distribution. For this profile we have: + \begin{equation} + \rho_\mathrm{g}(r) = f \Gamma (r/s)^{3 \Gamma - 3} \rho_\mathrm{DM}(s[r/s]^\Gamma). + \end{equation} + Defining $R=s[r/s]^\Gamma$, such that $r/s = (R/s)^{1/\Gamma}$, and $\mathrm{d}r = \Gamma^{-1} (R/s)^{1/\Gamma-1} \mathrm{d}R$, then + \begin{equation} + \int r^m \rho_\mathrm{g}(r) \mathrm{d}r = f s^{(m-2)(\Gamma-1)/\Gamma} \int R^{(2\Gamma-2+m)/\Gamma} \rho_\mathrm{DM}(R) \mathrm{d}R, + \end{equation} + or + \begin{equation} + \mathcal{R}_\mathrm{g}(r;m) = f s^{(m-2)(\Gamma-1)/\Gamma} \mathcal{R}_\mathrm{DM}(R;(2\Gamma-2+m)/\Gamma), + \end{equation} + where $\mathcal{R}(r;m)$ is the $m^\mathrm{th}$ radial moment of the density profile. + !!} + implicit none + class (massDistributionPatejLoeb2015), intent(inout) :: self + double precision , intent(in ) :: moment + double precision , intent(in ), optional :: radiusMinimum , radiusMaximum + logical , intent( out), optional :: isInfinite + double precision :: radiusMinimum_ , radiusMaximum_, & + & momentEffective + + if (present(radiusMinimum)) then + radiusMinimum_=radiusMinimum + else + radiusMinimum_=0.0d0 + end if + if (present(radiusMaximum)) then + radiusMaximum_=radiusMaximum + else + radiusMaximum_=huge(0.0d0) + end if + if (self%truncateAtOuterRadius) then + radiusMinimum_=min(radiusMinimum_,self%radiusOuter) + radiusMaximum_=min(radiusMaximum_,self%radiusOuter) + end if + momentEffective=(2.0d0*self%gamma-2.0d0+moment)/self%gamma + if (radiusMaximum_ < huge(0.0d0)) then + densityRadialMoment=+self %densityNormalization & + & *self %radiusShock **(moment-momentEffective) & + & *self%massDistribution_%densityRadialMoment (momentEffective,self%radiusDarkMatter(radiusMinimum_),self%radiusDarkMatter(radiusMaximum_)) + else + densityRadialMoment=+self %densityNormalization & + & *self %radiusShock **(moment-momentEffective) & + & *self%massDistribution_%densityRadialMoment (momentEffective,self%radiusDarkMatter(radiusMinimum_)) + end if + return + end function patejLoeb2015DensityRadialMoment diff --git a/source/mass_distributions.spherical.SIDM.F90 b/source/mass_distributions.spherical.SIDM.F90 new file mode 100644 index 0000000000..8b16885aae --- /dev/null +++ b/source/mass_distributions.spherical.SIDM.F90 @@ -0,0 +1,128 @@ +!! Copyright 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, +!! 2019, 2020, 2021, 2022, 2023 +!! Andrew Benson +!! +!! This file is part of Galacticus. +!! +!! Galacticus is free software: you can redistribute it and/or modify +!! it under the terms of the GNU General Public License as published by +!! the Free Software Foundation, either version 3 of the License, or +!! (at your option) any later version. +!! +!! Galacticus is distributed in the hope that it will be useful, +!! but WITHOUT ANY WARRANTY; without even the implied warranty of +!! MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +!! GNU General Public License for more details. +!! +!! You should have received a copy of the GNU General Public License +!! along with Galacticus. If not, see . + + !!{ + Implements an abstract spherical mass distribution class for SIDM models. + !!} + + use :: Dark_Matter_Particles, only : darkMatterParticleClass + + !![ + + + An abstract mass distribution class for spherical SIDM models. Provides a method to compute interaction radii. + + + !!] + type, abstract, extends(massDistributionSphericalDecorator) :: massDistributionSphericalSIDM + !!{ + Implementation of a spherical mass distribution for SIDM models. + !!} + private + class (darkMatterParticleClass), pointer :: darkMatterParticle_ => null() + double precision :: timeAge + contains + !![ + + + + !!] + procedure :: radiusInteraction => sidmRadiusInteraction + end type massDistributionSphericalSIDM + + ! Submodule-scope variables used in root finding. + class (massDistributionSphericalSIDM), pointer :: self_ + class (kinematicsDistributionClass ), pointer :: kinematicsDistribution_ + double precision :: crossSection_ + !$omp threadprivate(self_,kinematicsDistribution_,crossSection_) + +contains + + double precision function sidmRadiusInteraction(self) result(radiusInteraction) + !!{ + Returns the characteristic interaction radius (in Mpc) of the self-interacting dark matter profile of {\normalfont \ttfamily node}. + !!} + use :: Dark_Matter_Particles , only : darkMatterParticleSelfInteractingDarkMatter + use :: Error , only : Error_Report + use :: Numerical_Constants_Prefixes , only : centi , kilo + use :: Numerical_Constants_Astronomical, only : megaParsec , massSolar + use :: Root_Finder , only : rootFinder , rangeExpandMultiplicative, rangeExpandSignExpectNegative, rangeExpandSignExpectPositive + use :: ISO_Varying_String , only : char + implicit none + class (massDistributionSphericalSIDM), intent(inout), target :: self + type (rootFinder ), save :: finder + logical , save :: finderInitialized=.false. + double precision , parameter :: toleranceAbsolute=0.0d0 , toleranceRelative=1.0d-3 + !$omp threadprivate(finder,finderInitialized) + + self_ => self + select type (darkMatterParticle_ => self%darkMatterParticle_) + class is (darkMatterParticleSelfInteractingDarkMatter) + crossSection_=+darkMatterParticle_%crossSectionSelfInteraction() & + & *centi **2 & + & /megaParsec**2 & + & *kilo & + & *massSolar + class default + call Error_Report('expected self-interacting dark matter particle but found type "'//char(darkMatterParticle_%objectType())//'"'//{introspection:location}) + end select + if (.not.finderInitialized) then + finder=rootFinder( & + & rootFunction =sidmRadiusInteractionRoot, & + & toleranceAbsolute=toleranceAbsolute , & + & toleranceRelative=toleranceRelative & + & ) + call finder%rangeExpand( & + & rangeExpandUpward =2.0d0 , & + & rangeExpandDownward =0.5d0 , & + & rangeExpandDownwardSignExpect=rangeExpandSignExpectPositive, & + & rangeExpandUpwardSignExpect =rangeExpandSignExpectNegative, & + & rangeExpandType =rangeExpandMultiplicative & + & ) + finderInitialized=.true. + end if + kinematicsDistribution_ => self %massDistribution_%kinematicsDistribution( ) + radiusInteraction = finder %find (rootGuess=1.0d0) + !![ + + !!] + return + end function sidmRadiusInteraction + + double precision function sidmRadiusInteractionRoot(radius) result(residual) + !!{ + Root function used in seeking the characteristic interaction radius in self-interacting dark matter profiles. + !!} + use :: Numerical_Constants_Astronomical, only : Mpc_per_km_per_s_To_Gyr + use :: Coordinates , only : coordinateSpherical , assignment(=) + implicit none + double precision , intent(in ) :: radius + type (coordinateSpherical) :: coordinates + + coordinates= [radius,0.0d0,0.0d0] + residual =+4.0d0 & + & /sqrt(Pi) & + & /Mpc_per_km_per_s_To_Gyr & + & *self_%massDistribution_ %density (coordinates ) & + & * kinematicsDistribution_%velocityDispersion1D(coordinates,self_%massDistribution_) & + & *crossSection_ & + & -1.0d0 & + & /self_%timeAge + return + end function sidmRadiusInteractionRoot diff --git a/source/mass_distributions.spherical.SIDM.coreNFW.F90 b/source/mass_distributions.spherical.SIDM.coreNFW.F90 new file mode 100644 index 0000000000..9ebd68b9d4 --- /dev/null +++ b/source/mass_distributions.spherical.SIDM.coreNFW.F90 @@ -0,0 +1,327 @@ +!! Copyright 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, +!! 2019, 2020, 2021, 2022, 2023 +!! Andrew Benson +!! +!! This file is part of Galacticus. +!! +!! Galacticus is free software: you can redistribute it and/or modify +!! it under the terms of the GNU General Public License as published by +!! the Free Software Foundation, either version 3 of the License, or +!! (at your option) any later version. +!! +!! Galacticus is distributed in the hope that it will be useful, +!! but WITHOUT ANY WARRANTY; without even the implied warranty of +!! MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +!! GNU General Public License for more details. +!! +!! You should have received a copy of the GNU General Public License +!! along with Galacticus. If not, see . + + !!{ + An implementation of a cored-NFW mass distribution to approximate the effects of SIDM based on the model of \cite{jiang_semi-analytic_2023}. + !!} + + !![ + + + A mass distribution class implementing a cored-NFW dark matter halo profile to approximate the effects of SIDM based + on the model of Jiang et al. (2022). The profile is defined by the enclosed mass, with \citep{jiang_semi-analytic_2023}: + \begin{equation} + M(r) = M_\mathrm{NFW}(r) \mathrm{tanh}\left(\frac{r}{r_\mathrm{c}}\right), + \end{equation} + where $r_\mathrm{c} = \alpha r_1$ is a characteristic core size related to the interaction radius $r_1$ by a constant factor + $\alpha = ${\normalfont \ttfamily [factorRadiusCore]}. + + + !!] + type, extends(massDistributionSphericalSIDM) :: massDistributionSphericalSIDMCoreNFW + !!{ + Implementation of a cored-NFW mass distribution to approximate the effects of SIDM based on the model of \cite{jiang_semi-analytic_2023}. + !!} + private + double precision :: factorRadiusCore + contains + !![ + + + + !!] + final :: sphericalSIDMCoreNFWDestructor + procedure :: radiusCore => sphericalSIDMCoreNFWRadiusCore + procedure :: density => sphericalSIDMCoreNFWDensity + procedure :: densityGradientRadial => sphericalSIDMCoreNFWDensityGradientRadial + procedure :: massEnclosedBySphere => sphericalSIDMCoreNFWMassEnclosedBySphere + end type massDistributionSphericalSIDMCoreNFW + + interface massDistributionSphericalSIDMCoreNFW + !!{ + Constructors for the {\normalfont \ttfamily sphericalSIDMCoreNFW} mass distribution class. + !!} + module procedure sphericalSIDMCoreNFWConstructorParameters + module procedure sphericalSIDMCoreNFWConstructorInternal + end interface massDistributionSphericalSIDMCoreNFW + +contains + + function sphericalSIDMCoreNFWConstructorParameters(parameters) result(self) + !!{ + Constructor for the {\normalfont \ttfamily sidmCoreNFW} mass distribution class which takes a parameter set as input. + !!} + use :: Input_Parameters , only : inputParameter , inputParameters + use :: Galactic_Structure_Options, only : enumerationComponentTypeEncode, enumerationMassTypeEncode + implicit none + type (massDistributionSphericalSIDMCoreNFW) :: self + type (inputParameters ), intent(inout) :: parameters + class (massDistributionClass ), pointer :: massDistribution_ + class (darkMatterParticleClass ), pointer :: darkMatterParticle_ + double precision :: factorRadiusCore , timeAge + type (varying_string ) :: componentType , massType, & + & nonAnalyticSolver + + !![ + + timeAge + parameters + The age of the halo (in Gyr). + + + factorRadiusCore + 0.45d0 + \cite{jiang_semi-analytic_2023} + parameters + The factor $\alpha$ appearing in the definition of the core radius, $r_\mathrm{c}=\alpha r_1$ where $r_1$ is the radius at which an SIDM particle has had, on average, 1 interaction. + + + nonAnalyticSolver + var_str('fallThrough') + parameters + Selects how solutions are computed when no analytic solution is available. If set to ``{\normalfont \ttfamily fallThrough}'' then the solution ignoring heating is used, while if set to ``{\normalfont \ttfamily numerical}'' then numerical solvers are used to find solutions. + + + componentType + var_str('unknown') + The component type that this mass distribution represents. + parameters + + + massType + var_str('unknown') + The mass type that this mass distribution represents. + parameters + + + + !!] + select type (massDistribution_) + class is (massDistributionNFW) + self=massDistributionSphericalSIDMCoreNFW(factorRadiusCore,timeAge,enumerationNonAnalyticSolversEncode(char(nonAnalyticSolver),includesPrefix=.false.),massDistribution_,darkMatterParticle_,enumerationComponentTypeEncode(componentType,includesPrefix=.false.),enumerationMassTypeEncode(massType,includesPrefix=.false.)) + class default + call Error_Report('an NFW mass distribution is required'//{introspection:location}) + end select + !![ + + + + !!] + return + end function sphericalSIDMCoreNFWConstructorParameters + + function sphericalSIDMCoreNFWConstructorInternal(factorRadiusCore,timeAge,nonAnalyticSolver,massDistribution_,darkMatterParticle_,componentType,massType) result(self) + !!{ + Internal constructor for the {\normalfont \ttfamily sidmCoreNFW} mass distribution class. + !!} + use :: Dark_Matter_Particles, only : darkMatterParticleSelfInteractingDarkMatter + implicit none + type (massDistributionSphericalSIDMCoreNFW) :: self + class (massDistributionNFW ), intent(in ), target :: massDistribution_ + class (darkMatterParticleClass ), intent(in ), target :: darkMatterParticle_ + type (enumerationNonAnalyticSolversType ), intent(in ) :: nonAnalyticSolver + double precision , intent(in ) :: factorRadiusCore , timeAge + type (enumerationComponentTypeType ), intent(in ), optional :: componentType + type (enumerationMassTypeType ), intent(in ), optional :: massType + !![ + + !!] + + ! Validate the dark matter particle type. + select type (darkMatterParticle__ => self%darkMatterParticle_) + class is (darkMatterParticleSelfInteractingDarkMatter) + ! This is as expected. + class default + call Error_Report('this class expects a self-interacting dark matter particle'//{introspection:location}) + end select + ! Initialize state. + self%dimensionless=.false. + return + end function sphericalSIDMCoreNFWConstructorInternal + + subroutine sphericalSIDMCoreNFWDestructor(self) + !!{ + Destructor for the abstract {\normalfont \ttfamily massDistributionSphericalSIDMCoreNFW} class. + !!} + implicit none + type(massDistributionSphericalSIDMCoreNFW), intent(inout) :: self + + !![ + + + !!] + return + end subroutine sphericalSIDMCoreNFWDestructor + + double precision function sphericalSIDMCoreNFWRadiusCore(self) result(radiusCore) + !!{ + Returns the core radius (in Mpc) of the ``coreNFW'' approximation to the self-interacting dark matter profile. + !!} + implicit none + class(massDistributionSphericalSIDMCoreNFW), intent(inout) :: self + + radiusCore=+self%factorRadiusCore & + & *self%radiusInteraction() + return + end function sphericalSIDMCoreNFWRadiusCore + + double precision function sphericalSIDMCoreNFWDensity(self,coordinates) result(density) + !!{ + Compute the density at the specified {\normalfont \ttfamily coordinates} for the {\normalfont \ttfamily sidmCoreNFW} + mass distribution. + !!} + implicit none + class (massDistributionSphericalSIDMCoreNFW), intent(inout) :: self + class (coordinate ), intent(in ) :: coordinates + double precision , parameter :: radiusFractionalLarge=10.0d0 + double precision :: radiusFractional , radiusCore + + radiusCore =+self %radiusCore() + radiusFractional=+coordinates%rSpherical() & + & / radiusCore + if (radiusFractional < radiusFractionalLarge) then + ! Use the full solution for sufficiently small radii. + density=+self%massDistribution_%density (coordinates ) & + & *tanh( & + & +radiusFractional & + & ) & + & +self%massDistribution_%massEnclosedBySphere(coordinates%rSpherical()) & + & /4.0d0 & + & /Pi & + & / radiusFractional**2 & + & / radiusCore **3 & + & /cosh( & + & +radiusFractional & + & )**2 + else + ! For large fractional radii avoid floating point overflow by approximating cosh(x) ~ 1/2/exp(-x). + density=+self%massDistribution_%density (coordinates ) & + & *tanh( & + & +radiusFractional & + & ) & + & +self%massDistribution_%massEnclosedBySphere(coordinates%rSpherical()) & + & /4.0d0 & + & /Pi & + & / radiusFractional**2 & + & / radiusCore **3 & + & *4.0d0 & + & *exp( & + & -2.0d0 & + & * radiusFractional & + & ) + end if + return + end function sphericalSIDMCoreNFWDensity + + double precision function sphericalSIDMCoreNFWDensityGradientRadial(self,coordinates,logarithmic) result(densityGradient) + !!{ + Return the density at the specified {\normalfont \ttfamily coordinates} in a truncated spherical mass distribution. + !!} + implicit none + class (massDistributionSphericalSIDMCoreNFW), intent(inout), target :: self + class (coordinate ), intent(in ) :: coordinates + logical , intent(in ), optional :: logarithmic + double precision :: radiusCore , massEnclosedNFW , & + & densityNFW , densityLogSlopeNFW, & + & radius + !![ + + !!] + + radius =+coordinates %rSpherical ( ) + radiusCore =+self %radiusCore ( ) + massEnclosedNFW =+self%massDistribution_%massEnclosedBySphere (coordinates%rSpherical() ) + densityNFW =+self%massDistribution_%density (coordinates ) + densityLogSlopeNFW=+self%massDistribution_%densityGradientRadial(coordinates ,logarithmic=.true.) + densityGradient =+( & + & -2.0d0 & + & *massEnclosedNFW & + & *( & + & +radiusCore & + & +radius & + & *tanh( & + & +radius & + & /radiusCore & + & ) & + & ) & + & +radiusCore*radius & + & *( & + & +4.0d0 & + & *Pi & + & *radius**2 & + & *densityNFW & + & +2.0d0 & + & *Pi & + & *radius**2 & + & *( & + & +2.0d0 & + & *densityNFW & + & +( & + & +radiusCore & + & *sinh( & + & +2.0d0 & + & *radius & + & /radiusCore & + & ) & + & *densityLogSlopeNFW & + & *densityNFW & + & ) & + & /radius & + & ) & + & ) & + & ) & + & /( & + & +radiusCore & + & *( & + & +massEnclosedNFW & + & +2.0d0 & + & *Pi & + & *radiusCore & + & *radius **2 & + & *sinh( & + & +2.0d0 & + & *radius & + & /radiusCore & + & ) & + & *densityNFW & + & ) & + & ) + if (.not.logarithmic_) & + & densityGradient=+ densityGradient & + & *self%density (coordinates) & + & / radius + return + end function sphericalSIDMCoreNFWDensityGradientRadial + + double precision function sphericalSIDMCoreNFWMassEnclosedBySphere(self,radius) result(mass) + !!{ + Computes the mass enclosed within a sphere of given {\normalfont \ttfamily radius} for the {\normalfont \ttfamily sidmCoreNFW} + mass distribution. + !!} + implicit none + class (massDistributionSphericalSIDMCoreNFW), intent(inout), target :: self + double precision , intent(in ) :: radius + + mass =+self%massDistribution_%massEnclosedBySphere(radius) & + & *tanh( & + & + radius & + & /self%radiusCore() & + & ) + return + end function sphericalSIDMCoreNFWMassEnclosedBySphere diff --git a/source/mass_distributions.spherical.SIDM.isothermal.F90 b/source/mass_distributions.spherical.SIDM.isothermal.F90 new file mode 100644 index 0000000000..12a19e6860 --- /dev/null +++ b/source/mass_distributions.spherical.SIDM.isothermal.F90 @@ -0,0 +1,648 @@ +!! Copyright 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, +!! 2019, 2020, 2021, 2022, 2023 +!! Andrew Benson +!! +!! This file is part of Galacticus. +!! +!! Galacticus is free software: you can redistribute it and/or modify +!! it under the terms of the GNU General Public License as published by +!! the Free Software Foundation, either version 3 of the License, or +!! (at your option) any later version. +!! +!! Galacticus is distributed in the hope that it will be useful, +!! but WITHOUT ANY WARRANTY; without even the implied warranty of +!! MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +!! GNU General Public License for more details. +!! +!! You should have received a copy of the GNU General Public License +!! along with Galacticus. If not, see . + + !!{ + Provides a mass distribution implementing the ``isothermal'' approximation to the effects of SIDM based on the model of \cite{jiang_semi-analytic_2023}. + !!} + + use, intrinsic :: ISO_C_Binding , only : c_size_t + use :: Numerical_Interpolation, only : interpolator + use :: Numerical_ODE_Solvers , only : odeSolver + + !![ + + + A mass distribution class for self-interacting dark matter following the ``isothermal'' model of \cite{jiang_semi-analytic_2023}. This + model assumes that the dark matter within the interaction radius, $r_1$, has thermalized and can therefore be described by a + constant velocity dispersion, $\sigma_0$. Under this assumption the spherical Jeans equation has a solution of the form: + \begin{equation} + \rho(r) = \rho_0 \exp\left[-\frac{\phi(r)}{\sigma_0^2}\right], + \end{equation} + where $\rho(r)$ is the density $\rho_0$ is the density at $r=0$, and the gravitational potential satisfies \citep{jiang_semi-analytic_2023}: + \begin{equation} + \nabla^2 \phi(r) = 4 \pi \mathrm{G} \rho_0 \exp \left( - \frac{\phi(r)}{\sigma_0^2} \right). + \end{equation} + This second-order differential equation is solved using the boundary conditions $\phi(r=0)=0$ and + $\mathrm{d}\phi/\mathrm{d}r(r=0)=0$. The values of $\rho_0$ and $\sigma_0$ are then found by minimizing a function + \begin{equation} + \delta^2(\rho_0,\sigma_0) = \left[ \frac{\rho(r_1)}{\rho^\prime(r_1)} - 1 \right]^2 + \left[ \frac{M(r_1)}{M^\prime(r_1)} - 1 \right]^2, + \end{equation} + where $M(r)$ is the mass contained within radius $r$, and primes indicate the profile prior to SIDM thermalization. + + This can be expressed in a convenient dimensionless form. We define $x=r/r_1$, $y=\rho/\rho_1$, $z=\sigma/\sigma_1$, where + \begin{equation} + \sigma_1^2 = \frac{4 \pi}{3} \mathrm{G} \rho_1 r_1^2 \xi, + \end{equation} + and we define $\xi$ through the relation: + \begin{equation} + M_1 = \xi \frac{4 \pi}{3} \rho_1 r_1^3. + \end{equation} + Using these definitions we can define a dimensionless potential, $\Phi(r) = \phi(r) / \sigma_1^2$. The above differential + equation can then be written as + \begin{equation} + \nabla^{\prime 2} \Phi = \frac{3}{\xi} y_0 \exp\left[ - \frac{\Phi}{z_0^2} \right] , + \end{equation} + where $\nabla^{\prime 2}$ indicates the Laplacian with respect to coordinate $x$. Written in this form it is straightforward + to see that this equation has three parameters, $\xi$, $y_0$, and $z_0$. The value of $\xi$ is determined from the initial + (pre-thermalization) density profile. We then have two constraints at $x=1$, namely $y=1$ and $m=M/M_1=1$. We can solve for + the values of $y_0$ and $z_0$ which satisfy these constraints for a given $\xi$. As a result, we can tabulate solutions + $y_0(\xi)$ and $z_0(\xi)$ which are applicable to any initial density profile and depend only on the effective slope of the + density profile inside $r_1$, since if $\rho \propto r^\alpha$ then $\xi = 1/(1+\alpha/3)$, such that $\alpha=0$ (the + largest physically-allowed value of $\alpha$) implies $\xi=1$. + + + !!] + type, extends(massDistributionSphericalSIDM) :: massDistributionSphericalSIDMIsothermal + !!{ + A mass distribution implementing the ``isothermal'' approximation to the effects of SIDM based on the model of \cite{jiang_semi-analytic_2023}. + !!} + private + double precision :: velocityDispersionCentral , radiusInteraction_ , & + & densityInteraction , massInteraction , & + & velocityDispersionInteraction + type (interpolator ), allocatable :: densityCentralDimensionless , velocityDispersionCentralDimensionless, & + & interpolatorRadiiDimensionless, interpolatorXi + double precision :: xiTabulatedMinimum , xiTabulatedMaximum + double precision , allocatable, dimension( : ,:) :: densityProfileDimensionless , massProfileDimensionless + double precision , allocatable, dimension( : ) :: radiiDimensionless + integer (c_size_t ) :: indexXi + double precision , dimension(0:1 ) :: factorsXi + contains + !![ + + + + + !!] + final :: sphericalSIDMIsothermalDestructor + procedure :: computeSolution => sphericalSIDMIsothermalComputeSolution + procedure :: tabulateSolutions => sphericalSIDMIsothermalTabulateSolutions + procedure :: density => sphericalSIDMIsothermalDensity + procedure :: densityGradientRadial => sphericalSIDMIsothermalDensityGradientRadial + procedure :: massEnclosedBySphere => sphericalSIDMIsothermalMassEnclosedBySphere + procedure :: potential => sphericalSIDMIsothermalPotential + procedure :: potentialIsAnalytic => sphericalSIDMIsothermalPotentialIsAnalytic + end type massDistributionSphericalSIDMIsothermal + + interface massDistributionSphericalSIDMIsothermal + !!{ + Constructors for the {\normalfont \ttfamily sphericalSIDMIsothermal} mass distribution class. + !!} + module procedure sphericalSIDMIsothermalConstructorParameters + module procedure sphericalSIDMIsothermalConstructorInternal + end interface massDistributionSphericalSIDMIsothermal + + ! Number of properties in ODE. + integer (c_size_t ), parameter :: propertyCount=2 + + ! Submodule-scope variables. + double precision :: xi_ , y0_, & + & z0_ + type (odeSolver), allocatable :: odeSolver_ + !$omp threadprivate(xi_,y0_,z0_,odeSolver_) + +contains + + function sphericalSIDMIsothermalConstructorParameters(parameters) result(self) + !!{ + Constructor for the {\normalfont \ttfamily sidmIsothermal} mass distribution class which takes a parameter set as input. + !!} + use :: Input_Parameters , only : inputParameter , inputParameters + use :: Galactic_Structure_Options, only : enumerationComponentTypeEncode, enumerationMassTypeEncode + implicit none + type (massDistributionSphericalSIDMIsothermal) :: self + type (inputParameters ), intent(inout) :: parameters + class (massDistributionClass ), pointer :: massDistribution_ + class (darkMatterParticleClass ), pointer :: darkMatterParticle_ + type (varying_string ) :: componentType , massType, & + & nonAnalyticSolver + double precision :: timeAge + + !![ + + timeAge + parameters + The age of the halo (in Gyr). + + + nonAnalyticSolver + var_str('fallThrough') + parameters + Selects how solutions are computed when no analytic solution is available. If set to ``{\normalfont \ttfamily fallThrough}'' then the solution ignoring heating is used, while if set to ``{\normalfont \ttfamily numerical}'' then numerical solvers are used to find solutions. + + + componentType + var_str('unknown') + The component type that this mass distribution represents. + parameters + + + massType + var_str('unknown') + The mass type that this mass distribution represents. + parameters + + + + !!] + select type (massDistribution_) + class is (massDistributionSpherical) + self=massDistributionSphericalSIDMIsothermal(timeAge,enumerationNonAnalyticSolversEncode(char(nonAnalyticSolver),includesPrefix=.false.),massDistribution_,darkMatterParticle_,enumerationComponentTypeEncode(componentType,includesPrefix=.false.),enumerationMassTypeEncode(massType,includesPrefix=.false.)) + class default + call Error_Report('a spherically-symmetric mass distribution is required'//{introspection:location}) + end select + !![ + + + + !!] + return + end function sphericalSIDMIsothermalConstructorParameters + + function sphericalSIDMIsothermalConstructorInternal(timeAge,nonAnalyticSolver,massDistribution_,darkMatterParticle_,componentType,massType) result(self) + !!{ + Internal constructor for the {\normalfont \ttfamily sidmIsothermal} mass distribution class. + !!} + use :: Dark_Matter_Particles, only : darkMatterParticleSelfInteractingDarkMatter + implicit none + type (massDistributionSphericalSIDMIsothermal) :: self + double precision , intent(in ) :: timeAge + class (massDistributionSpherical ), intent(in ), target :: massDistribution_ + class (darkMatterParticleClass ), intent(in ), target :: darkMatterParticle_ + type (enumerationNonAnalyticSolversType ), intent(in ) :: nonAnalyticSolver + type (enumerationComponentTypeType ), intent(in ), optional :: componentType + type (enumerationMassTypeType ), intent(in ), optional :: massType + !![ + + !!] + + ! Validate the dark matter particle type. + select type (darkMatterParticle__ => self%darkMatterParticle_) + class is (darkMatterParticleSelfInteractingDarkMatter) + ! This is as expected. + class default + call Error_Report('this class expects a self-interacting dark matter particle'//{introspection:location}) + end select + self%dimensionless =.false. + self%xiTabulatedMinimum=+huge(0.0d0) + self%xiTabulatedMaximum=-huge(0.0d0) + call self%computeSolution() + return + end function sphericalSIDMIsothermalConstructorInternal + + subroutine sphericalSIDMIsothermalDestructor(self) + !!{ + Destructor for the {\normalfont \ttfamily massDistributionSphericalSIDMIsothermal} class. + !!} + implicit none + type(massDistributionSphericalSIDMIsothermal), intent(inout) :: self + + !![ + + + !!] + return + end subroutine sphericalSIDMIsothermalDestructor + + subroutine sphericalSIDMIsothermalTabulateSolutions(self,xiRequired) + !!{ + Tabulate solutions for $y_0(\xi)$, $z_0(\xi)$. + !!} + use :: Display , only : displayIndent , displayUnindent , displayMessage, verbosityLevelWorking, & + & displayCounter , displayCounterClear + use :: File_Utilities , only : Directory_Make , File_Exists , File_Lock , File_Path , & + & File_Unlock , lockDescriptor + use :: Input_Paths , only : inputPath , pathTypeDataDynamic + use :: HDF5_Access , only : hdf5Access + use :: IO_HDF5 , only : hdf5Object + use :: Numerical_Ranges , only : Make_Range , rangeTypeLinear + use :: Multidimensional_Minimizer, only : multiDMinimizer + implicit none + class (massDistributionSphericalSIDMIsothermal), intent(inout) :: self + double precision , intent(in ) :: xiRequired + integer , parameter :: countXiPerUnit = 100 + integer , parameter :: countRadii =1000 + double precision , parameter :: Y0Minimum =1.0d+0, Y0Maximum =1.0d+6 + double precision , parameter :: Z0Minimum =0.1d+0, Z0Maximum =3.0d+0 + double precision , parameter :: xiMinimum =1.1d+0, xiMaximum =1.0d+1 + double precision , parameter :: x1 =1.0d+0 + double precision , parameter :: odeToleranceAbsolute=1.0d-9, odeToleranceRelative=1.0d-9 + double precision , dimension(propertyCount+1 ) :: properties , propertyScales + double precision , dimension(propertyCount ) :: locationMinimum + double precision , dimension( : ), allocatable :: xi , y0 , & + & z0 + double precision :: x + type (multiDMinimizer ) , allocatable :: minimizer_ + integer :: countXi , count + integer (c_size_t ) :: i , j , & + & iteration + logical :: converged , retabulate + type (varying_string ) :: fileName + type (hdf5Object ) :: file + type (lockDescriptor ) :: fileLock + character (len=16 ) :: labelXiMinimum , labelXiMaximum + + ! Return immediately if solutions have been tabulated with sufficient extent already. + if ( & + & xiRequired >= self%xiTabulatedMinimum & + & .and. & + & xiRequired <= self%xiTabulatedMaximum & + & ) return + ! Deallocate existing table if necessary. + if (allocated(self%radiiDimensionless )) deallocate(self%radiiDimensionless ) + if (allocated(self%densityProfileDimensionless )) deallocate(self%densityProfileDimensionless ) + if (allocated(self%massProfileDimensionless )) deallocate(self%massProfileDimensionless ) + if (allocated(self%interpolatorXi )) deallocate(self%interpolatorXi ) + if (allocated(self%interpolatorRadiiDimensionless )) deallocate(self%interpolatorRadiiDimensionless ) + if (allocated(self%densityCentralDimensionless )) deallocate(self%densityCentralDimensionless ) + if (allocated(self%velocityDispersionCentralDimensionless)) deallocate(self%velocityDispersionCentralDimensionless) + ! By default assume that we do need to retabulate. + retabulate=.true. + ! Construct a file name for the table. + fileName=inputPath(pathTypeDataDynamic)// & + & 'darkMatter/' // & + & self%objectType() // & + & '.hdf5' + call Directory_Make(char(File_Path(char(fileName)))) + if (File_Exists(fileName)) then + ! Always obtain the file lock before the hdf5Access lock to avoid deadlocks between OpenMP threads. + call File_Lock(char(fileName),fileLock,lockIsShared=.true.) + ! Restore tables from file. + !$ call hdf5Access%set() + call file%openFile (char(fileName) ) + call file%readDataset('xi' , xi ) + call file%readDataset('radii' ,self%radiiDimensionless ) + call file%readDataset('y0' , y0 ) + call file%readDataset('z0' , z0 ) + call file%readDataset('densityProfileDimensionless',self%densityProfileDimensionless) + call file%readDataset('massProfileDimensionless' ,self%massProfileDimensionless ) + call file%close ( ) + !$ call hdf5Access%unset() + self%xiTabulatedMinimum=xi( 1 ) + self%xiTabulatedMaximum=xi(size(xi)) + ! Check if the table is sufficient. + retabulate= xiRequired < self%xiTabulatedMinimum & + & .or. & + & xiRequired > self%xiTabulatedMaximum + call File_Unlock(fileLock) + end if + ! Retabulate now if necessary. + if (retabulate) then + if (allocated( xi )) deallocate( xi ) + if (allocated( y0 )) deallocate( y0 ) + if (allocated( z0 )) deallocate( z0 ) + if (allocated(self%radiiDimensionless )) deallocate(self%radiiDimensionless ) + if (allocated(self%densityProfileDimensionless)) deallocate(self%densityProfileDimensionless) + if (allocated(self%massProfileDimensionless )) deallocate(self%massProfileDimensionless ) + ! Set extent for tabulation. + self%xiTabulatedMinimum=min(1.0d0*xiRequired,xiMinimum) + self%xiTabulatedMaximum=max(1.1d0*xiRequired,xiMaximum) + write (labelXiMinimum,'(f5.2)') self%xiTabulatedMinimum + write (labelXiMaximum,'(f5.2)') self%xiTabulatedMaximum + call displayIndent ('tabulating isothermal SIDM density profile solutions' ,verbosityLevelWorking) + call displayMessage('range: '//trim(adjustl(labelXiMinimum))//' < ξ < '//trim(adjustl(labelXiMaximum))//'',verbosityLevelWorking) + ! Construct ranges of the parameter ξ to span. + countXi=int((self%xiTabulatedMaximum-self%xiTabulatedMinimum)*dble(countXiPerUnit))+1 + allocate( xi ( countXi)) + allocate( y0 ( countXi)) + allocate( z0 ( countXi)) + allocate(self%radiiDimensionless (countRadii )) + allocate(self%densityProfileDimensionless(countRadii,countXi)) + allocate(self%massProfileDimensionless (countRadii,countXi)) + xi =Make_Range(self%xiTabulatedMinimum,self%xiTabulatedMaximum,countXi ,rangeTypeLinear) + self%radiiDimensionless=Make_Range( 0.0d0 , 1.0d0 ,countRadii,rangeTypeLinear) + ! Set absolute property scales for ODE solving. + propertyScales=1.0d0 + ! Start parallel region to solve for halo structure at each value of ξ. + count=0 + call displayCounter(count,isNew=.true.,verbosity=verbosityLevelWorking) + !$omp parallel private(i,j,x,properties,locationMinimum,iteration,converged,minimizer_) + !! Allocate and construct objects needed by each thread. + allocate(odeSolver_) + allocate(minimizer_) + odeSolver_=odeSolver (propertyCount+1,sphericalSIDMIsothermalDimensionlessODEs ,toleranceAbsolute=odeToleranceAbsolute,toleranceRelative=odeToleranceRelative,scale=propertyScales) + minimizer_=multiDMinimizer(propertyCount ,sphericalSIDMIsothermalDimensionlessFitMetric ) + !$omp do schedule(dynamic) + do i=1,countXi + xi_=xi(i) + ! Seek the low-density solution. + call minimizer_%set(x=[0.0d0,1.0d0],stepSize=[0.01d0,0.01d0]) + iteration=0 + converged=.false. + do while (.not.converged .and. iteration < 100) + call minimizer_%iterate() + iteration=iteration+1 + converged=minimizer_%testSize(toleranceAbsolute=1.0d-12) + end do + locationMinimum=minimizer_%x() + y0(i)=exp(locationMinimum(1)) + z0(i)= locationMinimum(2) + ! Tabulate solutions for density and mass. + do j=1,countRadii + x =0.0d0 + properties=0.0d0 + call odeSolver_%solve(x,self%radiiDimensionless(j),properties) + self%densityProfileDimensionless(j,i)=+y0(i) & + & *exp( & + & -properties(1) & + & /z0(i) **2 & + & ) + self%massProfileDimensionless (j,i)=+ properties(3) + end do + !$omp atomic + count=count+1 + call displayCounter(int(100.0d0*dble(count)/dble(countXi)),isNew=.false.,verbosity=verbosityLevelWorking) + end do + !$omp end do + call displayCounterClear(verbosityLevelWorking) + deallocate(odeSolver_) + deallocate(minimizer_) + !$omp end parallel + ! Write the data to file. + call File_Lock(char(fileName),fileLock,lockIsShared=.false.) + !$ call hdf5Access%set() + call file%openFile (char( fileName ) ,overWrite=.true.,readOnly=.false.) + call file%writeDataset( xi ,'xi' ) + call file%writeDataset( self%radiiDimensionless ,'radii' ) + call file%writeDataset( y0 ,'y0' ) + call file%writeDataset( z0 ,'z0' ) + call file%writeDataset( self%densityProfileDimensionless ,'densityProfileDimensionless' ) + call file%writeDataset( self%massProfileDimensionless ,'massProfileDimensionless' ) + call file%close ( ) + !$ call hdf5Access%unset() + call File_Unlock(fileLock) + call displayUnindent('done',verbosityLevelWorking) + end if + ! Build the interpolators. + allocate(self%interpolatorXi ) + allocate(self%interpolatorRadiiDimensionless ) + allocate(self%densityCentralDimensionless ) + allocate(self%velocityDispersionCentralDimensionless) + self%densityCentralDimensionless =interpolator( xi ,y0) + self%velocityDispersionCentralDimensionless=interpolator( xi ,z0) + self%interpolatorXi =interpolator( xi ) + self%interpolatorRadiiDimensionless =interpolator(self%radiiDimensionless ) + return + end subroutine sphericalSIDMIsothermalTabulateSolutions + + double precision function sphericalSIDMIsothermalDimensionlessFitMetric(propertiesCentral) + !!{ + Evaluate the fit metric. + !!} + implicit none + double precision, intent(in ), dimension(:) :: propertiesCentral + double precision, parameter :: x1 =1.0d0 + double precision , dimension(propertyCount+1) :: properties + double precision :: x , y1, & + & m1 + + ! Extract current parameters to submodule-scope. + y0_=exp(propertiesCentral(1)) + z0_= propertiesCentral(2) + ! Solve the ODE to x₁. + x =0.0d0 + properties=0.0d0 + call odeSolver_%solve(x,x1,properties) + ! Extract density and mass at x₁. + y1 =+y0_ & + & *exp( & + & -properties(1) & + & /z0_ **2 & + & ) + m1 =+ properties(3) + ! Evaluate the fit metric. + sphericalSIDMIsothermalDimensionlessFitMetric=+(y1-1.0d0)**2 & + & +(m1-1.0d0)**2 + return + end function sphericalSIDMIsothermalDimensionlessFitMetric + + integer function sphericalSIDMIsothermalDimensionlessODEs(x,properties,propertiesRateOfChange) + !!{ + Define the dimensionless ODE system to solve for isothermal self-interacting dark matter cores. + !!} + use :: Interface_GSL, only : GSL_Success + implicit none + double precision, intent(in ) :: x + double precision, intent(in ), dimension(:) :: properties + double precision, intent( out), dimension(:) :: propertiesRateOfChange + double precision :: y + + ! Compute the dimensionless density. + y =+y0_ & + & *exp( & + & -max(properties(1),0.0d0) & + & /z0_**2 & + & ) + ! Evaluate the ODE. + propertiesRateOfChange (1)=+properties(2) + propertiesRateOfChange (2)=+3.0d0 & + & /xi_ & + & *y + if (x > 0.0d0) & + & propertiesRateOfChange(2)=+propertiesRateOfChange(2) & + & -2.0d0 & + & *properties (2) & + & /x + propertiesRateOfChange (3)=+3.0d0 & + & /xi_ & + & *x**2 & + & *y + sphericalSIDMIsothermalDimensionlessODEs=GSL_Success + return + end function sphericalSIDMIsothermalDimensionlessODEs + + subroutine sphericalSIDMIsothermalComputeSolution(self) + !!{ + Compute a solution for the isothermal core of an SIDM halo. + !!} + use :: Coordinates , only : coordinateSpherical , assignment(=) + use :: Numerical_Constants_Math , only : Pi + use :: Numerical_Constants_Astronomical, only : gravitationalConstantGalacticus + implicit none + class (massDistributionSphericalSIDMIsothermal), intent(inout) :: self + integer , parameter :: countTable =1000 + double precision , parameter :: odeToleranceAbsolute =1.0d-3, odeToleranceRelative =1.0d-3 + double precision :: densityCentral , velocityDispersionCentral , & + & densityInteraction , massInteraction , & + & radiusInteraction , xi , & + & velocityDispersionInteraction + type (coordinateSpherical ) :: coordinatesInteraction + + ! Find the interaction radius. + radiusInteraction =self%radiusInteraction ( ) + coordinatesInteraction=[radiusInteraction,0.0d0,0.0d0] + ! Properties of the original density profile at the interaction radius. + densityInteraction =self%massDistribution_%density (coordinatesInteraction) + massInteraction =self%massDistribution_%massEnclosedBySphere(radiusInteraction ) + ! Find the velocity dispersion scale to be applied to the dimensionless solutions. + velocityDispersionInteraction=sqrt(gravitationalConstantGalacticus*massInteraction/radiusInteraction) + ! Compute the ξ parameter. + xi =+massInteraction & + & *3.0d0 & + & /4.0d0 & + & /Pi & + & /densityInteraction & + & /radiusInteraction **3 + ! Ensure dimensionless solutions have been tabulated. + call self%tabulateSolutions(xi) + ! Find the properties at the halo center. + densityCentral =self%densityCentralDimensionless %interpolate(xi)*densityInteraction + velocityDispersionCentral =self%velocityDispersionCentralDimensionless%interpolate(xi)*velocityDispersionInteraction + ! Store properties of current profile. + self%radiusInteraction_ =radiusInteraction + self%densityInteraction =densityInteraction + self%massInteraction =massInteraction + self%velocityDispersionInteraction=velocityDispersionInteraction + self%velocityDispersionCentral =velocityDispersionCentral + ! Compute interpolating factors in ξ. + call self%interpolatorXi%linearFactors(xi,self%indexXi,self%factorsXi) + return + end subroutine sphericalSIDMIsothermalComputeSolution + + double precision function sphericalSIDMIsothermalDensity(self,coordinates) result(density) + !!{ + Compute the density at the specified {\normalfont \ttfamily coordinates} for the {\normalfont \ttfamily sphericalSIDMIsothermal} + mass distribution. + !!} + implicit none + class (massDistributionSphericalSIDMIsothermal), intent(inout) :: self + class (coordinate ), intent(in ) :: coordinates + integer (c_size_t ) :: i , j, & + & indexRadius + double precision , dimension(0:1) :: factorsRadius + + if (coordinates%rSpherical() > self%radiusInteraction()) then + density=self%massDistribution_%density(coordinates) + else + call self%interpolatorRadiiDimensionless%linearFactors(coordinates%rSpherical()/self%radiusInteraction_,indexRadius,factorsRadius) + density=0.0d0 + do i=0,1 + do j=0,1 + density=+ density & + & +self%densityProfileDimensionless(indexRadius+i,self%indexXi+j) & + & * factorsRadius ( i ) & + & *self%factorsXi ( j) + end do + end do + density=+ density & + & *self%densityInteraction + end if + return + end function sphericalSIDMIsothermalDensity + + double precision function sphericalSIDMIsothermalDensityGradientRadial(self,coordinates,logarithmic) result(densityGradient) + !!{ + Return the density at the specified {\normalfont \ttfamily coordinates} in a truncated spherical mass distribution. + !!} + implicit none + class (massDistributionSphericalSIDMIsothermal), intent(inout) , target :: self + class (coordinate ), intent(in ) :: coordinates + logical , intent(in ) , optional :: logarithmic + integer (c_size_t ) :: indexRadius + double precision , dimension(0:1) :: factorsRadius + !![ + + !!] + + if (coordinates%rSpherical() > self%radiusInteraction()) then + densityGradient=self%massDistribution_%densityGradientRadial(coordinates,logarithmic=logarithmic) + else + call self%interpolatorRadiiDimensionless%linearFactors(coordinates%rSpherical()/self%radiusInteraction_,indexRadius,factorsRadius) + if (indexRadius > 1) then + densityGradient=+log(self%densityProfileDimensionless(indexRadius+1,self%indexXi+0)/self%densityProfileDimensionless(indexRadius+0,self%indexXi+0)) & + & /log(self%radiiDimensionless (indexRadius+1 )/self%radiiDimensionless (indexRadius+0 )) + if (.not.logarithmic_) & + densityGradient=+ densityGradient & + & *self %density (coordinates) & + & /coordinates%rSpherical ( ) + else + densityGradient=+0.0d0 + end if + end if + return + end function sphericalSIDMIsothermalDensityGradientRadial + + double precision function sphericalSIDMIsothermalMassEnclosedBySphere(self,radius) result(mass) + !!{ + Computes the mass enclosed within a sphere of given {\normalfont \ttfamily radius} for the {\normalfont \ttfamily sphericalSIDMIsothermal} + mass distribution. + !!} + implicit none + class (massDistributionSphericalSIDMIsothermal), intent(inout) , target :: self + double precision , intent(in ) :: radius + integer (c_size_t ) :: i , j, & + & indexRadius + double precision , dimension(0:1) :: factorsRadius + + if (radius > self%radiusInteraction()) then + mass=self%massDistribution_%massEnclosedBySphere(radius) + else + call self%interpolatorRadiiDimensionless%linearFactors(radius/self%radiusInteraction_,indexRadius,factorsRadius) + mass=0.0d0 + do i=0,1 + do j=0,1 + mass =+ mass & + & +self%massProfileDimensionless(indexRadius+i,self%indexXi+j) & + & * factorsRadius ( i ) & + & *self%factorsXi ( j) + end do + end do + mass =+ mass & + & *self%massInteraction + end if + return + end function sphericalSIDMIsothermalMassEnclosedBySphere + + logical function sphericalSIDMIsothermalPotentialIsAnalytic(self) result(isAnalytic) + !!{ + Return that the potential has an analytic form. + !!} + implicit none + class(massDistributionSphericalSIDMIsothermal), intent(inout) :: self + + isAnalytic=.true. + return + end function sphericalSIDMIsothermalPotentialIsAnalytic + + double precision function sphericalSIDMIsothermalPotential(self,coordinates,status) result(potential) + !!{ + Return the potential at the specified {\normalfont \ttfamily coordinates} in an burkert mass distribution. + !!} + use :: Coordinates , only : coordinateSpherical , assignment(=) + use :: Galactic_Structure_Options, only : structureErrorCodeSuccess + implicit none + class(massDistributionSphericalSIDMIsothermal), intent(inout), target :: self + class(coordinate ), intent(in ) :: coordinates + type (enumerationStructureErrorCodeType ), intent( out), optional :: status + type (coordinateSpherical ) :: coordinatesInteraction + + if (present(status)) status=structureErrorCodeSuccess + if (coordinates%rSpherical() > self%radiusInteraction()) then + potential =+ self%massDistribution_%potential (coordinates ) + else + coordinatesInteraction=[self%radiusInteraction_,0.0d0,0.0d0] + potential =+ self%massDistribution_%potential (coordinatesInteraction) & + & - self %velocityDispersionCentral **2 & + & *log( & + & +self %density (coordinates ) & + & /self %density (coordinatesInteraction) & + & ) + end if + return + end function sphericalSIDMIsothermalPotential diff --git a/source/mass_distributions.spherical.SIDM.isothermal.baryons.F90 b/source/mass_distributions.spherical.SIDM.isothermal.baryons.F90 new file mode 100644 index 0000000000..7f8b6af1e0 --- /dev/null +++ b/source/mass_distributions.spherical.SIDM.isothermal.baryons.F90 @@ -0,0 +1,484 @@ +!! Copyright 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, +!! 2019, 2020, 2021, 2022, 2023 +!! Andrew Benson +!! +!! This file is part of Galacticus. +!! +!! Galacticus is free software: you can redistribute it and/or modify +!! it under the terms of the GNU General Public License as published by +!! the Free Software Foundation, either version 3 of the License, or +!! (at your option) any later version. +!! +!! Galacticus is distributed in the hope that it will be useful, +!! but WITHOUT ANY WARRANTY; without even the implied warranty of +!! MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +!! GNU General Public License for more details. +!! +!! You should have received a copy of the GNU General Public License +!! along with Galacticus. If not, see . + + !!{ + Provides a mass distribution implementing the ``isothermal'' approximation to the effects of SIDM, including the baryonic + potential, based on the model of \cite{jiang_semi-analytic_2023}. + !!} + + use, intrinsic :: ISO_C_Binding , only : c_size_t + use :: Numerical_Interpolation, only : interpolator + use :: Numerical_ODE_Solvers , only : odeSolver + + public :: sphericalSIDMIsothermalBaryonsInitializor + + !![ + + + Mass distributions for self-interacting dark matter following the ``isothermal'' model of \cite{jiang_semi-analytic_2023}. This + model assumes that the dark matter within the interaction radius, $r_1$, has thermalized and can therefore be described by a + constant velocity dispersion, $\sigma_0$. Under this assumption the spherical Jeans equation has a solution of the form: + \begin{equation} + \rho(r) = \rho_0 \exp\left[-\frac{\phi(r)}{\sigma_0^2}\right], + \end{equation} + where $\rho(r)$ is the density $\rho_0$ is the density at $r=0$, and the gravitational potential satisfies \citep{jiang_semi-analytic_2023}: + \begin{equation} + \nabla^2 \phi(r) = 4 \pi \mathrm{G} \left[ \rho_0 \exp \left( - \frac{\phi(r)}{\sigma_0^2} \right) + \rho_\mathrm{b}(r) \right], + \end{equation} + where $\rho_\mathrm{b}(r)$ is the density of the baryonic component. This second-order differential equation is solved using the boundary conditions $\phi(r=0)=0$ and + $\mathrm{d}\phi/\mathrm{d}r(r=0)=0$. The values of $\rho_0$ and $\sigma_0$ are then found by minimizing a function + \begin{equation} + \delta^2(\rho_0,\sigma_0) = \left[ \frac{\rho(r_1)}{\rho^\prime(r_1)} - 1 \right]^2 + \left[ \frac{M(r_1)}{M^\prime(r_1)} - 1 \right]^2, + \end{equation} + where $M(r)$ is the mass contained within radius $r$, and primes indicate the profile prior to SIDM thermalization. + + + !!] + type, extends(massDistributionSphericalSIDM) :: massDistributionSphericalSIDMIsothermalBaryons + !!{ + A mass distribution implementing the ``isothermal'' approximation to the effects of SIDM, including the baryonic potential, + based on the model of \cite{jiang_semi-analytic_2023}. + !!} + private + double precision :: velocityDispersionCentral + class (massDistributionClass ), pointer :: massDistributionBaryonic => null() + type (interpolator ), allocatable :: densityProfile , massProfile + ! Call-back function and arguments used for as-needed initialization of the baryonic component. + logical :: initialized + procedure (sphericalSIDMIsothermalBaryonsInitializor), pointer , nopass :: initializationFunction + class (* ), pointer :: initializationSelf => null(), initializationArgument => null() + contains + !![ + + + + + !!] + final :: sphericalSIDMIsothermalBaryonsDestructor + procedure :: setBaryonicComponent => sphericalSIDMIsothermalBaryonsSetBaryonicComponent + procedure :: computeSolution => sphericalSIDMIsothermalBaryonsComputeSolution + procedure :: density => sphericalSIDMIsothermalBaryonsDensity + procedure :: densityGradientRadial => sphericalSIDMIsothermalBaryonsDensityGradientRadial + procedure :: massEnclosedBySphere => sphericalSIDMIsothermalBaryonsMassEnclosedBySphere + procedure :: potentialIsAnalytic => sphericalSIDMIsothermalBaryonsPotentialIsAnalytic + procedure :: potential => sphericalSIDMIsothermalBaryonsPotential + end type massDistributionSphericalSIDMIsothermalBaryons + + interface massDistributionSphericalSIDMIsothermalBaryons + !!{ + Constructors for the {\normalfont \ttfamily sphericalSIDMIsothermalBaryons} mass distribution class. + !!} + module procedure sphericalSIDMIsothermalBaryonsConstructorParameters + module procedure sphericalSIDMIsothermalBaryonsConstructorInternal + end interface massDistributionSphericalSIDMIsothermalBaryons + + abstract interface + subroutine sphericalSIDMIsothermalBaryonsInitializor(initializationSelf,initializationArgument,massDistributionBaryonic) + !!{ + Interface for call-back functions for as-needed initialization of the baryonic component. + !!} + import massDistributionClass + class(* ), intent(inout), target :: initializationSelf , initializationArgument + class(massDistributionClass), intent( out), pointer :: massDistributionBaryonic + end subroutine sphericalSIDMIsothermalBaryonsInitializor + end interface + +contains + + function sphericalSIDMIsothermalBaryonsConstructorParameters(parameters) result(self) + !!{ + Constructor for the {\normalfont \ttfamily sidmIsothermal} mass distribution class which takes a parameter set as input. + !!} + use :: Input_Parameters , only : inputParameter , inputParameters + use :: Galactic_Structure_Options, only : enumerationComponentTypeEncode, enumerationMassTypeEncode + implicit none + type (massDistributionSphericalSIDMIsothermalBaryons) :: self + type (inputParameters ), intent(inout) :: parameters + class (massDistributionClass ), pointer :: massDistribution_ , massDistributionBaryonic + class (darkMatterParticleClass ), pointer :: darkMatterParticle_ + procedure (sphericalSIDMIsothermalBaryonsInitializor ), pointer :: initializationFunction + class (* ), pointer :: initializationSelf , initializationArgument + type (varying_string ) :: componentType , massType , & + & nonAnalyticSolver + double precision :: timeAge + + !![ + + timeAge + parameters + The age of the halo (in Gyr). + + + nonAnalyticSolver + var_str('fallThrough') + parameters + Selects how solutions are computed when no analytic solution is available. If set to ``{\normalfont \ttfamily fallThrough}'' then the solution ignoring heating is used, while if set to ``{\normalfont \ttfamily numerical}'' then numerical solvers are used to find solutions. + + + componentType + var_str('unknown') + The component type that this mass distribution represents. + parameters + + + massType + var_str('unknown') + The mass type that this mass distribution represents. + parameters + + + + + !!] + select type (massDistribution_) + class is (massDistributionSpherical) + initializationFunction => null() + initializationSelf => null() + initializationArgument => null() + self=massDistributionSphericalSIDMIsothermalBaryons(timeAge,enumerationNonAnalyticSolversEncode(char(nonAnalyticSolver),includesPrefix=.false.),massDistribution_,massDistributionBaryonic,darkMatterParticle_,initializationFunction,initializationSelf,initializationArgument,enumerationComponentTypeEncode(componentType,includesPrefix=.false.),enumerationMassTypeEncode(massType,includesPrefix=.false.)) + class default + call Error_Report('a spherically-symmetric mass distribution is required'//{introspection:location}) + end select + !![ + + + + !!] + return + end function sphericalSIDMIsothermalBaryonsConstructorParameters + + function sphericalSIDMIsothermalBaryonsConstructorInternal(timeAge,nonAnalyticSolver,massDistribution_,massDistributionBaryonic,darkMatterParticle_,initializationFunction,initializationSelf,initializationArgument,componentType,massType) result(self) + !!{ + Internal constructor for the {\normalfont \ttfamily sidmIsothermal} mass distribution class. + !!} + use :: Dark_Matter_Particles, only : darkMatterParticleSelfInteractingDarkMatter + implicit none + type (massDistributionSphericalSIDMIsothermalBaryons) :: self + double precision , intent(in ) :: timeAge + class (massDistributionSpherical ), intent(in ), target :: massDistribution_ + class (massDistributionClass ), intent(in ), target :: massDistributionBaryonic + class (darkMatterParticleClass ), intent(in ), target :: darkMatterParticle_ + type (enumerationNonAnalyticSolversType ), intent(in ) :: nonAnalyticSolver + procedure (sphericalSIDMIsothermalBaryonsInitializor ), intent(in ), pointer :: initializationFunction + class (* ), intent(in ), pointer :: initializationSelf , initializationArgument + type (enumerationComponentTypeType ), intent(in ), optional :: componentType + type (enumerationMassTypeType ), intent(in ), optional :: massType + !![ + + !!] + + ! Validate the dark matter particle type. + select type (darkMatterParticle__ => self%darkMatterParticle_) + class is (darkMatterParticleSelfInteractingDarkMatter) + ! This is as expected. + class default + call Error_Report('this class expects a self-interacting dark matter particle'//{introspection:location}) + end select + ! Initialize state. + self%dimensionless=.false. + self%initialized =.not.associated(initializationFunction) + return + end function sphericalSIDMIsothermalBaryonsConstructorInternal + + subroutine sphericalSIDMIsothermalBaryonsDestructor(self) + !!{ + Destructor for the abstract {\normalfont \ttfamily massDistributionSphericalSIDMIsothermalBaryons} class. + !!} + implicit none + type(massDistributionSphericalSIDMIsothermalBaryons), intent(inout) :: self + + !![ + + + + !!] + return + end subroutine sphericalSIDMIsothermalBaryonsDestructor + + subroutine sphericalSIDMIsothermalBaryonsSetBaryonicComponent(self) + !!{ + Set the baryonic component properties in an adiabatically-contracted spherical mass distribution. + !!} + implicit none + class(massDistributionSphericalSIDMIsothermalBaryons), intent(inout) :: self + class(massDistributionClass ), pointer :: massDistributionBaryonic + + if (.not.self%initialized) then + call self%initializationFunction(self%initializationSelf,self%initializationArgument,massDistributionBaryonic) + self%massDistributionBaryonic => massDistributionBaryonic + self%initialized = .true. + call self%computeSolution() + end if + return + end subroutine sphericalSIDMIsothermalBaryonsSetBaryonicComponent + + subroutine sphericalSIDMIsothermalBaryonsComputeSolution(self) + !!{ + Compute a solution for the isothermal core of an SIDM halo. + !!} + use :: Coordinates , only : coordinateSpherical , assignment(=) + use :: Numerical_ODE_Solvers , only : odeSolver + use :: Numerical_Ranges , only : Make_Range , rangeTypeLinear + use :: Numerical_Constants_Math , only : Pi + use :: Numerical_Constants_Astronomical, only : gravitationalConstantGalacticus + use :: Multidimensional_Minimizer , only : multiDMinimizer + implicit none + class (massDistributionSphericalSIDMIsothermalBaryons), intent(inout) :: self + integer (c_size_t ), parameter :: propertyCount = 2 + integer , parameter :: countTable =1000 + double precision , parameter :: odeToleranceAbsolute =1.0d-3, odeToleranceRelative =1.0d-3 + double precision , parameter :: fractionRadiusInitial =1.0d-6 + double precision , dimension(propertyCount+1) :: properties , propertyScales + double precision , dimension(countTable ) :: radiusTable , densityTable , & + & massTable + double precision , dimension(propertyCount ) :: locationMinimum + type (odeSolver ) :: odeSolver_ + type (multiDMinimizer ) :: minimizer_ + integer :: i , iteration + logical :: converged + double precision :: densityCentral , velocityDispersionCentral , & + & densityInteraction , massInteraction , & + & radiusInteraction , radius , & + & velocityDispersionInteraction , mass , & + & density + type (coordinateSpherical ) :: coordinatesInteraction + + ! Find the interaction radius. + radiusInteraction =self%radiusInteraction ( ) + coordinatesInteraction =[radiusInteraction,0.0d0,0.0d0] + ! Properties of the original density profile at the interaction radius. + densityInteraction =self%massDistribution_%density (coordinatesInteraction) + massInteraction =self%massDistribution_%massEnclosedBySphere(radiusInteraction ) + ! Find the velocity dispersion scale. + velocityDispersionInteraction=sqrt(gravitationalConstantGalacticus*massInteraction/radiusInteraction) + ! Set ODE solver scales. + propertyScales =[velocityDispersionInteraction**2,velocityDispersionInteraction**2/radiusInteraction,massInteraction] + ! Construct an ODE solver. + odeSolver_ =odeSolver (propertyCount+1,sidmIsothermalODEs ,toleranceAbsolute=odeToleranceAbsolute,toleranceRelative=odeToleranceRelative,scale=propertyScales) + ! Construct a minimizer. + minimizer_ =multiDMinimizer(propertyCount ,sidmIsothermalFitMetric ) + ! Seek the solution. + call minimizer_%set(x=[0.0d0,1.0d0],stepSize=[1.0d0,1.0d0]) + iteration=0 + converged=.false. + do while (.not.converged .and. iteration < 100) + call minimizer_%iterate() + iteration=iteration+1 + converged=minimizer_%testSize(toleranceAbsolute=1.0d-3) + end do + locationMinimum =minimizer_%x() + densityCentral =exp(locationMinimum(1))*densityInteraction + velocityDispersionCentral= locationMinimum(2) *velocityDispersionInteraction + ! Tabulate solutions for density and mass. + radiusTable =Make_Range(rangeMinimum=0.0d0,rangeMaximum=radiusInteraction,rangeNumber=countTable,rangeType=rangeTypeLinear) + densityTable(1)=densityCentral + massTable (1)=0.0d0 + do i=2,countTable + radius =fractionRadiusInitial*radiusInteraction + properties=0.0d0 + call odeSolver_%solve(radius,radiusTable(i),properties) + densityTable(i)=+densityCentral & + & *exp( & + & -properties(1) & + & /velocityDispersionCentral**2 & + & ) + massTable (i)=+ properties(3) + end do + allocate(self%densityProfile) + allocate(self% massProfile) + self% densityProfile=interpolator(radiusTable, densityTable) + self% massProfile=interpolator(radiusTable, massTable) + self%velocityDispersionCentral= velocityDispersionCentral + return + + contains + + double precision function sidmIsothermalFitMetric(propertiesCentral) + !!{ + Evaluate the fit metric. + !!} + implicit none + double precision, intent(in ), dimension(:) :: propertiesCentral + double precision , dimension(propertyCount+1) :: properties + double precision :: radius + + ! Extract current parameters. + densityCentral =exp(propertiesCentral(1))*densityInteraction + velocityDispersionCentral= propertiesCentral(2) *velocityDispersionInteraction + ! Solve the ODE to r₁. + radius =fractionRadiusInitial*radiusInteraction + properties=0.0d0 + call odeSolver_%solve(radius,radiusInteraction,properties) + ! Extract density and mass at r₁. + density=+densityCentral & + & *exp( & + & -properties(1) & + & /velocityDispersionCentral**2 & + & ) + mass =+ properties(3) + ! Evaluate the fit metric. + sidmIsothermalFitMetric=+(density/densityInteraction-1.0d0)**2 & + & +( mass/ massInteraction-1.0d0)**2 + return + end function sidmIsothermalFitMetric + + integer function sidmIsothermalODEs(radius,properties,propertiesRateOfChange) + !!{ + Define the ODE system to solve for isothermal self-interacting dark matter cores. + !!} + use :: Interface_GSL , only : GSL_Success + use :: Numerical_Constants_Math , only : Pi + use :: Numerical_Constants_Astronomical, only : gravitationalConstantGalacticus + implicit none + double precision , intent(in ) :: radius + double precision , intent(in ), dimension(:) :: properties + double precision , intent( out), dimension(:) :: propertiesRateOfChange + double precision :: densityDarkMatter , densityBaryons + type (coordinateSpherical) :: coordinates + + coordinates =[radius,0.0d0,0.0d0] + densityDarkMatter =+densityCentral & + & *exp( & + & -max(properties(1),0.0d0) & + & /velocityDispersionCentral**2 & + & ) + densityBaryons =+self%massDistributionBaryonic%density(coordinates) + propertiesRateOfChange (1)=+properties(2) + propertiesRateOfChange (2)=+4.0d0 & + & *Pi & + & *gravitationalConstantGalacticus & + & *( & + & +densityDarkMatter & + & +densityBaryons & + & ) + if (radius > 0.0d0) & + & propertiesRateOfChange(2)=+propertiesRateOfChange(2) & + & -2.0d0 & + & *properties (2) & + & /radius + propertiesRateOfChange (3)=+4.0d0 & + & *Pi & + & *radius**2 & + & *densityDarkMatter + sidmIsothermalODEs = GSL_Success + return + end function sidmIsothermalODEs + + end subroutine sphericalSIDMIsothermalBaryonsComputeSolution + + double precision function sphericalSIDMIsothermalBaryonsDensity(self,coordinates) result(density) + !!{ + Compute the density at the specified {\normalfont \ttfamily coordinates} for the {\normalfont \ttfamily sphericalSIDMIsothermalBaryons} + mass distribution. + !!} + implicit none + class(massDistributionSphericalSIDMIsothermalBaryons), intent(inout) :: self + class(coordinate ), intent(in ) :: coordinates + + call self%setBaryonicComponent() + if (coordinates%rSpherical() > self%radiusInteraction()) then + density=self%massDistribution_%density (coordinates ) + else + density=self%densityProfile %interpolate(coordinates%rSpherical()) + end if + return + end function sphericalSIDMIsothermalBaryonsDensity + + double precision function sphericalSIDMIsothermalBaryonsDensityGradientRadial(self,coordinates,logarithmic) result(densityGradient) + !!{ + Return the density at the specified {\normalfont \ttfamily coordinates} in a truncated spherical mass distribution. + !!} + implicit none + class (massDistributionSphericalSIDMIsothermalBaryons), intent(inout) , target :: self + class (coordinate ), intent(in ) :: coordinates + logical , intent(in ) , optional :: logarithmic + !![ + + !!] + + if (coordinates%rSpherical() > self%radiusInteraction()) then + densityGradient=self%massDistribution_%densityGradientRadial(coordinates,logarithmic) + else + densityGradient=self%densityProfile%derivative(coordinates%rSpherical()) + if (logarithmic_) & + & densityGradient=+ densityGradient & + & *coordinates%rSpherical ( ) & + & /self %densityProfile%interpolate(coordinates%rSpherical()) + end if + return + end function sphericalSIDMIsothermalBaryonsDensityGradientRadial + + double precision function sphericalSIDMIsothermalBaryonsMassEnclosedBySphere(self,radius) result(mass) + !!{ + Computes the mass enclosed within a sphere of given {\normalfont \ttfamily radius} for the {\normalfont \ttfamily sphericalSIDMIsothermalBaryons} + mass distribution. + !!} + implicit none + class (massDistributionSphericalSIDMIsothermalBaryons), intent(inout) , target :: self + double precision , intent(in ) :: radius + + call self%setBaryonicComponent() + if (radius > self%radiusInteraction()) then + mass=self%massDistribution_%massEnclosedBySphere(radius) + else + mass=self%massProfile %interpolate (radius) + end if + return + end function sphericalSIDMIsothermalBaryonsMassEnclosedBySphere + + logical function sphericalSIDMIsothermalBaryonsPotentialIsAnalytic(self) result(isAnalytic) + !!{ + Return if the potential has an analytic form. + !!} + implicit none + class(massDistributionSphericalSIDMIsothermalBaryons), intent(inout) :: self + + isAnalytic=self%massDistribution_%potentialIsAnalytic() + return + end function sphericalSIDMIsothermalBaryonsPotentialIsAnalytic + + double precision function sphericalSIDMIsothermalBaryonsPotential(self,coordinates,status) result(potential) + !!{ + Return the potential at the specified {\normalfont \ttfamily coordinates} in an burkert mass distribution. + !!} + use :: Coordinates , only : coordinateSpherical , assignment(=) + use :: Galactic_Structure_Options, only : structureErrorCodeSuccess + implicit none + class (massDistributionSphericalSIDMIsothermalBaryons), intent(inout), target :: self + class (coordinate ), intent(in ) :: coordinates + type (enumerationStructureErrorCodeType ), intent( out), optional :: status + type (coordinateSpherical ) :: coordinatesInteraction + double precision :: radiusInteraction + + call self%setBaryonicComponent() + if (present(status)) status=structureErrorCodeSuccess + if (coordinates%rSpherical() > self%radiusInteraction()) then + potential =+self%massDistribution_%potential(coordinates,status=status) + else + radiusInteraction =+self%radiusInteraction() + coordinatesInteraction=[radiusInteraction,0.0d0,0.0d0] + potential =+self%massDistribution_%potential(coordinatesInteraction) & + & -self%velocityDispersionCentral**2 & + & *log( & + & +self%densityProfile%interpolate(coordinates%rSpherical ()) & + & /self%densityProfile%interpolate( radiusInteraction ) & + & ) + end if + return + end function sphericalSIDMIsothermalBaryonsPotential diff --git a/source/mass_distributions.spherical.Sersic.F90 b/source/mass_distributions.spherical.Sersic.F90 index 7489d468ab..3f2f1f32f5 100644 --- a/source/mass_distributions.spherical.Sersic.F90 +++ b/source/mass_distributions.spherical.Sersic.F90 @@ -33,17 +33,18 @@ !!{ The S\'ersic density profile. !!} - double precision :: densityNormalization , mass , & - & radiusHalfMass_ , index_ + double precision :: densityNormalization , mass , & + & radiusHalfMass_ , index_ ! Tabulation of the Sérsic profile. - double precision :: coefficient , radiusStart - logical :: tableInitialized =.false. + double precision :: coefficient , radiusStart + logical :: tableInitialized =.false. integer :: tableCount - double precision :: tableRadiusMaximum , tableRadiusMinimum + double precision :: tableRadiusMaximum , tableRadiusMinimum double precision :: table3dRadiusHalfMass double precision :: table2dRadiusHalfMass - double precision , allocatable, dimension(:) :: tableDensity , tableEnclosedMass , & - & tablePotential , tableRadius + double precision :: gradientLogarithmicMassCentral + double precision , allocatable, dimension(:) :: tableDensity , tableEnclosedMass , & + & tablePotential , tableRadius type (interpolator ) :: tableInterpolator !$ integer (omp_lock_kind ) :: tableLock contains @@ -58,6 +59,7 @@ procedure :: densityRadialMoment => sersicDensityRadialMoment procedure :: massEnclosedBySphere => sersicMassEnclosedBySphere procedure :: massTotal => sersicMassTotal + procedure :: potentialIsAnalytic => sersicPotentialIsAnalytic procedure :: potential => sersicPotential procedure :: radiusHalfMass => sersicRadiusHalfMass procedure :: radiusHalfMassProjected => sersicRadiusHalfMassProjected @@ -199,23 +201,17 @@ function sersicConstructorInternal(index,radiusHalfMass,mass,dimensionless,compo return end function sersicConstructorInternal - double precision function sersicDensity(self,coordinates,componentType,massType) + double precision function sersicDensity(self,coordinates) !!{ Return the density at the specified {\normalfont \ttfamily coordinates} in a S\'ersic mass distribution. !!} use :: Coordinates , only : assignment(=) , coordinateSpherical implicit none - class (massDistributionSersic ), intent(inout) :: self - class (coordinate ), intent(in ) :: coordinates - type (enumerationComponentTypeType), intent(in ), optional :: componentType - type (enumerationMassTypeType ), intent(in ), optional :: massType - type (coordinateSpherical ) :: position - double precision :: r + class (massDistributionSersic), intent(inout) :: self + class (coordinate ), intent(in ) :: coordinates + type (coordinateSpherical ) :: position + double precision :: r - if (.not.self%matches(componentType,massType)) then - sersicDensity=0.0d0 - return - end if ! Get position in spherical coordinate system. position= coordinates ! Compute the density at this position. @@ -230,26 +226,20 @@ double precision function sersicDensity(self,coordinates,componentType,massType) return end function sersicDensity - double precision function sersicDensityRadialMoment(self,moment,radiusMinimum,radiusMaximum,isInfinite,componentType,massType) + double precision function sersicDensityRadialMoment(self,moment,radiusMinimum,radiusMaximum,isInfinite) !!{ Returns a radial density moment for the S\'ersic mass distribution. !!} implicit none - class (massDistributionSersic ), intent(inout) :: self - double precision , intent(in ) :: moment - double precision , intent(in ), optional :: radiusMinimum , radiusMaximum - logical , intent( out), optional :: isInfinite - type (enumerationComponentTypeType), intent(in ), optional :: componentType - type (enumerationMassTypeType ), intent(in ), optional :: massType - integer :: iRadius - double precision :: deltaRadius , integrand , & - & previousIntegrand , fractionalRadiusMinimum, & - & fractionalRadiusMaximum - - if (.not.self%matches(componentType,massType)) then - sersicDensityRadialMoment=0.0d0 - return - end if + class (massDistributionSersic), intent(inout) :: self + double precision , intent(in ) :: moment + double precision , intent(in ), optional :: radiusMinimum , radiusMaximum + logical , intent( out), optional :: isInfinite + integer :: iRadius + double precision :: deltaRadius , integrand , & + & previousIntegrand , fractionalRadiusMinimum, & + & fractionalRadiusMaximum + isInfinite =.false. sersicDensityRadialMoment=0.0d0 !$ call OMP_Set_Lock(self%tableLock) @@ -291,38 +281,26 @@ double precision function sersicDensityRadialMoment(self,moment,radiusMinimum,ra return end function sersicDensityRadialMoment - double precision function sersicMassTotal(self,componentType,massType) + double precision function sersicMassTotal(self) !!{ Computes the total mass for S\'ersic mass distributions. !!} implicit none - class(massDistributionSersic ), intent(inout) :: self - type (enumerationComponentTypeType), intent(in ), optional :: componentType - type (enumerationMassTypeType ), intent(in ), optional :: massType + class(massDistributionSersic), intent(inout) :: self - if (.not.self%matches(componentType,massType)) then - sersicMassTotal=0.0d0 - return - end if sersicMassTotal=self%mass return end function sersicMassTotal - double precision function sersicMassEnclosedBySphere(self,radius,componentType,massType) + double precision function sersicMassEnclosedBySphere(self,radius) !!{ Computes the mass enclosed within a sphere of given {\normalfont \ttfamily radius} for S\'ersic mass distributions. !!} implicit none - class (massDistributionSersic ), intent(inout), target :: self - double precision , intent(in ) :: radius - type (enumerationComponentTypeType), intent(in ), optional :: componentType - type (enumerationMassTypeType ), intent(in ), optional :: massType - double precision :: fractionalRadius + class (massDistributionSersic), intent(inout), target :: self + double precision , intent(in ) :: radius + double precision :: fractionalRadius - if (.not.self%matches(componentType,massType)) then - sersicMassEnclosedBySphere=0.0d0 - return - end if if (radius <= 0.0d0) then sersicMassEnclosedBySphere=0.0d0 else @@ -341,37 +319,59 @@ double precision function sersicMassEnclosedBySphere(self,radius,componentType,m return end function sersicMassEnclosedBySphere - double precision function sersicPotential(self,coordinates,componentType,massType) + logical function sersicPotentialIsAnalytic(self) result(isAnalytic) + !!{ + Return that the potential has an analytic form. + !!} + implicit none + class(massDistributionSersic), intent(inout) :: self + + isAnalytic=.true. + return + end function sersicPotentialIsAnalytic + + double precision function sersicPotential(self,coordinates,status) !!{ Return the potential at the specified {\normalfont \ttfamily coordinates} in a S\'ersic mass distribution. !!} use :: Coordinates , only : assignment(=) , coordinateSpherical + use :: Error , only : Error_Report + use :: Galactic_Structure_Options , only : structureErrorCodeSuccess use :: Numerical_Constants_Astronomical, only : gravitationalConstantGalacticus implicit none - class (massDistributionSersic ), intent(inout) :: self - class (coordinate ), intent(in ) :: coordinates - type (enumerationComponentTypeType), intent(in ), optional :: componentType - type (enumerationMassTypeType ), intent(in ), optional :: massType - type (coordinateSpherical ) :: position - double precision :: r + class (massDistributionSersic ), intent(inout), target :: self + class (coordinate ), intent(in ) :: coordinates + type (enumerationStructureErrorCodeType), intent( out), optional :: status + type (coordinateSpherical ) :: position + double precision :: r - if (.not.self%matches(componentType,massType)) then - sersicPotential=0.0d0 - return - end if + if (present(status)) status=structureErrorCodeSuccess ! Get position in spherical coordinate system. position=coordinates - ! Compute the potential at this position. !$ call OMP_Set_Lock(self%tableLock) - r =+position%r () & - & /self %radiusHalfMass_ - call self%tabulate(r) - if (r < self%tableRadius(self%tableCount)) then - sersicPotential=+self%mass & - & /self%radiusHalfMass_ & - & *self%tableInterpolator%interpolate(r,self%tablePotential) + ! For small radii, use a simple power-law extrapolation. + if (position%r() < self%tableRadiusMinimum) then + if (position%r() <= 0.0d0 .and. self%gradientLogarithmicMassCentral <= 1.0d0) call Error_Report('potential is divergent at r=0'//{introspection:location}) + sersicPotential=+self%tablePotential (1) & + & -self%tableEnclosedMass(1) & + & /self%tableRadius (1) & + & *( & + & +1.0d0 & + & -(position%r()/self%tableRadius(1))**(self%gradientLogarithmicMassCentral-1.0d0) & + & ) & + & / (self%gradientLogarithmicMassCentral-1.0d0) else - sersicPotential=0.0d0 + ! Compute the potential at this position. + r =+position%r () & + & /self %radiusHalfMass_ + call self%tabulate(r) + if (r < self%tableRadius(self%tableCount)) then + sersicPotential=+self%mass & + & /self%radiusHalfMass_ & + & *self%tableInterpolator%interpolate(r,self%tablePotential) + else + sersicPotential=0.0d0 + end if end if !$ call OMP_Unset_Lock(self%tableLock) if (.not.self%isDimensionless()) sersicPotential=+gravitationalConstantGalacticus & @@ -379,38 +379,26 @@ double precision function sersicPotential(self,coordinates,componentType,massTyp return end function sersicPotential - double precision function sersicRadiusHalfMass(self,componentType,massType) + double precision function sersicRadiusHalfMass(self) !!{ Return the half-mass radius of a S\'ersic mass distribution. !!} implicit none - class(massDistributionSersic ), intent(inout) :: self - type (enumerationComponentTypeType), intent(in ), optional :: componentType - type (enumerationMassTypeType ), intent(in ), optional :: massType + class(massDistributionSersic), intent(inout) :: self - if (.not.self%matches(componentType,massType)) then - sersicRadiusHalfMass=0.0d0 - return - end if !$ call OMP_Set_Lock(self%tableLock) sersicRadiusHalfMass=+self%radiusHalfMass_ !$ call OMP_Unset_Lock(self%tableLock) return end function sersicRadiusHalfMass - double precision function sersicRadiusHalfMassProjected(self,componentType,massType) + double precision function sersicRadiusHalfMassProjected(self) !!{ Return the half-mass radius in projection of a S\'ersic mass distribution. !!} implicit none - class(massDistributionSersic ), intent(inout) :: self - type (enumerationComponentTypeType), intent(in ), optional :: componentType - type (enumerationMassTypeType ), intent(in ), optional :: massType + class(massDistributionSersic), intent(inout) :: self - if (.not.self%matches(componentType,massType)) then - sersicRadiusHalfMassProjected=0.0d0 - return - end if sersicRadiusHalfMassProjected=+self%radiusHalfMass_ & & *self%table2dRadiusHalfMass return @@ -418,7 +406,7 @@ end function sersicRadiusHalfMassProjected subroutine sersicTabulate(self,radius) !!{ - Tabulate the density and enclosed mass in a dimensionless S\'ersic profile. + Tabulate the density enclosed mass, and potential in a dimensionless S\'ersic profile. !!} use :: Numerical_Constants_Math, only : Pi use :: Numerical_Integration , only : integrator @@ -554,6 +542,9 @@ subroutine sersicTabulate(self,radius) & .and. & & (radiusActual <= self%tableRadiusMaximum) end do + ! Determine the central slope of the mass profile. + self%gradientLogarithmicMassCentral=+log(self%tableEnclosedMass(2)/self%tableEnclosedMass(1)) & + & /log(self%tableRadius (2)/self%tableRadius (1)) ! Build the interpolator. self%tableInterpolator=interpolator(self%tableRadius,extrapolationType=extrapolationTypeExtrapolate) ! Flag that the table is initialized. diff --git a/source/mass_distributions.spherical.Zhao1996.F90 b/source/mass_distributions.spherical.Zhao1996.F90 new file mode 100644 index 0000000000..7972b988c8 --- /dev/null +++ b/source/mass_distributions.spherical.Zhao1996.F90 @@ -0,0 +1,1558 @@ +!! Copyright 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, +!! 2019, 2020, 2021, 2022, 2023 +!! Andrew Benson +!! +!! This file is part of Galacticus. +!! +!! Galacticus is free software: you can redistribute it and/or modify +!! it under the terms of the GNU General Public License as published by +!! the Free Software Foundation, either version 3 of the License, or +!! (at your option) any later version. +!! +!! Galacticus is distributed in the hope that it will be useful, +!! but WITHOUT ANY WARRANTY; without even the implied warranty of +!! MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +!! GNU General Public License for more details. +!! +!! You should have received a copy of the GNU General Public License +!! along with Galacticus. If not, see . + + !!{ + Implementation of the \cite{zhao_analytical_1996} mass distribution class. + !!} + + use :: Numerical_Interpolation , only : interpolator + + !![ + + specialCase + Special cases for {\normalfont \ttfamily zhao1996} dark matter halo profile class. + + + + + + + !!] + + !![ + + + A mass distribution class which implements the \citep{zhao_analytical_1996} density profile: + \begin{equation} + \rho_\mathrm{dark matter}(r) = \rho_0 \left({r\over r_\mathrm{s}}\right)^{-\gamma} \left(1+[{r\over r_\mathrm{s}}]^\alpha\right)^{-(\beta-\gamma)/\alpha}. + \end{equation} + The mass enclosed within radius $r$ is given by + \begin{equation} + M(<r) = \frac{4 \pi}{3-\gamma} \rho_0 r_\mathrm{s}^{3-\gamma} {}_2F_1\left[\left(\frac{3-\gamma}{\alpha}\right),\left(\frac{-\beta+\gamma}{\alpha},1+\frac{3-\gamma}{\alpha}\right),-r^\alpha\right] + \end{equation} + where $R=r/r_\mathrm{s}$. The associated gravitational potential is + \begin{equation} + \Phi(r) = - \frac{4 \pi \mathrm{G}}{-3+\gamma} \rho_0 r^{2-\gamma} \frac{\Gamma[(3+\alpha-\gamma)/\alpha]}{\Gamma[(3-\gamma)/\alpha]} \left( \Gamma\left[\frac{2-\gamma}{\alpha}\right] {}_p\tilde{F}F_q\left[\left\{\frac{2-\gamma}{\alpha},\frac{\beta-\gamma}{\alpha}\right\},\left\{\frac{2+\alpha-\gamma}{\alpha}\right\},-r\alpha\right] - \Gamma\left[\frac{3-\gamma}{\alpha}\right] {}_p\tilde{F}F_q\left[\left\{\frac{3-\gamma}{\alpha},\frac{\beta-\gamma}{\alpha}\right\},\left\{\frac{3+\alpha-\gamma}{\alpha}\right\},-r\alpha\right] \right). + \end{equation} + + + !!] + type, public, extends(massDistributionSpherical) :: massDistributionZhao1996 + !!{ + The \citep{zhao_analytical_1996} mass distribution. + !!} + private + type (enumerationSpecialCaseType) :: specialCase + double precision :: densityNormalization , scaleLength , & + & alpha , beta , & + & gamma + double precision :: densityScaleFreeRadiusMinimum , densityScaleFreeRadiusMaximum + double precision :: densityScaleFreeMinimum , densityScaleFreeMaximum + type (interpolator ), allocatable :: densityScaleFree_ + double precision :: massScaleFreeRadiusMinimum , massScaleFreeRadiusMaximum + double precision :: massScaleFreeMinimum , massScaleFreeMaximum + type (interpolator ), allocatable :: massScaleFree_ + double precision :: angularMomentumSpecificScaleFreeRadiusMinimum, angularMomentumSpecificScaleFreeRadiusMaximum + double precision :: angularMomentumSpecificScaleFreeMinimum , angularMomentumSpecificScaleFreeMaximum + type (interpolator ), allocatable :: angularMomentumSpecificScaleFree_ + double precision :: timeFreefallScaleFreeRadiusMinimum , timeFreefallScaleFreeRadiusMaximum + double precision :: timeFreefallScaleFreeMinimum , timeFreefallScaleFreeMaximum + type (interpolator ), allocatable :: timeFreefallScaleFree_ + contains + !![ + + + + !!] + procedure :: massTotal => zhao1996MassTotal + procedure :: density => zhao1996Density + procedure :: densityGradientRadial => zhao1996DensityGradientRadial + procedure :: densityRadialMoment => zhao1996DensityRadialMoment + procedure :: massEnclosedBySphere => zhao1996MassEnclosedBySphere + procedure :: velocityRotationCurveMaximum => zhao1996VelocityRotationCurveMaximum + procedure :: radiusRotationCurveMaximum => zhao1996RadiusRotationCurveMaximum + procedure :: radiusEnclosingMass => zhao1996RadiusEnclosingMass + procedure :: radiusEnclosingDensity => zhao1996RadiusEnclosingDensity + procedure :: radiusFromSpecificAngularMomentum => zhao1996RadiusFromSpecificAngularMomentum + procedure :: radiusFreefall => zhao1996RadiusFreefall + procedure :: radiusFreefallIncreaseRate => zhao1996RadiusFreefallIncreaseRate + procedure :: timeFreefallTabulate => zhao1996TimeFreefallTabulate + procedure :: potentialIsAnalytic => zhao1996PotentialIsAnalytic + procedure :: potential => zhao1996Potential + procedure :: fourierTransform => zhao1996FourierTransform + procedure :: energyPotential => zhao1996EnergyPotential + procedure :: energyKinetic => zhao1996EnergyKinetic + procedure :: descriptor => zhao1996Descriptor + end type massDistributionZhao1996 + + interface massDistributionZhao1996 + !!{ + Constructors for the {\normalfont \ttfamily zhao1996} mass distribution class. + !!} + module procedure massDistributionZhao1996ConstructorParameters + module procedure massDistributionZhao1996ConstructorInternal + end interface massDistributionZhao1996 + + class(massDistributionZhao1996), pointer :: self_ + !$omp threadprivate(self_) + + ! The minimum (scale-free) freefall timescale in a cored NFW profile. + double precision , parameter :: timeFreefallScaleFreeMinimumCoredNFW=sqrt(3.0d0*Pi)/4.0d0 + +contains + + function massDistributionZhao1996ConstructorParameters(parameters) result(self) + !!{ + Constructor for the {\normalfont \ttfamily zhao1996} mass distribution class which builds the object from a parameter + set. + !!} + use :: Input_Parameters , only : inputParameter , inputParameters + use :: Galactic_Structure_Options, only : enumerationComponentTypeEncode, enumerationMassTypeEncode + use :: Numerical_Constants_Math , only : Pi + use :: Hypergeometric_Functions , only : Hypergeometric_2F1 + implicit none + type (massDistributionZhao1996) :: self + type (inputParameters ), intent(inout) :: parameters + double precision :: mass , scaleLength, & + & densityNormalization, radiusOuter, & + & alpha , beta , & + & gamma + logical :: dimensionless + type (varying_string ) :: componentType + type (varying_string ) :: massType + + !![ + + alpha + The parameter $\alpha$ of the Zhao1996 profile. + parameters + + + beta + The parameter $\beta$ of the Zhao1996 profile. + parameters + + + gamma + The parameter $\gamma$ of the Zhao1996 profile. + parameters + + + densityNormalization + (3.0d0-gamma)/4.0d0/Pi/Hypergeometric_2F1([(3.0d0-gamma)/alpha,(beta-gamma)/alpha],[1.0d0+(3.0d0-gamma)/alpha],-1.0d0) + The density normalization of the Zhao1996 profile. + parameters + + + scaleLength + 1.0d0 + The scale radius of the Zhao1996 profile. + parameters + + + mass + 1.0d0 + The mass of the Zhao1996 profile. + parameters + + + radiusOuter + The outer radius of the Zhao1996 profile. + parameters + + + dimensionless + .true. + If true the Zhao1996 profile is considered to be dimensionless. + parameters + + + componentType + var_str('unknown') + The component type that this mass distribution represents. + parameters + + + massType + var_str('unknown') + The mass type that this mass distribution represents. + parameters + + + self=massDistributionZhao1996(alpha=alpha,beta=beta,gamma=gamma,scaleLength=scaleLength,componentType=enumerationComponentTypeEncode(componentType,includesPrefix=.false.),massType=enumerationMassTypeEncode(massType,includesPrefix=.false.){conditions}) + + + + + + + !!] + return + end function massDistributionZhao1996ConstructorParameters + + function massDistributionZhao1996ConstructorInternal(alpha,beta,gamma,scaleLength,densityNormalization,mass,radiusOuter,dimensionless,componentType,massType) result(self) + !!{ + Internal constructor for ``zhao1996'' mass distribution class. + !!} + use :: Error , only : Error_Report + use :: Numerical_Constants_Math, only : Pi + use :: Hypergeometric_Functions, only : Hypergeometric_2F1 + use :: Numerical_Comparison , only : Values_Agree + implicit none + type (massDistributionZhao1996 ) :: self + double precision , intent(in ) :: alpha , beta , & + & gamma , scaleLength + double precision , intent(in ), optional :: densityNormalization, radiusOuter, & + & mass + logical , intent(in ), optional :: dimensionless + type (enumerationComponentTypeType), intent(in ), optional :: componentType + type (enumerationMassTypeType ), intent(in ), optional :: massType + double precision :: radiusScaleFree + !![ + + !!] + + ! Determine density normalization. + if ( & + & present(densityNormalization) & + & ) then + self%densityNormalization=densityNormalization + else if ( & + & present(mass ).and. & + & present(radiusOuter ) & + & ) then + radiusScaleFree =+radiusOuter/self%scaleLength + self%densityNormalization=+mass/self%scaleLength**3*(3.0d0-gamma)/4.0d0/Pi/radiusScaleFree**(3.0d0-gamma)/Hypergeometric_2F1([(3.0d0-gamma)/alpha,(beta-gamma)/alpha],[1.0d0+(3.0d0-gamma)/alpha],-radiusScaleFree**alpha) + else + call Error_Report('either "densityNormalization", or "mass" and "radiusOuter" must be specified'//{introspection:location}) + end if + ! Determine if profile is dimensionless. + if (present(dimensionless )) then + self%dimensionless=dimensionless + else + self%dimensionless=.false. + end if + ! Validate parameters. + if (gamma >= 3.0d0) call Error_Report('γ ≥ 3 gives divergent mass as r → 0'//{introspection:location}) + ! Detect special cases. + if ( & + & Values_Agree(alpha,1.0d0,absTol=1.0d-6) & + & .and. & + & Values_Agree(beta ,3.0d0,absTol=1.0d-6) & + & .and. & + & Values_Agree(gamma,1.0d0,absTol=1.0d-6) & + & ) then + ! The "NFW" profile. + self%specialCase=specialCaseNFW + else if ( & + & Values_Agree(alpha,1.0d0,absTol=1.0d-6) & + & .and. & + & Values_Agree(beta ,3.0d0,absTol=1.0d-6) & + & .and. & + & Values_Agree(gamma,0.0d0,absTol=1.0d-6) & + & ) then + ! The "cored NFW" profile. + self%specialCase=specialCaseCoredNFW + else if ( & + & Values_Agree(alpha,1.0d0,absTol=1.0d-6) & + & .and. & + & Values_Agree(beta ,3.0d0,absTol=1.0d-6) & + & .and. & + & Values_Agree(gamma,0.5d0,absTol=1.0d-6) & + & ) then + ! The "γ=1/2 NFW" profile. + self%specialCase=specialCaseGamma0_5NFW + else if ( & + & Values_Agree(alpha,1.0d0,absTol=1.0d-6) & + & .and. & + & Values_Agree(beta ,3.0d0,absTol=1.0d-6) & + & .and. & + & Values_Agree(gamma,1.5d0,absTol=1.0d-6) & + & ) then + ! The "γ=3/2 NFW" profile. + self%specialCase=specialCaseGamma1_5NFW + else + ! Use general solutions. + self%specialCase=specialCaseGeneral + end if + ! Initialize memoized results. + self%densityScaleFreeMinimum =+huge(0.0d0) + self%densityScaleFreeMaximum =-huge(0.0d0) + self%densityScaleFreeRadiusMinimum =+1.0d0 + self%densityScaleFreeRadiusMaximum =+1.0d0 + self%massScaleFreeMinimum =+huge(0.0d0) + self%massScaleFreeMaximum =-huge(0.0d0) + self%massScaleFreeRadiusMinimum =+1.0d0 + self%massScaleFreeRadiusMaximum =+1.0d0 + self%angularMomentumSpecificScaleFreeMinimum =+huge(0.0d0) + self%angularMomentumSpecificScaleFreeMaximum =-huge(0.0d0) + self%angularMomentumSpecificScaleFreeRadiusMinimum=+1.0d0 + self%angularMomentumSpecificScaleFreeRadiusMaximum=+1.0d0 + self%timeFreefallScaleFreeMinimum =+huge(0.0d0) + self%timeFreefallScaleFreeMaximum =-huge(0.0d0) + self%timeFreefallScaleFreeRadiusMinimum =+1.0d0 + self%timeFreefallScaleFreeRadiusMaximum =+1.0d0 + return + end function massDistributionZhao1996ConstructorInternal + + double precision function zhao1996MassTotal(self) result(massTotal) + !!{ + Return the total mass in an Zhao1996 mass distribution. + !!} + use :: Gamma_Functions, only : Gamma_Function + implicit none + class(massDistributionZhao1996), intent(inout) :: self + + if (self%beta <= 3.0d0) then + massTotal=+huge(0.0d0) + else + massTotal=+ 4.0d0 & + & /(3.0d0-self%gamma) & + & *Gamma_Function((-3.0d0 +self%beta )/self%alpha) & + & *Gamma_Function((+3.0d0+self%alpha -self%gamma)/self%alpha) & + & /Gamma_Function(( -self%alpha+self%beta )/self%alpha) & + & *self%densityNormalization & + & *self%scaleLength **3 + end if + return + end function zhao1996MassTotal + + double precision function zhao1996Density(self,coordinates) result(density) + !!{ + Return the density at the specified {\normalfont \ttfamily coordinates} in a Zhao1996 mass distribution. + !!} + implicit none + class (massDistributionZhao1996), intent(inout) :: self + class (coordinate ), intent(in ) :: coordinates + double precision :: radiusScaleFree + + ! Compute the density at this position. + radiusScaleFree=+coordinates%rSpherical () & + & /self %scaleLength + select case (self%specialCase%ID) + case (specialCaseGeneral%ID) + density=+self%densityNormalization & + & / radiusScaleFree**self%gamma & + & /( & + & +1.0d0 & + & +radiusScaleFree**self%alpha & + & )**((self%beta-self%gamma)/self%alpha) + case (specialCaseNFW%ID) + density=+self%densityNormalization & + & / radiusScaleFree & + & /( & + & +1.0d0 & + & +radiusScaleFree & + & )**2 + case (specialCaseCoredNFW%ID) + density=+self%densityNormalization & + & /( & + & +1.0d0 & + & +radiusScaleFree & + & )**3 + case (specialCaseGamma0_5NFW%ID) + density=+self%densityNormalization & + & /sqrt(radiusScaleFree) & + & /( & + & +1.0d0 & + & +radiusScaleFree & + & )**2.5d0 + case (specialCaseGamma1_5NFW%ID) + density=+self%densityNormalization & + & / radiusScaleFree**1.5d0 & + & /( & + & +1.0d0 & + & +radiusScaleFree & + & )**1.5d0 + case default + density=+0.0d0 + call Error_Report('unknown special case'//{introspection:location}) + end select + return + end function zhao1996Density + + double precision function zhao1996DensityGradientRadial(self,coordinates,logarithmic) result(densityGradientRadial) + !!{ + Return the density at the specified {\normalfont \ttfamily coordinates} in an Zhao1996 \citep{zhao_analytical_1996} mass distribution. + !!} + implicit none + class (massDistributionZhao1996), intent(inout), target :: self + class (coordinate ), intent(in ) :: coordinates + logical , intent(in ), optional :: logarithmic + double precision :: radiusScaleFree + !![ + + !!] + + radiusScaleFree =+coordinates%rSpherical() & + & /self %scaleLength + densityGradientRadial=-( & + & +self%beta *radiusScaleFree**self%alpha & + & +self%gamma & + & ) & + & /( & + & +1.0d0 & + & + radiusScaleFree**self%alpha & + & ) + if (.not.logarithmic_) densityGradientRadial=+ densityGradientRadial & + & *self %density (coordinates) & + & /coordinates%rSpherical ( ) + return + end function zhao1996DensityGradientRadial + + double precision function zhao1996DensityRadialMoment(self,moment,radiusMinimum,radiusMaximum,isInfinite) result(densityRadialMoment) + !!{ + Computes radial moments of the density in an Zhao1996 \citep{zhao_analytical_1996} mass distribution. + !!} + implicit none + class (massDistributionZhao1996), intent(inout) :: self + double precision , intent(in ) :: moment + double precision , intent(in ), optional :: radiusMinimum , radiusMaximum + logical , intent( out), optional :: isInfinite + double precision :: radialMomentMinimum, radialMomentMaximum, & + & radiusScaleFree + + densityRadialMoment=0.0d0 + if (present(isInfinite)) isInfinite=.false. + if (present(radiusMinimum)) then + radiusScaleFree =+ radiusMinimum & + & /self%scaleLength + radialMomentMinimum=+radialMomentIndefinite(radiusScaleFree) + else + radialMomentMinimum=+0.0d0 + if ( & + & 0.0d0 >= self%alpha & + & .or. & + & 1.0d0+moment <= self%gamma & + & ) call Error_Report('radial moment is undefined'//{introspection:location}) + end if + if (present(radiusMaximum)) then + radiusScaleFree =+ radiusMaximum & + & /self%scaleLength + radialMomentMaximum=+radialMomentIndefinite(radiusScaleFree) + else + radialMomentMaximum=+0.0d0 + if ( & + & self%alpha > 0.0d0 & + & .and. & + & 1.0d0+ moment > self%alpha+self%beta & + & .and. & + & 1.0d0+ moment > self%gamma & + & .and. & + & self%beta > self%gamma & + & ) call Error_Report('radial moment is undefined'//{introspection:location}) + end if + densityRadialMoment=+( & + & +radialMomentMaximum & + & -radialMomentMinimum & + & ) & + & *self%densityNormalization & + & *self%scaleLength **(moment+1.0d0) + return + + contains + + double precision function radialMomentIndefinite(radiusScaleFree) + !!{ + Compute the indefinite radial moment. + !!} + use :: Hypergeometric_Functions, only : Hypergeometric_2F1 + implicit none + double precision, intent(in ) :: radiusScaleFree + + radialMomentIndefinite=+radiusScaleFree** (1.0d0+moment-self%gamma) & + & / (1.0d0+moment-self%gamma) & + & *Hypergeometric_2F1([(1.0d0+moment-self%gamma)/self%alpha,(self%beta-self%gamma)/self%alpha],[1.0d0+(1.0d0+moment-self%gamma)/self%alpha],-radiusScaleFree**self%alpha) + return + end function radialMomentIndefinite + + end function zhao1996DensityRadialMoment + + double precision function zhao1996MassEnclosedBySphere(self,radius) result(mass) + !!{ + Computes the mass enclosed within a sphere of given {\normalfont \ttfamily radius} for zhao1996 mass distributions. + !!} + implicit none + class (massDistributionZhao1996), intent(inout), target :: self + double precision , intent(in ) :: radius + double precision :: radiusScaleFree + + self_ => self + radiusScaleFree = + radius & + & /self%scaleLength + mass = +self%densityNormalization & + & *self%scaleLength **3 & + & *massEnclosedScaleFree(radiusScaleFree) + return + end function zhao1996MassEnclosedBySphere + + double precision function zhao1996VelocityRotationCurveMaximum(self) result(velocity) + !!{ + Return the peak velocity in the rotation curve for a Zhao1996 mass distribution. + !!} + use :: Numerical_Constants_Astronomical, only : gravitationalConstantGalacticus + implicit none + class(massDistributionZhao1996), intent(inout) :: self + + select case (self%specialCase%ID) + case (specialCaseGeneral %ID) + velocity=+self%rotationCurve(self%radiusRotationCurveMaximum()) + return + case (specialCaseNFW %ID) + velocity=+1.6483500453640064578d0 + case (specialCaseGamma0_5NFW%ID) + velocity=+1.4026527358517898624d0 + case (specialCaseGamma1_5NFW%ID) + velocity=+2.0932014912026087087d0 + case (specialCaseCoredNFW %ID) + velocity=+1.2414383571567440046d0 + case default + velocity=+0.0d0 + call Error_Report('unknown special case'//{introspection:location}) + end select + velocity=+velocity & + & *sqrt( & + & +self%densityNormalization & + & ) & + & * self%scaleLength + if (.not.self%isDimensionless()) & + & velocity=+velocity & + & *sqrt(gravitationalConstantGalacticus) + return + end function zhao1996VelocityRotationCurveMaximum + + double precision function zhao1996RadiusRotationCurveMaximum(self) result(radius) + !!{ + Return the peak velocity in the rotation curve for a Zhao1996 mass distribution. + !!} + implicit none + class(massDistributionZhao1996), intent(inout), target :: self + + select case (self%specialCase%ID) + case (specialCaseGeneral %ID) + radius=+self%radiusRotationCurveMaximumNumerical() + return + case (specialCaseNFW %ID) + radius=+2.1625815870646098349d0 + case (specialCaseGamma0_5NFW%ID) + radius=+3.2892765613841120232d0 + case (specialCaseGamma1_5NFW%ID) + radius=+1.0549665718691230692d0 + case (specialCaseCoredNFW %ID) + radius=+4.4247006468722702269d0 + case default + radius=+0.0d0 + call Error_Report('unknown special case'//{introspection:location}) + end select + radius=+radius & + & *self%scaleLength + return + end function zhao1996RadiusRotationCurveMaximum + + double precision function zhao1996RadiusEnclosingMass(self,mass,massFractional) result(radius) + !!{ + Computes the radius enclosing a given mass or mass fraction for zhao1996 mass distributions. + !!} + use :: Numerical_Ranges, only : Make_Range, rangeTypeLogarithmic + use :: Error , only : Error_Report + implicit none + class (massDistributionZhao1996), intent(inout), target :: self + double precision , intent(in ), optional :: mass , massFractional + double precision , allocatable , dimension(:) :: radii , masses + double precision , parameter :: countRadiiPerDecade=100.0d0 + double precision :: massScaleFree , mass_ + integer :: countRadii + + mass_=0.0d0 + if (present(mass)) then + mass_=mass + else if (present(massFractional)) then + call Error_Report('mass is unbounded, so mass fraction is undefined'//{introspection:location}) + else + call Error_Report('either mass or massFractional must be supplied' //{introspection:location}) + end if + massScaleFree=+ mass_ & + & /self%densityNormalization & + & /self%scaleLength **3 + if ( & + & massScaleFree <= self%massScaleFreeMinimum & + & .or. & + & massScaleFree > self%massScaleFreeMaximum & + & ) then + self_ => self + do while (massEnclosedScaleFree(self%massScaleFreeRadiusMinimum) >= massScaleFree) + self%massScaleFreeRadiusMinimum=0.5d0*self%massScaleFreeRadiusMinimum + end do + do while (massEnclosedScaleFree(self%massScaleFreeRadiusMaximum) < massScaleFree) + self%massScaleFreeRadiusMaximum=2.0d0*self%massScaleFreeRadiusMaximum + end do + countRadii=int(log10(self%massScaleFreeRadiusMaximum/self%massScaleFreeRadiusMinimum)*countRadiiPerDecade)+1 + if (allocated(self%massScaleFree_)) deallocate(self%massScaleFree_) + allocate( radii (countRadii)) + allocate( masses (countRadii)) + allocate(self%massScaleFree_ ) + radii = Make_Range(self%massScaleFreeRadiusMinimum,self%massScaleFreeRadiusMaximum,countRadii,rangeTypeLogarithmic) + masses = massEnclosedScaleFree( radii) + self%massScaleFreeMinimum = masses ( 1 ) + self%massScaleFreeMaximum = masses (countRadii ) + self%massScaleFree_ = interpolator (masses ,radii) + end if + radius=+self%massScaleFree_%interpolate(massScaleFree) & + & *self%scaleLength + return + end function zhao1996RadiusEnclosingMass + + double precision function zhao1996RadiusEnclosingDensity(self,density,radiusGuess) result(radius) + !!{ + Computes the radius enclosing a given mean density for zhao1996 mass distributions. + !!} + use :: Numerical_Ranges, only : Make_Range, rangeTypeLogarithmic + implicit none + class (massDistributionZhao1996), intent(inout), target :: self + double precision , intent(in ) :: density + double precision , intent(in ), optional :: radiusGuess + double precision , allocatable , dimension(:) :: radii , densities + double precision , parameter :: countRadiiPerDecade=100.0d0 + double precision :: densityScaleFree + integer :: countRadii + + densityScaleFree=+density & + & /self%densityNormalization + if ( & + & densityScaleFree <= self%densityScaleFreeMinimum & + & .or. & + & densityScaleFree > self%densityScaleFreeMaximum & + & ) then + do while (densityEnclosedScaleFree(self%densityScaleFreeRadiusMinimum) < densityScaleFree) + self%densityScaleFreeRadiusMinimum=0.5d0*self%densityScaleFreeRadiusMinimum + end do + do while (densityEnclosedScaleFree(self%densityScaleFreeRadiusMaximum) >= densityScaleFree) + self%densityScaleFreeRadiusMaximum=2.0d0*self%densityScaleFreeRadiusMaximum + end do + countRadii=int(log10(self%densityScaleFreeRadiusMaximum/self%densityScaleFreeRadiusMinimum)*countRadiiPerDecade)+1 + if (allocated(self%densityScaleFree_)) deallocate(self%densityScaleFree_) + allocate( radii (countRadii)) + allocate( densities (countRadii)) + allocate(self%densityScaleFree_ ) + self_ => self + radii = Make_Range(self%densityScaleFreeRadiusMinimum,self%densityScaleFreeRadiusMaximum,countRadii,rangeTypeLogarithmic) + densities = -densityEnclosedScaleFree( radii) + self%densityScaleFreeMinimum = -densities (countRadii ) + self%densityScaleFreeMaximum = -densities ( 1 ) + self%densityScaleFree_ = interpolator (densities ,radii) + end if + radius=+self%densityScaleFree_%interpolate(-densityScaleFree) & + & *self%scaleLength + return + end function zhao1996RadiusEnclosingDensity + + impure elemental double precision function massEnclosedScaleFree(radius) result(mass) + !!{ + Evaluate the mass enclosed by a given radius in a scale-free Zhao1996 mass distribution. + !!} + use :: Error , only : Error_Report + use :: Numerical_Constants_Math, only : Pi + use :: Hypergeometric_Functions, only : Hypergeometric_2F1 + implicit none + double precision, intent(in ) :: radius + double precision, parameter :: radiusTiny=1.0d-3 + + select case (self_%specialCase%ID) + case (specialCaseGeneral%ID) + mass =+4.0d0 & + & *Pi & + & *radius ** (3.0d0-self_%gamma) & + & *Hypergeometric_2F1([(3.0d0-self_%gamma)/self_%alpha,(self_%beta-self_%gamma)/self_%alpha],[1.0d0+(3.0d0-self_%gamma)/self_%alpha],-radius**self_%alpha) & + & / (3.0d0-self_%gamma) + case (specialCaseNFW%ID) + if (radius < radiusTiny) then + ! Use series solution for small radii. + mass =+ 2.0d0 *Pi*radius**2 & + & - 8.0d0/3.0d0*Pi*radius**3 & + & + 3.0d0 *Pi*radius**4 & + & -16.0d0/5.0d0*Pi*radius**5 & + & +10.0d0/3.0d0*Pi*radius**6 & + & -24.0d0/7.0d0*Pi*radius**7 + else + ! Use full solution. + mass =+4.0d0 & + & *Pi & + & *( & + & +log(+1.0d0+radius) & + & - radius & + & / (+1.0d0+radius) & + & ) + end if + case (specialCaseGamma0_5NFW%ID) + if (radius 0.0d0) then + angularMomentumSpecificScaleFree=+angularMomentumSpecific & + & /sqrt( & + & +gravitationalConstantGalacticus & + & *self%densityNormalization & + & ) & + & / self%scaleLength **2 + if ( & + & angularMomentumSpecificScaleFree <= self%angularMomentumSpecificScaleFreeMinimum & + & .or. & + & angularMomentumSpecificScaleFree > self%angularMomentumSpecificScaleFreeMaximum & + & ) then + do while (angularMomentumSpecificEnclosedScaleFree(self%angularMomentumSpecificScaleFreeRadiusMinimum) >= angularMomentumSpecificScaleFree) + self%angularMomentumSpecificScaleFreeRadiusMinimum=0.5d0*self%angularMomentumSpecificScaleFreeRadiusMinimum + end do + do while (angularMomentumSpecificEnclosedScaleFree(self%angularMomentumSpecificScaleFreeRadiusMaximum) < angularMomentumSpecificScaleFree) + self%angularMomentumSpecificScaleFreeRadiusMaximum=2.0d0*self%angularMomentumSpecificScaleFreeRadiusMaximum + end do + countRadii=int(log10(self%angularMomentumSpecificScaleFreeRadiusMaximum/self%angularMomentumSpecificScaleFreeRadiusMinimum)*countRadiiPerDecade)+1 + if (allocated(self%angularMomentumSpecificScaleFree_)) deallocate(self%angularMomentumSpecificScaleFree_) + allocate( radii (countRadii)) + allocate( angularMomentaSpecific (countRadii)) + allocate(self%angularMomentumSpecificScaleFree_ ) + self_ => self + radii = Make_Range(self%angularMomentumSpecificScaleFreeRadiusMinimum,self%angularMomentumSpecificScaleFreeRadiusMaximum,countRadii,rangeTypeLogarithmic) + angularMomentaSpecific = angularMomentumSpecificEnclosedScaleFree( radii) + self%angularMomentumSpecificScaleFreeMinimum = angularMomentaSpecific ( 1 ) + self%angularMomentumSpecificScaleFreeMaximum = angularMomentaSpecific ( countRadii ) + self%angularMomentumSpecificScaleFree_ = interpolator (angularMomentaSpecific,radii) + end if + radius=+self%angularMomentumSpecificScaleFree_%interpolate(angularMomentumSpecificScaleFree) & + & *self%scaleLength + else + radius=+0.0d0 + end if + return + end function zhao1996RadiusFromSpecificAngularMomentum + + impure elemental double precision function angularMomentumSpecificEnclosedScaleFree(radius) result(angularMomentumSpecific) + !!{ + Evaluate the specific angular momentum at a given radius in a scale-free Zhao1996 mass distribution. + !!} + implicit none + double precision, intent(in ) :: radius + + angularMomentumSpecific=+sqrt( & + & +massEnclosedScaleFree(radius) & + & * radius & + & ) + return + end function angularMomentumSpecificEnclosedScaleFree + + logical function zhao1996PotentialIsAnalytic(self) result(isAnalytic) + !!{ + Return that the potential has an analytic form. + !!} + implicit none + class(massDistributionZhao1996), intent(inout) :: self + + isAnalytic=.true. + return + end function zhao1996PotentialIsAnalytic + + double precision function zhao1996Potential(self,coordinates,status) result(potential) + !!{ + Return the potential at the specified {\normalfont \ttfamily coordinates} in an zhao1996 mass distribution. + !!} + use :: Coordinates , only : assignment(=) + use :: Galactic_Structure_Options , only : structureErrorCodeSuccess , structureErrorCodeInfinite + use :: Numerical_Constants_Astronomical, only : gravitationalConstantGalacticus + use :: Error , only : Error_Report + implicit none + class (massDistributionZhao1996 ), intent(inout), target :: self + class (coordinate ), intent(in ) :: coordinates + type (enumerationStructureErrorCodeType), intent( out), optional :: status + double precision :: radiusScaleFree + + if (present(status)) status=structureErrorCodeSuccess + self_ => self + radiusScaleFree = +coordinates%rSpherical () & + & /self %scaleLength + potential=+potentialScaleFree (radiusScaleFree) & + & *self%densityNormalization & + & *self%scaleLength **2 + if (.not.self%isDimensionless()) potential=+gravitationalConstantGalacticus & + & *potential + return + end function zhao1996Potential + + impure elemental double precision function potentialScaleFree(radius) result(potential) + !!{ + Compute the potential in a scale-free Zhao1996 mass distribution. + !!} + use :: Gamma_Functions , only : Gamma_Function + use :: Numerical_Constants_Math, only : Pi + use :: Hypergeometric_Functions, only : Hypergeometric_2F1_Regularized + implicit none + double precision, intent(in ) :: radius + + select case (self_%specialCase%ID) + case (specialCaseGeneral %ID) + potential=+4.0d0 & + & *Pi & + & *radius **(2.0d0 -self_%gamma) & + & / (3.0d0 -self_%gamma) & + & * Gamma_Function((3.0d0+self_%alpha-self_%gamma)/self_%alpha) & + & / Gamma_Function((3.0d0 -self_%gamma)/self_%alpha) & + & *( & + & +Gamma_Function((2.0d0 -self_%gamma)/self_%alpha)*Hypergeometric_2F1_Regularized([(2.0d0-self_%gamma)/self_%alpha,(self_%beta-self_%gamma)/self_%alpha],[(2.0d0+self_%alpha-self_%gamma)/self_%alpha],-radius**self_%alpha) & + & -Gamma_Function((3.0d0 -self_%gamma)/self_%alpha)*Hypergeometric_2F1_Regularized([(3.0d0-self_%gamma)/self_%alpha,(self_%beta-self_%gamma)/self_%alpha],[(3.0d0+self_%alpha-self_%gamma)/self_%alpha],-radius**self_%alpha) & + & ) + case (specialCaseNFW %ID) + potential=+4.0d0 & + & *Pi & + & *( & + & + 1.0d0 & + & -log(+1.0d0+radius) & + & / radius & + & ) + case (specialCaseGamma0_5NFW%ID) + potential=+8.0d0 & + & *Pi & + & *( & + & + (+3.0d0+radius) & + & / 3.0d0 & + & / sqrt(radius*(+1.0d0+radius)) & + & -asinh(sqrt(radius )) & + & / radius & + & ) + case (specialCaseGamma1_5NFW%ID) + potential=+8.0d0 & + & *Pi & + & *( & + & + sqrt(radius*(1.0d0+radius)) & + & -asinh(sqrt(radius )) & + & ) & + & /radius + case (specialCaseCoredNFW %ID) + potential=+2.0d0 & + & *Pi & + & *( & + & + (+2.0d0+radius) & + & / (+1.0d0+radius) & + & - 2.0d0 & + & *log(+1.0d0+radius) & + & / radius & + & ) + case default + potential=+0.0d0 + call Error_Report('unknown special case'//{introspection:location}) + end select + return + end function potentialScaleFree + + double precision function potentialDifferenceScaleFree(radius1,radius2) result(potential) + !!{ + Compute the potential difference in a scale-free Zhao1996 mass distribution. + !!} + use :: Numerical_Constants_Math, only : Pi + use :: Numerical_Comparison , only : Values_Agree + use :: Hypergeometric_Functions, only : Hypergeometric_2F1 + implicit none + double precision, intent(in ) :: radius1 , radius2 + double precision, parameter :: radiusSmall =1.0d-3 + double precision, parameter :: toleranceRelative =1.0d-3 + double precision :: potentialGradientLogarithmic , radiusDifferenceLogarithmic + + if (Values_Agree(radius1,radius2,relTol=toleranceRelative) .or. max(radius1,radius2) < radiusSmall) then + if (radius1 < radiusSmall) then + select case (self_%specialCase%ID) + case (specialCaseGeneral %ID) + potentialGradientLogarithmic=- 1.0d0 & + & /( & + & -1.0d0 & + & + (3.0d0-self_%gamma) & + & / (2.0d0-self_%gamma) & + & *Hypergeometric_2F1([(2.0d0-self_%gamma)/self_%alpha,(self_%beta-self_%gamma)/self_%alpha],[(2.0d0+self_%alpha-self_%gamma)/self_%alpha],-radius1**self_%alpha) & + & /Hypergeometric_2F1([(3.0d0-self_%gamma)/self_%alpha,(self_%beta-self_%gamma)/self_%alpha],[(3.0d0+self_%alpha-self_%gamma)/self_%alpha],-radius1**self_%alpha) & + & ) + case (specialCaseNFW %ID) + potentialGradientLogarithmic=+1.0d0- 2.0d0*radius1/ 3.0d0+ 5.0d0*radius1**2/ 9.0d0- 67.0d0*radius1**3/ 135.0d0+ 371.0d0*radius1**4/ 810.0d0 + case (specialCaseGamma0_5NFW%ID) + potentialGradientLogarithmic=+1.5d0-15.0d0*radius1/14.0d0+275.0d0*radius1**2/294.0d0-6525.0d0*radius1**3/7546.0d0+5067175.0d0*radius1**4/6180174.0d0 + case (specialCaseGamma1_5NFW%ID) + potentialGradientLogarithmic=+0.5d0- 3.0d0*radius1/10.0d0+ 81.0d0*radius1**2/350.0d0- 341.0d0*radius1**3/1750.0d0+ 115477.0d0*radius1**4/ 673750.0d0 + case (specialCaseCoredNFW %ID) + potentialGradientLogarithmic=+2.0d0- 3.0d0*radius1/ 2.0d0+ 27.0d0*radius1**2/ 20.0d0- 51.0d0*radius1**3/ 40.0d0+ 3441.0d0*radius1**4/ 2800.0d0 + case default + potentialGradientLogarithmic=+0.0d0 + call Error_Report('unknown special case'//{introspection:location}) + end select + else + select case (self_%specialCase%ID) + case (specialCaseGeneral %ID) + potentialGradientLogarithmic=+ 1.0d0 & + & /( & + & -1.0d0 & + & + (3.0d0-self_%gamma) & + & / (2.0d0-self_%gamma) & + & *Hypergeometric_2F1([(2.0d0-self_%gamma)/self_%alpha,(self_%beta-self_%gamma)/self_%alpha],[(2.0d0+self_%alpha-self_%gamma)/self_%alpha],-radius1**self_%alpha) & + & /Hypergeometric_2F1([(3.0d0-self_%gamma)/self_%alpha,(self_%beta-self_%gamma)/self_%alpha],[(3.0d0+self_%alpha-self_%gamma)/self_%alpha],-radius1**self_%alpha) & + & ) + case (specialCaseNFW %ID) + potentialGradientLogarithmic=+ 1.0d0 & + & /( & + & -1.0d0 & + & + radius1 **2 & + & /( & + & - radius1 & + & + (1.0d0+radius1) & + & *log(1.0d0+radius1) & + & ) & + & ) + case (specialCaseGamma0_5NFW%ID) + potentialGradientLogarithmic=+( & + & - (3.0d0+4.0d0*radius1) * sqrt(radius1) *sqrt(1.0d0+radius1) & + & + 3.0d0*(1.0d0+ radius1)**2*asinh(sqrt(radius1)) & + & ) & + & /( & + & + (1.0d0+ radius1) & + & *( & + & + (3.0d0+ radius1) * sqrt(radius1) *sqrt(1.0d0+radius1) & + & -3.0d0*(1.0d0+ radius1) *asinh(sqrt(radius1)) & + & ) & + & ) + case (specialCaseGamma1_5NFW%ID) + potentialGradientLogarithmic=-1.0d0 & + & /( & + & +1.0d0 & + & +1.0d0 & + & /( & + & +1.0d0 & + & / radius1 & + & - sqrt(1.0d0+radius1 ) & + & *asinh(sqrt( radius1 )) & + & / radius1**1.5d0 & + & ) & + & ) + case (specialCaseCoredNFW %ID) + potentialGradientLogarithmic=+1.0d0 & + & /( & + & - 1.0d0 & + & + radius1**3 & + & /( & + & -radius1* (2.0d0+3.0d0*radius1) & + & +2.0d0 * (1.0d0+ radius1)**2 & + & * log(1.0d0+ radius1) & + & ) & + & ) + case default + potentialGradientLogarithmic=+0.0d0 + call Error_Report('unknown special case'//{introspection:location}) + end select + end if + radiusDifferenceLogarithmic=+1.0d0 & + & -radius2 & + & /radius1 + potential =+potentialScaleFree (radius1) & + & *potentialGradientLogarithmic & + & *radiusDifferenceLogarithmic + else + potential=+potentialScaleFree(radius1) & + & -potentialScaleFree(radius2) + end if + return + end function potentialDifferenceScaleFree + + double precision function zhao1996RadiusFreefall(self,time) result(radius) + !!{ + Compute the freefall radius at the given {\normalfont \ttfamily time} in an Zhao1996 mass distribution. + !!} + use :: Numerical_Constants_Astronomical, only : Mpc_per_km_per_s_To_Gyr, gravitationalConstantGalacticus + implicit none + class (massDistributionZhao1996), intent(inout) :: self + double precision , intent(in ) :: time + double precision :: timeScaleFree, timeScale + + timeScale =+1.0d0/sqrt( & + & +gravitationalConstantGalacticus & + & *self%densityNormalization & + & ) & + & *Mpc_per_km_per_s_To_Gyr + timeScaleFree=+time & + & /timeScale + if (self%specialCase == specialCaseCoredNFW .and. timeScaleFree <= timeFreefallScaleFreeMinimumCoredNFW) then + radius=0.0d0 + return + end if + call self%timeFreefallTabulate(timeScaleFree) + radius=+self%timeFreefallScaleFree_%interpolate(timeScaleFree) & + & *self%scaleLength + return + end function zhao1996RadiusFreefall + + double precision function zhao1996RadiusFreefallIncreaseRate(self,time) result(radiusIncreaseRate) + !!{ + Compute the rate of increase of the freefall radius at the given {\normalfont \ttfamily time} in an zhao1996 mass + distribution. + !!} + use :: Numerical_Constants_Astronomical, only : Mpc_per_km_per_s_To_Gyr, gravitationalConstantGalacticus + implicit none + class (massDistributionZhao1996), intent(inout) :: self + double precision , intent(in ) :: time + double precision :: timeScaleFree, timeScale + + timeScale =+1.0d0/sqrt( & + & +gravitationalConstantGalacticus & + & *self%densityNormalization & + & ) & + & *Mpc_per_km_per_s_To_Gyr + timeScaleFree=+time & + & /timeScale + if (self%specialCase == specialCaseCoredNFW .and. timeScaleFree <= timeFreefallScaleFreeMinimumCoredNFW) then + radiusIncreaseRate=0.0d0 + return + end if + call self%timeFreefallTabulate(timeScaleFree) + radiusIncreaseRate=+self%timeFreefallScaleFree_%derivative(timeScaleFree) & + & *self%scaleLength & + & / timeScale + return + end function zhao1996RadiusFreefallIncreaseRate + + subroutine zhao1996TimeFreefallTabulate(self,timeScaleFree) + !!{ + Tabulate the freefall radius at the given {\normalfont \ttfamily time} in an Zhao1996 mass distribution. + !!} + use :: Numerical_Integration, only : integrator + use :: Numerical_Ranges , only : Make_Range, rangeTypeLogarithmic + implicit none + class (massDistributionZhao1996), intent(inout), target :: self + double precision , intent(in ) :: timeScaleFree + double precision , allocatable , dimension(:) :: radii , timesFreefall + double precision , parameter :: countRadiiPerDecade=100.0d0 + double precision :: radiusStart + integer :: countRadii , i + type (integrator ) :: integrator_ + + if ( & + & timeScaleFree <= self%timeFreefallScaleFreeMinimum & + & .or. & + & timeScaleFree > self%timeFreefallScaleFreeMaximum & + & ) then + self_ => self + integrator_ = integrator(timeFreeFallIntegrand,toleranceRelative=1.0d-6) + do while (timeFreefallScaleFree(self%timeFreefallScaleFreeRadiusMinimum) >= timeScaleFree) + self%timeFreefallScaleFreeRadiusMinimum=0.5d0*self%timeFreefallScaleFreeRadiusMinimum + end do + do while (timeFreefallScaleFree(self%timeFreefallScaleFreeRadiusMaximum) < timeScaleFree) + self%timeFreefallScaleFreeRadiusMaximum=2.0d0*self%timeFreefallScaleFreeRadiusMaximum + end do + countRadii=int(log10(self%timeFreefallScaleFreeRadiusMaximum/self%timeFreefallScaleFreeRadiusMinimum)*countRadiiPerDecade)+1 + if (allocated(self%timeFreefallScaleFree_)) deallocate(self%timeFreefallScaleFree_) + allocate( radii (countRadii)) + allocate( timesFreefall (countRadii)) + allocate(self%timeFreefallScaleFree_ ) + radii=Make_Range(self%timeFreefallScaleFreeRadiusMinimum,self%timeFreefallScaleFreeRadiusMaximum,countRadii,rangeTypeLogarithmic) + do i=1,countRadii + timesFreefall(i)=timeFreefallScaleFree(radii(i)) + end do + self%timeFreefallScaleFreeMinimum=timesFreefall( 1 ) + self%timeFreefallScaleFreeMaximum=timesFreefall( countRadii ) + self%timeFreefallScaleFree_ =interpolator (timesFreefall,radii) + end if + return + + contains + + double precision function timeFreefallScaleFree(radius) + !!{ + Evaluate the freefall time from a given radius in a scale-free Zhao1996 mass distribution. + !!} + implicit none + double precision, intent(in ) :: radius + + radiusStart = radius + timeFreefallScaleFree=integrator_%integrate(0.0d0,radius) + return + end function timeFreefallScaleFree + + double precision function timeFreeFallIntegrand(radius) + !!{ + Integrand used to find the freefall time in a scale-free Zhao1996 mass distribution. + !!} + implicit none + double precision, intent(in ) :: radius + double precision :: potentialDifference + + if (radius == 0.0d0) then + timeFreeFallIntegrand=+0.0d0 + else + potentialDifference=+potentialDifferenceScaleFree(radiusStart,radius) + if (potentialDifference > 0.0d0) then + timeFreeFallIntegrand=+1.0d0 & + & /sqrt( & + & +2.0d0 & + & *potentialDifference & + & ) + else + timeFreeFallIntegrand=+0.0d0 + end if + end if + return + end function timeFreeFallIntegrand + + end subroutine zhao1996TimeFreefallTabulate + + double precision function zhao1996FourierTransform(self,radiusOuter,wavenumber) result(fourierTransform) + !!{ + Compute the Fourier transform of the density profile at the given {\normalfont \ttfamily wavenumber} in an Zhao1996 mass + distribution. + !!} + use :: Exponential_Integrals , only : Exponential_Integral + use :: Numerical_Constants_Math, only : Pi + implicit none + class (massDistributionZhao1996), intent(inout) :: self + double precision , intent(in ) :: radiusOuter , wavenumber + double precision :: wavenumberScaleFree, radiusOuterScaleFree + + waveNumberScaleFree =+waveNumber *self%scaleLength + radiusOuterScaleFree=+radiusOuter/self%scaleLength + select case (self%specialCase%ID) + case ( & + & specialCaseGeneral %ID, & + & specialCaseGamma0_5NFW%ID, & + & specialCaseGamma1_5NFW%ID & + & ) + fourierTransform=+self%fourierTransformNumerical(radiusOuter,wavenumber) + return + case (specialCaseNFW %ID) + fourierTransform=+dimag( & + & +4.0d0 & + & *Pi & + & *( & + & -exp(dcmplx(0.0d0,1.0d0)*wavenumberScaleFree*(1.0d0+radiusOuterScaleFree)) & + & + (1.0d0+radiusOuterScaleFree) & + & *( & + & +exp(dcmplx(0.0d0,1.0d0)*wavenumberScaleFree) & + & - dcmplx(0.0d0,1.0d0)*wavenumberScaleFree & + & *( & + & +Exponential_Integral(dcmplx(0.0d0,1.0d0)*wavenumberScaleFree ) & + & -Exponential_Integral(dcmplx(0.0d0,1.0d0)*wavenumberScaleFree*(1.0d0+radiusOuterScaleFree)) & + & ) & + & ) & + & ) & + & /exp(dcmplx(0.0d0,1.0d0)*wavenumberScaleFree) & + & /wavenumberScaleFree & + & /(1.0d0+radiusOuterScaleFree) & + & ) + case (specialCaseCoredNFW %ID) + fourierTransform=+dimag( & + & +2.0d0 & + & *Pi & + & *( & + & + 1.0d0 & + & - dcmplx(0.0d0,1.0d0)* wavenumberScaleFree & + & +( & + & + dcmplx(0.0d0,1.0d0)*exp(dcmplx(0.0d0,1.0d0)*wavenumberScaleFree*radiusOuterScaleFree) & + & *( & + & + dcmplx(0.0d0,1.0d0)+wavenumberScaleFree & + & +(dcmplx(0.0d0,2.0d0)+wavenumberScaleFree)*radiusOuterScaleFree & + & ) & + & ) & + & /(1.0d0+radiusOuterScaleFree)**2 & + & -( & + & + wavenumberScaleFree & + & *(dcmplx(0.0d0,2.0d0)+wavenumberScaleFree) & + & *( & + & +Exponential_Integral(dcmplx(0.0d0,1.0d0)*wavenumberScaleFree) & + & -Exponential_Integral(dcmplx(0.0d0,1.0d0)*wavenumberScaleFree*(1.0d0+radiusOuterScaleFree)) & + & ) & + & ) & + & /exp(dcmplx(0.0d0,1.0d0)*wavenumberScaleFree) & + & ) & + & /wavenumberScaleFree & + & ) + case default + fourierTransform=+0.0d0 + call Error_Report('unknown special case'//{introspection:location}) + end select + fourierTransform=+fourierTransform & + & /massEnclosedScaleFree(radiusOuterScaleFree) + return + end function zhao1996FourierTransform + + double precision function zhao1996EnergyPotential(self,radiusOuter) result(energy) + !!{ + Compute the potential energy within a given {\normalfont \ttfamily radius} in a Zhao1996 mass distribution. + \begin{eqnarray} + \end{eqnarray} + where $x=r/r_\mathrm{s}$ and $\mathrm{G}$ is Catalan's constant. + !!} + use :: Numerical_Constants_Astronomical, only : gravitationalConstantGalacticus + use :: Numerical_Constants_Math , only : Pi + implicit none + class (massDistributionZhao1996), intent(inout) :: self + double precision , intent(in ) :: radiusOuter + double precision :: radiusOuterScaleFree + logical :: analytic + + analytic=.false. + radiusOuterScaleFree=+ radiusOuter & + & /self%scaleLength + select case (self_%specialCase%ID) + case (specialCaseGeneral %ID) + analytic=.false. + case (specialCaseNFW %ID) + analytic=.true. + energy =+8.0d0 & + & *Pi**2 & + & *( & + & + radiusOuterScaleFree * (2.0d0+radiusOuterScaleFree) & + & -2.0d0*(1.0d0+radiusOuterScaleFree)*log(1.0d0+radiusOuterScaleFree) & + & ) & + & /(1.0d0+radiusOuterScaleFree)**2 + case (specialCaseGamma0_5NFW%ID) + analytic=.true. + energy =-16.0d0 & + & *Pi**2 & + & *( & + & -12.0d0 & + & *(1.0d0+radiusOuterScaleFree) & + & *( & + & -3.0d0*sqrt(radiusOuterScaleFree )*sqrt(1.0d0+radiusOuterScaleFree) & + & -4.0d0* radiusOuterScaleFree**1.5d0 *sqrt(1.0d0+radiusOuterScaleFree) & + & +3.0d0*sqrt(radiusOuterScaleFree * (1.0d0+radiusOuterScaleFree)) & + & + sqrt(radiusOuterScaleFree**3 * (1.0d0+radiusOuterScaleFree)) & + & +radiusOuterScaleFree & + & *( & + & +3.0d0*sqrt(radiusOuterScaleFree *(1.0d0+radiusOuterScaleFree)) & + & + sqrt(radiusOuterScaleFree**3*(1.0d0+radiusOuterScaleFree)) & + & ) & + & ) & + & *asinh(sqrt(radiusOuterScaleFree)) & + & +radiusOuterScaleFree & + & *( & + & +radiusOuterScaleFree & + & *( & + & -6.0d0 & + & +radiusOuterScaleFree*(-3.0d0+5.0d0*radiusOuterScaleFree) & + & ) & + & +6.0d0*(1.0d0+radiusOuterScaleFree)**3*log(1.0d0+radiusOuterScaleFree) & + & ) & + & ) & + & /9.0d0 & + & /radiusOuterScaleFree & + & /(1.0d0+radiusOuterScaleFree)**3 + case (specialCaseGamma1_5NFW%ID) + analytic=.true. + energy =+32.0d0 & + & *Pi**2 & + & *( & + & -1.0d0 & + & +1.0d0 & + & /(1.0d0+radiusOuterScaleFree) & + & +( & + & +2.0d0 & + & *( & + & -radiusOuterScaleFree**1.5d0/sqrt(1.0d0+radiusOuterScaleFree) & + & +sqrt(radiusOuterScaleFree**3*(1.0d0+radiusOuterScaleFree)) & + & ) & + & *asinh(sqrt(radiusOuterScaleFree)) & + & ) & + & /radiusOuterScaleFree**2 & + & +( & + & +( & + & +radiusOuterScaleFree**2 & + & +radiusOuterScaleFree**3 & + & -sqrt(radiusOuterScaleFree *(1.0d0+radiusOuterScaleFree)) & + & *sqrt(radiusOuterScaleFree**3*(1.0d0+radiusOuterScaleFree)) & + & ) & + & *asinh(sqrt(radiusOuterScaleFree))**2 & + & ) & + & /radiusOuterScaleFree**3 & + & /(1.0d0+radiusOuterScaleFree) & + & -log(1.0d0+radiusOuterScaleFree) & + & ) + case (specialCaseCoredNFW %ID) + analytic=.true. + energy =+2.0d0 & + & *Pi**2 & + & *( & + & +radiusOuterScaleFree*(12.0d0+radiusOuterScaleFree*(42.0d0+radiusOuterScaleFree*(40.0d0+7.0d0*radiusOuterScaleFree))) & + & -12.0d0*(1.0d0+radiusOuterScaleFree)**2*(1.0d0+2.0d0*radiusOuterScaleFree)*log(1.0d0+radiusOuterScaleFree) & + & ) & + & /(3.0d0*(1.0d0+radiusOuterScaleFree)**4) + case default + energy=+0.0d0 + call Error_Report('unknown special case'//{introspection:location}) + end select + if (analytic) then + energy =-energy & + & *gravitationalConstantGalacticus & + & *self%scaleLength **5 & + & *self%densityNormalization **2 + else + energy =+self%energyPotentialNumerical(radiusOuter) + end if + return + end function zhao1996EnergyPotential + + double precision function zhao1996EnergyKinetic(self,radiusOuter,massDistributionEmbedding) result(energy) + !!{ + Compute the kinetic energy within a given {\normalfont \ttfamily radius} in a Zhao1996 mass distribution. + !!} + use :: Numerical_Constants_Astronomical, only : gravitationalConstantGalacticus + use :: Numerical_Constants_Math , only : Pi + use :: Dilogarithms , only : Dilogarithm + implicit none + class (massDistributionZhao1996), intent(inout) :: self + double precision , intent(in ) :: radiusOuter + class (massDistributionClass ), intent(inout) :: massDistributionEmbedding + logical :: analytic + double precision :: radiusOuterScaleFree + + analytic=.false. + select type (massDistributionEmbedding) + class is (massDistributionZhao1996) + select type (kinematicsDistribution_ => massDistributionEmbedding%kinematicsDistribution_) + class is (kinematicsDistributionZhao1996) + radiusOuterScaleFree=+ radiusOuter & + & /self%scaleLength + select case (self_%specialCase%ID) + case (specialCaseGeneral %ID) + analytic=.false. + case (specialCaseNFW %ID) + analytic=.true. + energy =+4.0d0 & + & *Pi**2 & + & *( & + & +Pi**2 & + & *(+1.0d0+radiusOuterScaleFree ) & + & *(+2.0d0+radiusOuterScaleFree**3) & + & +radiusOuterScaleFree*(2.0d0-radiusOuterScaleFree*(2.0d0+7.0d0*radiusOuterScaleFree)) & + & +2.0d0*radiusOuterScaleFree**4*atanh(1.0d0/(1.0d0+2.0d0*radiusOuterScaleFree)) & + & -radiusOuterScaleFree**3*log( radiusOuterScaleFree) & + & -2.0d0 *log(1.0d0+radiusOuterScaleFree) & + & +( & + & + radiusOuterScaleFree & + & - 3.0d0* radiusOuterScaleFree**2 & + & - 5.0d0* radiusOuterScaleFree**3 & + & +12.0d0*(1.0d0+radiusOuterScaleFree )*log(radiusOuterScaleFree) & + & ) & + & *log(1.0d0+radiusOuterScaleFree) & + & + 3.0d0*(1.0d0+radiusOuterScaleFree)*(-2.0d0+radiusOuterScaleFree**3)* log( 1.0d0+radiusOuterScaleFree )**2 & + & + 6.0d0*(1.0d0+radiusOuterScaleFree)*(+2.0d0+radiusOuterScaleFree**3)*Dilogarithm( -radiusOuterScaleFree ) & + & -12.0d0*(1.0d0+radiusOuterScaleFree) *Dilogarithm(1.0d0/(1.0d0+radiusOuterScaleFree)) & + & ) & + & /(1.0d0+radiusOuterScaleFree) + case (specialCaseGamma0_5NFW%ID) + analytic=.true. + energy =+16.0d0 & + & /3.0d0 & + & *Pi**2 & + & *( & + & -4.0d0*radiusOuterScaleFree**1.5d0 & + & *(-1.0d0+4.0d0*radiusOuterScaleFree+8.0d0*radiusOuterScaleFree**2) & + & *asinh(sqrt( radiusOuterScaleFree)) & + & / sqrt(1.0d0+radiusOuterScaleFree) & + & +radiusOuterScaleFree & + & *( & + & + 2.0d0+ radiusOuterScaleFree & + & *( & + & -5.0d0+2.0d0*radiusOuterScaleFree & + & *( & + & -11.0d0 & + & +32.0d0*log(2.0d0) & + & + 8.0d0*radiusOuterScaleFree*(-1.0d0+radiusOuterScaleFree*log(16.0d0)+log(256.0d0)) & + & ) & + & ) & + & ) & + & / 2.0d0 & + & /(+1.0d0+ radiusOuterScaleFree )**2 & + & +(-1.0d0+16.0d0*radiusOuterScaleFree**3) & + & *log(1.0d0+radiusOuterScaleFree) & + & ) + case (specialCaseGamma1_5NFW%ID) + analytic=.true. + energy =-16.0d0 & + & /5.0d0 & + & *Pi**2 & + & *( & + & -4.0d0 & + & *( & + & +3.0d0*sqrt(radiusOuterScaleFree *(1.0d0+radiusOuterScaleFree)) & + & -4.0d0*sqrt(radiusOuterScaleFree**3*(1.0d0+radiusOuterScaleFree)) & + & +8.0d0*sqrt(radiusOuterScaleFree**5*(1.0d0+radiusOuterScaleFree)) & + & ) & + & *asinh(sqrt(radiusOuterScaleFree)) & + & +radiusOuterScaleFree*(7.0d0+4.0d0*radiusOuterScaleFree*(-3.0d0+8.0d0*radiusOuterScaleFree*log(2.0d0))) & + & -4.0d0 *radiusOuterScaleFree**3 *log(radiusOuterScaleFree) & + & +5.0d0*(1.0d0+4.0d0*radiusOuterScaleFree**3) & + & *log(1.0d0+radiusOuterScaleFree) & + & ) + case (specialCaseCoredNFW %ID) + analytic=.true. + energy =+Pi**2 & + & *( & + & +radiusOuterScaleFree & + & *( & + & +4.0d0+radiusOuterScaleFree & + & *( & + & +10.0d0+radiusOuterScaleFree & + & *( & + & +35.0d0-4.0d0*Pi**2*(1.0d0+radiusOuterScaleFree)**3 & + & + 6.0d0*radiusOuterScaleFree & + & *( & + & +9.0d0+4.0d0*radiusOuterScaleFree & + & ) & + & ) & + & ) & + & ) & + & /(1.0d0+radiusOuterScaleFree)**3 & + & -4.0d0*log(1.0d0+radiusOuterScaleFree) & + & *( & + & +1.0d0+radiusOuterScaleFree & + & -3.0d0*radiusOuterScaleFree**2 & + & -6.0d0*radiusOuterScaleFree**3 & + & +3.0d0*radiusOuterScaleFree**3*(1.0d0+radiusOuterScaleFree)*log(1.0d0+radiusOuterScaleFree) & + & ) & + & /(1.0d0+radiusOuterScaleFree) & + & -24.0d0*radiusOuterScaleFree**3*Dilogarithm(-radiusOuterScaleFree) & + & ) + case default + energy=+0.0d0 + call Error_Report('unknown special case'//{introspection:location}) + end select + end select + end select + if (analytic) then + energy=+energy & + & *gravitationalConstantGalacticus & + & *self%scaleLength **5 & + & *self%densityNormalization **2 + else + energy=+self%energyKineticNumerical(radiusOuter,massDistributionEmbedding) + end if + return + end function zhao1996EnergyKinetic + + subroutine zhao1996Descriptor(self,descriptor,includeClass,includeFileModificationTimes) + !!{ + Return an input parameter list descriptor which could be used to recreate this object. + !!} + use :: Input_Parameters, only : inputParameters + implicit none + class (massDistributionZhao1996), intent(inout) :: self + type (inputParameters ), intent(inout) :: descriptor + logical , intent(in ), optional :: includeClass , includeFileModificationTimes + character(len=18 ) :: parameterLabel + type (inputParameters ) :: parameters + + if (.not.present(includeClass).or.includeClass) call descriptor%addParameter('massDistribution','Zhao1996') + parameters=descriptor%subparameters('massDistribution') + write (parameterLabel,'(e17.10)') self%densityNormalization + call parameters%addParameter('densityNormalization',trim(adjustl(parameterLabel))) + write (parameterLabel,'(e17.10)') self%scaleLength + call parameters%addParameter('scaleLength' ,trim(adjustl(parameterLabel))) + return + end subroutine zhao1996Descriptor + diff --git a/source/mass_distributions.spherical.accelerator.F90 b/source/mass_distributions.spherical.accelerator.F90 new file mode 100644 index 0000000000..182ff4d49a --- /dev/null +++ b/source/mass_distributions.spherical.accelerator.F90 @@ -0,0 +1,221 @@ +!! Copyright 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, +!! 2019, 2020, 2021, 2022, 2023 +!! Andrew Benson +!! +!! This file is part of Galacticus. +!! +!! Galacticus is free software: you can redistribute it and/or modify +!! it under the terms of the GNU General Public License as published by +!! the Free Software Foundation, either version 3 of the License, or +!! (at your option) any later version. +!! +!! Galacticus is distributed in the hope that it will be useful, +!! but WITHOUT ANY WARRANTY; without even the implied warranty of +!! MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +!! GNU General Public License for more details. +!! +!! You should have received a copy of the GNU General Public License +!! along with Galacticus. If not, see . + + !!{ + Implements an accelerator for spherical mass distributions. + !!} + + use :: Binary_Search_Trees , only : binaryTree + + !![ + + + Accelerates spherical mass distribution classes by storing previous results for the enclosed mass and interpolating where + possible. + + + !!] + type, extends(massDistributionSphericalDecorator) :: massDistributionSphericalAccelerator + !!{ + Implementation of a finite resolution spherical mass distribution. + !!} + private + type (binaryTree) :: treeMassEnclosed + double precision :: toleranceRelative, factorRadiusMaximum, factorRadiusLogarithmicMaximum + contains + final :: sphericalAcceleratorDestructor + procedure :: density => sphericalAcceleratorDensity + procedure :: massEnclosedBySphere => sphericalAcceleratorMassEnclosedBySphere + procedure :: useUndecorated => sphericalAcceleratorUseUndecorated + end type massDistributionSphericalAccelerator + + interface massDistributionSphericalAccelerator + !!{ + Constructors for the {\normalfont \ttfamily sphericalAccelerator} mass distribution class. + !!} + module procedure sphericalAcceleratorConstructorParameters + module procedure sphericalAcceleratorConstructorInternal + end interface massDistributionSphericalAccelerator + +contains + + function sphericalAcceleratorConstructorParameters(parameters) result(self) + !!{ + Constructor for the {\normalfont \ttfamily sphericalAccelerator} mass distribution class which builds the object from a parameter + set. + !!} + use :: Input_Parameters , only : inputParameters + use :: Galactic_Structure_Options, only : enumerationComponentTypeEncode, enumerationMassTypeEncode + implicit none + type (massDistributionSphericalAccelerator) :: self + type (inputParameters ), intent(inout) :: parameters + class (massDistributionClass ), pointer :: massDistribution_ + double precision :: toleranceRelative, factorRadiusMaximum + type (varying_string ) :: componentType , massType , & + & nonAnalyticSolver + + !![ + + toleranceRelative + 1.0d-2 + parameters + The tolerance with which to accept accelerated estimates. + + + factorRadiusMaximum + 3.0d0 + parameters + The maximum factor by which to interpolate in radius. + + + nonAnalyticSolver + var_str('fallThrough') + parameters + Selects how solutions are computed when no analytic solution is available. If set to ``{\normalfont \ttfamily fallThrough}'' then the solution ignoring heating is used, while if set to ``{\normalfont \ttfamily numerical}'' then numerical solvers are used to find solutions. + + + componentType + var_str('unknown') + The component type that this mass distribution represents. + parameters + + + massType + var_str('unknown') + The mass type that this mass distribution represents. + parameters + + + !!] + select type (massDistribution_) + class is (massDistributionSpherical) + self=massDistributionSphericalAccelerator(toleranceRelative,factorRadiusMaximum,enumerationNonAnalyticSolversEncode(char(nonAnalyticSolver),includesPrefix=.false.),massDistribution_,enumerationComponentTypeEncode(componentType,includesPrefix=.false.),enumerationMassTypeEncode(massType,includesPrefix=.false.)) + class default + call Error_Report('a spherically-symmetric mass distribution is required'//{introspection:location}) + end select + !![ + + + !!] + return + end function sphericalAcceleratorConstructorParameters + + function sphericalAcceleratorConstructorInternal(toleranceRelative,factorRadiusMaximum,nonAnalyticSolver,massDistribution_,componentType,massType) result(self) + !!{ + Constructor for ``sphericalAccelerator'' mass distribution class. + !!} + implicit none + type (massDistributionSphericalAccelerator) :: self + class (massDistributionSpherical ), intent(in ), target :: massDistribution_ + double precision , intent(in ) :: toleranceRelative, factorRadiusMaximum + type (enumerationNonAnalyticSolversType ), intent(in ) :: nonAnalyticSolver + type (enumerationComponentTypeType ), intent(in ), optional :: componentType + type (enumerationMassTypeType ), intent(in ), optional :: massType + !![ + + !!] + + self%factorRadiusLogarithmicMaximum=+log(sqrt(factorRadiusMaximum)) + self%dimensionless =self%massDistribution_%isDimensionless() + return + end function sphericalAcceleratorConstructorInternal + + subroutine sphericalAcceleratorDestructor(self) + !!{ + Destructor for the abstract {\normalfont \ttfamily massDistributionSphericalAccelerator} class. + !!} + implicit none + type(massDistributionSphericalAccelerator), intent(inout) :: self + + !![ + + !!] + return + end subroutine sphericalAcceleratorDestructor + + logical function sphericalAcceleratorUseUndecorated(self) result(useUndecorated) + !!{ + Determines whether to use the undecorated solution. + !!} + implicit none + class(massDistributionSphericalAccelerator), intent(inout) :: self + + useUndecorated=.false. + return + end function sphericalAcceleratorUseUndecorated + + double precision function sphericalAcceleratorDensity(self,coordinates) result(density) + !!{ + Return the density at the specified {\normalfont \ttfamily coordinates} in an accelerated mass distribution. + !!} + implicit none + class(massDistributionSphericalAccelerator), intent(inout) :: self + class(coordinate ), intent(in ) :: coordinates + + density=self%massDistribution_%density(coordinates) + return + end function sphericalAcceleratorDensity + + double precision function sphericalAcceleratorMassEnclosedBySphere(self,radius) result(mass) + !!{ + Computes the mass enclosed within a sphere of given {\normalfont \ttfamily radius} for accelerated mass distributions. + !!} + use :: Binary_Search_Trees , only : binaryTreeNode + use :: Numerical_Comparison, only : Values_Agree + implicit none + class (massDistributionSphericalAccelerator), intent(inout), target :: self + double precision , intent(in ) :: radius + type (binaryTreeNode ), pointer :: left1 , left2 , & + & right1 , right2 + double precision :: massEnclosed1 , massEnclosed2, & + & radiusLogarithmic + logical :: found + + found =.false. + radiusLogarithmic=log(radius) + call self%treeMassEnclosed%bracket(radiusLogarithmic,left1,right1) + if (associated(left1).and.associated(right1)) then + if (associated(left1,right1)) then + mass=exp(left1%value) + found =.true. + else + if ( & + & +radiusLogarithmic- left1%key < self%factorRadiusLogarithmicMaximum & + & .and. & + & -radiusLogarithmic+right1%key < self%factorRadiusLogarithmicMaximum & + & ) then + left2 => left1%predecessor() + right2 => right1% successor() + if (associated(left2).and.associated(right2)) then + massEnclosed1=(radiusLogarithmic-left1%key)*(right1%value-left1%value)/(right1%key-left1%key)+left1%value + massEnclosed2=(radiusLogarithmic-left2%key)*(right2%value-left2%value)/(right2%key-left2%key)+left2%value + if (Values_Agree(massEnclosed1,massEnclosed2,relTol=self%toleranceRelative)) then + mass=exp(massEnclosed1) + found =.true. + end if + end if + end if + end if + end if + if (.not.found) then + mass=self%massDistribution_%massEnclosedBySphere(radius) + call self%treeMassEnclosed%insert(radiusLogarithmic,log(mass)) + end if + return + end function sphericalAcceleratorMassEnclosedBySphere diff --git a/source/mass_distributions.spherical.accretion_flow.DiemerKravtsov2014.F90 b/source/mass_distributions.spherical.accretion_flow.DiemerKravtsov2014.F90 new file mode 100644 index 0000000000..1fc509bfdb --- /dev/null +++ b/source/mass_distributions.spherical.accretion_flow.DiemerKravtsov2014.F90 @@ -0,0 +1,186 @@ +!! Copyright 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, +!! 2019, 2020, 2021, 2022, 2023 +!! Andrew Benson +!! +!! This file is part of Galacticus. +!! +!! Galacticus is free software: you can redistribute it and/or modify +!! it under the terms of the GNU General Public License as published by +!! the Free Software Foundation, either version 3 of the License, or +!! (at your option) any later version. +!! +!! Galacticus is distributed in the hope that it will be useful, +!! but WITHOUT ANY WARRANTY; without even the implied warranty of +!! MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +!! GNU General Public License for more details. +!! +!! You should have received a copy of the GNU General Public License +!! along with Galacticus. If not, see . + + !!{ + Implementation of a mass distribution for accretion flow using the fitting function of \cite{diemer_dependence_2014}. + !!} + + !![ + + + A mass distribution class for accretion flows which models the accretion flow using the fitting function of + \cite{diemer_dependence_2014}. Specifically, the density profile of the accretion flow is modeled using their equation~(4). + + + !!] + type, public, extends(massDistributionSpherical) :: massDistributionDiemerKravtsov2014 + !!{ + A mass distribution for accretion flow using the fitting function of \cite{diemer_dependence_2014}. + !!} + private + double precision :: radius200Mean, densityMean, & + & b , s + logical :: includeMean + contains + procedure :: density => diemerKravtsov2014Density + procedure :: densityGradientRadial => diemerKravtsov2014DensityGradientRadial + end type massDistributionDiemerKravtsov2014 + + interface massDistributionDiemerKravtsov2014 + !!{ + Constructors for the {\normalfont \ttfamily diemerKravtsov2014} mass distribution class. + !!} + module procedure massDistributionDiemerKravtsov2014ConstructorParameters + module procedure massDistributionDiemerKravtsov2014ConstructorInternal + end interface massDistributionDiemerKravtsov2014 + +contains + + function massDistributionDiemerKravtsov2014ConstructorParameters(parameters) result(self) + !!{ + Constructor for the {\normalfont \ttfamily diemerKravtsov2014} mass distribution class which builds the object from a parameter + set. + !!} + use :: Input_Parameters , only : inputParameter , inputParameters + use :: Galactic_Structure_Options, only : enumerationComponentTypeEncode, enumerationMassTypeEncode + implicit none + type (massDistributionDiemerKravtsov2014) :: self + type (inputParameters ), intent(inout) :: parameters + double precision :: radius200Mean, densityMean, & + & b , s + logical :: includeMean + type (varying_string ) :: componentType + type (varying_string ) :: massType + + !![ + + densityMean + The mean density of the universe in the \cite{diemer_dependence_2014} accretion flow mass distribution. + parameters + + + radius200Mean + The radius enclosing a density of 200 times the mean density of the universe in the \cite{diemer_dependence_2014} accretion flow mass distribution. + parameters + + + includeMean + If true, include the mean density of the universe in the profile, otherwise, subtract off that mean density. + parameters + .true. + + + b + The coefficient $b$ in the \cite{diemer_dependence_2014} accretion flow mass distribution. + parameters + + + s + The exponent $s$ in the \cite{diemer_dependence_2014} accretion flow mass distribution. + parameters + + + componentType + var_str('unknown') + The component type that this mass distribution represents. + parameters + + + massType + var_str('unknown') + The mass type that this mass distribution represents. + parameters + + !!] + self=massDistributionDiemerKravtsov2014(densityMean,radius200Mean,includeMean,b,s,componentType=enumerationComponentTypeEncode(componentType,includesPrefix=.false.),massType=enumerationMassTypeEncode(massType,includesPrefix=.false.)) + !![ + + !!] + return + end function massDistributionDiemerKravtsov2014ConstructorParameters + + function massDistributionDiemerKravtsov2014ConstructorInternal(densityMean,radius200Mean,includeMean,b,s,componentType,massType) result(self) + !!{ + Internal constructor for ``diemerKravtsov2014'' mass distribution class. + !!} + implicit none + type (massDistributionDiemerKravtsov2014) :: self + double precision , intent(in ) :: densityMean , radius200Mean, & + & b , s + logical , intent(in ) :: includeMean + type (enumerationComponentTypeType ), intent(in ), optional :: componentType + type (enumerationMassTypeType ), intent(in ), optional :: massType + !![ + + !!] + + self%dimensionless=.false. + return + end function massDistributionDiemerKravtsov2014ConstructorInternal + + double precision function diemerKravtsov2014Density(self,coordinates) result(density) + !!{ + Return the density at the specified {\normalfont \ttfamily coordinates} in a \cite{diemer_dependence_2014} mass distribution. + !!} + use :: Coordinates, only : assignment(=), coordinateSpherical + implicit none + class(massDistributionDiemerKravtsov2014), intent(inout) :: self + class(coordinate ), intent(in ) :: coordinates + + density=+self%densityMean & + & *self%b & + & /( & + & +coordinates%rSpherical () & + & /5.0d0 & + & /self %radius200Mean & + & )**self%s + if (self%includeMean) & + & density=+ density & + & +self%densityMean + return + end function diemerKravtsov2014Density + + double precision function diemerKravtsov2014DensityGradientRadial(self,coordinates,logarithmic) result(densityGradient) + !!{ + Return the density gradient at the specified {\normalfont \ttfamily coordinates} in a \cite{diemer_dependence_2014} mass distribution. + !!} + implicit none + class (massDistributionDiemerKravtsov2014), intent(inout), target :: self + class (coordinate ), intent(in ) :: coordinates + logical , intent(in ), optional :: logarithmic + !![ + + !!] + + densityGradient=-self%s & + & /( & + & +1.0d0 & + & +( & + & +coordinates%rSpherical () & + & /5.0d0 & + & /self %radius200Mean & + & )**self%s & + & /self%b & + & ) + if (.not.logarithmic_) & + & densityGradient=+ densityGradient & + & *self %density (coordinates) & + & /coordinates%rSpherical ( ) + return + end function diemerKravtsov2014DensityGradientRadial diff --git a/source/mass_distributions.spherical.accretion_flow.F90 b/source/mass_distributions.spherical.accretion_flow.F90 new file mode 100644 index 0000000000..227cc1f906 --- /dev/null +++ b/source/mass_distributions.spherical.accretion_flow.F90 @@ -0,0 +1,269 @@ +!! Copyright 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, +!! 2019, 2020, 2021, 2022, 2023 +!! Andrew Benson +!! +!! This file is part of Galacticus. +!! +!! Galacticus is free software: you can redistribute it and/or modify +!! it under the terms of the GNU General Public License as published by +!! the Free Software Foundation, either version 3 of the License, or +!! (at your option) any later version. +!! +!! Galacticus is distributed in the hope that it will be useful, +!! but WITHOUT ANY WARRANTY; without even the implied warranty of +!! MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +!! GNU General Public License for more details. +!! +!! You should have received a copy of the GNU General Public License +!! along with Galacticus. If not, see . + + !!{ + Implements a accretionFlow spherical mass distribution. + !!} + + !![ + + + An implementation of a mass distribution which includes the accretion flow surrounding a halo. The density + profile is modeled as + \begin{equation} + \rho(r) = f_\mathrm{trans}(r) \rho_\mathrm{halo}(r) + \rho_\mathrm{accretion}(r), + \end{equation} + where $\rho_\mathrm{halo}(r)$ is the halo mass distribution, $\rho_\mathrm{accretion}(r)$ is the accretion flow mass distribution, + and + \begin{equation} + f_\mathrm{trans}(r) = \left( 1 + \left[\frac{r}{r_\mathrm{trans}}\right]^4 \right)^{-2}. + \end{equation} + + + !!] + type, extends(massDistributionSphericalDecorator) :: massDistributionSphericalAccretionFlow + !!{ + Implementation of an accretion flow spherical mass distribution. + !!} + private + class (massDistributionClass), pointer :: massDistributionAccretionFlow_ => null() + double precision :: radiusTransition + contains + !![ + + + + !!] + final :: sphericalAccretionFlowDestructor + procedure :: density => sphericalAccretionFlowDensity + procedure :: densityGradientRadial => sphericalAccretionFlowDensityGradientRadial + procedure :: energyPotential => sphericalAccretionFlowEnergyPotential + procedure :: energyKinetic => sphericalAccretionFlowEnergyKinetic + procedure :: transitionFunction => sphericalAccretionFlowTransitionFunction + end type massDistributionSphericalAccretionFlow + + interface massDistributionSphericalAccretionFlow + !!{ + Constructors for the {\normalfont \ttfamily sphericalAccretionFlow} mass distribution class. + !!} + module procedure sphericalAccretionFlowConstructorParameters + module procedure sphericalAccretionFlowConstructorInternal + end interface massDistributionSphericalAccretionFlow + +contains + + function sphericalAccretionFlowConstructorParameters(parameters) result(self) + !!{ + Constructor for the {\normalfont \ttfamily sphericalAccretionFlow} mass distribution class which builds the object from a parameter + set. + !!} + use :: Input_Parameters , only : inputParameters + use :: Galactic_Structure_Options, only : enumerationComponentTypeEncode, enumerationMassTypeEncode + implicit none + type (massDistributionSphericalAccretionFlow) :: self + type (inputParameters ), intent(inout) :: parameters + class (massDistributionClass ), pointer :: massDistribution_, massDistributionAccretionFlow_ + type (varying_string ) :: nonAnalyticSolver + double precision :: radiusTransition + type (varying_string ) :: componentType , massType + + !![ + + radiusTransition + parameters + The transition radius. + + + nonAnalyticSolver + var_str('fallThrough') + parameters + Selects how solutions are computed when no analytic solution is available. If set to ``{\normalfont \ttfamily fallThrough}'' then the solution ignoring heating is used, while if set to ``{\normalfont \ttfamily numerical}'' then numerical solvers are used to find solutions. + + + componentType + var_str('unknown') + The component type that this mass distribution represents. + parameters + + + massType + var_str('unknown') + The mass type that this mass distribution represents. + parameters + + + + !!] + select type (massDistribution_) + class is (massDistributionSpherical) + select type (massDistributionAccretionFlow_) + class is (massDistributionSpherical) + self=massDistributionSphericalAccretionFlow(radiusTransition,enumerationNonAnalyticSolversEncode(char(nonAnalyticSolver),includesPrefix=.false.),massDistribution_,massDistributionAccretionFlow_,enumerationComponentTypeEncode(componentType,includesPrefix=.false.),enumerationMassTypeEncode(massType,includesPrefix=.false.)) + class default + call Error_Report('a spherically-symmetric accretion flow mass distribution is required'//{introspection:location}) + end select + class default + call Error_Report('a spherically-symmetric mass distribution is required' //{introspection:location}) + end select + !![ + + + !!] + return + end function sphericalAccretionFlowConstructorParameters + + function sphericalAccretionFlowConstructorInternal(radiusTransition,nonAnalyticSolver,massDistribution_,massDistributionAccretionFlow_,componentType,massType) result(self) + !!{ + Constructor for ``sphericalAccretionFlow'' mass distribution class. + !!} + implicit none + type (massDistributionSphericalAccretionFlow) :: self + class (massDistributionSpherical ), intent(in ), target :: massDistribution_, massDistributionAccretionFlow_ + double precision , intent(in ) :: radiusTransition + type (enumerationNonAnalyticSolversType ), intent(in ) :: nonAnalyticSolver + type (enumerationComponentTypeType ), intent(in ), optional :: componentType + type (enumerationMassTypeType ), intent(in ), optional :: massType + !![ + + !!] + + self%dimensionless=.false. + return + end function sphericalAccretionFlowConstructorInternal + + subroutine sphericalAccretionFlowDestructor(self) + !!{ + Destructor for the abstract {\normalfont \ttfamily massDistributionSphericalAccretionFlow} class. + !!} + implicit none + type(massDistributionSphericalAccretionFlow), intent(inout) :: self + + !![ + + + !!] + return + end subroutine sphericalAccretionFlowDestructor + + subroutine sphericalAccretionFlowTransitionFunction(self,radius,multiplier,multiplierGradient) + !!{ + Return the scaled truncation radial coordinate, and the truncation multiplier. + !!} + implicit none + class (massDistributionSphericalAccretionFlow), intent(inout) :: self + double precision , intent(in ) :: radius + double precision , intent( out), optional :: multiplier, multiplierGradient + double precision :: x + + x =+ radius & + & /self%radiusTransition + if (present(multiplier )) then + multiplier =+1.0d0 & + & /( & + & +1.0d0 & + & +x**4 & + & )**2 + end if + if (present(multiplierGradient)) then + multiplierGradient=+8.0d0 & + & * x**3 & + & /( & + & +1.0d0 & + & +x**4 & + & )**3 & + & /self%radiusTransition + end if + return + end subroutine sphericalAccretionFlowTransitionFunction + + double precision function sphericalAccretionFlowDensity(self,coordinates) result(density) + !!{ + Return the density at the specified {\normalfont \ttfamily coordinates} in a scaled spherical mass distribution. + !!} + implicit none + class (massDistributionSphericalAccretionFlow), intent(inout) :: self + class (coordinate ), intent(in ) :: coordinates + double precision :: multiplier + + call self%transitionFunction(radius=coordinates%rSpherical(),multiplier=multiplier) + density=+self%massDistribution_ %density(coordinates) & + & * multiplier & + & +self%massDistributionAccretionFlow_%density(coordinates) & + & *(1.0d0-multiplier) + return + end function sphericalAccretionFlowDensity + + double precision function sphericalAccretionFlowDensityGradientRadial(self,coordinates,logarithmic) result(densityGradient) + !!{ + Return the density at the specified {\normalfont \ttfamily coordinates} in a accretionFlow spherical mass distribution. + !!} + implicit none + class (massDistributionSphericalAccretionFlow), intent(inout), target :: self + class (coordinate ), intent(in ) :: coordinates + logical , intent(in ), optional :: logarithmic + double precision :: multiplier , multiplierGradient + !![ + + !!] + + call self%transitionFunction(radius=coordinates%rSpherical(),multiplier=multiplier,multiplierGradient=multiplierGradient) + densityGradient=+ self%massDistribution_ %densityGradientRadial(coordinates,logarithmic=.false.) & + & * multiplier & + & + self%massDistributionAccretionFlow_%densityGradientRadial(coordinates,logarithmic=.false.) & + & *(1.0d0-multiplier ) & + & +( & + & +self%massDistribution_ %density (coordinates ) & + & -self%massDistributionAccretionFlow_%density (coordinates ) & + & ) & + & + multiplierGradient + if (logarithmic_) densityGradient=+ densityGradient & + & *coordinates%rSpherical ( ) & + & /self %density (coordinates) + return + end function sphericalAccretionFlowDensityGradientRadial + + double precision function sphericalAccretionFlowEnergyPotential(self,radiusOuter) result(energy) + !!{ + Compute the potential energy within a given {\normalfont \ttfamily radius} in a spherical accretion flow mass + distribution. Note that this is defined to be the potential energy of the \emph{virialized} component of the mass + distribution---the accretion flow itself is excluded. + !!} + implicit none + class (massDistributionSphericalAccretionFlow), intent(inout) :: self + double precision , intent(in ) :: radiusOuter + + energy=self%massDistribution_%energyPotential(radiusOuter) + return + end function sphericalAccretionFlowEnergyPotential + + double precision function sphericalAccretionFlowEnergyKinetic(self,radiusOuter,massDistributionEmbedding) result(energy) + !!{ + Compute the kinetic energy within a given {\normalfont \ttfamily radius} in a spherical accretion flow mass distribution. Note + that this is defined to be the potential energy of the \emph{virialized} component of the mass distribution---the accretion + flow itself is excluded. + !!} + implicit none + class (massDistributionSphericalAccretionFlow), intent(inout) :: self + double precision , intent(in ) :: radiusOuter + class (massDistributionClass ), intent(inout) :: massDistributionEmbedding + + energy=self%massDistribution_%energyKinetic(radiusOuter,massDistributionEmbedding) + return + end function sphericalAccretionFlowEnergyKinetic + diff --git a/source/mass_distributions.spherical.accretion_flow.Shi2016.F90 b/source/mass_distributions.spherical.accretion_flow.Shi2016.F90 new file mode 100644 index 0000000000..680da88ce6 --- /dev/null +++ b/source/mass_distributions.spherical.accretion_flow.Shi2016.F90 @@ -0,0 +1,760 @@ +!! Copyright 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, +!! 2019, 2020, 2021, 2022, 2023 +!! Andrew Benson +!! +!! This file is part of Galacticus. +!! +!! Galacticus is free software: you can redistribute it and/or modify +!! it under the terms of the GNU General Public License as published by +!! the Free Software Foundation, either version 3 of the License, or +!! (at your option) any later version. +!! +!! Galacticus is distributed in the hope that it will be useful, +!! but WITHOUT ANY WARRANTY; without even the implied warranty of +!! MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +!! GNU General Public License for more details. +!! +!! You should have received a copy of the GNU General Public License +!! along with Galacticus. If not, see . + + !!{ + An accretion flow mass distribution using the framework of \cite{shi_outer_2016}. + !!} + + use :: Cosmology_Functions , only : cosmologyFunctionsClass + use :: Numerical_Interpolation, only : gsl_interp_cspline , interpolator + + ! Note: Throughout this class the following acronyms are used: + ! * HRTA - "half radius turnaround" - i.e. half of the turnaround radius for a given shell (more precisely, we use the + ! ratio of the virial radius to turnaround radius determined by spherical collapse models - this is previously + ! 1/2 for an Einstein-de Sitter universe, but differs by a small amount for other cosmologies). + + ! Note: Throughout this class different three separate unit systems are used, identified by the variable name suffix: + ! * Scaled - these correspond to the scaled, self-similar variables used in Appendix A of Shi (2016) - i.e. column 3 of Table A1, "y" for radius, etc. + ! * Original - these correspond to the original variables used in Appendix A of Shi (2016) - i.e. column 2 of Table A1, "R" for radius, etc. + ! * ScaleHRTA - these correspond to the HRTA unit system - that is, quantities are scaled to Rₕᵣₜₐ(a) and Mₕᵣₜₐ(a). + ! * Physical - these correspond to physical units (Mpc, M☉, km/s). + + !![ + + + A mass distribution for accretion flows using the framework of \cite{shi_outer_2016}. + + + !!] + type, public, extends(massDistributionSpherical) :: massDistributionShi2016 + !!{ + A mass distribution for accretion flows using the framework of \cite{shi_outer_2016}. + !!} + private + class (cosmologyFunctionsClass ), pointer :: cosmologyFunctions_ => null() + type (interpolator ), allocatable :: interpolatorDensityPhysical , interpolatorVelocityPhysical , & + & interpolatorScaleFactorHalfRadiusTurnaroundScaled , interpolatorRadiusScaled , & + & interpolatorRadiusTurnaroundOriginal , interpolatorRadiusHRTAOriginal , & + & interpolatorRadiusComovingInitialOriginal , interpolatorMassTurnaroundScaled , & + & interpolatorMassHRTAOriginal , interpolatorMassMultiStreamScaleHRTA + double precision , allocatable, dimension(:) :: radiusScaled , overdensityScaled , & + & expansionFactorHRTAScaled , radiusGrowthRateScaled , & + & radiusComovingInitialOriginal , massEnclosedInitialOriginal , & + & timeTurnaroundScaled , timeHRTAScaled , & + & radiusTurnaroundScaled , radiusOrderedOriginal , & + & massShellOrderedOriginal , densityOrderedOriginal , & + & massEnclosedOrderedOriginal + double precision :: radiusMaximumPhysical , scaleFactorVelocity , & + & expansionFactorScaled , radiusMultistreamMinimumScaledHRTA , & + & radiusMultistreamMaximumScaledHRTA , cosmologicalConstantScaled , & + & radiusTurnaroundNowOriginal , radiusHRTANowOriginal , & + & timeNowScaled , radiusSplashbackTurnaround , & + & ratioRadiusSplashbackHRTA , radiusSplashbackOriginal , & + & radiusSplashbackScaled , radiusMinimumPhysical , & + & radiusVirial , mass , & + & massAccretionRate , redshift , & + & time , ratioRadiusTurnaroundVirial + contains + !![ + + + + !!] + procedure :: density => shi2016Density + procedure :: densityGradientRadial => shi2016DensityGradientRadial + procedure :: solve => shi2016Solve + end type massDistributionShi2016 + + interface massDistributionShi2016 + !!{ + Constructors for the {\normalfont \ttfamily shi2016} mass distribution class. + !!} + module procedure massDistributionShi2016ConstructorParameters + module procedure massDistributionShi2016ConstructorInternal + end interface massDistributionShi2016 + + ! Sub-module scope variables used in root finding. + double precision :: timeTurnaroundScaled__, radiusTurnaroundScaled__ + !$omp threadprivate(timeTurnaroundScaled__, radiusTurnaroundScaled__) + + ! Sub-module scope variables used in ODE solving. + double precision :: radiusComovingInitialOriginal , massEnclosedInitialOriginal + class (massDistributionShi2016), pointer :: self_ + logical :: noShellCrossing =.false. + !$omp threadprivate(self_,massEnclosedInitialOriginal,radiusComovingInitialOriginal,noShellCrossing) + +contains + + function massDistributionShi2016ConstructorParameters(parameters) result(self) + !!{ + Constructor for the {\normalfont \ttfamily shi2016} mass distribution class which builds the object from a parameter + set. + !!} + use :: Input_Parameters , only : inputParameter , inputParameters + use :: Galactic_Structure_Options, only : enumerationComponentTypeEncode, enumerationMassTypeEncode + implicit none + type (massDistributionShi2016 ) :: self + type (inputParameters ), intent(inout) :: parameters + type (varying_string ) :: componentType + type (varying_string ) :: massType + class (cosmologyFunctionsClass ), pointer :: cosmologyFunctions_ + double precision :: scaleFactorVelocity , mass , & + & massAccretionRate , ratioRadiusTurnaroundVirial, & + & radiusVirial , redshift + + !![ + + mass + The mass of the halo. + parameters + + + massAccretionRate + The mass accretion rate of the halo. + parameters + + + radiusVirial + The virial radius of the halo. + parameters + + + ratioRadiusTurnaroundVirial + The ratio of the turnaround to virial radii of the halo. + parameters + + + redshift + The redshift of the halo. + parameters + + + componentType + var_str('unknown') + The component type that this mass distribution represents. + parameters + + + massType + var_str('unknown') + The mass type that this mass distribution represents. + parameters + + + !!] + self=massDistributionShi2016(mass,massAccretionRate,radiusVirial,ratioRadiusTurnaroundVirial,cosmologyFunctions_%cosmicTime(cosmologyFunctions_%expansionFactorFromRedshift(redshift)),scaleFactorVelocity,cosmologyFunctions_,componentType=enumerationComponentTypeEncode(componentType,includesPrefix=.false.),massType=enumerationMassTypeEncode(massType,includesPrefix=.false.)) + !![ + + + !!] + return + end function massDistributionShi2016ConstructorParameters + + function massDistributionShi2016ConstructorInternal(mass,massAccretionRate,radiusVirial,ratioRadiusTurnaroundVirial,time,scaleFactorVelocity,cosmologyFunctions_,componentType,massType) result(self) + !!{ + Internal constructor for ``shi2016'' mass distribution class. + !!} + implicit none + type (massDistributionShi2016 ) :: self + class (cosmologyFunctionsClass ), intent(in ), target :: cosmologyFunctions_ + double precision , intent(in ) :: mass , massAccretionRate , & + & time , scaleFactorVelocity , & + & radiusVirial , ratioRadiusTurnaroundVirial + type (enumerationComponentTypeType), intent(in ), optional :: componentType + type (enumerationMassTypeType ), intent(in ), optional :: massType + !![ + + !!] + + self%dimensionless=.false. + self%redshift =self%cosmologyFunctions_%redshiftFromExpansionFactor(self%cosmologyFunctions_%expansionFactor(time)) + call self%solve() + return + end function massDistributionShi2016ConstructorInternal + + subroutine shi2016Destructor(self) + !!{ + Destructor for the {\normalfont \ttfamily shi2016} accretion flow mass distribution class. + !!} + implicit none + type(massDistributionShi2016), intent(inout) :: self + + !![ + + !!] + return + end subroutine shi2016Destructor + + double precision function shi2016Density(self,coordinates) result(density) + !!{ + Return the density at the specified {\normalfont \ttfamily coordinates} in a accretion flow modeled on the 2-halo correlation function. + !!} + implicit none + class(massDistributionShi2016), intent(inout) :: self + class(coordinate ), intent(in ) :: coordinates + + if (coordinates%rSpherical() > self%radiusMaximumPhysical) then + ! Beyond the maximum radius for the flow just return the mean matter density. + density=self%cosmologyFunctions_ %matterDensityEpochal(self %time ) + else if (coordinates%rSpherical() < self%radiusMinimumPhysical) then + density=0.0d0 + call Error_Report('radius is less than minimum tabulated for accretion flow'//{introspection:location}) + else + density=self%interpolatorDensityPhysical%interpolate (coordinates%rSpherical()) + end if + return + end function shi2016Density + + double precision function shi2016DensityGradientRadial(self,coordinates,logarithmic) result(densityGradientRadial) + !!{ + Return the radial density gradient at the specified {\normalfont \ttfamily coordinates} in a accretion flow modeled on the 2-halo correlation function. + !!} + implicit none + class (massDistributionShi2016), intent(inout), target :: self + class (coordinate ), intent(in ) :: coordinates + logical , intent(in ), optional :: logarithmic + !![ + + !!] + + if (coordinates%rSpherical() > self%radiusMaximumPhysical) then + ! Beyond the maximum radius for the flow just return the mean matter density. + densityGradientRadial=0.0d0 + else if (coordinates%rSpherical() < self%radiusMinimumPhysical) then + densityGradientRadial=0.0d0 + call Error_Report('radius is less than minimum tabulated for accretion flow'//{introspection:location}) + else + densityGradientRadial=self%interpolatorDensityPhysical%derivative(coordinates%rSpherical()) + end if + return + end function shi2016DensityGradientRadial + + subroutine shi2016Solve(self) + !!{ + Solve the accretion flow. + !!} + use :: Array_Utilities , only : Array_Reverse + use :: Display , only : displayCounter , displayCounterClear , displayIndent , displayUnindent, & + & verbosityLevelWorking + use :: Elliptic_Integrals , only : Elliptic_Integral_K , Elliptic_Integral_Pi + use :: Error , only : Error_Report + use :: ISO_Varying_String , only : var_str + use :: Numerical_Comparison , only : Values_Differ + use :: Numerical_Constants_Astronomical, only : gigaYear , megaParsec + use :: Numerical_Constants_Math , only : Pi + use :: Numerical_Constants_Prefixes , only : kilo + use :: Numerical_Ranges , only : Make_Range , rangeTypeLogarithmic + use :: Root_Finder , only : rangeExpandMultiplicative, rangeExpandSignExpectNegative, rangeExpandSignExpectPositive, rootFinder + use :: Sorting , only : sortIndex + use :: String_Handling , only : operator(//) + implicit none + class (massDistributionShi2016), intent(inout), target :: self + double precision , allocatable , dimension(:) :: radiusOriginal , densityOriginal , & + & velocityOriginal , radiusSingleStreamAnalyticPhysical , & + & velocitySingleStreamAnalyticPhysical , densitySingleStreamAnalyticPhysical , & + & expansionFactorHRTAScaled , radiusPhysical , & + & velocityPhysical , densityPhysical + integer (c_size_t ), allocatable , dimension(:) :: order + type (interpolator ), allocatable :: interpolatorMassMultiStreamNewScaleHRTA + integer , parameter :: countRadii =1000 + integer , parameter :: countCompare = 10 + integer , parameter :: iterationMaximum = 30 + double precision , parameter :: expansionFactorRelativeInitial =1.0d-6 + double precision , parameter :: radiusMultiStreamFractionalSmall =1.0d-6 + double precision , parameter :: multistreamToleranceRelative =5.0d-2 + double precision :: expansionFactorOriginal , timeInitialScaled , & + & radiusInitialScaled , radiusGrowthRateInitialScaled , & + & overdensityMinimumScaled , overdensityMaximumScaled , & + & bigA , bigB , & + & bigC , expansionFactorScaledInitial , & + & massVirialOriginal , radiusVirialOriginal , & + & growthIndex , radiusScaleHRTA , & + & radiusMultistreamMinimumNewScaledHRTA , radiusMultistreamMaximumNewScaledHRTA, & + & radiusCompareMinimumScaledHRTA , radiusCompareMaximumScaledHRTA , & + & massMultiStream , massMultiStreamNew , & + & changeRelative , changeRelativeMaximum , & + & radiusSplashbackPhysical , h + integer :: i , j , & + & iTurnaround , iFirstZero , & + & iVirial , iteration + type (rootFinder ) :: finder + logical :: firstZeroFound , multistreamConverged + character (len=12 ) :: label + + ! The growth index specifies the profile of the initial mass perturbation, δMᵢ/Mᵢ ∝ M^{-1/s}, or, equivalently, the growth rate, + ! M(t) ∝ aˢ. + growthIndex=+self %massAccretionRate & + & /self%cosmologyFunctions_%expansionRate (self%cosmologyFunctions_%expansionFactor(self%time)) & + & /self %mass + ! Compute the scale-free solution. + allocate(self%overdensityScaled (countRadii)) + allocate(self%radiusScaled (countRadii)) + allocate(self%radiusGrowthRateScaled (countRadii)) + allocate(self%expansionFactorHRTAScaled (countRadii)) + allocate(self%radiusComovingInitialOriginal(countRadii)) + allocate(self%massEnclosedInitialOriginal (countRadii)) + allocate(self%radiusTurnaroundScaled (countRadii)) + allocate(self%timeTurnaroundScaled (countRadii)) + allocate(self%radiusOrderedOriginal (countRadii)) + allocate(self%massShellOrderedOriginal (countRadii)) + allocate(self%massEnclosedOrderedOriginal (countRadii)) + allocate(self%densityOrderedOriginal (countRadii)) + allocate(self%timeHRTAScaled (countRadii)) + allocate( order (countRadii)) + allocate( expansionFactorHRTAScaled (countRadii)) + allocate( radiusOriginal (countRadii)) + ! Create a module-scope pointer to self for use in ODE solver functions. + self_ => self + ! Compute the scaled cosmological constant parameter ("w" in the notation of Shi 2016, Table A1). + self%cosmologicalConstantScaled=+1.0d0/self%cosmologyFunctions_ %OmegaMatterEpochal(self%time) & + & -1.0d0 + ! Compute expansion factor, and scaled expansion factor ("y" in the notation of Shi 2016, Table A1). + expansionFactorOriginal =+self%cosmologyFunctions_ %expansionFactor (self%time) + self%expansionFactorScaled =+self%cosmologicalConstantScaled **(1.0d0/3.0d0) & + & *expansionFactorOriginal + ! Find the scaled time at the current epoch. + self%timeNowScaled =+sqrt(1.0d0-self %cosmologyFunctions_%OmegaMatterEpochal(self%time )) & ! Equation A5 from Shi (2016). + & * self %cosmologyFunctions_%expansionRate (expansionFactorOriginal) & + & * self%time + ! Choose an initial epoch and (scaled) radius for the ODE. This is chosen to be an expansion factor much smaller than the + ! present day such that the perturbations will be small. + expansionFactorScaledInitial =+ expansionFactorRelativeInitial & + & *self%expansionFactorScaled + radiusInitialScaled =+ expansionFactorScaledInitial + ! Choose a value of the scaled overdensity, "β" in the notation of Shi (2016), identifying a mass shell. We avoid β=1 because such a shell never collapses. + overdensityMinimumScaled =+1.001d0 + ! Choose a maximum overdensity. We use β=10 here as it's more than sufficient to allow most of the accretion stream to be captured, + overdensityMaximumScaled =+1.000d1 + ! Build array of overdensities. + self%overdensityScaled =Make_Range(overdensityMaximumScaled,overdensityMinimumScaled,countRadii,rangeTypeLogarithmic) + ! Build a root finder which will be used for finding the time at half the turnaround radius. + finder=rootFinder( & + & rootFunction =halfRadiusTurnAroundRoot, & + & toleranceAbsolute=0.0d+0 , & + & toleranceRelative=1.0d-3 & + & ) + ! Find turnaround radius and mass as a function of time, along with epoch at which half the turnaround radius is + ! reached. This is all in scale-free units. + do i=1,countRadii + ! Find the epoch of turnaround. + self%radiusTurnaroundScaled(i)=+2.0d0**(2.0d0/3.0d0) & ! Equation A9 from Shi (2016). + & * sqrt( self%overdensityScaled(i) ) & + & *sin((1.0d0/3.0d0)*asin(1.0d0/self%overdensityScaled(i)**1.5d0)) + bigA =+1.0d0 & ! Text after equation of A12 from Shi (2016). + & /self%radiusTurnaroundScaled(i)**3 + bigB =+2.0d0 & ! Text after equation of A12 from Shi (2016). + & / ( +3.0d0+sqrt(1.0d0+4.0d0*bigA) ) + bigC =+ sqrt(1.0d0+4.0d0*bigA) & ! Text after equation of A12 from Shi (2016). + & / (bigA-0.5d0+sqrt(1.0d0+4.0d0*bigA)/2.0d0) + self%timeTurnaroundScaled (i)=+ ( +1.0d0+sqrt(1.0d0+4.0d0*bigA) ) & ! Equation A12 of Shi (2016). + & /sqrt(bigA-0.5d0+sqrt(1.0d0+4.0d0*bigA)/2.0d0) & + & *( & + & +Elliptic_Integral_Pi(bigC,bigB) & + & -Elliptic_Integral_K (bigC ) & + & ) + ! Find the epoch corresponding to reaching half of the turnaround radius. + radiusTurnaroundScaled__=self%radiusTurnaroundScaled(i) + timeTurnaroundScaled__ =self%timeTurnaroundScaled (i) + call finder%rangeExpand( & + & rangeExpandUpward =1.1d0 , & + & rangeExpandDownward =0.5d0 , & + & rangeExpandType =rangeExpandMultiplicative , & + & rangeExpandUpwardSignExpect =rangeExpandSignExpectNegative, & + & rangeExpandDownwardSignExpect=rangeExpandSignExpectPositive, & + & rangeDownwardLimit =timeTurnaroundScaled__ & + & ) + self%timeHRTAScaled (i)=finder%find(rootGuess=timeTurnaroundScaled__) + self%expansionFactorHRTAScaled(i)=expansionFactorFromTimeScaled(self%timeHRTAScaled(i)) + end do + ! Find enclosed masses. Note that this is found using the self-similarity assumption that the mass of a shell should scale + ! as the expansion factor at "half-radius-turnaround" to the power of the growth index, i.e. M ∝ uₕᵣₜₐˢ. In regions + ! where the cosmological constant is negligible (high overdensities, which collapse at early times), this will also give + ! the expected scaling with overdensity, M ∝ β⁻ˢ. But, at lower overdensities, which collapse when the + ! cosmological constant is non-negligible, the scaling with overdensity will change. What we do here seems to be consistent + ! with what Shi (2016) assumes, and has the nice feature that it ensures the mass-epoch relation is the simple scale-free + ! expectation at all times. + expansionFactorHRTAScaled =expansionFactorFromTimeScaled(self%timeHRTAScaled) + self%massEnclosedInitialOriginal =(expansionFactorHRTAScaled/self%expansionFactorScaled )**growthIndex + self%radiusComovingInitialOriginal= self%massEnclosedInitialOriginal **(1.0d0/3.0d0) + ! Build a variety of interpolators for different radii and masses as functions of scaled time. + allocate(self%interpolatorRadiusTurnaroundOriginal ) + allocate(self%interpolatorRadiusHRTAOriginal ) + allocate(self%interpolatorRadiusComovingInitialOriginal ) + allocate(self%interpolatorMassTurnaroundScaled ) + allocate(self%interpolatorMassHRTAOriginal ) + allocate(self%interpolatorScaleFactorHalfRadiusTurnaroundScaled) + self%interpolatorRadiusTurnaroundOriginal =interpolator( self%timeTurnaroundScaled , self%radiusTurnaroundScaled *self%radiusComovingInitialOriginal/self%cosmologicalConstantScaled**(1.0d0/3.0d0) ) + self%interpolatorRadiusComovingInitialOriginal =interpolator( self%timeTurnaroundScaled , self%radiusComovingInitialOriginal ) + self%interpolatorMassTurnaroundScaled =interpolator( self%timeTurnaroundScaled , self%massEnclosedInitialOriginal ) + self%interpolatorRadiusHRTAOriginal =interpolator( self%timeHRTAScaled , self%radiusTurnaroundScaled *self%radiusComovingInitialOriginal/self%cosmologicalConstantScaled**(1.0d0/3.0d0)/self%ratioRadiusTurnaroundVirial ) + self%interpolatorMassHRTAOriginal =interpolator( self%timeHRTAScaled , self%massEnclosedInitialOriginal ) + self%interpolatorScaleFactorHalfRadiusTurnaroundScaled=interpolator(Array_Reverse(self%overdensityScaled ),Array_Reverse(self%expansionFactorHRTAScaled )) + ! Compute present epoch turnaround and half-turnaround radii. + self%radiusTurnaroundNowOriginal=self%interpolatorRadiusTurnaroundOriginal%interpolate(self%timeNowScaled) + self%radiusHRTANowOriginal =self%interpolatorRadiusHRTAOriginal %interpolate(self%timeNowScaled) + ! Make an estimate of the splashback radius (in units of the turnaround radius, using eqn. 2 of Shi 2016 which is for an + ! Einstein-de Sitter universe). An approximate value is acceptable here as this is used only on the first iteration. It's + ! not clear if this is precisely what Shi (2016) chose, but it should not matter. + if (growthIndex <= 1.5d0) then + self%radiusSplashbackTurnaround=+1.0d0/3.0d0**(2.0d0/3.0d0+2.0d0*growthIndex/9.0d0) + else + self%radiusSplashbackTurnaround=+1.0d0/(1.0d0+4.0d0*(4.0d0*growthIndex/9.0d0+1.0d0/3.0d0)/sqrt(Pi)) + end if + ! Find the splashback radius in units of the present day half-turnaround radius. + self%ratioRadiusSplashbackHRTA=self%radiusSplashbackTurnaround*self%radiusTurnaroundNowOriginal/self%radiusHRTANowOriginal + ! For the first iteration, adopt a mass profile solution in the multi-stream region that has the form f(x)=x (Shi 2016, + ! section 2.1). + allocate(self%interpolatorMassMultiStreamScaleHRTA) + self%radiusMultistreamMinimumScaledHRTA=radiusMultiStreamFractionalSmall*self%ratioRadiusSplashbackHRTA + self%radiusMultistreamMaximumScaledHRTA= self%ratioRadiusSplashbackHRTA + self%interpolatorMassMultiStreamScaleHRTA=interpolator([self%radiusMultistreamMinimumScaledHRTA,self%radiusMultistreamMaximumScaledHRTA],[self%radiusMultistreamMinimumScaledHRTA,self%radiusMultistreamMaximumScaledHRTA]) + ! Begin iterating to find a solution. + iteration =0 + multistreamConverged=.false. + do while (iteration < iterationMaximum .and. .not.multistreamConverged) + iteration=iteration+1 + call displayIndent(var_str('multistream mass profile iteration ')//iteration,verbosity=verbosityLevelWorking) + ! Iterate over all overdensity shells solving for their radial position and velocity at the present epoch. + do i=1,countRadii + call displayCounter(int(100.0d0*dble(i-1)/dble(countRadii)),isNew=i==1,verbosity=verbosityLevelWorking) + ! Solve the dynamical ODEs to get the scaled radius at the final time. + radiusComovingInitialOriginal=self%radiusComovingInitialOriginal(i) + massEnclosedInitialOriginal =self%massEnclosedInitialOriginal (i) + radiusGrowthRateInitialScaled=+sqrt( & ! Equation A8 from Shi (2016). + & +1.0d0/ radiusInitialScaled & + & + radiusInitialScaled **2 & + & -3.0d0*self%overdensityScaled (i) & + & /2.0d0 **(2.0d0/3.0d0) & + & ) + timeInitialScaled =+2.0d0 & ! Equation A6 from Shi (2016). + & /3.0d0 & + & *asinh( expansionFactorScaledInitial **1.5d0) + call radiusScaledSolver(timeInitialScaled,self%timeNowScaled,radiusInitialScaled,radiusGrowthRateInitialScaled,self%radiusScaled(i),self%radiusGrowthRateScaled(i)) + end do + call displayCounterClear(verbosity=verbosityLevelWorking) + ! Build an interpolator for the scaled radius as a function of overdensity. + if (allocated(self%interpolatorRadiusScaled)) deallocate(self%interpolatorRadiusScaled) + allocate(self%interpolatorRadiusScaled) + self%interpolatorRadiusScaled=interpolator(Array_Reverse(self%overdensityScaled),Array_Reverse(self%radiusScaled)) + ! Construct the mass and density profile by ordering the shells in radius. Also find the splashback radius. + radiusOriginal =abs(self%radiusScaled)*self%radiusComovingInitialOriginal/self%cosmologicalConstantScaled**(1.0d0/3.0d0) + order =sortIndex(radiusOriginal) + self%radiusSplashBackOriginal=-huge(0.0d0) + firstZeroFound =.false. + do i=1,countRadii + ! Order radii. + self%radiusOrderedOriginal(i)=radiusOriginal(order(i)) + ! Compute mass and density in this shell. + if (order(i) == 1) then + self%massShellOrderedOriginal(i)=self%massEnclosedInitialOriginal(order(i)) + self%densityOrderedOriginal (i)=self%massShellOrderedOriginal(i)*3.0d0/4.0d0/Pi/ self%radiusOrderedOriginal(i)**3 + else + self%massShellOrderedOriginal(i)=self%massEnclosedInitialOriginal(order(i))-self%massEnclosedInitialOriginal(order(i)-1) + self%densityOrderedOriginal (i)=self%massShellOrderedOriginal(i)*3.0d0/4.0d0/Pi/(self%radiusOrderedOriginal(i)**3-self%radiusOrderedOriginal(i-1)**3) + end if + ! Find the mass enclosed by this shell. + self%massEnclosedOrderedOriginal(i)=sum(self%massShellOrderedOriginal(1:i)) + end do + ! Find the splashback radius. + do i=countRadii,1,-1 + firstZeroFound=firstZeroFound.or.self%radiusScaled(i) <= 0.0d0 + if (firstZeroFound .and. radiusOriginal(i) > self%radiusSplashBackOriginal) then + self%radiusSplashbackOriginal= radiusOriginal(i) + self%radiusSplashbackScaled =abs(self%radiusScaled (i)) + end if + end do + deallocate(radiusOriginal) + ! Find the splashback radius in units of the turnaround radius, and the half-turnaround radius. + self%radiusSplashbackTurnaround=self%radiusSplashBackOriginal/self%radiusTurnaroundNowOriginal + self%ratioRadiusSplashbackHRTA =self%radiusSplashBackOriginal/self%radiusHRTANowOriginal + ! Build a new interpolator for the mass profile in the multistream region, and compare it to the previous one. + allocate(interpolatorMassMultiStreamNewScaleHRTA) + interpolatorMassMultiStreamNewScaleHRTA=interpolator(self%radiusOrderedOriginal/self%radiusHRTANowOriginal,self%massEnclosedOrderedOriginal) + radiusMultistreamMinimumNewScaledHRTA =self%radiusOrderedOriginal( 1)/self%radiusHRTANowOriginal + radiusMultistreamMaximumNewScaledHRTA =self%radiusOrderedOriginal(countRadii)/self%radiusHRTANowOriginal + radiusCompareMinimumScaledHRTA =max(radiusMultistreamMinimumNewScaledHRTA,self%radiusMultistreamMinimumScaledHRTA) + radiusCompareMaximumScaledHRTA =min(radiusMultistreamMaximumNewScaledHRTA,self%radiusMultistreamMaximumScaledHRTA) + changeRelativeMaximum =-huge(0.0d0) + do j=1,countCompare + radiusScaleHRTA =+ radiusCompareMinimumScaledHRTA & + & +(radiusCompareMaximumScaledHRTA-radiusCompareMinimumScaledHRTA) & + & *dble( j) & + & /dble(countCompare) + massMultiStream =self%interpolatorMassMultiStreamScaleHRTA %interpolate(radiusScaleHRTA) + massMultiStreamNew = interpolatorMassMultiStreamNewScaleHRTA%interpolate(radiusScaleHRTA) + changeRelative =+abs(massMultiStream-massMultiStreamNew) & + & / (massMultiStream+massMultiStreamNew) & + & /0.5d0 + changeRelativeMaximum=max(changeRelative,changeRelativeMaximum) + end do + multistreamConverged=changeRelativeMaximum <= multistreamToleranceRelative + deallocate(interpolatorMassMultiStreamNewScaleHRTA) + ! Replace the interpolator for the mass profile in the multistream region with the updated one. + deallocate(self%interpolatorMassMultiStreamScaleHRTA) + allocate (self%interpolatorMassMultiStreamScaleHRTA) + self%interpolatorMassMultiStreamScaleHRTA=interpolator(self%radiusOrderedOriginal/self%radiusHRTANowOriginal,self%massEnclosedOrderedOriginal) + self%radiusMultistreamMinimumScaledHRTA =self%radiusOrderedOriginal( 1)/self%radiusHRTANowOriginal + self%radiusMultistreamMaximumScaledHRTA =self%radiusOrderedOriginal(countRadii)/self%radiusHRTANowOriginal + write (label,'(e8.2)') changeRelativeMaximum + call displayUnindent(var_str('done [fractional change = ')//trim(adjustl(label))//']',verbosity=verbosityLevelWorking) + end do + if (.not.multistreamConverged) call Error_Report('failed to reach convergence in the multistream region'//{introspection:location}) + ! Compute properties along the stream. + allocate(radiusOriginal (countRadii)) + allocate(densityOriginal (countRadii)) + allocate(velocityOriginal(countRadii)) + !! Find the radii and velocities in the stream. + radiusOriginal =self%radiusScaled *self%radiusComovingInitialOriginal/self%cosmologicalConstantScaled**(1.0d0/3.0d0) + velocityOriginal=self%radiusGrowthRateScaled*self%radiusComovingInitialOriginal/self%cosmologicalConstantScaled**(1.0d0/3.0d0) + !! Compute densities. + do i=1,countRadii + if (i == 1) then + densityOriginal(i)= self%massEnclosedInitialOriginal(i) *3.0d0/4.0d0/Pi/abs(radiusOriginal(i))**3 + else + densityOriginal(i)=(self%massEnclosedInitialOriginal(i)-self%massEnclosedInitialOriginal(i-1))*3.0d0/4.0d0/Pi/abs(radiusOriginal(i) **3-radiusOriginal(i-1)**3) + end if + end do + ! Find the shells at their turnaround radius and "half" of their turnaround radius (this defines the virial mass/radius), and + ! also the shell which is about to make its first passage through zero radius. + iVirial =-1 + iTurnaround=-1 + iFirstZero =-1 + do i=countRadii,1,-1 + if (iVirial < 0 .and. self%radiusGrowthRateScaled(i) < 0.0d0 .and. self%radiusScaled(i) <= self%radiusTurnaroundScaled(i)/self%ratioRadiusTurnaroundVirial) & + & iVirial =i + if (iTurnaround < 0 .and. self%radiusGrowthRateScaled(i) <= 0.0d0 ) & + & iTurnaround=i + if (iFirstZero < 0 .and. self%radiusGrowthRateScaled(i) < 0.0d0 .and. self%radiusScaled(i) < 0.0d0 ) & + & iFirstZero =i+1 + end do + ! Interpolate to get a more precise virial radius. + h =+(1.0d0 /self%ratioRadiusTurnaroundVirial -self%radiusScaled(iVirial)/self%radiusTurnaroundScaled(iVirial)) & + & /(self%radiusScaled(iVirial+1)/self%radiusTurnaroundScaled(iVirial+1)-self%radiusScaled(iVirial)/self%radiusTurnaroundScaled(iVirial)) + radiusVirialOriginal=+( & + & +self%radiusScaled(iVirial )*self%radiusComovingInitialOriginal(iVirial )*(1.0d0-h) & + & +self%radiusScaled(iVirial+1)*self%radiusComovingInitialOriginal(iVirial+1)* h & + & ) & + & /self%cosmologicalConstantScaled**(1.0d0/3.0d0) + massVirialOriginal =+self%interpolatorMassHRTAOriginal %interpolate( self%timeNowScaled ) & + & *self%interpolatorMassMultiStreamScaleHRTA%interpolate(radiusVirialOriginal/self%radiusHRTANowOriginal) + ! Scale radii, velocities, and densities to the virial radius/mass. + allocate(radiusPhysical (countRadii)) + allocate(velocityPhysical(countRadii)) + allocate(densityPhysical (countRadii)) + radiusPhysical =+radiusOriginal & + & * self_%radiusVirial/radiusVirialOriginal + velocityPhysical=+velocityOriginal & + & * self_%radiusVirial/radiusVirialOriginal & + & * self_%timeNowScaled /self%time & + & *megaParsec & + & /gigaYear & + & /kilo + densityPhysical =+densityOriginal & + & * self%mass /massVirialOriginal & + & /(self_%radiusVirial/radiusVirialOriginal)**3 + ! Build interpolators into the infall stream. + if (allocated(self%interpolatorDensityPhysical )) deallocate(self%interpolatorDensityPhysical ) + if (allocated(self%interpolatorVelocityPhysical)) deallocate(self%interpolatorVelocityPhysical) + allocate(self%interpolatorDensityPhysical ) + allocate(self%interpolatorVelocityPhysical) + self%interpolatorDensityPhysical =interpolator(radiusPhysical(iFirstZero:countRadii),densityPhysical (iFirstZero:countRadii),interpolationType=gsl_interp_cspline) + self%interpolatorVelocityPhysical=interpolator(radiusPhysical(iFirstZero:countRadii),velocityPhysical(iFirstZero:countRadii),interpolationType=gsl_interp_cspline) + self%radiusMinimumPhysical =radiusPhysical(iFirstZero) + self%radiusMaximumPhysical =radiusPhysical(countRadii) + ! Compute the analytic solution in the single stream regime. + allocate(radiusSingleStreamAnalyticPhysical (countRadii-iVirial-1)) + allocate(densitySingleStreamAnalyticPhysical (countRadii-iVirial-1)) + allocate(velocitySingleStreamAnalyticPhysical(countRadii-iVirial-1)) + do i=iVirial+1,countRadii-1 + radiusSingleStreamAnalyticPhysical (i-iVirial)=+( & + & +3.0d0 & + & *self%mass & + & /4.0d0 & + & /Pi & + & /self%cosmologyFunctions_%matterDensityEpochal(self%time) & + & )**(1.0d0/3.0d0) & + & *( & + & +self%expansionFactorHRTAScaled(i) & + & /self%expansionFactorScaled & + & )**(growthIndex/3.0d0) & + & *self%radiusScaled(i) & + & /self%expansionFactorScaled + densitySingleStreamAnalyticPhysical (i-iVirial)=+self%cosmologyFunctions_%matterDensityEpochal(self%time) & + & *( & + & +self%expansionFactorScaled & + & /self%radiusScaled (i) & + & )**3 & + & /( & + & +1.0d0 & + & +3.0d0 & + & /growthIndex & + & /(self%interpolatorScaleFactorHalfRadiusTurnaroundScaled%derivative(self%overdensityScaled(i))/self%expansionFactorHRTAScaled(i)) & + & *(self%interpolatorRadiusScaled %derivative(self%overdensityScaled(i))/self%radiusScaled (i)) & + & ) + velocitySingleStreamAnalyticPhysical(i-iVirial)=+self%radiusGrowthRateScaled (i ) & + & * radiusSingleStreamAnalyticPhysical(i-iVirial) & + & /self%radiusScaled (i ) & + & *sqrt(1.0d0-self%cosmologyFunctions_%OmegaMatterEpochal (self%time)) & + & * self%cosmologyFunctions_%HubbleParameterEpochal(self%time) + end do + ! Check that the numerical solution matches the analytic solution in the single stream region. + radiusSplashbackPhysical=+self%radiusSplashBackOriginal & + & *self%radiusVirial & + & / radiusVirialOriginal + do i=iVirial+1,countRadii-1 + if ( & + & radiusPhysical(i) > radiusSplashBackPhysical & + & .and. & + & Values_Differ( & + & radiusSingleStreamAnalyticPhysical(i-iVirial)/radiusSingleStreamAnalyticPhysical(iTurnaround-iVirial), & + & radiusPhysical (i )/radiusPhysical (iTurnaround ), & + & relTol=1.0d-6 & + & ) & + & ) & + & call Error_Report('numerical and analytic solutions disagree in single stream region'//{introspection:location}) + end do + return + end subroutine shi2016Solve + + double precision function halfRadiusTurnAroundRoot(timeFinalScaled) + !!{ + Root function used in finding the epoch at which a shell reaches a radius equal to half of its turnaround radius, $y^*$. + !!} + implicit none + double precision, intent(in ) :: timeFinalScaled + double precision :: radiusScaled , radiusGrowthRateScaled + + ! Integrate the dynamical equations governing the evolution of the shell radius starting from the turnaround time, I⋆, at + ! which the radius is y⋆, and the rate of change of radius is zero (by definition). + noShellCrossing=.true. + call radiusScaledSolver(timeTurnaroundScaled__,timeFinalScaled,radiusTurnaroundScaled__,0.0d0,radiusScaled,radiusGrowthRateScaled) + noShellCrossing=.false. + halfRadiusTurnAroundRoot=+ radiusScaled & + & - radiusTurnaroundScaled__ & + & /self_%ratioRadiusTurnaroundVirial + return + end function halfRadiusTurnAroundRoot + + subroutine radiusScaledSolver(timeInitialScaled,timeFinalScaled,radiusInitialScaled,radiusGrowthRateInitialScaled,radiusScaled,radiusGrowthRateScaled) + !!{ + Compute the scaled radius (and its growth rate) as a function of the initial state and final time. + !!} + use :: Interface_GSL , only : GSL_Success + use :: Numerical_ODE_Solvers, only : odeSolver + implicit none + double precision , intent(in ) :: timeInitialScaled , timeFinalScaled , & + & radiusInitialScaled , radiusGrowthRateInitialScaled + double precision , intent( out) :: radiusScaled , radiusGrowthRateScaled + double precision , dimension(2) :: odeVariables + double precision , parameter :: odeToleranceAbsolute=0.0d0, odeToleranceRelative =1.0d-9 + type (odeSolver) :: solver + double precision :: timeScaled + + solver =odeSolver(2_c_size_t,dynamicalODEs,toleranceAbsolute=odeToleranceAbsolute,toleranceRelative=odeToleranceRelative) + odeVariables=[radiusInitialScaled,radiusGrowthRateInitialScaled] + timeScaled = timeInitialScaled + call solver%solve(timeScaled,timeFinalScaled,odeVariables) + radiusScaled =odeVariables(1) + radiusGrowthRateScaled=odeVariables(2) + return + end subroutine radiusScaledSolver + + integer function dynamicalODES(timeScaled,odeVariables,odeVariablesGrowthRate) + !!{ + The dynamical equation describing the motion of a shell of matter in scaled variables \citep[][eqn.~A7]{shi_outer_2016}. + !!} + use :: Interface_GSL, only : GSL_Success + implicit none + double precision , intent(in ) :: timeScaled + double precision, dimension(:), intent(in ) :: odeVariables + double precision, dimension(:), intent( out) :: odeVariablesGrowthRate + double precision :: radiusScaled , radiusGrowthRateScaled, & + & radiusHRTA , radiusSplashback , & + & radius , massHRTA , & + & massEnclosedRatio + !$GLC attributes unused :: timeScaled + + ! Extract ODE variables into named variables for clarity. + radiusScaled =odeVariables(1) + radiusGrowthRateScaled=odeVariables(2) + ! Determine the ratio of the mass enclosed within the current radius to the mass enclosed within the shell at the initial + ! time. + if (noShellCrossing .or. timeScaled < self_%timeHRTAScaled(1)) then + ! If shell crossing is being ignored, or if the current time is less than the earliest time for which we have the + ! half-turnaround radius tabulated, simply assume a mass ratio of unity. + massEnclosedRatio=1.0d0 + else + ! Our solution for the mass in the multistream regime is the self-similar solution expressed in units of the radius of the + ! shell at its half-turnaround radius, and the mass within that shell (assuming no shell crossing). Compute that radius and + ! mass at the present epoch. + radiusHRTA=self_%interpolatorRadiusHRTAOriginal%interpolate(timeScaled) + massHRTA =self_%interpolatorMassHRTAOriginal %interpolate(timeScaled) + ! Find the current splashback radius by scaling the ratio of splashback to half-turnaround radius to the current epoch. + radiusSplashback=+self_%ratioRadiusSplashbackHRTA & + & * radiusHRTA + ! Find the radius of the current shell in unscaled units. + radius=abs(radiusScaled)*radiusComovingInitialOriginal/self_%cosmologicalConstantScaled**(1.0d0/3.0d0) + ! Determine where in the stream our shell is. + if (radius < radiusSplashback) then + ! The shell is within the splashback radius. + if (radius > self_%radiusMultistreamMinimumScaledHRTA*radiusHRTA) then + ! Shell is outside the minimum radius that we have tabulated for the multistream region. Therefore, simply + ! interpolate to get the mass ratio. + massEnclosedRatio=+massHRTA & + & /massEnclosedInitialOriginal & + & *self_%interpolatorMassMultiStreamScaleHRTA%interpolate(radius/radiusHRTA) + else + ! Shell is inside the minimum radius that we have tabulated for the multistream region. In this region we assume that + ! the mass enclosed by the innermost tabulated point is distributed with uniform density. The mass enclosed therefore + ! grows as the cube of radius. + massEnclosedRatio=+massHRTA & + & /massEnclosedInitialOriginal & + & *self_%interpolatorMassMultiStreamScaleHRTA%interpolate(self_%radiusMultistreamMinimumScaledHRTA) & + & *(radius/radiusHRTA/self_%radiusMultistreamMinimumScaledHRTA)**3 + end if + else + ! Mass is outside the splashback radius, so no shell crossing has occurred. The mass ratio is therefore unity. + massEnclosedRatio=1.0d0 + end if + end if + ! Set ODE rates of change. + !! Radius rate of change is just the velocity. + odeVariablesGrowthRate(1)=+radiusGrowthRateScaled + !! Velocity rate of change is given by equation (A7) of Shi (2016). + if (radiusScaled == 0.0d0) then + odeVariablesGrowthRate(2)=+0.0d0 + else + odeVariablesGrowthRate(2)=-0.5d0*massEnclosedRatio*sign(1.0d0,radiusScaled)/radiusScaled**2 & + & + radiusScaled + end if + dynamicalODES=GSL_Success + return + end function dynamicalODES + + elemental double precision function expansionFactorFromTimeScaled(timeScaled) result(expansionFactor) + !!{ + Compute the scaled expansion factor from the scaled time using equation~(A6) of \cite{shi_outer_2016}. + !!} + implicit none + double precision, intent(in ) :: timeScaled + + expansionFactor=sinh(1.5d0*timeScaled)**(2.0d0/3.0d0) + return + end function expansionFactorFromTimeScaled + diff --git a/source/mass_distributions.spherical.accretion_flow.correlation_function.F90 b/source/mass_distributions.spherical.accretion_flow.correlation_function.F90 new file mode 100644 index 0000000000..1f3e0abcf7 --- /dev/null +++ b/source/mass_distributions.spherical.accretion_flow.correlation_function.F90 @@ -0,0 +1,184 @@ +!! Copyright 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, +!! 2019, 2020, 2021, 2022, 2023 +!! Andrew Benson +!! +!! This file is part of Galacticus. +!! +!! Galacticus is free software: you can redistribute it and/or modify +!! it under the terms of the GNU General Public License as published by +!! the Free Software Foundation, either version 3 of the License, or +!! (at your option) any later version. +!! +!! Galacticus is distributed in the hope that it will be useful, +!! but WITHOUT ANY WARRANTY; without even the implied warranty of +!! MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +!! GNU General Public License for more details. +!! +!! You should have received a copy of the GNU General Public License +!! along with Galacticus. If not, see . + + !!{ + Implementation of a mass distribution for accretion flow using the 2-halo correlation function. + !!} + + use :: Cosmology_Functions , only : cosmologyFunctionsClass + use :: Numerical_Interpolation, only : interpolator + + !![ + + + An accretion flow class which models the accretion flow using the 2-halo correlation function. + + + !!] + type, public, extends(massDistributionSpherical) :: massDistributionCorrelationFunction + !!{ + A mass distribution for accretion flow using the 2-halo correlation function. + !!} + private + class (cosmologyFunctionsClass), pointer :: cosmologyFunctions_ => null() + type (interpolator ) :: correlationFunction_ + double precision :: mass , time , & + & redshift + double precision , allocatable, dimension(:) :: radius , correlationFunction + contains + procedure :: density => correlationFunctionDensity + procedure :: densityGradientRadial => correlationFunctionDensityGradientRadial + end type massDistributionCorrelationFunction + + interface massDistributionCorrelationFunction + !!{ + Constructors for the {\normalfont \ttfamily correlationFunction} mass distribution class. + !!} + module procedure massDistributionCorrelationFunctionConstructorParameters + module procedure massDistributionCorrelationFunctionConstructorInternal + end interface massDistributionCorrelationFunction + +contains + + function massDistributionCorrelationFunctionConstructorParameters(parameters) result(self) + !!{ + Constructor for the {\normalfont \ttfamily correlationFunction} mass distribution class which builds the object from a parameter + set. + !!} + use :: Input_Parameters , only : inputParameter , inputParameters + use :: Galactic_Structure_Options, only : enumerationComponentTypeEncode, enumerationMassTypeEncode + use :: Numerical_Interpolation , only : interpolator + implicit none + type (massDistributionCorrelationFunction) :: self + type (inputParameters ), intent(inout) :: parameters + class (cosmologyFunctionsClass ), pointer :: cosmologyFunctions_ + double precision , allocatable, dimension(:) :: radius , correlationFunction + double precision :: mass , redshift + type (varying_string ) :: componentType + type (varying_string ) :: massType + + !![ + + mass + The mass of the halo. + parameters + + + redshift + The redshift of the halo. + parameters + + + radius + The radius in the tabulated correlation function. + parameters + + + correlationFunction + The correlation in the tabulated correlation function. + parameters + + + componentType + var_str('unknown') + The component type that this mass distribution represents. + parameters + + + massType + var_str('unknown') + The mass type that this mass distribution represents. + parameters + + + !!] + self=massDistributionCorrelationFunction(mass,cosmologyFunctions_%cosmicTime(cosmologyFunctions_%expansionFactorFromRedshift(redshift)),radius,correlationFunction,cosmologyFunctions_,componentType=enumerationComponentTypeEncode(componentType,includesPrefix=.false.),massType=enumerationMassTypeEncode(massType,includesPrefix=.false.)) + !![ + + + !!] + return + end function massDistributionCorrelationFunctionConstructorParameters + + function massDistributionCorrelationFunctionConstructorInternal(mass,time,radius,correlationFunction,cosmologyFunctions_,componentType,massType) result(self) + !!{ + Internal constructor for ``correlationFunction'' mass distribution class. + !!} + implicit none + type (massDistributionCorrelationFunction) :: self + class (cosmologyFunctionsClass ), intent(in ), target :: cosmologyFunctions_ + double precision , intent(in ) :: mass , time + double precision , intent(in ), dimension(:) :: radius , correlationFunction + type (enumerationComponentTypeType ), intent(in ), optional :: componentType + type (enumerationMassTypeType ), intent(in ), optional :: massType + !![ + + !!] + + self%dimensionless =.false. + self%redshift =self%cosmologyFunctions_%redshiftFromExpansionFactor(self%cosmologyFunctions_%expansionFactor(time)) + self%correlationFunction_=interpolator(radius,correlationFunction) + return + end function massDistributionCorrelationFunctionConstructorInternal + + subroutine correlationFunctionDestructor(self) + !!{ + Destructor for the {\normalfont \ttfamily correlationFunction} accretion flow mass distribution class. + !!} + implicit none + type(massDistributionCorrelationFunction), intent(inout) :: self + + !![ + + !!] + return + end subroutine correlationFunctionDestructor + + double precision function correlationFunctionDensity(self,coordinates) result(density) + !!{ + Return the density at the specified {\normalfont \ttfamily coordinates} in a accretion flow modeled on the 2-halo correlation function. + !!} + implicit none + class(massDistributionCorrelationFunction), intent(inout) :: self + class(coordinate ), intent(in ) :: coordinates + + density=+( & + & +1.0d0 & + & +self%correlationFunction_%interpolate (coordinates%rSpherical()) & + & ) & + & * self%cosmologyFunctions_ %matterDensityEpochal(self %time ) + return + end function correlationFunctionDensity + + double precision function correlationFunctionDensityGradientRadial(self,coordinates,logarithmic) result(densityGradientRadial) + !!{ + Return the radial density gradient at the specified {\normalfont \ttfamily coordinates} in a accretion flow modeled on the 2-halo correlation function. + !!} + implicit none + class (massDistributionCorrelationFunction), intent(inout), target :: self + class (coordinate ), intent(in ) :: coordinates + logical , intent(in ), optional :: logarithmic + !![ + + !!] + + densityGradientRadial=+self%correlationFunction_%derivative (coordinates%rSpherical()) & + & *self%cosmologyFunctions_ %matterDensityEpochal(self %time ) + return + end function correlationFunctionDensityGradientRadial diff --git a/source/mass_distributions.spherical.adiabatic_Gnedin2004.F90 b/source/mass_distributions.spherical.adiabatic_Gnedin2004.F90 new file mode 100644 index 0000000000..84b4ee5443 --- /dev/null +++ b/source/mass_distributions.spherical.adiabatic_Gnedin2004.F90 @@ -0,0 +1,698 @@ +!! Copyright 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, +!! 2019, 2020, 2021, 2022, 2023 +!! Andrew Benson +!! +!! This file is part of Galacticus. +!! +!! Galacticus is free software: you can redistribute it and/or modify +!! it under the terms of the GNU General Public License as published by +!! the Free Software Foundation, either version 3 of the License, or +!! (at your option) any later version. +!! +!! Galacticus is distributed in the hope that it will be useful, +!! but WITHOUT ANY WARRANTY; without even the implied warranty of +!! MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +!! GNU General Public License for more details. +!! +!! You should have received a copy of the GNU General Public License +!! along with Galacticus. If not, see . + + !!{ + Implements an adiabatically-contracted spherical mass distribution. + !!} + + use :: Math_Exponentiation, only : fastExponentiator + use :: Root_Finder , only : rootFinder + + public :: sphericalAdiabaticGnedin2004Initializor + + ! Number of previous radius solutions to store. + !![ + + + + !!] + integer, parameter :: sphericalAdiabaticGnedin2004StoreCount=10 + + !![ + + + A dark matter profile class which applies adiabatic contraction to the halo as it responds to the presence of + baryons. Adiabatic contraction follows the algorithm of \cite{gnedin_response_2004}. The parameters $A$ and $\omega$ of + that model are specified via input parameters {\normalfont \ttfamily A} and {\normalfont \ttfamily omega} respectively. + + Given the final radius, $r_\mathrm{f}$, the corresponding initial radius, $r_\mathrm{i}$, is found by solving: + \begin{equation} + f_\mathrm{i} M_\mathrm{total,0}(\bar{r}_\mathrm{i}) r_\mathrm{i} = f_\mathrm{f} M_\mathrm{total,0}(\bar{r}_\mathrm{i}) + r_\mathrm{f} + V^2_\mathrm{b}(\bar{r}_\mathrm{f}) \bar{r}_\mathrm{f} r_\mathrm{f}/ \mathrm{G}, + \label{eq:adiabaticContractionGnedinSolution} + \end{equation} + where $M_\mathrm{total,0}(r)$ is the initial total matter profile, $V_\mathrm{b}(r)$ is the baryonic contribution to the + rotation curve, $f_\mathrm{i}$, is the fraction of mass within the virial radius compared to the node mass\footnote{In + \protect\glc\ the ``node mass'' refers to the total mass of the node, assuming it has the universal complement of + baryons. Since some halos may contain less than the complete complement of baryons it is possible that $f_\mathrm{i}<1$.}, + $f_\mathrm{f}=(\Omega_\mathrm{M}-\Omega_\mathrm{b})/\Omega_\mathrm{M}+M_\mathrm{satellite, baryonic}/M_\mathrm{total}$, + $M_\mathrm{satellite, baryonic}$ is the baryonic mass in any satellite halos, $M_\mathrm{total}$ is the node mass, and + \begin{equation} + {\bar{r} \over r_0} = A \left({r \over r_0}\right)^\omega, + \label{eq:adiabaticContractionGnedinPowerLaw} + \end{equation} + where the pivot radius $r_0$ is set to $f_0 r_\mathrm{vir}$ where $f_0=${\normalfont \ttfamily [radiusFractionalPivot]}, and + $r_\mathrm{vir}$ is the virial radius. The original \cite{gnedin_response_2004} assumed $f_0=1$, but the revised model of + \cite{gnedin_halo_2011} found that $f_0=0.03$ lead to an improved model (less scatter in the best fit values of $(A,\omega)$ + when comparing to N-body simulations). + + Note that we explicitly assume that the initial, uncontracted total density profile has the same shape as the initial dark + matter density profile, that contraction of the halo occurs with no shell crossing, and that satellite halos trace the dark + matter profile of their host halo. The derivative, $\mathrm{d} r_\mathrm{f}/\mathrm{d}d_\mathrm{i}\equiv r^\prime_\mathrm{i}$ + is found by taking the derivative of eqn.~(\ref{eq:adiabaticContractionGnedinSolution}) to give: + \begin{eqnarray} + & & f_\mathrm{i} M_\mathrm{total,0}(\bar{r}_\mathrm{i}) r^\prime_\mathrm{i} + f_\mathrm{i} 4 \pi + \bar{r}_\mathrm{i}^2 \rho_\mathrm{total,0}(\bar{r}_\mathrm{i}) {\mathrm{d} \bar{r}_\mathrm{i}\over\mathrm{d} r_\mathrm{i}} + r_\mathrm{i} r^\prime_\mathrm{i} \nonumber \\ + & = & f_\mathrm{f} M_\mathrm{total,0}(\bar{r}_\mathrm{i}) + f_\mathrm{i} 4 \pi \bar{r}_\mathrm{i}^2 + \rho_\mathrm{total,0}(\bar{r}_\mathrm{i}) {\mathrm{d} \bar{r}_\mathrm{i}\over\mathrm{d} r_\mathrm{i}} r_\mathrm{f} + r^\prime_\mathrm{i} \nonumber \\ + & + & V^2_\mathrm{b}(\bar{r}_\mathrm{f}) \bar{r}_\mathrm{f} / \mathrm{G} + V^2_\mathrm{b}(\bar{r}_\mathrm{f}) + {\mathrm{d}\bar{r}_\mathrm{f}\over \mathrm{d} r_\mathrm{f}} r_\mathrm{f}/ \mathrm{G} + + {\mathrm{d}V^2_\mathrm{b}\over\mathrm{d} \bar{r}_\mathrm{f}}(\bar{r}_\mathrm{f}) {\mathrm{d}\bar{r}_\mathrm{f}\over + \mathrm{d} r_\mathrm{f}} \bar{r}_\mathrm{f} r_\mathrm{f}/ \mathrm{G}, + \end{eqnarray} + where + \begin{equation} + {\mathrm{d}\bar{r} \over \mathrm{d} r} = A \left({r \over r_0}\right)^{\omega-1}, + \end{equation} + and which can then be solved numerically for $r^\prime_\mathrm{i}$. + + + !!] + type, extends(massDistributionSphericalDecorator) :: massDistributionSphericalAdiabaticGnedin2004 + !!{ + An adiabatically-contracted spherical mass distribution. + !!} + private + class (massDistributionClass ), pointer :: massDistributionBaryonic => null() + type (rootFinder ) :: finder + ! Parameters of the adiabatic contraction algorithm. + double precision :: A , omega , & + & radiusFractionalPivot + ! Stored solutions for reuse. + integer :: radiusPreviousIndex , radiusPreviousIndexMaximum + double precision , dimension(sphericalAdiabaticGnedin2004StoreCount) :: radiusPrevious , radiusInitialPrevious + ! Quantities used in solving the initial radius root function. + double precision :: baryonicFinalTerm , baryonicFinalTermDerivative , & + & darkMatterDistributedFraction , massFractionInitial , & + & radiusFinal , radiusFinalMean , & + & darkMatterFraction , radiusVirial , & + & toleranceRelative , massTotal_ + ! Call-back function and arguments used for as-needed initialization of the baryonic component. + logical :: initialized + procedure (sphericalAdiabaticGnedin2004Initializor), pointer , nopass :: initializationFunction + class (* ), pointer :: initializationSelf => null(), initializationArgument => null() + contains + !![ + + + + + + + + + !!] + final :: sphericalAdiabaticGnedin2004Destructor + procedure :: setBaryonicComponent => sphericalAdiabaticGnedin2004SetBaryonicComponent + procedure :: density => sphericalAdiabaticGnedin2004Density + procedure :: massEnclosedBySphere => sphericalAdiabaticGnedin2004MassEnclosedBySphere + procedure :: radiusInitial => sphericalAdiabaticGnedin2004RadiusInitial + procedure :: radiusInitialDerivative => sphericalAdiabaticGnedin2004RadiusInitialDerivative + procedure :: computeFactors => sphericalAdiabaticGnedin2004ComputeFactors + procedure :: radiusOrbitalMean => sphericalAdiabaticGnedin2004RadiusOrbitalMean + procedure :: radiusOrbitalMeanDerivative => sphericalAdiabaticGnedin2004RadiusOrbitalMeanDerivative + end type massDistributionSphericalAdiabaticGnedin2004 + + interface massDistributionSphericalAdiabaticGnedin2004 + !!{ + Constructors for the {\normalfont \ttfamily sphericalAdiabaticGnedin2004} mass distribution class. + !!} + module procedure sphericalAdiabaticGnedin2004ConstructorParameters + module procedure sphericalAdiabaticGnedin2004ConstructorInternal + end interface massDistributionSphericalAdiabaticGnedin2004 + + ! Module-scope quantities used in solving the initial radius root function. + double precision , parameter :: toleranceAbsolute =0.0d0 + class (massDistributionSphericalAdiabaticGnedin2004), pointer :: self_ + !$omp threadprivate(self_) + + ! Module-scope shared fast exponentiator. + type (fastExponentiator ), allocatable :: radiusExponentiator + double precision :: omegaPrevious =-huge(0.0d0) + !$omp threadprivate(radiusExponentiator,omegaPrevious) + + abstract interface + subroutine sphericalAdiabaticGnedin2004Initializor(initializationSelf,initializationArgument,massDistributionBaryonic,darkMatterDistributedFraction,massFractionInitial) + !!{ + Interface for call-back functions for as-needed initialization of the baryonic component. + !!} + import massDistributionClass + class (* ), intent(inout), target :: initializationSelf , initializationArgument + class (massDistributionClass), intent( out), pointer :: massDistributionBaryonic + double precision , intent( out) :: darkMatterDistributedFraction, massFractionInitial + end subroutine sphericalAdiabaticGnedin2004Initializor + end interface + +contains + + function sphericalAdiabaticGnedin2004ConstructorParameters(parameters) result(self) + !!{ + Constructor for the {\normalfont \ttfamily sphericalAdiabaticGnedin2004} mass distribution class which builds the object from a parameter + set. + !!} + use :: Input_Parameters , only : inputParameters + use :: Galactic_Structure_Options, only : enumerationComponentTypeEncode, enumerationMassTypeEncode + implicit none + type (massDistributionSphericalAdiabaticGnedin2004) :: self + type (inputParameters ), intent(inout) :: parameters + class (massDistributionClass ), pointer :: massDistribution_ , massDistributionBaryonic + procedure (sphericalAdiabaticGnedin2004Initializor ), pointer :: initializationFunction + class (* ), pointer :: initializationSelf , initializationArgument + double precision :: A , omega , & + & radiusFractionalPivot , toleranceRelative , & + & radiusVirial , darkMatterFraction , & + & darkMatterDistributedFraction, massFractionInitial + type (varying_string ) :: componentType , massType , & + & nonAnalyticSolver + + !![ + + A + (\citealt{gustafsson_baryonic_2006}; from their Fig. 9, strong feedback case) + 0.80d0 + The parameter $A$ appearing in the \cite{gnedin_response_2004} adiabatic contraction algorithm. + parameters + + + omega + (\citealt{gustafsson_baryonic_2006}; from their Fig. 9, strong feedback case) + 0.77d0 + The parameter $\omega$ appearing in the \cite{gnedin_response_2004} adiabatic contraction algorithm. + parameters + + + radiusFractionalPivot + \citep{gnedin_response_2004} + 1.0d0 + The pivot radius (in units of the virial radius), $r_0$, appearing in equation~(\ref{eq:adiabaticContractionGnedinPowerLaw}). + parameters + + + radiusVirial + The virial radius, $r_\mathrm{v}$, appearing in equation~(\ref{eq:adiabaticContractionGnedinPowerLaw}). + parameters + + + darkMatterFraction + The universal dark matter fraction. + parameters + + + darkMatterDistributedFraction + The fraction of matter assumed to be distributed as the dark matter. + parameters + + + massFractionInitial + The fraction of matter assumed to be initially distributed as the dark matter. + parameters + + + toleranceRelative + 1.0d-2 + parameters + The relative tolerance to use in solving for the initial radius in the adiabatically-contracted dark matter profile. + + + nonAnalyticSolver + var_str('fallThrough') + parameters + Selects how solutions are computed when no analytic solution is available. If set to ``{\normalfont \ttfamily fallThrough}'' then the solution ignoring heating is used, while if set to ``{\normalfont \ttfamily numerical}'' then numerical solvers are used to find solutions. + + + componentType + var_str('unknown') + The component type that this mass distribution represents. + parameters + + + massType + var_str('unknown') + The mass type that this mass distribution represents. + parameters + + + + !!] + select type (massDistribution_) + class is (massDistributionSpherical) + initializationFunction => null() + initializationSelf => null() + initializationArgument => null() + self=massDistributionSphericalAdiabaticGnedin2004(A,omega,radiusVirial,radiusFractionalPivot,darkMatterFraction,darkMatterDistributedFraction,massFractionInitial,toleranceRelative,enumerationNonAnalyticSolversEncode(char(nonAnalyticSolver),includesPrefix=.false.),massDistribution_,massDistributionBaryonic,initializationFunction,initializationSelf,initializationArgument,enumerationComponentTypeEncode(componentType,includesPrefix=.false.),enumerationMassTypeEncode(massType,includesPrefix=.false.)) + class default + call Error_Report('a spherically-symmetric mass distribution is required'//{introspection:location}) + end select + !![ + + + + !!] + return + end function sphericalAdiabaticGnedin2004ConstructorParameters + + function sphericalAdiabaticGnedin2004ConstructorInternal(A,omega,radiusVirial,radiusFractionalPivot,darkMatterFraction,darkMatterDistributedFraction,massFractionInitial,toleranceRelative,nonAnalyticSolver,massDistribution_,massDistributionBaryonic,initializationFunction,initializationSelf,initializationArgument,componentType,massType) result(self) + !!{ + Constructor for ``sphericalAdiabaticGnedin2004'' mass distribution class. + !!} + implicit none + type (massDistributionSphericalAdiabaticGnedin2004) :: self + double precision , intent(in ) :: A , omega , & + & radiusVirial , radiusFractionalPivot , & + & darkMatterFraction , darkMatterDistributedFraction, & + & massFractionInitial , toleranceRelative + class (massDistributionSpherical ), intent(in ), target :: massDistribution_ + class (massDistributionClass ), intent(in ), target :: massDistributionBaryonic + type (enumerationNonAnalyticSolversType ), intent(in ) :: nonAnalyticSolver + procedure (sphericalAdiabaticGnedin2004Initializor ), intent(in ), pointer :: initializationFunction + class (* ), intent(in ), pointer :: initializationSelf , initializationArgument + type (enumerationComponentTypeType ), intent(in ), optional :: componentType + type (enumerationMassTypeType ), intent(in ), optional :: massType + !![ + + !!] + + ! Validate. + if (.not.enumerationNonAnalyticSolversIsValid(nonAnalyticSolver)) call Error_Report('invalid non-analytic solver type'//{introspection:location}) + ! Evaluate the original total mass. + self%massTotal_=self%massDistribution_%massEnclosedBySphere(radiusVirial) + ! Construct a root finder. + self%finder=rootFinder( & + & rootFunction =sphericalAdiabaticGnedin2004Solver, & + & toleranceAbsolute=toleranceAbsolute , & + & toleranceRelative=toleranceRelative & + & ) + self%dimensionless=self%massDistribution_%isDimensionless() + ! Initialize state. + self%radiusPreviousIndex = 0 + self%radiusPreviousIndexMaximum= 0 + self%radiusPrevious =-1.0d0 + self%initialized =.not.associated(initializationFunction) + return + end function sphericalAdiabaticGnedin2004ConstructorInternal + + subroutine sphericalAdiabaticGnedin2004Destructor(self) + !!{ + Destructor for the abstract {\normalfont \ttfamily massDistributionSphericalAdiabaticGnedin2004} class. + !!} + implicit none + type(massDistributionSphericalAdiabaticGnedin2004), intent(inout) :: self + + !![ + + + !!] + return + end subroutine sphericalAdiabaticGnedin2004Destructor + + subroutine sphericalAdiabaticGnedin2004SetBaryonicComponent(self) + !!{ + Set the baryonic component properties in an adiabatically-contracted spherical mass distribution. + !!} + implicit none + class (massDistributionSphericalAdiabaticGnedin2004), intent(inout) :: self + class (massDistributionClass ), pointer :: massDistributionBaryonic + double precision :: darkMatterDistributedFraction, massFractionInitial + + if (.not.self%initialized) then + call self%initializationFunction(self%initializationSelf,self%initializationArgument,massDistributionBaryonic,darkMatterDistributedFraction,massFractionInitial) + self%massDistributionBaryonic => massDistributionBaryonic + self%darkMatterDistributedFraction = darkMatterDistributedFraction + self%massFractionInitial = massFractionInitial + self%initialized = .true. + end if + return + end subroutine sphericalAdiabaticGnedin2004SetBaryonicComponent + + double precision function sphericalAdiabaticGnedin2004Density(self,coordinates) result(density) + !!{ + Return the density at the specified {\normalfont \ttfamily coordinates} in an adiabatically-contracted spherical mass distribution. + !!} + use :: Coordinates, only : coordinateSpherical, assignment(=) + implicit none + class (massDistributionSphericalAdiabaticGnedin2004), intent(inout) :: self + class (coordinate ), intent(in ) :: coordinates + type (coordinateSpherical ) :: coordinatesInitial + double precision :: radiusInitial , radiusInitialDerivative, & + & densityInitial + + radiusInitial =self %radiusInitial(coordinates %rSpherical()) + coordinatesInitial=[radiusInitial,0.0d0,0.0d0] + densityInitial =self%massDistribution_%density (coordinatesInitial ) + if (coordinates%rSpherical() == radiusInitial) then + density=+self%darkMatterFraction & + & * densityInitial + else + radiusInitialDerivative=+self%radiusInitialDerivative(coordinates%rSpherical()) + density =+self%darkMatterFraction & + & *densityInitial & + & *( & + & + radiusInitial & + & /coordinates%rSpherical () & + & )**2 & + & *radiusInitialDerivative + end if + return + end function sphericalAdiabaticGnedin2004Density + + double precision function sphericalAdiabaticGnedin2004MassEnclosedBySphere(self,radius) result(mass) + !!{ + Computes the mass enclosed within a sphere of given {\normalfont \ttfamily radius} for adiabatically-contracted mass distributions. + !!} + implicit none + class (massDistributionSphericalAdiabaticGnedin2004), intent(inout), target :: self + double precision , intent(in ) :: radius + + mass =+self %darkMatterFraction & + & *self%massDistribution_%massEnclosedBySphere(self%radiusInitial(radius)) + return + end function sphericalAdiabaticGnedin2004MassEnclosedBySphere + + double precision function sphericalAdiabaticGnedin2004RadiusInitial(self,radius) + !!{ + Compute the initial radius in the dark matter halo using the adiabatic contraction algorithm of + \cite{gnedin_response_2004}. + !!} + use :: Root_Finder, only : rangeExpandMultiplicative, rangeExpandSignExpectNegative, rangeExpandSignExpectPositive, rootFinder + implicit none + class (massDistributionSphericalAdiabaticGnedin2004), intent(inout) :: self + double precision , intent(in ) :: radius + integer :: i , j , & + & iMod + double precision :: radiusUpperBound, massEnclosed + + call self%setBaryonicComponent() + ! Handle non-positive radii. + if (radius <= 0.0d0) then + sphericalAdiabaticGnedin2004RadiusInitial=0.0d0 + return + end if + ! Check for a previously computed solution. + if (self%radiusPreviousIndexMaximum > 0 .and. any(self%radiusPrevious(1:self%radiusPreviousIndexMaximum) == radius)) then + sphericalAdiabaticGnedin2004RadiusInitial=0.0d0 + do i=1,self%radiusPreviousIndexMaximum + if (self%radiusPrevious(i) == radius) then + sphericalAdiabaticGnedin2004RadiusInitial=self%radiusInitialPrevious(i) + exit + end if + end do + return + end if + ! Return radius unchanged if larger than the virial radius. + if (radius >= self%radiusVirial) then + sphericalAdiabaticGnedin2004RadiusInitial=radius + return + end if + ! Compute the various factors needed by this calculation. + call self%computeFactors(radius,computeGradientFactors=.false.) + !! Note that even if no baryons are present at this radius we can not assume that the initial radius is unchanged because it + !! is possible that the initial fraction of baryons and the fraction of mass distributed as the dark matter are not equal, fᵢ + !! ≠ fᵪ. + ! Check that solution is within bounds. + if (sphericalAdiabaticGnedin2004Solver(self%radiusVirial) < 0.0d0) then + sphericalAdiabaticGnedin2004RadiusInitial=self%radiusVirial + return + end if + j=-1 + if (self%radiusPreviousIndexMaximum > 0) then + ! No exact match exists, look for approximate matches. + do i=1,self%radiusPreviousIndexMaximum + iMod=modulo(self%radiusPreviousIndex-i,sphericalAdiabaticGnedin2004StoreCount)+1 + if (abs(radius-self%radiusPrevious(iMod))/self%radiusPrevious(iMod) < self%toleranceRelative) then + j=iMod + exit + end if + end do + end if + ! Find the solution for initial radius. + if (j == -1) then + ! No previous solution to use as an initial guess. Instead, we make an estimate of the initial radius under the + ! assumption that the mass of dark matter (in the initial profile) enclosed within the mean initial radius is the + ! same as enclosed within the mean final radius. Since the initial and final radii are typically not too + ! different, and since the mean radius is a weak (ѡ<1) function of the radius this is a useful + ! approximation. Furthermore, since it will underestimate the actual mass within the initial mean radius it gives + ! an overestimate of the initial radius. This means that we have a bracketing of the initial radius which we can + ! use in the solver. + massEnclosed=+self%massDistribution_%massEnclosedBySphere(self%radiusOrbitalMean(self%radiusFinal)) + if (massEnclosed > 0.0d0) then + radiusUpperBound=+( & + & +self%baryonicFinalTerm & + & / massEnclosed & + & +self%darkMatterDistributedFraction & + & *self%radiusFinal & + & ) & + & / self%massFractionInitial + if (radiusUpperBound < radius) radiusUpperBound=radius + else + radiusUpperBound=radius + end if + call self%finder%rangeExpand( & + & rangeExpandUpward =1.1d0 , & + & rangeExpandDownward =0.9d0 , & + & rangeExpandUpwardSignExpect =rangeExpandSignExpectPositive, & + & rangeExpandDownwardSignExpect=rangeExpandSignExpectNegative, & + & rangeExpandType =rangeExpandMultiplicative & + & ) + sphericalAdiabaticGnedin2004RadiusInitial=self%finder%find(rootRange=[radius,radiusUpperBound]) + else + ! Use previous solution as an initial guess. + call self%finder%rangeExpand( & + & rangeExpandDownward =1.0d0/sqrt(1.0d0+self%toleranceRelative), & + & rangeExpandUpward =1.0d0*sqrt(1.0d0+self%toleranceRelative), & + & rangeExpandDownwardSignExpect=rangeExpandSignExpectNegative , & + & rangeExpandUpwardSignExpect =rangeExpandSignExpectPositive , & + & rangeExpandType =rangeExpandMultiplicative & + & ) + sphericalAdiabaticGnedin2004RadiusInitial=self%finder%find( & + & rootRange=[ & + & self%radiusInitialPrevious(j)/sqrt(1.0d0+self%toleranceRelative), & + & self%radiusInitialPrevious(j)*sqrt(1.0d0+self%toleranceRelative) & + & ] & + & ) + end if + ! Store this solution. + self%radiusPreviousIndex =modulo(self%radiusPreviousIndex ,sphericalAdiabaticGnedin2004StoreCount)+1 + self%radiusPreviousIndexMaximum =min (self%radiusPreviousIndexMaximum+1,sphericalAdiabaticGnedin2004StoreCount) + self%radiusPrevious (self%radiusPreviousIndex)=radius + self%radiusInitialPrevious (self%radiusPreviousIndex)=sphericalAdiabaticGnedin2004RadiusInitial + return + end function sphericalAdiabaticGnedin2004RadiusInitial + + double precision function sphericalAdiabaticGnedin2004RadiusInitialDerivative(self,radius) result(radiusInitialDerivative) + !!{ + Compute the derivative of the initial radius in the dark matter halo using the adiabatic contraction algorithm of + \cite{gnedin_response_2004}. + !!} + use :: Display , only : displayMessage , displayIndent, displayUnindent + use :: Error , only : Error_Report + use :: Numerical_Constants_Math, only : Pi + use :: Coordinates , only : coordinateSpherical, assignment(=) + implicit none + class (massDistributionSphericalAdiabaticGnedin2004), intent(inout) :: self + double precision , intent(in ) :: radius + type (varying_string ), save :: message + !$omp threadprivate(message) + character (len=12 ) :: label + double precision :: radiusInitial , radiusInitialMean , & + & massDarkMatterInitial , densityDarkMatterInitial , & + & radiusInitialMeanSelfDerivative, radiusFinalMeanSelfDerivative, & + & numerator , denominator + type (coordinateSpherical ) :: coordinatesInitialMean + + call self%setBaryonicComponent() + ! Compute the various factors needed by this calculation. + call self%computeFactors(radius,computeGradientFactors=.true.) + ! Return unit derivative if radius is larger than the virial radius. + if (radius >= self%radiusVirial) then + radiusInitialDerivative=1.0d0 + return + end if + ! Validate. + if (radius <= 0.0d0) call Error_Report('non-positive radius') + ! Compute initial radius, and derivatives of initial and final mean radii. + radiusInitial =self %radiusInitial (radius ) + radiusInitialMeanSelfDerivative=self %radiusOrbitalMeanDerivative(radiusInitial ) + radiusFinalMeanSelfDerivative =self %radiusOrbitalMeanDerivative(radius ) + ! Find the initial mean orbital radius. + radiusInitialMean =self %radiusOrbitalMean (radiusInitial ) + coordinatesInitialMean =[radiusInitialMean,0.0d0,0.0d0] + ! Get the mass of dark matter inside the initial radius. + massDarkMatterInitial =self%massDistribution_%massEnclosedBySphere (radiusInitialMean ) + ! Get the mass of dark matter inside the initial radius. + densityDarkMatterInitial =self%massDistribution_%density (coordinatesInitialMean) + ! Find the solution for the derivative of the initial radius. + numerator =+( & + & +massDarkMatterInitial & + & *self%darkMatterDistributedFraction & + & +self%baryonicFinalTerm & + & *( & + & +1.0d0 / radius & + & +radiusFinalMeanSelfDerivative/self%radiusFinalMean & + & ) & + & +self%baryonicFinalTermDerivative & + & ) + denominator =+( & + & +massDarkMatterInitial*self%massFractionInitial & + & +( & + & +self%massFractionInitial *radiusInitial & + & -self%darkMatterDistributedFraction*radius & + & ) & + & *4.0d0 & + & *Pi & + & *radiusInitialMean**2 & + & *densityDarkMatterInitial & + & *radiusInitialMeanSelfDerivative & + & ) + if (exponent(numerator)-exponent(denominator) > maxExponent(0.0d0)) then + call displayIndent ('Radius derivative calculation') + write (label,'(e12.6)') radius + message='r_final = '//label//' Mpc' + call displayMessage (message) + write (label,'(e12.6)') radiusInitial + message='r_initial = '//label//' Mpc' + call displayMessage (message) + write (label,'(e12.6)') self%radiusFinalMean + message='⟨r_final⟩ = '//label//' Mpc' + call displayMessage (message) + write (label,'(e12.6)') radiusInitialMean + message='⟨r_initial⟩ = '//label//' Mpc' + call displayMessage (message) + write (label,'(e12.6)') radiusInitialMeanSelfDerivative + message='d⟨r_initial⟩/dr_initial = '//label + call displayMessage (message) + write (label,'(e12.6)') radiusFinalMeanSelfDerivative + message='d⟨r_final⟩/dr_final = '//label + call displayMessage (message) + write (label,'(e12.6)') massDarkMatterInitial + message='M_dark,initial = '//label//' M☉' + call displayMessage (message) + write (label,'(e12.6)') self%baryonicFinalTerm + message='M_baryonic,final = '//label//' M☉' + call displayMessage (message) + write (label,'(e12.6)') self%baryonicFinalTermDerivative + message='dM_braryonic,final/dr_initial = '//label//' M☉/Mpc' + call displayMessage (message) + call displayUnindent('' ) + call Error_Report('Overflow in initial radius derivative calculation'//{introspection:location}) + end if + radiusInitialDerivative=+numerator & + & /denominator + return + end function sphericalAdiabaticGnedin2004RadiusInitialDerivative + + subroutine sphericalAdiabaticGnedin2004ComputeFactors(self,radius,computeGradientFactors) + !!{ + Compute various factors needed when solving for the initial radius in the dark matter halo using the adiabatic contraction + algorithm of \cite{gnedin_response_2004}. + !!} + use :: Numerical_Constants_Astronomical, only : gravitationalConstantGalacticus + implicit none + class (massDistributionSphericalAdiabaticGnedin2004), intent(inout), target :: self + double precision , intent(in ) :: radius + logical , intent(in ) :: computeGradientFactors + double precision :: velocityCircularSquaredGradient, velocityCircularSquared + + ! Set module-scope pointer to self. + self_ => self + ! Store the final radius and its orbit-averaged mean. + self%radiusFinal = radius + self%radiusFinalMean=self%radiusOrbitalMean(radius) + ! Compute the baryonic contribution to the rotation curve. + velocityCircularSquared=self%massDistributionBaryonic%rotationCurve(self%radiusFinalMean)**2 + self%baryonicFinalTerm=velocityCircularSquared*self%radiusFinalMean*self%radiusFinal/gravitationalConstantGalacticus + ! Compute the baryonic contribution to the rotation curve. + if (computeGradientFactors) then + velocityCircularSquaredGradient =+self%massDistributionBaryonic%rotationCurveGradient(self%radiusFinalMean) + self%baryonicFinalTermDerivative=+ velocityCircularSquaredGradient & + & *self%radiusOrbitalMeanDerivative(self%radiusFinal) & + & *self%radiusFinalMean & + & *self%radiusFinal & + & / gravitationalConstantGalacticus + end if + return + end subroutine sphericalAdiabaticGnedin2004ComputeFactors + + double precision function sphericalAdiabaticGnedin2004RadiusOrbitalMean(self,radius) + !!{ + Returns the orbit averaged radius for dark matter corresponding the given {\normalfont \ttfamily radius} using the model of + \cite{gnedin_response_2004}. + !!} + implicit none + class (massDistributionSphericalAdiabaticGnedin2004), intent(inout) :: self + double precision , intent(in ) :: radius + + if (self%omega /= omegaPrevious) then + if (allocated(radiusExponentiator)) deallocate(radiusExponentiator) + allocate(radiusExponentiator) + radiusExponentiator=fastExponentiator(1.0d-3,1.0d0,self%omega,1.0d4,.false.) + omegaPrevious = self%omega + end if + sphericalAdiabaticGnedin2004RadiusOrbitalMean=+self %A & + & *self %radiusFractionalPivot & + & *self %radiusVirial & + & *radiusExponentiator%exponentiate ( & + & + radius & + & /self%radiusFractionalPivot & + & /self%radiusVirial & + & ) + return + end function sphericalAdiabaticGnedin2004RadiusOrbitalMean + + double precision function sphericalAdiabaticGnedin2004RadiusOrbitalMeanDerivative(self,radius) + !!{ + Returns the derivative of the orbit averaged radius for dark matter corresponding the given {\normalfont \ttfamily radius} using the model of + \cite{gnedin_response_2004}. + !!} + implicit none + class (massDistributionSphericalAdiabaticGnedin2004), intent(inout) :: self + double precision , intent(in ) :: radius + + sphericalAdiabaticGnedin2004RadiusOrbitalMeanDerivative=+self%A & + & *self%omega & + & *( & + & + radius & + & /self%radiusFractionalPivot & + & /self%radiusVirial & + & )**(self%omega-1.0d0) + return + end function sphericalAdiabaticGnedin2004RadiusOrbitalMeanDerivative + + double precision function sphericalAdiabaticGnedin2004Solver(radiusInitial) + !!{ + Root function used in finding the initial radius in the dark matter halo when solving for adiabatic contraction. + !!} + implicit none + double precision, intent(in ) :: radiusInitial + double precision :: massDarkMatterInitial, radiusInitialMean + + ! Find the initial mean orbital radius. + radiusInitialMean =self_ %radiusOrbitalMean(radiusInitial ) + ! Get the mass of dark matter inside the initial radius. + massDarkMatterInitial=self_%massDistribution_%massEnclosedBySphere(radiusInitialMean) + ! Compute the root function. + sphericalAdiabaticGnedin2004Solver=+massDarkMatterInitial & + & *( & + & +self_%massFractionInitial* radiusInitial & + & -self_%darkMatterDistributedFraction *self_%radiusFinal & + & ) & + & -self_%baryonicFinalTerm + return + end function sphericalAdiabaticGnedin2004Solver diff --git a/source/mass_distributions.spherical.beta_profile.F90 b/source/mass_distributions.spherical.beta_profile.F90 index f0bb7909d0..a45ec6a921 100644 --- a/source/mass_distributions.spherical.beta_profile.F90 +++ b/source/mass_distributions.spherical.beta_profile.F90 @@ -32,14 +32,15 @@ !!} double precision :: beta , coreRadius , densityNormalization , & & momentRadial2Previous , momentRadial3Previous, momentRadial2XPrevious, & - & momentRadial3XPrevious - logical :: betaIsTwoThirds + & momentRadial3XPrevious, outerRadius + logical :: betaIsTwoThirds , truncateAtOuterRadius contains procedure :: density => betaProfileDensity procedure :: densityGradientRadial => betaProfileDensityGradientRadial procedure :: densityRadialMoment => betaProfileDensityRadialMoment procedure :: densitySquareIntegral => betaProfileDensitySquareIntegral procedure :: massEnclosedBySphere => betaProfileMassEnclosedBySphere + procedure :: potentialIsAnalytic => betaProfilePotentialIsAnalytic procedure :: potential => betaProfilePotential procedure :: descriptor => betaProfileDescriptor end type massDistributionBetaProfile @@ -64,10 +65,10 @@ function betaProfileConstructorParameters(parameters) result(self) implicit none type (massDistributionBetaProfile) :: self type (inputParameters ), intent(inout) :: parameters - double precision :: beta , densityNormalization, & - & mass , outerRadius , & + double precision :: beta , densityNormalization , & + & mass , outerRadius , & & coreRadius - logical :: dimensionless + logical :: dimensionless, truncateAtOuterRadius type (varying_string ) :: componentType type (varying_string ) :: massType @@ -108,6 +109,12 @@ function betaProfileConstructorParameters(parameters) result(self) If true then the $\beta$-model mass distribution is considered to be in dimensionless units. parameters + + truncateAtOuterRadius + .false. + If true then the $\beta$-model mass distribution is truncated beyond the outer radius. + parameters + componentType var_str('unknown') @@ -122,18 +129,19 @@ function betaProfileConstructorParameters(parameters) result(self) self=massDistributionBetaProfile(beta,componentType=enumerationComponentTypeEncode(componentType,includesPrefix=.false.),massType=enumerationMassTypeEncode(massType,includesPrefix=.false.){conditions}) - - - - - + + + + + + !!] return end function betaProfileConstructorParameters - function betaProfileConstructorInternal(beta,densityNormalization,mass,outerRadius,coreRadius,dimensionless,componentType,massType) result(self) + function betaProfileConstructorInternal(beta,densityNormalization,mass,outerRadius,coreRadius,dimensionless,truncateAtOuterRadius,componentType,massType) result(self) !!{ Constructor for ``betaProfile'' convergence class. !!} @@ -148,7 +156,7 @@ function betaProfileConstructorInternal(beta,densityNormalization,mass,outerRadi double precision , intent(in ) :: beta double precision , intent(in ), optional :: densityNormalization , mass , & & outerRadius , coreRadius - logical , intent(in ), optional :: dimensionless + logical , intent(in ), optional :: dimensionless , truncateAtOuterRadius type (enumerationComponentTypeType), intent(in ), optional :: componentType type (enumerationMassTypeType ), intent(in ), optional :: massType double precision , parameter :: radiusTiny =1.0d-6 @@ -186,7 +194,7 @@ function betaProfileConstructorInternal(beta,densityNormalization,mass,outerRadi & present(densityNormalization) & & ) then self%densityNormalization=densityNormalization - else if (& + else if ( & & present(mass ).and. & & present(outerRadius ) & & ) then @@ -230,82 +238,84 @@ function betaProfileConstructorInternal(beta,densityNormalization,mass,outerRadi end if end if end if + ! Check for truncation. + if (present(truncateAtOuterRadius)) then + self%truncateAtOuterRadius=truncateAtOuterRadius + else + self%truncateAtOuterRadius=.false. + end if + if (self%truncateAtOuterRadius) then + if (.not.present(outerRadius)) call Error_Report('can not truncate profile without an outer radius'//{introspection:location}) + self%outerRadius=outerRadius + end if ! Initialize stored results. self%momentRadial2XPrevious=-1.0d0 self%momentRadial3XPrevious=-1.0d0 return end function betaProfileConstructorInternal - double precision function betaProfileDensity(self,coordinates,componentType,massType) + double precision function betaProfileDensity(self,coordinates) !!{ Return the density at the specified {\normalfont \ttfamily coordinates} in a $\beta$-profile mass distribution. !!} - use :: Coordinates, only : assignment(=), coordinateSpherical implicit none - class (massDistributionBetaProfile ), intent(inout) :: self - class (coordinate ), intent(in ) :: coordinates - type (enumerationComponentTypeType), intent(in ), optional :: componentType - type (enumerationMassTypeType ), intent(in ), optional :: massType - type (coordinateSpherical ) :: position - double precision :: r + class (massDistributionBetaProfile ), intent(inout) :: self + class (coordinate ), intent(in ) :: coordinates + double precision :: radius - if (.not.self%matches(componentType,massType)) then + ! Compute density. + radius=coordinates%rSpherical() + if (self%truncateAtOuterRadius .and. radius > self%outerRadius) then betaProfileDensity=0.0d0 - return + else + betaProfileDensity=self%densityNormalization/(1.0d0+(radius/self%coreRadius)**2)**(1.5d0*self%beta) end if - ! Get position in spherical coordinate system. - position =coordinates - ! Compute density. - r =position%r()/self%coreRadius - betaProfileDensity=self%densityNormalization/(1.0d0+r**2)**(1.5d0*self%beta) return end function betaProfileDensity - double precision function betaProfileDensityGradientRadial(self,coordinates,logarithmic,componentType,massType) + double precision function betaProfileDensityGradientRadial(self,coordinates,logarithmic) !!{ Return the density at the specified {\normalfont \ttfamily coordinates} in a $\beta$-profile mass distribution. !!} - use :: Coordinates, only : assignment(=), coordinateSpherical implicit none - class (massDistributionBetaProfile ), intent(inout) :: self + class (massDistributionBetaProfile ), intent(inout), target :: self class (coordinate ), intent(in ) :: coordinates logical , intent(in ), optional :: logarithmic - type (enumerationComponentTypeType), intent(in ), optional :: componentType - type (enumerationMassTypeType ), intent(in ), optional :: massType - type (coordinateSpherical ) :: position - double precision :: r + double precision :: radius logical :: logarithmicActual - if (.not.self%matches(componentType,massType)) then - betaProfileDensityGradientRadial=0.0d0 - return - end if ! Set default options. logarithmicActual=.false. if (present(logarithmic)) logarithmicActual=logarithmic ! Get position in spherical coordinate system. - position=coordinates - r =position%r()/self%coreRadius + radius=coordinates%rSpherical() + ! Apply truncation. + if (self%truncateAtOuterRadius .and. radius > self%outerRadius) then + betaProfileDensityGradientRadial=0.0d0 + return + end if + ! Convert to dimensionless radius. + radius=radius/self%coreRadius ! Compute density gradient. if (logarithmicActual) then - betaProfileDensityGradientRadial= & - & -3.0d0 & - & *self%beta & - & * r**2 & - & /(r**2+1.0d0) + betaProfileDensityGradientRadial= & + & -3.0d0 & + & *self%beta & + & * radius**2 & + & /(radius**2+1.0d0) else - betaProfileDensityGradientRadial= & - & -3.0d0 & - & *self%beta & - & *self%densityNormalization & - & /self%coreRadius & - & * r**2 & - & /(r**2+1.0d0)**(1.5d0*self%beta+1.0d0) + betaProfileDensityGradientRadial= & + & -3.0d0 & + & *self%beta & + & *self%densityNormalization & + & /self%coreRadius & + & * radius & + & /(radius**2+1.0d0)**(1.5d0*self%beta+1.0d0) end if return end function betaProfileDensityGradientRadial - double precision function betaProfileMassEnclosedBySphere(self,radius,componentType,massType) + double precision function betaProfileMassEnclosedBySphere(self,radius) !!{ Computes the mass enclosed within a sphere of given {\normalfont \ttfamily radius} for $\beta$-profile mass distributions. Result computed using \href{http://www.wolframalpha.com/input/?i=integrate+4*pi*r^2*rho\%2F\%281\%2Br^2\%29^\%283*beta\%2F2\%29}{Wolfram Alpha}. @@ -313,53 +323,52 @@ double precision function betaProfileMassEnclosedBySphere(self,radius,componentT use :: Hypergeometric_Functions, only : Hypergeometric_2F1 use :: Numerical_Constants_Math, only : Pi implicit none - class (massDistributionBetaProfile ), intent(inout), target :: self - double precision , intent(in ) :: radius - type (enumerationComponentTypeType), intent(in ), optional :: componentType - type (enumerationMassTypeType ), intent(in ), optional :: massType - double precision , parameter :: radiusTiny =1.0d-6 - double precision :: fractionalRadius + class (massDistributionBetaProfile ), intent(inout), target :: self + double precision , intent(in ) :: radius + double precision , parameter :: radiusTiny =1.0d-6 + double precision :: fractionalRadius , radius_ - if (.not.self%matches(componentType,massType)) then - betaProfileMassEnclosedBySphere=0.0d0 - return + if (self%truncateAtOuterRadius .and. radius > self%outerRadius) then + radius_=self%outerRadius + else + radius_=radius end if - fractionalRadius=radius/self%coreRadius + fractionalRadius=radius_/self%coreRadius if (self%betaIsTwoThirds) then ! Solution for special case of β=2/3. if (fractionalRadius < radiusTiny) then ! Use a series solution. - betaProfileMassEnclosedBySphere= & - & +4.0d0 & - & *Pi & - & *self%densityNormalization & - & *self%coreRadius **3 & - & * fractionalRadius**3 & - & *( +1.0d0/3.0d0+fractionalRadius**2 & - & * ( -1.0d0/5.0d0+fractionalRadius**2 & - & * (+1.0d0/7.0d0 & - & ) & - & ) & + betaProfileMassEnclosedBySphere= & + & +4.0d0 & + & *Pi & + & *self%densityNormalization & + & *self%coreRadius **3 & + & * fractionalRadius**3 & + & *( +1.0d0/3.0d0+fractionalRadius**2 & + & * ( -1.0d0/5.0d0+fractionalRadius**2 & + & * (+1.0d0/7.0d0 & + & ) & + & ) & & ) else - betaProfileMassEnclosedBySphere= & - & +4.0d0 & - & *Pi & - & *self%densityNormalization & - & *( & - & + fractionalRadius & - & -atan(fractionalRadius) & - & ) & + betaProfileMassEnclosedBySphere= & + & +4.0d0 & + & *Pi & + & *self%densityNormalization & + & *( & + & + fractionalRadius & + & -atan(fractionalRadius) & + & ) & & *self%coreRadius**3 end if else ! General solution. - betaProfileMassEnclosedBySphere= & + betaProfileMassEnclosedBySphere= & & +4.0d0 & & /3.0d0 & & *Pi & & *self%densityNormalization & - & *radius**3 & + & *radius_**3 & & *Hypergeometric_2F1( & & [1.5d0,1.5d0*self%beta], & & [2.5d0 ], & @@ -369,37 +378,46 @@ double precision function betaProfileMassEnclosedBySphere(self,radius,componentT return end function betaProfileMassEnclosedBySphere - double precision function betaProfilePotential(self,coordinates,componentType,massType) + logical function betaProfilePotentialIsAnalytic(self) result(isAnalytic) + !!{ + Return that the potential has an analytic form. + !!} + implicit none + class(massDistributionBetaProfile), intent(inout) :: self + + isAnalytic=.true. + return + end function betaProfilePotentialIsAnalytic + + double precision function betaProfilePotential(self,coordinates,status) !!{ Return the potential at the specified {\normalfont \ttfamily coordinates} in a $\beta$-profile mass distribution. Calculated using \href{http://www.wolframalpha.com/input/?i=integrate+4\%2F3+\%CF\%80+r+\%CF\%81+2F1\%283\%2F2\%2C+\%283+\%CE\%B2\%29\%2F2\%2C+5\%2F2\%2C+-r^2\%29}{Wolfram Alpha}. !!} - use :: Coordinates , only : assignment(=) , coordinateSpherical + use :: Galactic_Structure_Options , only : structureErrorCodeSuccess use :: Hypergeometric_Functions , only : Hypergeometric_2F1 use :: Numerical_Comparison , only : Values_Agree use :: Numerical_Constants_Astronomical, only : gravitationalConstantGalacticus use :: Numerical_Constants_Math , only : Pi implicit none - class (massDistributionBetaProfile ), intent(inout) :: self - class (coordinate ), intent(in ) :: coordinates - type (enumerationComponentTypeType), intent(in ), optional :: componentType - type (enumerationMassTypeType ), intent(in ), optional :: massType - type (coordinateSpherical ) :: position - double precision , parameter :: fractionalRadiusMinimum=1.0d-3 - double precision :: fractionalRadius + class (massDistributionBetaProfile ), intent(inout), target :: self + class (coordinate ), intent(in ) :: coordinates + type (enumerationStructureErrorCodeType), intent( out), optional :: status + double precision , parameter :: fractionalRadiusMinimum=1.0d-3 + double precision :: fractionalRadius , radius - if (.not.self%matches(componentType,massType)) then - betaProfilePotential=0.0d0 - return - end if - ! Get position in spherical coordinate system. - position=coordinates + if (present(status)) status=structureErrorCodeSuccess ! Compute the potential at this position. - fractionalRadius=position%r()/self%coreRadius + radius=coordinates%rSpherical() + if (self%truncateAtOuterRadius .and. radius > self%outerRadius) then + fractionalRadius=self%outerRadius/self%coreRadius + else + fractionalRadius= radius/self%coreRadius + end if if (Values_Agree(self%beta,2.0d0/3.0d0,absTol=1.0d-6)) then if (fractionalRadius < fractionalRadiusMinimum) then - betaProfilePotential= & + betaProfilePotential= & & Pi & & *self%densityNormalization & & *( & @@ -411,7 +429,7 @@ double precision function betaProfilePotential(self,coordinates,componentType,ma & /5.0d0 & & ) else - betaProfilePotential= & + betaProfilePotential= & & 2.0d0 & & *Pi & & *self%densityNormalization & @@ -427,7 +445,7 @@ double precision function betaProfilePotential(self,coordinates,componentType,ma end if else if (fractionalRadius < fractionalRadiusMinimum) then - betaProfilePotential= & + betaProfilePotential= & & Pi & & *self%densityNormalization & & *fractionalRadius**3 & @@ -439,7 +457,7 @@ double precision function betaProfilePotential(self,coordinates,componentType,ma & *fractionalRadius**2 & & ) else - betaProfilePotential= & + betaProfilePotential= & & 2.0d0 & & /3.0d0 & & *Pi & @@ -463,14 +481,14 @@ double precision function betaProfilePotential(self,coordinates,componentType,ma & ) end if end if - if (.not.self%isDimensionless()) & - & betaProfilePotential= & - & betaProfilePotential & + if (.not.self%isDimensionless()) & + & betaProfilePotential= & + & betaProfilePotential & & *gravitationalConstantGalacticus return end function betaProfilePotential - double precision function betaProfileDensityRadialMoment(self,moment,radiusMinimum,radiusMaximum,isInfinite,componentType,massType) + double precision function betaProfileDensityRadialMoment(self,moment,radiusMinimum,radiusMaximum,isInfinite) !!{ Computes radial moments of the density in a $\beta$-profile mass distribution. !!} @@ -483,14 +501,27 @@ double precision function betaProfileDensityRadialMoment(self,moment,radiusMinim double precision , intent(in ) :: moment double precision , intent(in ), optional :: radiusMinimum , radiusMaximum logical , intent( out), optional :: isInfinite - type (enumerationComponentTypeType), intent(in ), optional :: componentType - type (enumerationMassTypeType ), intent(in ), optional :: massType - double precision :: fractionalRadiusMinimum, fractionalRadiusMaximum + logical :: haveRadiusMinimum , haveRadiusMaximum + double precision :: radiusMinimum_ , radiusMaximum_ , & + & fractionalRadiusMinimum, fractionalRadiusMaximum integer :: specialCaseMoment - if (.not.self%matches(componentType,massType)) then - betaProfileDensityRadialMoment=0.0d0 - return + ! Determine effective radii. + haveRadiusMinimum=present(radiusMinimum) + haveRadiusMaximum=present(radiusMaximum) + radiusMinimum_ = 0.0d0 + radiusMaximum_ =huge ( 0.0d0) + if (haveRadiusMinimum) then + radiusMinimum_ = radiusMinimum + end if + if (haveRadiusMaximum) then + radiusMaximum_ = radiusMaximum + end if + if (self%truncateAtOuterRadius) then + radiusMinimum_ =min(radiusMinimum_,self%outerRadius) + radiusMaximum_ =min(radiusMaximum_,self%outerRadius) + haveRadiusMinimum=.true. + haveRadiusMaximum=.true. end if ! Determine if special case solutions can be used. specialCaseMoment=-huge(0) @@ -506,8 +537,8 @@ double precision function betaProfileDensityRadialMoment(self,moment,radiusMinim end if end if if (present(isInfinite)) isInfinite=.false. - if (present(radiusMaximum)) then - fractionalRadiusMaximum=radiusMaximum/self%coreRadius + if (haveRadiusMaximum) then + fractionalRadiusMaximum=radiusMaximum_/self%coreRadius if (specialCaseMoment /= -huge(0)) then ! Special case for 0ᵗʰ, 1ˢᵗ, 2ⁿᵈ, and 3ʳᵈ moments of a β=2/3 distribution. betaProfileDensityRadialMoment= & @@ -539,10 +570,25 @@ double precision function betaProfileDensityRadialMoment(self,moment,radiusMinim & / (+1.0d0+moment ) end if end if - if (present(radiusMinimum)) then - fractionalRadiusMinimum=radiusMinimum/self%coreRadius + if (haveRadiusMinimum) then + fractionalRadiusMinimum=radiusMinimum_/self%coreRadius + if (specialCaseMoment /= -huge(0)) then + ! Special case for 0ᵗʰ, 1ˢᵗ, 2ⁿᵈ, and 3ʳᵈ moments of a β=2/3 distribution. + betaProfileDensityRadialMoment= & + & +betaProfileDensityRadialMoment & + & -radialMomentTwoThirds(specialCaseMoment,fractionalRadiusMinimum) + else + betaProfileDensityRadialMoment= & + & +betaProfileDensityRadialMoment & + & -fractionalRadiusMinimum**(moment+1.0d0) & + & *Hypergeometric_2F1 ( & + & [(moment+1.0d0)/2.0d0,1.5d0*self%beta], & + & [(moment+3.0d0)/2.0d0 ], & + & -fractionalRadiusMinimum**2 & + & ) & + & / (moment+1.0d0) + end if else - fractionalRadiusMinimum=0.0d0 if (moment <= -1.0d0) then betaProfileDensityRadialMoment=0.0d0 if (present(isInfinite)) then @@ -553,22 +599,6 @@ double precision function betaProfileDensityRadialMoment(self,moment,radiusMinim end if end if end if - if (specialCaseMoment /= -huge(0)) then - ! Special case for 0ᵗʰ, 1ˢᵗ, 2ⁿᵈ, and 3ʳᵈ moments of a β=2/3 distribution. - betaProfileDensityRadialMoment= & - & +betaProfileDensityRadialMoment & - & -radialMomentTwoThirds(specialCaseMoment,fractionalRadiusMinimum) - else - betaProfileDensityRadialMoment= & - & +betaProfileDensityRadialMoment & - & -fractionalRadiusMinimum**(moment+1.0d0) & - & *Hypergeometric_2F1 ( & - & [(moment+1.0d0)/2.0d0,1.5d0*self%beta], & - & [(moment+3.0d0)/2.0d0 ], & - & -fractionalRadiusMinimum**2 & - & ) & - & / (moment+1.0d0) - end if ! Convert to dimensionful units. betaProfileDensityRadialMoment & & =betaProfileDensityRadialMoment & @@ -651,25 +681,44 @@ double precision function betaProfileDensitySquareIntegral(self,radiusMinimum,ra use :: Hypergeometric_Functions, only : Hypergeometric_2F1 use :: Numerical_Constants_Math, only : Pi implicit none - class (massDistributionBetaProfile), intent(inout) :: self - double precision , intent(in ), optional :: radiusMinimum , radiusMaximum - logical , intent( out), optional :: isInfinite - double precision :: fractionalRadiusMinimum, fractionalRadiusMaximum + class (massDistributionBetaProfile ), intent(inout) :: self + double precision , intent(in ), optional :: radiusMinimum , radiusMaximum + logical , intent( out), optional :: isInfinite + logical :: haveRadiusMinimum , haveRadiusMaximum + double precision :: radiusMinimum_ , radiusMaximum_ , & + & fractionalRadiusMinimum, fractionalRadiusMaximum if (present(isInfinite)) isInfinite=.false. betaProfileDensitySquareIntegral=0.0d0 + ! Determine effective radii. + haveRadiusMinimum=present(radiusMinimum) + haveRadiusMaximum=present(radiusMaximum) + radiusMinimum_ = 0.0d0 + radiusMaximum_ =huge ( 0.0d0) + if (haveRadiusMinimum) then + radiusMinimum_ = radiusMinimum + end if + if (haveRadiusMaximum) then + radiusMaximum_ = radiusMaximum + end if + if (self%truncateAtOuterRadius) then + radiusMinimum_ =min(radiusMinimum_,self%outerRadius) + radiusMaximum_ =min(radiusMaximum_,self%outerRadius) + haveRadiusMinimum=.true. + haveRadiusMaximum=.true. + end if ! Determine if the special case solution can be used. if (self%betaIsTwoThirds) then ! Compute the integral for the case β=2/3. - if (present(radiusMinimum)) then - fractionalRadiusMinimum=+ radiusMinimum & + if (haveRadiusMinimum) then + fractionalRadiusMinimum=+ radiusMinimum_ & & /self%coreRadius betaProfileDensitySquareIntegral=+betaProfileDensitySquareIntegral-4.0d0*Pi*(atan(fractionalRadiusMinimum)/2.0d0-fractionalRadiusMinimum/2.0d0/(1.0d0+fractionalRadiusMinimum**2)) else betaProfileDensitySquareIntegral=+betaProfileDensitySquareIntegral+0.0d0 end if - if (present(radiusMaximum)) then - fractionalRadiusMaximum=+ radiusMaximum & + if (haveRadiusMaximum) then + fractionalRadiusMaximum=+ radiusMaximum_ & & /self%coreRadius betaProfileDensitySquareIntegral=betaProfileDensitySquareIntegral+4.0d0*Pi*(atan(fractionalRadiusMaximum)/2.0d0-fractionalRadiusMaximum/2.0d0/(1.0d0+fractionalRadiusMaximum**2)) else @@ -677,15 +726,15 @@ double precision function betaProfileDensitySquareIntegral(self,radiusMinimum,ra end if else ! Compute the integral for the general case. - if (present(radiusMinimum)) then - fractionalRadiusMinimum=+ radiusMinimum & + if (haveRadiusMinimum) then + fractionalRadiusMinimum=+ radiusMinimum_ & & /self%coreRadius betaProfileDensitySquareIntegral=+betaProfileDensitySquareIntegral-4.0d0*Pi/3.0d0*fractionalRadiusMinimum**3*Hypergeometric_2F1([1.5d0,3.0d0*self%beta],[2.5d0],-fractionalRadiusMinimum**2) else betaProfileDensitySquareIntegral=+betaProfileDensitySquareIntegral+0.0d0 end if - if (present(radiusMaximum)) then - fractionalRadiusMaximum=+ radiusMaximum & + if (haveRadiusMaximum) then + fractionalRadiusMaximum=+ radiusMaximum_ & & /self%coreRadius betaProfileDensitySquareIntegral=+betaProfileDensitySquareIntegral+4.0d0*Pi/3.0d0*fractionalRadiusMaximum**3*Hypergeometric_2F1([1.5d0,3.0d0*self%beta],[2.5d0],-fractionalRadiusMaximum**2) else diff --git a/source/mass_distributions.spherical.black_hole.F90 b/source/mass_distributions.spherical.black_hole.F90 new file mode 100644 index 0000000000..4b5e79e586 --- /dev/null +++ b/source/mass_distributions.spherical.black_hole.F90 @@ -0,0 +1,319 @@ +!! Copyright 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, +!! 2019, 2020, 2021, 2022, 2023 +!! Andrew Benson +!! +!! This file is part of Galacticus. +!! +!! Galacticus is free software: you can redistribute it and/or modify +!! it under the terms of the GNU General Public License as published by +!! the Free Software Foundation, either version 3 of the License, or +!! (at your option) any later version. +!! +!! Galacticus is distributed in the hope that it will be useful, +!! but WITHOUT ANY WARRANTY; without even the implied warranty of +!! MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +!! GNU General Public License for more details. +!! +!! You should have received a copy of the GNU General Public License +!! along with Galacticus. If not, see . + + !!{ + Implementation of a black hole distribution class. + !!} + + !![ + + A mass distribution class for point masses. + + !!] + type, public, extends(massDistributionSpherical) :: massDistributionBlackHole + !!{ + A black hole distribution. + !!} + double precision :: mass, radiusGravitational + contains + procedure :: massTotal => blackHoleMassTotal + procedure :: density => blackHoleDensity + procedure :: densityGradientRadial => blackHoleDensityGradientRadial + procedure :: densityRadialMoment => blackHoleDensityRadialMoment + procedure :: massEnclosedBySphere => blackHoleMassEnclosedBySphere + procedure :: rotationCurve => blackHoleRotationCurve + procedure :: rotationCurveGradient => blackHoleRotationCurveGradient + procedure :: potentialIsAnalytic => blackHolePotentialIsAnalytic + procedure :: potential => blackHolePotential + end type massDistributionBlackHole + + interface massDistributionBlackHole + !!{ + Constructors for the {\normalfont \ttfamily blackHole} mass distribution class. + !!} + module procedure blackHoleConstructorParameters + module procedure blackHoleConstructorInternal + end interface massDistributionBlackHole + +contains + + function blackHoleConstructorParameters(parameters) result(self) + !!{ + Constructor for the {\normalfont \ttfamily blackHole} mass distribution class which builds the object from a parameter + set. + !!} + use :: Input_Parameters , only : inputParameters + use :: Galactic_Structure_Options, only : enumerationComponentTypeEncode, enumerationMassTypeEncode + implicit none + type (massDistributionBlackHole) :: self + type (inputParameters ), intent(inout) :: parameters + double precision :: mass + logical :: dimensionless + type (varying_string ) :: componentType + type (varying_string ) :: massType + + !![ + + mass + 1.0d0 + The mass of the black hole. + parameters + + + dimensionless + .true. + If true the point mass distribution is considered to be dimensionless. + parameters + + + componentType + var_str('unknown') + The component type that this mass distribution represents. + parameters + + + massType + var_str('unknown') + The mass type that this mass distribution represents. + parameters + + + self=massDistributionBlackHole(componentType=enumerationComponentTypeEncode(componentType,includesPrefix=.false.),massType=enumerationMassTypeEncode(massType,includesPrefix=.false.){conditions}) + + + + + !!] + return + end function blackHoleConstructorParameters + + function blackHoleConstructorInternal(mass,dimensionless,componentType,massType) result(self) + !!{ + Constructor for {\normalfont \ttfamily blackHole} mass distribution class. + !!} + use :: Error , only : Error_Report + use :: Numerical_Comparison , only : Values_Differ + use :: Numerical_Constants_Astronomical, only : gravitationalConstantGalacticus + use :: Numerical_Constants_Physical , only : speedLight + use :: Numerical_Constants_Prefixes , only : milli + implicit none + type (massDistributionBlackHole ) :: self + double precision , intent(in ), optional :: mass + logical , intent(in ), optional :: dimensionless + type (enumerationComponentTypeType), intent(in ), optional :: componentType + type (enumerationMassTypeType ), intent(in ), optional :: massType + !![ + + !!] + ! Determine if profile is dimensionless. + self%dimensionless=.false. + if (present(dimensionless)) self%dimensionless=dimensionless + ! If dimensionless, then set scale length and mass to unity. + if (self%dimensionless) then + if (present(mass)) then + if (Values_Differ(mass,1.0d0,absTol=1.0d-6)) call Error_Report('mass should be unity for a dimensionless profile (or simply do not specify a mass)'//{introspection:location}) + end if + self%mass =1.0d0 + else if (present(mass)) then + self%mass =mass + self%dimensionless=.false. + else + call Error_Report('either specify a mass, or declare the distribution to be dimensionless'//{introspection:location}) + end if + ! Compute the gravitational radius for the black hole. + if (self%dimensionless) then + self%radiusGravitational=+1.0d0 + else + self%radiusGravitational=+ gravitationalConstantGalacticus & + & *self%mass & + & / milli **2 & + & / speedLight **2 + end if + return + end function blackHoleConstructorInternal + + double precision function blackHoleMassTotal(self) + !!{ + Return the total mass in the black hole. + !!} + implicit none + class(massDistributionBlackHole), intent(inout) :: self + + blackHoleMassTotal=self%mass + return + end function blackHoleMassTotal + + double precision function blackHoleDensity(self,coordinates) + !!{ + Return the density at the specified {\normalfont \ttfamily coordinates} in a $\beta$-profile mass distribution. + !!} + implicit none + class(massDistributionBlackHole), intent(inout) :: self + class(coordinate ), intent(in ) :: coordinates + + blackHoleDensity= 0.0d0 + if (coordinates%rSphericalSquared() > 0.0d0) return + blackHoleDensity=huge(0.0d0) + return + end function blackHoleDensity + + double precision function blackHoleDensityGradientRadial(self,coordinates,logarithmic) + !!{ + Return the density gradient in the radial direction for a point mass. + !!} + implicit none + class (massDistributionBlackHole), intent(inout), target :: self + class (coordinate ), intent(in ) :: coordinates + logical , intent(in ), optional :: logarithmic + !$GLC attributes unused :: logarithmic + + blackHoleDensityGradientRadial= 0.0d0 + if (coordinates%rSphericalSquared() > 0.0d0) return + blackHoleDensityGradientRadial=-huge(0.0d0) + return + end function blackHoleDensityGradientRadial + + double precision function blackHoleMassEnclosedBySphere(self,radius) + !!{ + Computes the mass enclosed within a sphere of given {\normalfont \ttfamily radius} for a black hole. + !!} + implicit none + class (massDistributionBlackHole), intent(inout), target :: self + double precision , intent(in ) :: radius + + blackHoleMassEnclosedBySphere=+self%mass + return + end function blackHoleMassEnclosedBySphere + + double precision function blackHoleRotationCurve(self,radius) + !!{ + Return the rotation curve for a blackHole mass distribution. + !!} + use :: Numerical_Constants_Astronomical, only : gravitationalConstantGalacticus + use :: Numerical_Constants_Physical , only : speedLight + use :: Numerical_Constants_Prefixes , only : milli + implicit none + class (massDistributionBlackHole), intent(inout) :: self + double precision , intent(in ) :: radius + + if (self%mass <= 0.0d0) then + blackHoleRotationCurve=0.0d0 + else if (radius <= self%radiusGravitational) then + if (self%dimensionless) then + blackHoleRotationCurve=+1.0d0 + else + blackHoleRotationCurve=+milli & + & *speedLight + end if + else + blackHoleRotationCurve=+sqrt( & + & +self%massEnclosedBySphere(radius) & + & / radius & + & ) + ! Make dimensionful if necessary. + if (.not.self%dimensionless) blackHoleRotationCurve=+sqrt(gravitationalConstantGalacticus) & + & *blackHoleRotationCurve + end if + return + end function blackHoleRotationCurve + + double precision function blackHoleRotationCurveGradient(self,radius) + !!{ + Return the rotation curve gradient for a spherical mass distribution. + !!} + use :: Numerical_Constants_Astronomical, only : gravitationalConstantGalacticus + use :: Numerical_Constants_Math , only : Pi + implicit none + class (massDistributionBlackHole), intent(inout) :: self + double precision , intent(in ) :: radius + + if ( & + & self%mass <= 0.0d0 & + & .or. & + & self%radiusGravitational > radius & + &) then + blackHoleRotationCurveGradient=+0.0d0 + else + blackHoleRotationCurveGradient=-self%mass & + & / radius**2 + ! Make dimensionful if necessary. + if (.not.self%dimensionless) blackHoleRotationCurveGradient=+gravitationalConstantGalacticus & + & *blackHoleRotationCurveGradient + end if + return + end function blackHoleRotationCurveGradient + + logical function blackHolePotentialIsAnalytic(self) result(isAnalytic) + !!{ + Return that the potential has an analytic form. + !!} + implicit none + class(massDistributionBlackHole), intent(inout) :: self + + isAnalytic=.true. + return + end function blackHolePotentialIsAnalytic + + double precision function blackHolePotential(self,coordinates,status) + !!{ + Return the potential at the specified {\normalfont \ttfamily coordinates} for a point mass. + !!} + use :: Galactic_Structure_Options , only : structureErrorCodeSuccess + use :: Numerical_Constants_Astronomical, only : gravitationalConstantGalacticus + implicit none + class(massDistributionBlackHole ), intent(inout), target :: self + class(coordinate ), intent(in ) :: coordinates + type (enumerationStructureErrorCodeType), intent( out), optional :: status + + if (present(status)) status=structureErrorCodeSuccess + blackHolePotential=- self %mass & + & /max( & + & coordinates%rSpherical (), & + & self %radiusGravitational & + & ) + if (.not.self%dimensionless) & + & blackHolePotential=+blackHolePotential & + & *gravitationalConstantGalacticus + return + end function blackHolePotential + + double precision function blackHoleDensityRadialMoment(self,moment,radiusMinimum,radiusMaximum,isInfinite) + !!{ + Computes radial moments of the density for a point mass. + !!} + use :: Error, only : Error_Report + implicit none + class (massDistributionBlackHole), intent(inout) :: self + double precision , intent(in ) :: moment + double precision , intent(in ), optional :: radiusMinimum , radiusMaximum + logical , intent( out), optional :: isInfinite + + blackHoleDensityRadialMoment=0.0d0 + ! Moment is zero if: + if (moment > 0.0d0) return ! The moment is positive (in which case ∫ δ(r) rᵐ dr = 0). + if (present(radiusMinimum)) then + if (radiusMinimum > 0.0d0) return ! The lower limit of the integral does not extend to zero. + end if + if (present(isInfinite)) then + isInfinite=.true. + else + call Error_Report('radial moment is infinite'//{introspection:location}) + end if + return + end function blackHoleDensityRadialMoment diff --git a/source/mass_distributions.spherical.constant_density_cloud.F90 b/source/mass_distributions.spherical.constant_density_cloud.F90 index f6796600d9..80c7986a0a 100644 --- a/source/mass_distributions.spherical.constant_density_cloud.F90 +++ b/source/mass_distributions.spherical.constant_density_cloud.F90 @@ -37,7 +37,8 @@ procedure :: densityGradientRadial => constantDensityCloudDensityGradientRadial procedure :: densityRadialMoment => constantDensityCloudDensityRadialMoment procedure :: massEnclosedBySphere => constantDensityCloudMassEnclosedBySphere - procedure :: potential => constantDensityCloudPotential + procedure :: potentialIsAnalytic => constantDensityCloudPotentialIsAnalytic + procedure :: potential => constantDensityCloudPotential end type massDistributionConstantDensityCloud interface massDistributionConstantDensityCloud @@ -118,20 +119,14 @@ function constantDensityCloudConstructorInternal(mass,radius,componentType,massT return end function constantDensityCloudConstructorInternal - double precision function constantDensityCloudDensity(self,coordinates,componentType,massType) + double precision function constantDensityCloudDensity(self,coordinates) !!{ Return the density at the specified {\normalfont \ttfamily coordinates} in a $\beta$-profile mass distribution. !!} implicit none - class(massDistributionConstantDensityCloud), intent(inout) :: self - class(coordinate ), intent(in ) :: coordinates - type (enumerationComponentTypeType ), intent(in ), optional :: componentType - type (enumerationMassTypeType ), intent(in ), optional :: massType + class(massDistributionConstantDensityCloud), intent(inout) :: self + class(coordinate ), intent(in ) :: coordinates - if (.not.self%matches(componentType,massType)) then - constantDensityCloudDensity=0.0d0 - return - end if if (coordinates%rSphericalSquared() < self%radiusSquared) then constantDensityCloudDensity=self%density_ else @@ -140,36 +135,28 @@ double precision function constantDensityCloudDensity(self,coordinates,component return end function constantDensityCloudDensity - double precision function constantDensityCloudDensityGradientRadial(self,coordinates,logarithmic,componentType,massType) + double precision function constantDensityCloudDensityGradientRadial(self,coordinates,logarithmic) !!{ Return the density gradient in the radial direction in a constant density cloud. !!} implicit none - class (massDistributionConstantDensityCloud), intent(inout) :: self + class (massDistributionConstantDensityCloud), intent(inout), target :: self class (coordinate ), intent(in ) :: coordinates logical , intent(in ), optional :: logarithmic - type (enumerationComponentTypeType ), intent(in ), optional :: componentType - type (enumerationMassTypeType ), intent(in ), optional :: massType - !$GLC attributes unused :: self, coordinates, logarithmic, componentType, massType + !$GLC attributes unused :: self, coordinates, logarithmic constantDensityCloudDensityGradientRadial=0.0d0 return end function constantDensityCloudDensityGradientRadial - double precision function constantDensityCloudMassEnclosedBySphere(self,radius,componentType,massType) + double precision function constantDensityCloudMassEnclosedBySphere(self,radius) !!{ Computes the mass enclosed within a sphere of given {\normalfont \ttfamily radius} for a constant density cloud. !!} implicit none - class (massDistributionConstantDensityCloud), intent(inout), target :: self - double precision , intent(in ) :: radius - type (enumerationComponentTypeType ), intent(in ), optional :: componentType - type (enumerationMassTypeType ), intent(in ), optional :: massType + class (massDistributionConstantDensityCloud), intent(inout), target :: self + double precision , intent(in ) :: radius - if (.not.self%matches(componentType,massType)) then - constantDensityCloudMassEnclosedBySphere=0.0d0 - return - end if if (radius > self%radius) then constantDensityCloudMassEnclosedBySphere=+self%mass else @@ -182,22 +169,30 @@ double precision function constantDensityCloudMassEnclosedBySphere(self,radius,c return end function constantDensityCloudMassEnclosedBySphere - double precision function constantDensityCloudPotential(self,coordinates,componentType,massType) + logical function constantDensityCloudPotentialIsAnalytic(self) result(isAnalytic) + !!{ + Return that the potential has an analytic form. + !!} + implicit none + class(massDistributionConstantDensityCloud), intent(inout) :: self + + isAnalytic=.true. + return + end function constantDensityCloudPotentialIsAnalytic + + double precision function constantDensityCloudPotential(self,coordinates,status) !!{ Return the potential at the specified {\normalfont \ttfamily coordinates} in a constant density cloud. !!} + use :: Galactic_Structure_Options , only : structureErrorCodeSuccess use :: Numerical_Constants_Astronomical, only : gravitationalConstantGalacticus implicit none - class (massDistributionConstantDensityCloud), intent(inout) :: self + class (massDistributionConstantDensityCloud), intent(inout), target :: self class (coordinate ), intent(in ) :: coordinates - type (enumerationComponentTypeType ), intent(in ), optional :: componentType - type (enumerationMassTypeType ), intent(in ), optional :: massType + type (enumerationStructureErrorCodeType ), intent( out), optional :: status double precision :: radius - if (.not.self%matches(componentType,massType)) then - constantDensityCloudPotential=0.0d0 - return - end if + if (present(status)) status=structureErrorCodeSuccess radius=coordinates%rSpherical() if (radius > self%radius) then constantDensityCloudPotential=-gravitationalConstantGalacticus & @@ -219,7 +214,7 @@ double precision function constantDensityCloudPotential(self,coordinates,compone return end function constantDensityCloudPotential - double precision function constantDensityCloudDensityRadialMoment(self,moment,radiusMinimum,radiusMaximum,isInfinite,componentType,massType) + double precision function constantDensityCloudDensityRadialMoment(self,moment,radiusMinimum,radiusMaximum,isInfinite) !!{ Computes radial moments of the density in a constant density cloud. !!} @@ -230,14 +225,8 @@ double precision function constantDensityCloudDensityRadialMoment(self,moment,ra double precision , intent(in ) :: moment double precision , intent(in ), optional :: radiusMinimum , radiusMaximum logical , intent( out), optional :: isInfinite - type (enumerationComponentTypeType ), intent(in ), optional :: componentType - type (enumerationMassTypeType ), intent(in ), optional :: massType double precision :: radiusMaximum_ - if (.not.self%matches(componentType,massType)) then - constantDensityCloudDensityRadialMoment=0.0d0 - return - end if constantDensityCloudDensityRadialMoment=+0.0d0 if (present(isInfinite)) isInfinite=.false. radiusMaximum_=min(radiusMaximum,self%radius) diff --git a/source/mass_distributions.spherical.decorator.F90 b/source/mass_distributions.spherical.decorator.F90 new file mode 100644 index 0000000000..61ea44d8b6 --- /dev/null +++ b/source/mass_distributions.spherical.decorator.F90 @@ -0,0 +1,457 @@ +!! Copyright 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, +!! 2019, 2020, 2021, 2022, 2023 +!! Andrew Benson +!! +!! This file is part of Galacticus. +!! +!! Galacticus is free software: you can redistribute it and/or modify +!! it under the terms of the GNU General Public License as published by +!! the Free Software Foundation, either version 3 of the License, or +!! (at your option) any later version. +!! +!! Galacticus is distributed in the hope that it will be useful, +!! but WITHOUT ANY WARRANTY; without even the implied warranty of +!! MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +!! GNU General Public License for more details. +!! +!! You should have received a copy of the GNU General Public License +!! along with Galacticus. If not, see . + + !!{ + Implements an abstract spherical mass distribution decorator class. + !!} + + !![ + + + An abstract mass distribution class for decorators of other mass distributions. ``Fallthrough'' functions are provided that + all the decorated class or numerical solutions to be used. + + + !!] + type, abstract, extends(massDistributionSpherical) :: massDistributionSphericalDecorator + !!{ + Implementation of a decorator spherical mass distribution. + !!} + private + type (enumerationNonAnalyticSolversType) :: nonAnalyticSolver + class(massDistributionSpherical ), pointer :: massDistribution_ => null() + contains + !![ + + + + + + + + + + + + + + + + !!] + procedure :: massEnclosedBySphere => sphericalDecoratorMassEnclosedBySphere + procedure :: radiusEnclosingMass => sphericalDecoratorRadiusEnclosingMass + procedure :: densityGradientRadial => sphericalDecoratorDensityGradientRadial + procedure :: densityRadialMoment => sphericalDecoratorDensityRadialMoment + procedure :: radiusEnclosingDensity => sphericalDecoratorRadiusEnclosingDensity + procedure :: radiusFromSpecificAngularMomentum => sphericalDecoratorRadiusFromSpecificAngularMomentum + procedure :: fourierTransform => sphericalDecoratorFourierTransform + procedure :: radiusFreefall => sphericalDecoratorRadiusFreefall + procedure :: radiusFreefallIncreaseRate => sphericalDecoratorRadiusFreefallIncreaseRate + procedure :: potentialIsAnalytic => sphericalDecoratorPotentialIsAnalytic + procedure :: potential => sphericalDecoratorPotential + procedure :: energyPotential => sphericalDecoratorEnergyPotential + procedure :: energyKinetic => sphericalDecoratorEnergyKinetic + procedure :: massEnclosedBySphereNonAnalytic => sphericalDecoratorMassEnclosedBySphereNonAnalytic + procedure :: radiusEnclosingMassNonAnalytic => sphericalDecoratorRadiusEnclosingMassNonAnalytic + procedure :: densityGradientRadialNonAnalytic => sphericalDecoratorDensityGradientRadialNonAnalytic + procedure :: densityRadialMomentNonAnalytic => sphericalDecoratorDensityRadialMomentNonAnalytic + procedure :: radiusEnclosingDensityNonAnalytic => sphericalDecoratorRadiusEnclosingDensityNonAnalytic + procedure :: radiusFromSpecificAngularMomentumNonAnalytic => sphericalDecoratorRadiusFromSpecificAngularMomentumNonAnalytic + procedure :: fourierTransformNonAnalytic => sphericalDecoratorFourierTransformNonAnalytic + procedure :: radiusFreefallNonAnalytic => sphericalDecoratorRadiusFreefallNonAnalytic + procedure :: radiusFreefallIncreaseRateNonAnalytic => sphericalDecoratorRadiusFreefallIncreaseRateNonAnalytic + procedure :: potentialNonAnalytic => sphericalDecoratorPotentialNonAnalytic + procedure :: energyPotentialNonAnalytic => sphericalDecoratorEnergyPotentialNonAnalytic + procedure :: energyKineticNonAnalytic => sphericalDecoratorEnergyKineticNonAnalytic + procedure :: useUndecorated => sphericalDecoratorUseUndecorated + end type massDistributionSphericalDecorator + +contains + + logical function sphericalDecoratorUseUndecorated(self) result(useUndecorated) + !!{ + Determines whether to use the undecorated solution. + !!} + implicit none + class(massDistributionSphericalDecorator), intent(inout) :: self + + useUndecorated=self%nonAnalyticSolver == nonAnalyticSolversFallThrough + return + end function sphericalDecoratorUseUndecorated + + double precision function sphericalDecoratorMassEnclosedBySphere(self,radius) result(mass) + !!{ + Computes the mass enclosed within a sphere of given {\normalfont \ttfamily radius} for decorator mass distributions. + !!} + implicit none + class (massDistributionSphericalDecorator), intent(inout), target :: self + double precision , intent(in ) :: radius + + mass=self%massEnclosedBySphereNonAnalytic(radius) + return + end function sphericalDecoratorMassEnclosedBySphere + + double precision function sphericalDecoratorMassEnclosedBySphereNonAnalytic(self,radius) result(mass) + !!{ + Computes the mass enclosed within a sphere of given {\normalfont \ttfamily radius} for decorator mass distributions. + !!} + implicit none + class (massDistributionSphericalDecorator), intent(inout), target :: self + double precision , intent(in ) :: radius + + if (self%useUndecorated()) then + mass=self%massDistribution_%massEnclosedBySphere (radius) + else + mass=self %massEnclosedBySphereNumerical(radius) + end if + return + end function sphericalDecoratorMassEnclosedBySphereNonAnalytic + + double precision function sphericalDecoratorDensityGradientRadial(self,coordinates,logarithmic) result(densityGradient) + !!{ + Return the density at the specified {\normalfont \ttfamily coordinates} in a decorator spherical mass distribution. + !!} + implicit none + class (massDistributionSphericalDecorator), intent(inout), target :: self + class (coordinate ), intent(in ) :: coordinates + logical , intent(in ), optional :: logarithmic + + densityGradient=self%densityGradientRadialNonAnalytic(coordinates,logarithmic) + return + end function sphericalDecoratorDensityGradientRadial + + double precision function sphericalDecoratorDensityGradientRadialNonAnalytic(self,coordinates,logarithmic) result(densityGradient) + !!{ + Return the density at the specified {\normalfont \ttfamily coordinates} in a decorator spherical mass distribution. + !!} + implicit none + class (massDistributionSphericalDecorator), intent(inout), target :: self + class (coordinate ), intent(in ) :: coordinates + logical , intent(in ), optional :: logarithmic + + if (self%useUndecorated()) then + densityGradient=self%massDistribution_%densityGradientRadial (coordinates,logarithmic) + else + densityGradient=self %densityGradientRadialNumerical(coordinates,logarithmic) + end if + return + end function sphericalDecoratorDensityGradientRadialNonAnalytic + + double precision function sphericalDecoratorRadiusEnclosingDensity(self,density,radiusGuess) result(radius) + !!{ + Computes the radius enclosing a given mean density for decorator spherical mass distributions. + !!} + implicit none + class (massDistributionSphericalDecorator), intent(inout), target :: self + double precision , intent(in ) :: density + double precision , intent(in ), optional :: radiusGuess + + radius=self%radiusEnclosingDensityNonAnalytic(density,radiusGuess) + return + end function sphericalDecoratorRadiusEnclosingDensity + + double precision function sphericalDecoratorRadiusEnclosingDensityNonAnalytic(self,density,radiusGuess) result(radius) + !!{ + Computes the radius enclosing a given mean density for decorator spherical mass distributions. + !!} + implicit none + class (massDistributionSphericalDecorator), intent(inout), target :: self + double precision , intent(in ) :: density + double precision , intent(in ), optional :: radiusGuess + + if (self%useUndecorated()) then + radius=self%massDistribution_%radiusEnclosingDensity (density,radiusGuess) + else + radius=self %radiusEnclosingDensityNumerical(density,radiusGuess) + end if + return + end function sphericalDecoratorRadiusEnclosingDensityNonAnalytic + + double precision function sphericalDecoratorRadiusEnclosingMass(self,mass,massFractional) result(radius) + !!{ + Computes the radius enclosing a given mass or mass fraction for heated spherical mass distributions. + !!} + implicit none + class (massDistributionSphericalDecorator), intent(inout), target :: self + double precision , intent(in ), optional :: mass, massFractional + + radius=self%radiusEnclosingMassNonAnalytic(mass,massFractional) + return + end function sphericalDecoratorRadiusEnclosingMass + + double precision function sphericalDecoratorRadiusEnclosingMassNonAnalytic(self,mass,massFractional) result(radius) + !!{ + Computes the radius enclosing a given mass or mass fraction for heated spherical mass distributions. + !!} + implicit none + class (massDistributionSphericalDecorator), intent(inout), target :: self + double precision , intent(in ), optional :: mass, massFractional + + if (self%useUndecorated()) then + radius=self%massDistribution_%radiusEnclosingMass (mass,massFractional) + else + radius=self %radiusEnclosingMassNumerical(mass,massFractional) + end if + return + end function sphericalDecoratorRadiusEnclosingMassNonAnalytic + + double precision function sphericalDecoratorRadiusFromSpecificAngularMomentum(self,angularMomentumSpecific) result(radius) + !!{ + Computes the radius corresponding to a given specific angular momentum for decorator spherical mass distributions. + !!} + implicit none + class (massDistributionSphericalDecorator), intent(inout), target :: self + double precision , intent(in ) :: angularMomentumSpecific + + radius=self%radiusFromSpecificAngularMomentumNonAnalytic(angularMomentumSpecific) + return + end function sphericalDecoratorRadiusFromSpecificAngularMomentum + + double precision function sphericalDecoratorRadiusFromSpecificAngularMomentumNonAnalytic(self,angularMomentumSpecific) result(radius) + !!{ + Computes the radius corresponding to a given specific angular momentum for decorator spherical mass distributions. + !!} + implicit none + class (massDistributionSphericalDecorator), intent(inout), target :: self + double precision , intent(in ) :: angularMomentumSpecific + + if (self%useUndecorated()) then + radius=self%massDistribution_%radiusFromSpecificAngularMomentum (angularMomentumSpecific) + else + radius=self %radiusFromSpecificAngularMomentumNumerical(angularMomentumSpecific) + end if + return + end function sphericalDecoratorRadiusFromSpecificAngularMomentumNonAnalytic + + double precision function sphericalDecoratorDensityRadialMoment(self,moment,radiusMinimum,radiusMaximum,isInfinite) result(densityRadialMoment) + !!{ + Returns a radial density moment for the decorator spherical mass distribution. + !!} + implicit none + class (massDistributionSphericalDecorator), intent(inout) :: self + double precision , intent(in ) :: moment + double precision , intent(in ), optional :: radiusMinimum, radiusMaximum + logical , intent( out), optional :: isInfinite + + densityRadialMoment=self%densityRadialMomentNonAnalytic(moment,radiusMinimum,radiusMaximum,isInfinite) + return + end function sphericalDecoratorDensityRadialMoment + + double precision function sphericalDecoratorDensityRadialMomentNonAnalytic(self,moment,radiusMinimum,radiusMaximum,isInfinite) result(densityRadialMoment) + !!{ + Returns a radial density moment for the decorator spherical mass distribution. + !!} + implicit none + class (massDistributionSphericalDecorator), intent(inout) :: self + double precision , intent(in ) :: moment + double precision , intent(in ), optional :: radiusMinimum, radiusMaximum + logical , intent( out), optional :: isInfinite + + if (self%useUndecorated()) then + densityRadialMoment=self%massDistribution_%densityRadialMoment (moment,radiusMinimum,radiusMaximum,isInfinite) + else + densityRadialMoment=self %densityRadialMomentNumerical(moment,radiusMinimum,radiusMaximum,isInfinite) + end if + return + end function sphericalDecoratorDensityRadialMomentNonAnalytic + + logical function sphericalDecoratorPotentialIsAnalytic(self) result(isAnalytic) + !!{ + Return if the potential has an analytic form. + !!} + implicit none + class(massDistributionSphericalDecorator), intent(inout) :: self + + isAnalytic=self%useUndecorated() .and. self%massDistribution_%potentialIsAnalytic() + return + end function sphericalDecoratorPotentialIsAnalytic + + double precision function sphericalDecoratorPotential(self,coordinates,status) result(potential) + !!{ + Return the potential at the specified {\normalfont \ttfamily coordinates} in a decorator spherical mass distribution. + !!} + implicit none + class(massDistributionSphericalDecorator), intent(inout), target :: self + class(coordinate ), intent(in ) :: coordinates + type (enumerationStructureErrorCodeType ), intent( out), optional :: status + + potential=self%potentialNonAnalytic(coordinates,status) + return + end function sphericalDecoratorPotential + + double precision function sphericalDecoratorPotentialNonAnalytic(self,coordinates,status) result(potential) + !!{ + Return the potential at the specified {\normalfont \ttfamily coordinates} in a decorator spherical mass distribution. + !!} + implicit none + class(massDistributionSphericalDecorator), intent(inout), target :: self + class(coordinate ), intent(in ) :: coordinates + type (enumerationStructureErrorCodeType ), intent( out), optional :: status + + if (self%useUndecorated()) then + potential=self%massDistribution_%potential (coordinates,status) + else + potential=self %potentialNumerical(coordinates,status) + end if + return + end function sphericalDecoratorPotentialNonAnalytic + + double precision function sphericalDecoratorFourierTransform(self,radiusOuter,wavenumber) result(fourierTransform) + !!{ + Compute the Fourier transform of the density profile at the given {\normalfont \ttfamily wavenumber} in a decorator spherical mass distribution. + !!} + implicit none + class (massDistributionSphericalDecorator), intent(inout) :: self + double precision , intent(in ) :: radiusOuter, wavenumber + + fourierTransform=self%fourierTransformNonAnalytic(radiusOuter,wavenumber) + return + end function sphericalDecoratorFourierTransform + + double precision function sphericalDecoratorFourierTransformNonAnalytic(self,radiusOuter,wavenumber) result(fourierTransform) + !!{ + Compute the Fourier transform of the density profile at the given {\normalfont \ttfamily wavenumber} in a decorator spherical mass distribution. + !!} + implicit none + class (massDistributionSphericalDecorator), intent(inout) :: self + double precision , intent(in ) :: radiusOuter, wavenumber + + if (self%useUndecorated()) then + fourierTransform=self%massDistribution_%fourierTransform (radiusOuter,wavenumber) + else + fourierTransform=self %fourierTransformNumerical(radiusOuter,wavenumber) + end if + return + end function sphericalDecoratorFourierTransformNonAnalytic + + double precision function sphericalDecoratorRadiusFreefall(self,time) result(radius) + !!{ + Compute the freefall radius at the given {\normalfont \ttfamily time} in a decorator spherical mass distribution. + !!} + implicit none + class (massDistributionSphericalDecorator), intent(inout) :: self + double precision , intent(in ) :: time + + radius=self%radiusFreefallNonAnalytic(time) + return + end function sphericalDecoratorRadiusFreefall + + double precision function sphericalDecoratorRadiusFreefallNonAnalytic(self,time) result(radius) + !!{ + Compute the freefall radius at the given {\normalfont \ttfamily time} in a decorator spherical mass distribution. + !!} + implicit none + class (massDistributionSphericalDecorator), intent(inout) :: self + double precision , intent(in ) :: time + + if (self%useUndecorated()) then + radius=self%massDistribution_%radiusFreefall (time) + else + radius=self %radiusFreefallNumerical(time) + end if + return + end function sphericalDecoratorRadiusFreefallNonAnalytic + + double precision function sphericalDecoratorRadiusFreefallIncreaseRate(self,time) result(radiusIncreaseRate) + !!{ + Compute the rate of increase of the freefall radius at the given {\normalfont \ttfamily time} in a decorator spherical mass + distribution. + !!} + implicit none + class (massDistributionSphericalDecorator), intent(inout) :: self + double precision , intent(in ) :: time + + radiusIncreaseRate=self%radiusFreefallIncreaseRateNonAnalytic(time) + return + end function sphericalDecoratorRadiusFreefallIncreaseRate + + double precision function sphericalDecoratorRadiusFreefallIncreaseRateNonAnalytic(self,time) result(radiusIncreaseRate) + !!{ + Compute the rate of increase of the freefall radius at the given {\normalfont \ttfamily time} in a decorator spherical mass + distribution. + !!} + implicit none + class (massDistributionSphericalDecorator), intent(inout) :: self + double precision , intent(in ) :: time + + if (self%useUndecorated()) then + radiusIncreaseRate=self%massDistribution_%radiusFreefallIncreaseRate (time) + else + radiusIncreaseRate=self %radiusFreefallIncreaseRateNumerical(time) + end if + return + end function sphericalDecoratorRadiusFreefallIncreaseRateNonAnalytic + + double precision function sphericalDecoratorEnergyPotential(self,radiusOuter) result(energy) + !!{ + Compute the potential energy within a given {\normalfont \ttfamily radius}. + !!} + implicit none + class (massDistributionSphericalDecorator), intent(inout) :: self + double precision , intent(in ) :: radiusOuter + + energy=self%energyPotentialNonAnalytic(radiusOuter) + return + end function sphericalDecoratorEnergyPotential + + double precision function sphericalDecoratorEnergyPotentialNonAnalytic(self,radiusOuter) result(energy) + !!{ + Compute the potential energy within a given {\normalfont \ttfamily radius}. + !!} + implicit none + class (massDistributionSphericalDecorator), intent(inout) :: self + double precision , intent(in ) :: radiusOuter + + if (self%useUndecorated()) then + energy=self%massDistribution_%energyPotential (radiusOuter) + else + energy=self %energyPotentialNumerical(radiusOuter) + end if + return + end function sphericalDecoratorEnergyPotentialNonAnalytic + + double precision function sphericalDecoratorEnergyKinetic(self,radiusOuter,massDistributionEmbedding) result(energy) + !!{ + Compute the kinetic energy within a given {\normalfont \ttfamily radius}. + !!} + implicit none + class (massDistributionSphericalDecorator), intent(inout) :: self + double precision , intent(in ) :: radiusOuter + class (massDistributionClass ), intent(inout) :: massDistributionEmbedding + + energy=self%energyKineticNonAnalytic(radiusOuter,massDistributionEmbedding) + return + end function sphericalDecoratorEnergyKinetic + + double precision function sphericalDecoratorEnergyKineticNonAnalytic(self,radiusOuter,massDistributionEmbedding) result(energy) + !!{ + Compute the kinetic energy within a given {\normalfont \ttfamily radius}. + !!} + implicit none + class (massDistributionSphericalDecorator), intent(inout) :: self + double precision , intent(in ) :: radiusOuter + class (massDistributionClass ), intent(inout) :: massDistributionEmbedding + + if (self%useUndecorated()) then + energy=self%massDistribution_%energyKinetic (radiusOuter,self%massDistribution_ ) + else + energy=self %energyKineticNumerical(radiusOuter, massDistributionEmbedding) + end if + return + end function sphericalDecoratorEnergyKineticNonAnalytic diff --git a/source/mass_distributions.spherical.finite_resolution.F90 b/source/mass_distributions.spherical.finite_resolution.F90 new file mode 100644 index 0000000000..cb6266b978 --- /dev/null +++ b/source/mass_distributions.spherical.finite_resolution.F90 @@ -0,0 +1,197 @@ +!! Copyright 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, +!! 2019, 2020, 2021, 2022, 2023 +!! Andrew Benson +!! +!! This file is part of Galacticus. +!! +!! Galacticus is free software: you can redistribute it and/or modify +!! it under the terms of the GNU General Public License as published by +!! the Free Software Foundation, either version 3 of the License, or +!! (at your option) any later version. +!! +!! Galacticus is distributed in the hope that it will be useful, +!! but WITHOUT ANY WARRANTY; without even the implied warranty of +!! MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +!! GNU General Public License for more details. +!! +!! You should have received a copy of the GNU General Public License +!! along with Galacticus. If not, see . + + !!{ + Implements a finite resolution spherical mass distribution. + !!} + + !![ + + + A mass distribution class which applies a finite resolution to some other mass distribution class, typically to mimic the + effects of finite resolution in an N-body simulation. Specifically, the density profile is given by + \begin{equation} + \rho(r) = \rho^\prime(r) \left( 1 + \left[ \frac{\Delta x}{r} \right]^2 \right)^{-1/2}, + \end{equation} + where $\Delta x$ is the larger of the resolution length, {\normalfont \ttfamily [lengthResolution]}, and the radius in the + original profile enclosing the mass resolution, {\normalfont \ttfamily [massResolution]}. + + Note that this choice was constructed to give a constant density core in an NFW density profile. For a density profile, + $\rho^\prime(r)$, which rises more steeply than $r^{-1}$ as $r \rightarrow 0$ we will still have a cuspy density profile + under this model. + + + !!] + type, extends(massDistributionSphericalDecorator) :: massDistributionSphericalFiniteResolution + !!{ + Implementation of a finite resolution spherical mass distribution. + !!} + private + double precision :: lengthResolution + contains + final :: sphericalFiniteResolutionDestructor + procedure :: density => sphericalFiniteResolutionDensity + procedure :: densityGradientRadial => sphericalFiniteResolutionDensityGradientRadial + end type massDistributionSphericalFiniteResolution + + interface massDistributionSphericalFiniteResolution + !!{ + Constructors for the {\normalfont \ttfamily sphericalFiniteResolution} mass distribution class. + !!} + module procedure sphericalFiniteResolutionConstructorParameters + module procedure sphericalFiniteResolutionConstructorInternal + end interface massDistributionSphericalFiniteResolution + +contains + + function sphericalFiniteResolutionConstructorParameters(parameters) result(self) + !!{ + Constructor for the {\normalfont \ttfamily sphericalFiniteResolution} mass distribution class which builds the object from a parameter + set. + !!} + use :: Input_Parameters , only : inputParameters + use :: Galactic_Structure_Options, only : enumerationComponentTypeEncode, enumerationMassTypeEncode + implicit none + type (massDistributionSphericalFiniteResolution) :: self + type (inputParameters ), intent(inout) :: parameters + class (massDistributionClass ), pointer :: massDistribution_ + type (varying_string ) :: nonAnalyticSolver + double precision :: lengthResolution + type (varying_string ) :: componentType , massType + + !![ + + lengthResolution + parameters + The resolution length scale. + + + nonAnalyticSolver + var_str('fallThrough') + parameters + Selects how solutions are computed when no analytic solution is available. If set to ``{\normalfont \ttfamily fallThrough}'' then the solution ignoring heating is used, while if set to ``{\normalfont \ttfamily numerical}'' then numerical solvers are used to find solutions. + + + componentType + var_str('unknown') + The component type that this mass distribution represents. + parameters + + + massType + var_str('unknown') + The mass type that this mass distribution represents. + parameters + + + !!] + select type (massDistribution_) + class is (massDistributionSpherical) + self=massDistributionSphericalFiniteResolution(lengthResolution,enumerationNonAnalyticSolversEncode(char(nonAnalyticSolver),includesPrefix=.false.),massDistribution_,enumerationComponentTypeEncode(componentType,includesPrefix=.false.),enumerationMassTypeEncode(massType,includesPrefix=.false.)) + class default + call Error_Report('a spherically-symmetric mass distribution is required'//{introspection:location}) + end select + !![ + + + !!] + return + end function sphericalFiniteResolutionConstructorParameters + + function sphericalFiniteResolutionConstructorInternal(lengthResolution,nonAnalyticSolver,massDistribution_,componentType,massType) result(self) + !!{ + Constructor for ``sphericalFiniteResolution'' mass distribution class. + !!} + implicit none + type (massDistributionSphericalFiniteResolution) :: self + class (massDistributionSpherical ), intent(in ), target :: massDistribution_ + type (enumerationNonAnalyticSolversType ), intent(in ) :: nonAnalyticSolver + double precision , intent(in ) :: lengthResolution + type (enumerationComponentTypeType ), intent(in ), optional :: componentType + type (enumerationMassTypeType ), intent(in ), optional :: massType + !![ + + !!] + + self%dimensionless=self%massDistribution_%isDimensionless() + return + end function sphericalFiniteResolutionConstructorInternal + + subroutine sphericalFiniteResolutionDestructor(self) + !!{ + Destructor for the abstract {\normalfont \ttfamily massDistributionSphericalFiniteResolution} class. + !!} + implicit none + type(massDistributionSphericalFiniteResolution), intent(inout) :: self + + !![ + + !!] + return + end subroutine sphericalFiniteResolutionDestructor + + double precision function sphericalFiniteResolutionDensity(self,coordinates) result(density) + !!{ + Return the density at the specified {\normalfont \ttfamily coordinates} in a scaled spherical mass distribution. + !!} + implicit none + class(massDistributionSphericalFiniteResolution), intent(inout) :: self + class(coordinate ), intent(in ) :: coordinates + + density=+self%massDistribution_%density(coordinates) & + & /sqrt( & + & +1.0d0 & + & +( & + & +self %lengthResolution & + & /coordinates%rSpherical () & + & )**2 & + & ) + return + end function sphericalFiniteResolutionDensity + + double precision function sphericalFiniteResolutionDensityGradientRadial(self,coordinates,logarithmic) result(densityGradient) + !!{ + Return the density at the specified {\normalfont \ttfamily coordinates} in a finiteResolution spherical mass distribution. + !!} + implicit none + class (massDistributionSphericalFiniteResolution), intent(inout), target :: self + class (coordinate ), intent(in ) :: coordinates + logical , intent(in ), optional :: logarithmic + !![ + + !!] + + densityGradient=+self%massDistribution_%densityGradientRadial(coordinates,logarithmic=.true.) & + & +( & + & + self %lengthResolution & + & / coordinates%rSpherical () & + & ) **2 & + & /( & + & +1.0d0 & + & +( & + & +self %lengthResolution & + & /coordinates%rSpherical () & + & )**2 & + & ) + if (.not.logarithmic_) & + densityGradient=+ densityGradient & + & *self %density (coordinates) & + & /coordinates%rSpherical ( ) + return + end function sphericalFiniteResolutionDensityGradientRadial diff --git a/source/mass_distributions.spherical.finite_resolution.NFW.F90 b/source/mass_distributions.spherical.finite_resolution.NFW.F90 new file mode 100644 index 0000000000..2cdd2fc26e --- /dev/null +++ b/source/mass_distributions.spherical.finite_resolution.NFW.F90 @@ -0,0 +1,1340 @@ +!! Copyright 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, +!! 2019, 2020, 2021, 2022, 2023 +!! Andrew Benson +!! +!! This file is part of Galacticus. +!! +!! Galacticus is free software: you can redistribute it and/or modify +!! it under the terms of the GNU General Public License as published by +!! the Free Software Foundation, either version 3 of the License, or +!! (at your option) any later version. +!! +!! Galacticus is distributed in the hope that it will be useful, +!! but WITHOUT ANY WARRANTY; without even the implied warranty of +!! MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +!! GNU General Public License for more details. +!! +!! You should have received a copy of the GNU General Public License +!! along with Galacticus. If not, see . + + !!{ + Implements a finite resolution NFW spherical mass distribution. + !!} + + use :: Numerical_Interpolation, only : interpolator + + !![ + + + A mass distribution class which applies a finite resolution to an NFW density profile, typically to mimic the effects + of finite resolution in an N-body simulation. Specifically, the density profile is given by + \begin{equation} + \rho(r) = \rho_\mathrm{NFW}(r) \left( 1 + \left[ \frac{\Delta x}{r} \right]^2 \right)^{-1/2}, + \end{equation} + where $\Delta x$ is the larger of the resolution length, {\normalfont \ttfamily [lengthResolution]}, and the radius in the + original profile enclosing the mass resolution, {\normalfont \ttfamily [massResolution]}. + + + !!] + type, extends(massDistributionSpherical) :: massDistributionSphericalFiniteResolutionNFW + !!{ + Implementation of a finite resolution spherical mass distribution. + !!} + private + double precision :: lengthResolution , radiusScale , & + & radiusVirial , mass , & + & densityNormalization , lengthResolutionScaleFree + double precision :: potentialRadiusPrevious , potentialPrevious , & + & massEnclosedMassPrevious , massEnclosedRadiusPrevious , & + & densityRadiusPrevious , densityPrevious , & + & densityNormalizationPrevious , radiusEnclosingDensityDensityPrevious , & + & radiusEnclosingDensityPrevious , radiusEnclosingMassMassPrevious , & + & radiusEnclosingMassPrevious , energyPrevious + ! Radius-enclosing-density tabulation. + logical :: radiusEnclosingDensityTableInitialized + integer :: radiusEnclosingDensityTableLengthResolutionCount , radiusEnclosingDensityTableDensityCount + double precision , allocatable, dimension(: ) :: radiusEnclosingDensityTableLengthResolution , radiusEnclosingDensityTableDensity + double precision , allocatable, dimension(:,:) :: radiusEnclosingDensityTable + type (interpolator), allocatable :: radiusEnclosingDensityTableLengthResolutionInterpolator, radiusEnclosingDensityTableDensityInterpolator + double precision :: radiusEnclosingDensityDensityMinimum , radiusEnclosingDensityDensityMaximum , & + & radiusEnclosingDensityLengthResolutionMinimum , radiusEnclosingDensityLengthResolutionMaximum + ! Radius-enclosing-mass tabulation. + logical :: radiusEnclosingMassTableInitialized + integer :: radiusEnclosingMassTableLengthResolutionCount , radiusEnclosingMassTableMassCount + double precision , allocatable, dimension(: ) :: radiusEnclosingMassTableLengthResolution , radiusEnclosingMassTableMass + double precision , allocatable, dimension(:,:) :: radiusEnclosingMassTable + type (interpolator), allocatable :: radiusEnclosingMassTableLengthResolutionInterpolator , radiusEnclosingMassTableMassInterpolator + double precision :: radiusEnclosingMassMassMinimum , radiusEnclosingMassMassMaximum , & + & radiusEnclosingMassLengthResolutionMinimum , radiusEnclosingMassLengthResolutionMaximum + ! Energy tabulation. + logical :: energyTableInitialized + integer :: energyTableLengthResolutionCount , energyTableRadiusOuterCount + double precision , allocatable, dimension(: ) :: energyTableLengthResolution , energyTableRadiusOuter + double precision , allocatable, dimension(:,:) :: energyTable + type (interpolator), allocatable :: energyTableLengthResolutionInterpolator , energyTableRadiusOuterInterpolator + double precision :: energyRadiusOuterMinimum , energyRadiusOuterMaximum , & + & energyLengthResolutionMinimum , energyLengthResolutionMaximum + ! Enclosed mass quantities. + double precision :: lengthResolutionScaleFreeLowerTerm , lengthResolutionScaleFreeSquared , & + & lengthResolutionScaleFreeCubed , lengthResolutionScaleFreeOnePlusTerm , & + & lengthResolutionScaleFreeOnePlus2Term , lengthResolutionScaleFreeSqrtTerm , & + & lengthResolutionScaleFreeSqrt2Term , lengthResolutionScaleFreeSqrtCubedTerm , & + & lengthResolutionScaleFreePrevious + contains + !![ + + + + + + + + + + + + + + !!] + procedure :: density => sphericalFiniteResolutionNFWDensity + procedure :: densityGradientRadial => sphericalFiniteResolutionNFWDensityGradientRadial + procedure :: massEnclosedBySphere => sphericalFiniteResolutionNFWMassEnclosedBySphere + procedure :: potentialIsAnalytic => sphericalFiniteResolutionNFWPotentialIsAnalytic + procedure :: potential => sphericalFiniteResolutionNFWPotential + procedure :: radiusEnclosingMass => sphericalFiniteResolutionNFWRadiusEnclosingMass + procedure :: radiusEnclosingDensity => sphericalFiniteResolutionNFWRadiusEnclosingDensity + procedure :: energy => sphericalFiniteResolutionNFWEnergy + procedure :: radiusEnclosingDensityTabulate => sphericalFiniteResolutionNFWRadiusEnclosingDensityTabulate + procedure :: radiusEnclosingMassTabulate => sphericalFiniteResolutionNFWRadiusEnclosingMassTabulate + procedure :: energyTabulate => sphericalFiniteResolutionNFWEnergyTabulate + procedure :: densityScaleFree => sphericalFiniteResolutionNFWDensityScaleFree + procedure :: massEnclosedScaleFree => sphericalFiniteResolutionNFWMassEnclosedScaleFree + procedure :: storeDensityTable => sphericalFiniteResolutionNFWStoreDensityTable + procedure :: restoreDensityTable => sphericalFiniteResolutionNFWRestoreDensityTable + procedure :: storeMassTable => sphericalFiniteResolutionNFWStoreMassTable + procedure :: restoreMassTable => sphericalFiniteResolutionNFWRestoreMassTable + procedure :: storeEnergyTable => sphericalFiniteResolutionNFWStoreEnergyTable + procedure :: restoreEnergyTable => sphericalFiniteResolutionNFWRestoreEnergyTable + end type massDistributionSphericalFiniteResolutionNFW + + interface massDistributionSphericalFiniteResolutionNFW + !!{ + Constructors for the {\normalfont \ttfamily sphericalFiniteResolutionNFW} mass distribution class. + !!} + module procedure sphericalFiniteResolutionNFWConstructorParameters + module procedure sphericalFiniteResolutionNFWConstructorInternal + end interface massDistributionSphericalFiniteResolutionNFW + + ! Tabulation resolution parameters. + integer , parameter :: radiusEnclosingDensityTableDensityPointsPerDecade =100 + integer , parameter :: radiusEnclosingDensityTableLengthResolutionPointsPerDecade=100 + integer , parameter :: radiusEnclosingMassTableMassPointsPerDecade =100 + integer , parameter :: radiusEnclosingMassTableLengthResolutionPointsPerDecade =100 + integer , parameter :: energyTableRadiusOuterPointsPerDecade =100 + integer , parameter :: energyTableLengthResolutionPointsPerDecade =100 + + ! Sub-module-scope variables used in integrations. + class (massDistributionSphericalFiniteResolutionNFW), pointer :: self_ + integer :: iLengthResolution_ , iDensity_, & + & iMass_ + !$omp threadprivate(self_,iLengthResolution_,iDensity_,iMass_) + + ! Largest radius for precise arctanh() evaluation. + double precision , parameter :: radiusScaleFreeLargeATanh =1.0d+6 +contains + + function sphericalFiniteResolutionNFWConstructorParameters(parameters) result(self) + !!{ + Constructor for the {\normalfont \ttfamily sphericalFiniteResolutionNFW} mass distribution class which builds the object from a parameter + set. + !!} + use :: Input_Parameters , only : inputParameters + use :: Galactic_Structure_Options, only : enumerationComponentTypeEncode, enumerationMassTypeEncode + implicit none + type (massDistributionSphericalFiniteResolutionNFW) :: self + type (inputParameters ), intent(inout) :: parameters + double precision :: lengthResolution, radiusScale, & + & radiusVirial , mass + type (varying_string ) :: componentType , massType + + !![ + + lengthResolution + parameters + The resolution length scale. + + + radiusScale + parameters + The NFW scale radius. + + + radiusVirial + parameters + The virial radius. + + + mass + parameters + The mass within the virial radius. + + + componentType + var_str('unknown') + The component type that this mass distribution represents. + parameters + + + massType + var_str('unknown') + The mass type that this mass distribution represents. + parameters + + !!] + self=massDistributionSphericalFiniteResolutionNFW(lengthResolution,radiusScale,radiusVirial,mass,enumerationComponentTypeEncode(componentType,includesPrefix=.false.),enumerationMassTypeEncode(massType,includesPrefix=.false.)) + !![ + + !!] + return + end function sphericalFiniteResolutionNFWConstructorParameters + + function sphericalFiniteResolutionNFWConstructorInternal(lengthResolution,radiusScale,radiusVirial,mass,componentType,massType) result(self) + !!{ + Constructor for ``sphericalFiniteResolutionNFW'' mass distribution class. + !!} + use :: Numerical_Constants_Math, only : Pi + implicit none + type (massDistributionSphericalFiniteResolutionNFW) :: self + double precision , intent(in ) :: lengthResolution , radiusScale, & + & radiusVirial , mass + type (enumerationComponentTypeType ), intent(in ), optional :: componentType + type (enumerationMassTypeType ), intent(in ), optional :: massType + double precision :: radiusScaleFree + !![ + + !!] + + self%dimensionless =.false. + self%lengthResolutionScalefreePrevious =-huge(0.0d0) + self%massEnclosedMassPrevious =-huge(0.0d0) + self%massEnclosedRadiusPrevious =-huge(0.0d0) + self%potentialPrevious =-huge(0.0d0) + self%potentialRadiusPrevious =-huge(0.0d0) + self%densityRadiusPrevious =-huge(0.0d0) + self%densityPrevious =-huge(0.0d0) + self%densityNormalizationPrevious =-huge(0.0d0) + self%radiusEnclosingDensityDensityPrevious =-huge(0.0d0) + self%radiusEnclosingDensityPrevious =-huge(0.0d0) + self%radiusEnclosingMassMassPrevious =-huge(0.0d0) + self%radiusEnclosingMassPrevious =-huge(0.0d0) + self%energyPrevious =+huge(0.0d0) + ! Radius enclosing density table initialization. + self%radiusEnclosingDensityDensityMinimum =+huge(0.0d0) + self%radiusEnclosingDensityDensityMaximum =-huge(0.0d0) + self%radiusEnclosingDensityLengthResolutionMinimum =+huge(0.0d0) + self%radiusEnclosingDensityLengthResolutionMaximum =-huge(0.0d0) + self%radiusEnclosingDensityTableInitialized =.false. + ! Radius enclosing mass table initialization. + self%radiusEnclosingMassMassMinimum =+huge(0.0d0) + self%radiusEnclosingMassMassMaximum =-huge(0.0d0) + self%radiusEnclosingMassLengthResolutionMinimum =+huge(0.0d0) + self%radiusEnclosingMassLengthResolutionMaximum =-huge(0.0d0) + self%radiusEnclosingMassTableInitialized =.false. + ! Energy table initialization. + self%energyRadiusOuterMinimum =+huge(0.0d0) + self%energyRadiusOuterMaximum =-huge(0.0d0) + self%energyLengthResolutionMinimum =+huge(0.0d0) + self%energyLengthResolutionMaximum =-huge(0.0d0) + self%energyTableInitialized =.false. + ! Construct profile quantities. + radiusScaleFree =+ radiusVirial/radiusScale + self%lengthResolutionScaleFree=+lengthResolution/radiusScale + self%densityNormalization =+mass/4.0d0/Pi/radiusScale**3/(log(1.0d0+radiusScaleFree)-radiusScaleFree/(1.0d0+radiusScaleFree)) + return + end function sphericalFiniteResolutionNFWConstructorInternal + + double precision function sphericalFiniteResolutionNFWDensity(self,coordinates) result(density) + !!{ + Return the density at the specified {\normalfont \ttfamily coordinates} in a scaled spherical mass distribution. + !!} + implicit none + class (massDistributionSphericalFiniteResolutionNFW), intent(inout) :: self + class (coordinate ), intent(in ) :: coordinates + double precision :: radiusScaleFree + + ! Compute the density at this position. + radiusScaleFree=+coordinates%rSpherical () & + & /self %radiusScale + density =+self %densityNormalization & + & /sqrt(+self%lengthResolutionScaleFree**2+radiusScaleFree **2) & + & / (+1.0d0 +radiusScaleFree)**2 + return + end function sphericalFiniteResolutionNFWDensity + + double precision function sphericalFiniteResolutionNFWDensityGradientRadial(self,coordinates,logarithmic) result(densityGradient) + !!{ + Return the density at the specified {\normalfont \ttfamily coordinates} in a finiteResolution spherical mass distribution. + !!} + implicit none + class (massDistributionSphericalFiniteResolutionNFW), intent(inout), target :: self + class (coordinate ), intent(in ) :: coordinates + logical , intent(in ), optional :: logarithmic + double precision :: radiusScaleFree + !![ + + !!] + + radiusScaleFree=+coordinates%rSpherical () & + & /self %radiusScale + densityGradient=-3.0d0 & + & +2.0d0/(1.0d0+ radiusScaleFree ) & + & +1.0d0/(1.0d0+(radiusScaleFree/self%lengthResolutionScaleFree)**2) + if (.not.logarithmic_) & + densityGradient=+ densityGradient & + & *self %density (coordinates) & + & /coordinates%rSpherical ( ) + return + end function sphericalFiniteResolutionNFWDensityGradientRadial + + double precision function sphericalFiniteResolutionNFWMassEnclosedBySphere(self,radius) result(mass) + !!{ + Returns the enclosed mass (in $M_\odot$) at the given {\normalfont \ttfamily radius} (given in units of Mpc). The analytic + solution (computed using Mathematica) is + \begin{equation} + M(x) = 4 \pi \rho_0 r_\mathrm{s}^3 \left[ -\frac{\sqrt{x^2+X^2}}{(1+x) \left(1+X^2\right)}+\tanh ^{-1}\left(\frac{x}{\sqrt{x^2+X^2}}\right)+\frac{\left(1+2X^2\right) \tanh ^{-1}\left(\frac{X^2-x}{\sqrt{1+X^2} \sqrt{x^2+X^2}}\right)}{\left(1+X^2\right)^{3/2}} -\frac{\left(1 + 2 X^2\right) \tanh ^{-1}\left(\sqrt{\frac{X^2}{1 + X^2}}\right)}{\left(1+ X^2\right)^{3/2}}+\frac{\sqrt{X^2}}{1 + X^2} \right], + \end{equation} + where $x=r/r_\mathrm{s}$, $X = \Delta x/r_\mathrm{s}$, and $r_\mathrm{s}$ is the NFW scale length. + !!} + use :: Numerical_Constants_Math, only : Pi + implicit none + class (massDistributionSphericalFiniteResolutionNFW), intent(inout), target :: self + double precision , intent(in ) :: radius + double precision :: radiusScaleFree + + radiusScaleFree=+ radius & + & /self%radiusScale + mass =+self%densityNormalization & + & *self%radiusScale **3 & + & *self%massEnclosedScaleFree(radiusScaleFree,self%lengthResolutionScaleFree) + return + end function sphericalFiniteResolutionNFWMassEnclosedBySphere + + double precision function sphericalFiniteResolutionNFWMassEnclosedScaleFree(self,radiusScaleFree,lengthResolutionScaleFree) result(mass) + !!{ + Returns the scale-free enclosed mass at the given scale-free radius. The analytic solution (computed using Mathematica) is + \begin{equation} + M(x) = 4 \pi \left[ -\frac{\sqrt{x^2+X^2}}{(1+x) \left(1+X^2\right)}+\tanh ^{-1}\left(\frac{x}{\sqrt{x^2+X^2}}\right)+\frac{\left(1+2X^2\right) \tanh ^{-1}\left(\frac{X^2-x}{\sqrt{1+X^2} \sqrt{x^2+X^2}}\right)}{\left(1+X^2\right)^{3/2}} -\frac{\left(1 + 2 X^2\right) \tanh ^{-1}\left(\sqrt{\frac{X^2}{1 + X^2}}\right)}{\left(1+ X^2\right)^{3/2}}+\frac{\sqrt{X^2}}{1 + X^2} \right], + \end{equation} + where $x=r/r_\mathrm{s}$, $X = \Delta x/r_\mathrm{s}$, and $r_\mathrm{s}$ is the NFW scale length. + !!} + implicit none + class (massDistributionSphericalFiniteResolutionNFW), intent(inout) :: self + double precision , intent(in ) :: radiusScaleFree , lengthResolutionScaleFree + double precision , parameter :: radiusScaleFreeSmall =1.0d-3, radiusScaleFreeLarge =1.0d4 + double precision :: radiusScaleFreeEffective , arctanhTerm1 , & + & arctanhTerm + + if (radiusScaleFree /= self%massEnclosedRadiusPrevious) then + self%massEnclosedRadiusPrevious=+radiusScaleFree + if (lengthResolutionScaleFree /= self%lengthResolutionScaleFreePrevious) then + ! Construct quantities used in the mass enclosed within a sphere. + self%lengthResolutionScaleFreePrevious = lengthResolutionScaleFree + self%lengthResolutionScaleFreeSquared =self%lengthResolutionScaleFreePrevious**2 + self%lengthResolutionScaleFreeCubed =self%lengthResolutionScaleFreePrevious**3 + self%lengthResolutionScaleFreeOnePlusTerm =+1.0d0+ self%lengthResolutionScaleFreeSquared + self%lengthResolutionScaleFreeOnePlus2Term =+1.0d0+2.0d0*self%lengthResolutionScaleFreeSquared + self%lengthResolutionScaleFreeSqrtTerm =sqrt(self%lengthResolutionScaleFreeOnePlusTerm ) + self%lengthResolutionScaleFreeSqrt2Term =sqrt(self%lengthResolutionScaleFreeOnePlus2Term) + self%lengthResolutionScaleFreeSqrtCubedTerm=self%lengthResolutionScaleFreeSqrtTerm**3 + ! For large values of the argument to arctanh(), use a series solution to avoiding floating point errors. + if (self%lengthResolutionScaleFreePrevious > radiusScaleFreeLargeATanh) then + arctanhTerm=-log( & + & +2.0d0 & + & *self%lengthResolutionScaleFreePrevious & + & ) & + & /2.0d0 & + & +1.0d0 & + & /2.0d0 & + & /self%lengthResolutionScaleFreePrevious & + & +1.0d0 & + & /8.0d0 & + & /self%lengthResolutionScaleFreePrevious**2 + else + arctanhTerm=+atanh( & + & +(+1.0d0-self%lengthResolutionScaleFreePrevious) & + & /self%lengthResolutionScaleFreeSqrtTerm & + & ) + end if + self%lengthResolutionScaleFreeLowerTerm=+self%lengthResolutionScaleFreePrevious & + & /self%lengthResolutionScaleFreeOnePlusTerm & + & +2.0d0 & + & *self%lengthResolutionScaleFreeOnePlus2Term & + & *arctanhTerm & + & /self%lengthResolutionScaleFreeSqrtCubedTerm + end if + if (radiusScaleFree < radiusScaleFreeSmall) then + ! Series expansion for small radii. + self%massEnclosedMassPrevious=+ radiusScaleFree**3 & + & *( & + & +1.0d0 /self%lengthResolutionScaleFreePrevious/ 3.0d0 & + & +radiusScaleFree *( +1.0d0 /self%lengthResolutionScaleFreePrevious/ 2.0d0 & + & +radiusScaleFree * ( 1.0d0+(+6.0d0*self%lengthResolutionScaleFreeSquared-1.0d0)/self%lengthResolutionScaleFreeCubed /10.0d0 & + & +radiusScaleFree * (1.0d0-(+4.0d0*self%lengthResolutionScaleFreeSquared-1.0d0)/self%lengthResolutionScaleFreeCubed / 6.0d0 & + & ) & + & ) & + & ) & + & ) + else + ! Full analytic solution. + !! Limit the evaluation to some large radius. + radiusScaleFreeEffective=min(radiusScaleFree,radiusScaleFreeLarge) + if (radiusScaleFreeEffective > radiusScaleFreeLargeATanh*self%lengthResolutionScaleFreePrevious) then + arctanhTerm1=+log ( & + & +4.0d0 & + & *radiusScaleFreeEffective**2 & + & /self%lengthResolutionScaleFreeSquared & + & ) & + & /2.0d0 & + & -self%lengthResolutionScaleFreeSquared & + & /8.0d0 & + & / radiusScaleFreeEffective**2 + else + arctanhTerm1=+atanh( & + & +radiusScaleFreeEffective & + & /sqrt(+radiusScaleFreeEffective**2+self%lengthResolutionScaleFreeSquared) & + & ) + end if + self%massEnclosedMassPrevious=- sqrt(+radiusScaleFreeEffective**2+self%lengthResolutionScaleFreePrevious**2) & + & /(+1.0d0+radiusScaleFreeEffective) & + & /self%lengthResolutionScaleFreeOnePlusTerm & + & -2.0d0 & + & *self%lengthResolutionScaleFreeOnePlus2Term & + & *atanh( & + & +( & + & +1.0d0 & + & +radiusScaleFreeEffective & + & -sqrt(+radiusScaleFreeEffective**2+self%lengthResolutionScaleFreePrevious**2) & + & ) & + & /self%lengthResolutionScaleFreeSqrtTerm & + & ) & + & /self%lengthResolutionScaleFreeSqrtCubedTerm & + & +arctanhTerm1 & + & +self%lengthResolutionScaleFreeLowerTerm + !! Beyond the limiting radius assume logarithmic growth in mass as appropriate for an r⁻³ profile. + if (radiusScaleFree > radiusScaleFreeEffective) & + & self%massEnclosedMassPrevious=+self%massEnclosedMassPrevious & + & *log( & + & +radiusScaleFree & + & /radiusScaleFreeEffective & + & ) + end if + self%massEnclosedMassPrevious=+4.0d0 & + & *Pi & + & *self%massEnclosedMassPrevious + end if + mass=self%massEnclosedMassPrevious + return + end function sphericalFiniteResolutionNFWMassEnclosedScaleFree + + logical function sphericalFiniteResolutionNFWPotentialIsAnalytic(self) result(isAnalytic) + !!{ + Return that the potential has an analytic form. + !!} + implicit none + class(massDistributionSphericalFiniteResolutionNFW), intent(inout) :: self + + isAnalytic=.true. + return + end function sphericalFiniteResolutionNFWPotentialIsAnalytic + + double precision function sphericalFiniteResolutionNFWPotential(self,coordinates,status) result(potential) + !!{ + Returns the potential (in (km/s)$^2$) in the dark matter profile of {\normalfont \ttfamily node} at the given {\normalfont + \ttfamily radius} (given in units of Mpc). The analytic solution (computed using Mathematica) is + \begin{eqnarray} + \Phi(x) &=& -\frac{\mathrm{G} M}{r_\mathrm{s}} \nonumber \\ + & & \left\{ +\frac{\sqrt{x^2+X^2}}{x \left(X^2+1\right)} \right. \nonumber \\ + & & -\frac{X^2 \log \left(\sqrt{X^2+1} \sqrt{x^2+X^2}-x+X^2\right)}{\left(X^2+1\right)^{3/2}} \nonumber \\ + & & -\frac{\tanh ^{-1}\left(\frac{x}{\sqrt{x^2+X^2}}\right)}{x} \nonumber \\ + & & -\frac{\left(2 X^2+1\right) \tanh ^{-1}\left(\frac{X^2-x}{\sqrt{X^2+1} \sqrt{x^2+X^2}}\right)}{x \left(X^2+1\right)^{3/2}} \nonumber \\ + & & -\frac{\sqrt{X^2}}{x \left(X^2+1\right)}+\frac{X^2 \log (x+1)}{\left(X^2+1\right)^{3/2}} \nonumber \\ + & & +\frac{\left(2 X^2+1\right) \tanh ^{-1}\left(\sqrt{\frac{X^2}{X^2+1}}\right)}{x \left(X^2+1\right)^{3/2}} \nonumber \\ + & & \left. +\frac{ \left(\sqrt{X^2+1}-X^2 \log \left(\sqrt{X^2+1}-1\right)\right)}{\left(X^2+1\right)^{3/2}} \right\} \nonumber \\ + & & /\left[\log (1+c)-\frac{c}{1+c}\right] + \end{eqnarray} + !!} + use :: Coordinates , only : assignment(=) + use :: Galactic_Structure_Options , only : structureErrorCodeSuccess , structureErrorCodeInfinite + use :: Numerical_Constants_Astronomical, only : gravitationalConstantGalacticus + use :: Error , only : Error_Report + implicit none + class (massDistributionSphericalFiniteResolutionNFW), intent(inout), target :: self + class (coordinate ), intent(in ) :: coordinates + type (enumerationStructureErrorCodeType ), intent( out), optional :: status + double precision , parameter :: radiusScaleFreeSmall=1.0d-3 + double precision :: radiusScaleFree + + if (present(status)) status=structureErrorCodeSuccess + if (coordinates%rSpherical()/= self%potentialRadiusPrevious) then + self%potentialRadiusPrevious=+coordinates%rSpherical () + radiusScaleFree =+coordinates%rSpherical () & + & /self %radiusScale + if (radiusScaleFree < radiusScaleFreeSmall) then + ! Series expansion for small radii. + self%potentialPrevious = -4.0d0 & + & *Pi & + & *gravitationalConstantGalacticus & + & *self%densityNormalization & + & *self%radiusScale **2 & + & *( & + & +(+1.0d0-self%lengthResolutionScaleFree ) & + & /(+1.0d0+self%lengthResolutionScaleFree**2) & + & + self%lengthResolutionScaleFree**2 & + & *( & + & +asinh(self%lengthResolutionScaleFree ) & + & +log ( & + & +(1.0d0+sqrt(+1.0d0+self%lengthResolutionScaleFree**2)) & + & / self%lengthResolutionScaleFree & + & ) & + & ) & + & /(+1.0d0+self%lengthResolutionScaleFree**2)**1.5d0 & + & - radiusScaleFree**2 & + & *(+1.0d0-radiusScaleFree ) & + & / self%lengthResolutionScaleFree & + & /6.0d0 & + & ) + else + self%potentialPrevious = -4.0d0 & + & *Pi & + & *gravitationalConstantGalacticus & + & *self%densityNormalization & + & *self%radiusScale **2 & + & *( & + & + sqrt( self%lengthResolutionScaleFree**2 ) & + & / radiusScaleFree /(+1.0d0+ self%lengthResolutionScaleFree**2) & + & - sqrt( +radiusScaleFree**2+self%lengthResolutionScaleFree**2 ) & + & / radiusScaleFree /(+1.0d0+ self%lengthResolutionScaleFree**2) & + & - (+1.0d0+2.0d0*self%lengthResolutionScaleFree**2) & + & *atanh( & + & +sqrt( +self%lengthResolutionScaleFree**2/(+1.0d0+ self%lengthResolutionScaleFree**2) ) & + & ) & + & / radiusScaleFree /(+1.0d0+ self%lengthResolutionScaleFree**2)**1.5d0 & + & +atanh( & + & + radiusScaleFree & + & /sqrt( +radiusScaleFree**2+self%lengthResolutionScaleFree**2 ) & + & ) & + & / radiusScaleFree & + & + (+1.0d0+2.0d0*self%lengthResolutionScaleFree**2) & + & *atanh( & + & ( -radiusScaleFree +self%lengthResolutionScaleFree**2 ) & + & /sqrt (+1.0d0+ self%lengthResolutionScaleFree**2) & + & /sqrt( +radiusScaleFree**2+self%lengthResolutionScaleFree**2 ) & + & ) & + & / radiusScaleFree /(+1.0d0+ self%lengthResolutionScaleFree**2)**1.5d0 & + & - self%lengthResolutionScaleFree**2 & + & *log ( & + & +1.0d0+radiusScaleFree & + & ) & + & / (+1.0d0+ self%lengthResolutionScaleFree**2)**1.5d0 & + & + self%lengthResolutionScaleFree**2/(+1.0d0+ self%lengthResolutionScaleFree**2)**1.5d0 & + & *log ( & + & -radiusScaleFree +self%lengthResolutionScaleFree**2 & + & +sqrt(+1.0d0 +self%lengthResolutionScaleFree**2 ) & + & *sqrt( +radiusScaleFree**2+self%lengthResolutionScaleFree**2 ) & + & ) & + & +( & + & + sqrt(+1.0d0 +self%lengthResolutionScaleFree**2 ) & + & - self%lengthResolutionScaleFree**2 & + & *log( & + & -1.0d0 & + & +sqrt(+1.0d0 +self%lengthResolutionScaleFree**2 ) & + & ) & + & ) & + & / (+1.0d0+ self%lengthResolutionScaleFree**2)**1.5d0 & + & ) + end if + end if + potential=self%potentialPrevious + return + end function sphericalFiniteResolutionNFWPotential + + double precision function sphericalFiniteResolutionNFWRadiusEnclosingMass(self,mass,massFractional) result(radius) + !!{ + Computes the radius enclosing a given mass or mass fraction for finite-resolution NFW distributions. + !!} + use :: Error , only : Error_Report + implicit none + class (massDistributionSphericalFiniteResolutionNFW), intent(inout), target :: self + double precision , intent(in ), optional :: mass , massFractional + integer (c_size_t ), dimension(0:1) :: jLengthResolution + double precision , dimension(0:1) :: hLengthResolution + integer :: iLengthResolution + double precision :: mass_ , massScaleFree + + mass_=0.0d0 + if (present(mass)) then + mass_=mass + else if (present(massFractional)) then + call Error_Report('mass is unbounded, so mass fraction is undefined'//{introspection:location}) + else + call Error_Report('either mass or massFractional must be supplied' //{introspection:location}) + end if + if (mass /= self%radiusEnclosingMassMassPrevious) then + self%radiusEnclosingMassMassPrevious=mass + ! Find scale free mass, and the maximum such mass reached in the profile. + massScaleFree=+ mass & + & /self%densityNormalization & + & /self%radiusScale **3 + ! Ensure table is sufficiently extensive. + call self%radiusEnclosingMassTabulate(massScaleFree,self%lengthResolutionScaleFree) + ! Interpolate to get the scale free radius enclosing the scale free mass. + call self%radiusEnclosingMassTableLengthResolutionInterpolator%linearFactors(self%lengthResolutionScaleFree,jLengthResolution(0),hLengthResolution) + jLengthResolution(1)=jLengthResolution(0)+1 + self%radiusEnclosingMassPrevious=0.0d0 + do iLengthResolution=0,1 + self%radiusEnclosingMassPrevious=+self%radiusEnclosingMassPrevious & + & +self%radiusEnclosingMassTableMassInterpolator%interpolate(massScaleFree,self%radiusEnclosingMassTable(:,jLengthResolution(iLengthResolution))) & + & * hLengthResolution(iLengthResolution) + end do + self%radiusEnclosingMassPrevious=+self%radiusEnclosingMassPrevious & + & *self%radiusScale + end if + radius=self%radiusEnclosingMassPrevious + return + end function sphericalFiniteResolutionNFWRadiusEnclosingMass + + subroutine sphericalFiniteResolutionNFWRadiusEnclosingMassTabulate(self,mass,lengthResolution) + !!{ + Tabulates the radius enclosing a given mass for finite resolution NFW mass profiles. + !!} + use :: Numerical_Constants_Math, only : Pi + use :: Numerical_Ranges , only : Make_Range , rangeTypeLogarithmic + use :: Root_Finder , only : rangeExpandMultiplicative, rangeExpandSignExpectNegative, rangeExpandSignExpectPositive, rootFinder + implicit none + class (massDistributionSphericalFiniteResolutionNFW), intent(inout), target :: self + double precision , intent(in ) :: mass , lengthResolution + double precision , parameter :: toleranceAbsolute=0.0d0, toleranceRelative=1.0d-9 + logical :: retabulate + integer :: iLengthResolution , iMass , & + & i + type (rootFinder ) :: finder + + do i=1,2 + retabulate=.false. + if (.not.self%radiusEnclosingMassTableInitialized) then + retabulate=.true. + else if ( & + & mass < self%radiusEnclosingMassMassMinimum & + & .or. & + & mass > self%radiusEnclosingMassMassMaximum & + & .or. & + & lengthResolution < self%radiusEnclosingMassLengthResolutionMinimum & + & .or. & + & lengthResolution > self%radiusEnclosingMassLengthResolutionMaximum & + & ) then + retabulate=.true. + end if + if (retabulate .and.i==1) call self%restoreMassTable() + if (.not.retabulate ) exit + end do + if (retabulate) then + ! Decide how many points to tabulate and allocate table arrays. + self%radiusEnclosingMassMassMinimum =min(0.5d0*mass ,self%radiusEnclosingMassMassMinimum ) + self%radiusEnclosingMassMassMaximum =max(2.0d0*mass ,self%radiusEnclosingMassMassMaximum ) + self%radiusEnclosingMassLengthResolutionMinimum =min(0.5d0*lengthResolution,self%radiusEnclosingMassLengthResolutionMinimum) + self%radiusEnclosingMassLengthResolutionMaximum =max(2.0d0*lengthResolution,self%radiusEnclosingMassLengthResolutionMaximum) + self%radiusEnclosingMassTableMassCount =int(log10(self%radiusEnclosingMassMassMaximum /self%radiusEnclosingMassMassMinimum )*dble(radiusEnclosingMassTableMassPointsPerDecade ))+1 + self%radiusEnclosingMassTableLengthResolutionCount=int(log10(self%radiusEnclosingMassLengthResolutionMaximum/self%radiusEnclosingMassLengthResolutionMinimum)*dble(radiusEnclosingMassTableLengthResolutionPointsPerDecade))+1 + if (allocated(self%radiusEnclosingMassTableMass)) then + deallocate(self%radiusEnclosingMassTableLengthResolution) + deallocate(self%radiusEnclosingMassTableMass ) + deallocate(self%radiusEnclosingMassTable ) + end if + allocate(self%radiusEnclosingMassTableLengthResolution( self%radiusEnclosingMassTableLengthResolutionCount)) + allocate(self%radiusEnclosingMassTableMass (self%radiusEnclosingMassTableMassCount )) + allocate(self%radiusEnclosingMassTable (self%radiusEnclosingMassTableMassCount,self%radiusEnclosingMassTableLengthResolutionCount)) + ! Create a range of radii and core radii. + self%radiusEnclosingMassTableMass =Make_Range(self%radiusEnclosingMassMassMinimum ,self%radiusEnclosingMassMassMaximum ,self%radiusEnclosingMassTableMassCount ,rangeType=rangeTypeLogarithmic) + self%radiusEnclosingMassTableLengthResolution=Make_Range(self%radiusEnclosingMassLengthResolutionMinimum,self%radiusEnclosingMassLengthResolutionMaximum,self%radiusEnclosingMassTableLengthResolutionCount,rangeType=rangeTypeLogarithmic) + ! Initialize our root finder. + finder=rootFinder( & + & rootFunction =rootMass , & + & toleranceAbsolute =toleranceAbsolute , & + & toleranceRelative =toleranceRelative , & + & rangeExpandDownward =0.5d0 , & + & rangeExpandUpward =2.0d0 , & + & rangeExpandType =rangeExpandMultiplicative , & + & rangeExpandUpwardSignExpect =rangeExpandSignExpectPositive, & + & rangeExpandDownwardSignExpect=rangeExpandSignExpectNegative & + & ) + ! Loop over mass and core radius and populate tables. + self_ => self + do iLengthResolution=1,self%radiusEnclosingMassTableLengthResolutionCount + iLengthResolution_=iLengthResolution + do iMass=1,self%radiusEnclosingMassTableMassCount + iMass_=iMass + ! Check that the root condition is satisfied at infinitely large radius. If it is not, then no radius encloses the + ! required mass. Simply set the radius to an infinitely large value in such case. + if (rootMass(radius=huge(0.0d0)) < 0.0d0) then + self%radiusEnclosingMassTable(iMass,iLengthResolution)=huge(0.0d0) + else + self%radiusEnclosingMassTable(iMass,iLengthResolution)=finder%find(rootGuess=1.0d0) + end if + end do + end do + ! Build interpolators. + if (allocated(self%radiusEnclosingMassTableLengthResolutionInterpolator)) deallocate(self%radiusEnclosingMassTableLengthResolutionInterpolator) + if (allocated(self%radiusEnclosingMassTableMassInterpolator )) deallocate(self%radiusEnclosingMassTableMassInterpolator ) + allocate(self%radiusEnclosingMassTableLengthResolutionInterpolator) + allocate(self%radiusEnclosingMassTableMassInterpolator ) + self%radiusEnclosingMassTableLengthResolutionInterpolator=interpolator(self%radiusEnclosingMassTableLengthResolution) + self%radiusEnclosingMassTableMassInterpolator =interpolator(self%radiusEnclosingMassTableMass ) + ! Specify that tabulation has been made. + self%radiusEnclosingMassTableInitialized=.true. + call self%storeMassTable() + end if + return + end subroutine sphericalFiniteResolutionNFWRadiusEnclosingMassTabulate + + double precision function rootMass(radius) + !!{ + Root function used in finding the radius enclosing a given mean mass. + !!} + implicit none + double precision, intent(in ) :: radius + + rootMass=+self_%massEnclosedScaleFree (radius,self_%radiusEnclosingMassTableLengthResolution(iLengthResolution_)) & + & -self_%radiusEnclosingMassTableMass( iMass_ ) + return + end function rootMass + + subroutine sphericalFiniteResolutionNFWStoreMassTable(self) + !!{ + Store the tabulated radius-enclosing-mass data to file. + !!} + use :: File_Utilities , only : File_Lock , File_Unlock , lockDescriptor, Directory_Make, & + & File_Path + use :: HDF5_Access , only : hdf5Access + use :: IO_HDF5 , only : hdf5Object + use :: Input_Paths , only : inputPath , pathTypeDataDynamic + use :: ISO_Varying_String, only : varying_string, operator(//) , char + implicit none + class(massDistributionSphericalFiniteResolutionNFW), intent(inout) :: self + type (lockDescriptor ) :: fileLock + type (hdf5Object ) :: file + type (varying_string ) :: fileName + + fileName=inputPath(pathTypeDataDynamic) // & + & 'darkMatter/' // & + & self%objectType ( )// & + & 'Mass_' // & + & self%hashedDescriptor(includeSourceDigest=.true.)// & + & '.hdf5' + call Directory_Make(char(File_Path(char(fileName)))) + ! Always obtain the file lock before the hdf5Access lock to avoid deadlocks between OpenMP threads. + call File_Lock(char(fileName),fileLock,lockIsShared=.false.) + !$ call hdf5Access%set() + call file%openFile(char(fileName),overWrite=.true.,objectsOverwritable=.true.,readOnly=.false.) + call file%writeDataset(self%radiusEnclosingMassTableLengthResolution,'lengthResolution') + call file%writeDataset(self%radiusEnclosingMassTableMass ,'mass' ) + call file%writeDataset(self%radiusEnclosingMassTable ,'radius' ) + call file%close() + !$ call hdf5Access%unset() + call File_Unlock(fileLock) + return + end subroutine sphericalFiniteResolutionNFWStoreMassTable + + subroutine sphericalFiniteResolutionNFWRestoreMassTable(self) + !!{ + Restore the tabulated radius-enclosing-mass data from file, returning true if successful. + !!} + use :: File_Utilities , only : File_Exists , File_Lock , File_Unlock, lockDescriptor + use :: HDF5_Access , only : hdf5Access + use :: IO_HDF5 , only : hdf5Object + use :: Input_Paths , only : inputPath , pathTypeDataDynamic + use :: ISO_Varying_String, only : varying_string, operator(//) + implicit none + class(massDistributionSphericalFiniteResolutionNFW), intent(inout) :: self + type (lockDescriptor ) :: fileLock + type (hdf5Object ) :: file + type (varying_string ) :: fileName + + fileName=inputPath(pathTypeDataDynamic) // & + & 'darkMatter/' // & + & self%objectType ( )// & + & 'Mass_' // & + & self%hashedDescriptor(includeSourceDigest=.true.)// & + & '.hdf5' + if (File_Exists(fileName)) then + if (allocated(self%radiusEnclosingMassTableMass)) then + deallocate(self%radiusEnclosingMassTableLengthResolution) + deallocate(self%radiusEnclosingMassTableMass ) + deallocate(self%radiusEnclosingMassTable ) + end if + ! Always obtain the file lock before the hdf5Access lock to avoid deadlocks between OpenMP threads. + call File_Lock(char(fileName),fileLock,lockIsShared=.true.) + !$ call hdf5Access%set() + call file%openFile(char(fileName)) + call file%readDataset('lengthResolution',self%radiusEnclosingMassTableLengthResolution) + call file%readDataset('mass' ,self%radiusEnclosingMassTableMass ) + call file%readDataset('radius' ,self%radiusEnclosingMassTable ) + call file%close() + !$ call hdf5Access%unset() + call File_Unlock(fileLock) + self%radiusEnclosingMassTableMassCount =size(self%radiusEnclosingMassTableMass ) + self%radiusEnclosingMassTableLengthResolutionCount=size(self%radiusEnclosingMassTableLengthResolution) + self%radiusEnclosingMassMassMinimum =self%radiusEnclosingMassTableMass ( 1) + self%radiusEnclosingMassMassMaximum =self%radiusEnclosingMassTableMass (self%radiusEnclosingMassTableMassCount ) + self%radiusEnclosingMassLengthResolutionMinimum =self%radiusEnclosingMassTableLengthResolution( 1) + self%radiusEnclosingMassLengthResolutionMaximum =self%radiusEnclosingMassTableLengthResolution(self%radiusEnclosingMassTableLengthResolutionCount) + if (allocated(self%radiusEnclosingMassTableLengthResolutionInterpolator)) deallocate(self%radiusEnclosingMassTableLengthResolutionInterpolator) + if (allocated(self%radiusEnclosingMassTableMassInterpolator )) deallocate(self%radiusEnclosingMassTableMassInterpolator ) + allocate(self%radiusEnclosingMassTableLengthResolutionInterpolator) + allocate(self%radiusEnclosingMassTableMassInterpolator ) + self%radiusEnclosingMassTableLengthResolutionInterpolator=interpolator(self%radiusEnclosingMassTableLengthResolution) + self%radiusEnclosingMassTableMassInterpolator =interpolator(self%radiusEnclosingMassTableMass ) + self%radiusEnclosingMassTableInitialized =.true. + end if + return + end subroutine sphericalFiniteResolutionNFWRestoreMassTable + + double precision function sphericalFiniteResolutionNFWRadiusEnclosingDensity(self,density,radiusGuess) result(radius) + !!{ + Computes the radius enclosing a given mean density for finite-resolution NFW mass distributions. + !!} + use :: Numerical_Ranges, only : Make_Range, rangeTypeLogarithmic + implicit none + class (massDistributionSphericalFiniteResolutionNFW), intent(inout), target :: self + double precision , intent(in ) :: density + double precision , intent(in ), optional :: radiusGuess + double precision , parameter :: epsilonDensity =1.0d-3 + double precision :: densityScaleFreeMaximum , densityScaleFree + integer (c_size_t ), dimension(0:1) :: jLengthResolution + double precision , dimension(0:1) :: hLengthResolution + integer :: iLengthResolution + + if (density /= self%radiusEnclosingDensityDensityPrevious) then + self%radiusEnclosingDensityDensityPrevious=density + ! Find scale free density, and the maximum such density reached in the profile. + densityScaleFree =+ density & + & /self%densityNormalization + densityScaleFreeMaximum=+1.0d0 & + & /self%lengthResolutionScaleFree + if (densityScaleFree >= densityScaleFreeMaximum) then + ! Maximum density is exceeded - return zero radius. + self%radiusEnclosingDensityPrevious=0.0d0 + else if (densityScaleFree >= densityScaleFreeMaximum*(1.0d0-epsilonDensity)) then + ! For densities close to the maximum density, use a series solution. + self%radiusEnclosingDensityPrevious=+0.5d0 & + & *( & + & +1.0d0 & + & -densityScaleFree & + & /densityScaleFreeMaximum & + & ) & + & *self%radiusScale + else + ! Use a tabulated solution in other regimes. + ! Ensure table is sufficiently extensive. + call self%radiusEnclosingDensityTabulate(densityScaleFree,self%lengthResolutionScaleFree) + ! Interpolate to get the scale free radius enclosing the scale free density. + call self%radiusEnclosingDensityTableLengthResolutionInterpolator%linearFactors(self%lengthResolutionScaleFree,jLengthResolution(0),hLengthResolution) + jLengthResolution(1)=jLengthResolution(0)+1 + self%radiusEnclosingDensityPrevious=0.0d0 + do iLengthResolution=0,1 + self%radiusEnclosingDensityPrevious=+self%radiusEnclosingDensityPrevious & + & +self%radiusEnclosingDensityTableDensityInterpolator%interpolate(densityScaleFree,self%radiusEnclosingDensityTable(:,jLengthResolution(iLengthResolution))) & + & * hLengthResolution(iLengthResolution) + end do + self%radiusEnclosingDensityPrevious=+self%radiusEnclosingDensityPrevious & + & *self%radiusScale + end if + end if + radius=self%radiusEnclosingDensityPrevious + return + end function sphericalFiniteResolutionNFWRadiusEnclosingDensity + + subroutine sphericalFiniteResolutionNFWRadiusEnclosingDensityTabulate(self,density,lengthResolution) + !!{ + Tabulates the radius enclosing a given density for finite resolution NFW density profiles. + !!} + use :: Numerical_Constants_Math, only : Pi + use :: Numerical_Ranges , only : Make_Range , rangeTypeLogarithmic + use :: Root_Finder , only : rangeExpandMultiplicative, rangeExpandSignExpectNegative, rangeExpandSignExpectPositive, rootFinder + implicit none + class (massDistributionSphericalFiniteResolutionNFW), intent(inout), target :: self + double precision , intent(in ) :: density , lengthResolution + double precision , parameter :: toleranceAbsolute=0.0d0, toleranceRelative=1.0d-9 + logical :: retabulate + integer :: iLengthResolution , iDensity , & + & i + type (rootFinder ) :: finder + + do i=1,2 + retabulate=.false. + if (.not.self%radiusEnclosingDensityTableInitialized) then + retabulate=.true. + else if ( & + & density < self%radiusEnclosingDensityDensityMinimum & + & .or. & + & density > self%radiusEnclosingDensityDensityMaximum & + & .or. & + & lengthResolution < self%radiusEnclosingDensityLengthResolutionMinimum & + & .or. & + & lengthResolution > self%radiusEnclosingDensityLengthResolutionMaximum & + & ) then + retabulate=.true. + end if + if (retabulate .and.i==1) call self%restoreDensityTable() + if (.not.retabulate ) exit + end do + if (retabulate) then + ! Decide how many points to tabulate and allocate table arrays. + self%radiusEnclosingDensityDensityMinimum =min(0.5d0*density ,self%radiusEnclosingDensityDensityMinimum ) + self%radiusEnclosingDensityDensityMaximum =max(2.0d0*density ,self%radiusEnclosingDensityDensityMaximum ) + self%radiusEnclosingDensityLengthResolutionMinimum =min(0.5d0*lengthResolution,self%radiusEnclosingDensityLengthResolutionMinimum) + self%radiusEnclosingDensityLengthResolutionMaximum =max(2.0d0*lengthResolution,self%radiusEnclosingDensityLengthResolutionMaximum) + self%radiusEnclosingDensityTableDensityCount =int(log10(self%radiusEnclosingDensityDensityMaximum /self%radiusEnclosingDensityDensityMinimum )*dble(radiusEnclosingDensityTableDensityPointsPerDecade ))+1 + self%radiusEnclosingDensityTableLengthResolutionCount=int(log10(self%radiusEnclosingDensityLengthResolutionMaximum/self%radiusEnclosingDensityLengthResolutionMinimum)*dble(radiusEnclosingDensityTableLengthResolutionPointsPerDecade))+1 + if (allocated(self%radiusEnclosingDensityTableDensity)) then + deallocate(self%radiusEnclosingDensityTableLengthResolution) + deallocate(self%radiusEnclosingDensityTableDensity ) + deallocate(self%radiusEnclosingDensityTable ) + end if + allocate(self%radiusEnclosingDensityTableLengthResolution( self%radiusEnclosingDensityTableLengthResolutionCount)) + allocate(self%radiusEnclosingDensityTableDensity (self%radiusEnclosingDensityTableDensityCount )) + allocate(self%radiusEnclosingDensityTable (self%radiusEnclosingDensityTabledensityCount,self%radiusEnclosingDensityTableLengthResolutionCount)) + ! Create a range of radii and core radii. + self%radiusEnclosingDensityTableDensity =Make_Range(self%radiusEnclosingDensityDensityMinimum ,self%radiusEnclosingDensityDensityMaximum ,self%radiusEnclosingDensityTableDensityCount ,rangeType=rangeTypeLogarithmic) + self%radiusEnclosingDensityTableLengthResolution=Make_Range(self%radiusEnclosingDensityLengthResolutionMinimum,self%radiusEnclosingDensityLengthResolutionMaximum,self%radiusEnclosingDensityTableLengthResolutionCount,rangeType=rangeTypeLogarithmic) + ! Initialize our root finder. + finder=rootFinder( & + & rootFunction =rootDensity , & + & toleranceAbsolute =toleranceAbsolute , & + & toleranceRelative =toleranceRelative , & + & rangeExpandDownward =0.5d0 , & + & rangeExpandUpward =2.0d0 , & + & rangeExpandType =rangeExpandMultiplicative , & + & rangeExpandUpwardSignExpect =rangeExpandSignExpectNegative, & + & rangeExpandDownwardSignExpect=rangeExpandSignExpectPositive & + & ) + ! Loop over density and core radius and populate tables. + self_ => self + do iLengthResolution=1,self%radiusEnclosingDensityTableLengthResolutionCount + iLengthResolution_=iLengthResolution + do iDensity=1,self%radiusEnclosingDensityTableDensityCount + iDensity_=iDensity + if (self%radiusEnclosingDensityTableDensity(iDensity) > 1.0d0/self%radiusEnclosingDensityTableLengthResolution(iLengthResolution)) then + ! Density exceeds the maximum density in the profile - so set zero radius. + self%radiusEnclosingDensityTable(iDensity,iLengthResolution)=0.0d0 + else + self%radiusEnclosingDensityTable(iDensity,iLengthResolution)=finder%find(rootGuess=1.0d0) + end if + end do + end do + ! Build interpolators. + if (allocated(self%radiusEnclosingDensityTableLengthResolutionInterpolator)) deallocate(self%radiusEnclosingDensityTableLengthResolutionInterpolator) + if (allocated(self%radiusEnclosingDensityTableDensityInterpolator )) deallocate(self%radiusEnclosingDensityTableDensityInterpolator ) + allocate(self%radiusEnclosingDensityTableLengthResolutionInterpolator) + allocate(self%radiusEnclosingDensityTableDensityInterpolator ) + self%radiusEnclosingDensityTableLengthResolutionInterpolator=interpolator(self%radiusEnclosingDensityTableLengthResolution) + self%radiusEnclosingDensityTableDensityInterpolator =interpolator(self%radiusEnclosingDensityTableDensity ) + ! Specify that tabulation has been made. + self%radiusEnclosingDensityTableInitialized=.true. + call self%storeDensityTable() + end if + return + end subroutine sphericalFiniteResolutionNFWRadiusEnclosingDensityTabulate + + double precision function rootDensity(radius) + !!{ + Root function used in finding the radius enclosing a given mean density. + !!} + use :: Numerical_Constants_Math, only : Pi + implicit none + double precision, intent(in ) :: radius + + rootDensity=+3.0d0 & + & *self_%massEnclosedScaleFree (radius,self_%radiusEnclosingDensityTableLengthResolution(iLengthResolution_)) & + & /4.0d0 & + & /Pi & + & / radius **3 & + & -self_%radiusEnclosingDensityTableDensity( iDensity_ ) + return + end function rootDensity + + subroutine sphericalFiniteResolutionNFWStoreDensityTable(self) + !!{ + Store the tabulated radius-enclosing-density data to file. + !!} + use :: File_Utilities , only : File_Lock , File_Unlock , lockDescriptor, Directory_Make, & + & File_Path + use :: HDF5_Access , only : hdf5Access + use :: IO_HDF5 , only : hdf5Object + use :: Input_Paths , only : inputPath , pathTypeDataDynamic + use :: ISO_Varying_String, only : varying_string, operator(//) , char + implicit none + class(massDistributionSphericalFiniteResolutionNFW), intent(inout) :: self + type (lockDescriptor ) :: fileLock + type (hdf5Object ) :: file + type (varying_string ) :: fileName + + fileName=inputPath(pathTypeDataDynamic) // & + & 'darkMatter/' // & + & self%objectType ( )// & + & 'Density_' // & + & self%hashedDescriptor(includeSourceDigest=.true.)// & + & '.hdf5' + call Directory_Make(char(File_Path(char(fileName)))) + ! Always obtain the file lock before the hdf5Access lock to avoid deadlocks between OpenMP threads. + call File_Lock(char(fileName),fileLock,lockIsShared=.false.) + !$ call hdf5Access%set() + call file%openFile(char(fileName),overWrite=.true.,objectsOverwritable=.true.,readOnly=.false.) + call file%writeDataset(self%radiusEnclosingDensityTableLengthResolution,'lengthResolution') + call file%writeDataset(self%radiusEnclosingDensityTableDensity ,'density' ) + call file%writeDataset(self%radiusEnclosingDensityTable ,'radius' ) + call file%close() + !$ call hdf5Access%unset() + call File_Unlock(fileLock) + return + end subroutine sphericalFiniteResolutionNFWStoreDensityTable + + subroutine sphericalFiniteResolutionNFWRestoreDensityTable(self) + !!{ + Restore the tabulated radius-enclosing-density data from file, returning true if successful. + !!} + use :: File_Utilities , only : File_Exists , File_Lock , File_Unlock, lockDescriptor + use :: HDF5_Access , only : hdf5Access + use :: IO_HDF5 , only : hdf5Object + use :: Input_Paths , only : inputPath , pathTypeDataDynamic + use :: ISO_Varying_String, only : varying_string, operator(//) + implicit none + class(massDistributionSphericalFiniteResolutionNFW), intent(inout) :: self + type (lockDescriptor ) :: fileLock + type (hdf5Object ) :: file + type (varying_string ) :: fileName + + fileName=inputPath(pathTypeDataDynamic) // & + & 'darkMatter/' // & + & self%objectType ( )// & + & 'Density_' // & + & self%hashedDescriptor(includeSourceDigest=.true.)// & + & '.hdf5' + if (File_Exists(fileName)) then + if (allocated(self%radiusEnclosingDensityTableDensity)) then + deallocate(self%radiusEnclosingDensityTableLengthResolution) + deallocate(self%radiusEnclosingDensityTableDensity ) + deallocate(self%radiusEnclosingDensityTable ) + end if + ! Always obtain the file lock before the hdf5Access lock to avoid deadlocks between OpenMP threads. + call File_Lock(char(fileName),fileLock,lockIsShared=.true.) + !$ call hdf5Access%set() + call file%openFile(char(fileName)) + call file%readDataset('lengthResolution',self%radiusEnclosingDensityTableLengthResolution) + call file%readDataset('density' ,self%radiusEnclosingDensityTableDensity ) + call file%readDataset('radius' ,self%radiusEnclosingDensityTable ) + call file%close() + !$ call hdf5Access%unset() + call File_Unlock(fileLock) + self%radiusEnclosingDensityTableDensityCount =size(self%radiusEnclosingDensityTableDensity ) + self%radiusEnclosingDensityTableLengthResolutionCount=size(self%radiusEnclosingDensityTableLengthResolution) + self%radiusEnclosingDensityDensityMinimum =self%radiusEnclosingDensityTableDensity ( 1) + self%radiusEnclosingDensityDensityMaximum =self%radiusEnclosingDensityTableDensity (self%radiusEnclosingDensityTableDensityCount ) + self%radiusEnclosingDensityLengthResolutionMinimum =self%radiusEnclosingDensityTableLengthResolution( 1) + self%radiusEnclosingDensityLengthResolutionMaximum =self%radiusEnclosingDensityTableLengthResolution(self%radiusEnclosingDensityTableLengthResolutionCount) + if (allocated(self%radiusEnclosingDensityTableLengthResolutionInterpolator)) deallocate(self%radiusEnclosingDensityTableLengthResolutionInterpolator) + if (allocated(self%radiusEnclosingDensityTableDensityInterpolator )) deallocate(self%radiusEnclosingDensityTableDensityInterpolator ) + allocate(self%radiusEnclosingDensityTableLengthResolutionInterpolator) + allocate(self%radiusEnclosingDensityTableDensityInterpolator ) + self%radiusEnclosingDensityTableLengthResolutionInterpolator=interpolator(self%radiusEnclosingDensityTableLengthResolution) + self%radiusEnclosingDensityTableDensityInterpolator =interpolator(self%radiusEnclosingDensityTableDensity ) + self%radiusEnclosingDensityTableInitialized =.true. + end if + return + end subroutine sphericalFiniteResolutionNFWRestoreDensityTable + + double precision function sphericalFiniteResolutionNFWEnergy(self,radiusOuter,massDistributionEmbedding) result(energy) + !!{ + Compute the energy within a given {\normalfont \ttfamily radius} in a finite-resolution NFW mass distribution. + !!} + use :: Numerical_Constants_Astronomical, only : gravitationalConstantGalacticus + implicit none + class (massDistributionSphericalFiniteResolutionNFW), intent(inout) , target :: self + double precision , intent(in ) :: radiusOuter + class (massDistributionClass ), intent(inout) , target :: massDistributionEmbedding + integer (c_size_t ), dimension(0:1) :: jLengthResolution + double precision , dimension(0:1) :: hLengthResolution + integer :: iLengthResolution + + if (self%energyPrevious > 0.0d0) then + ! Ensure table is sufficiently extensive. + call self%energyTabulate(self%lengthResolutionScaleFree,radiusOuter/self%radiusScale) + ! Interpolate to get the scale free energy. + call self%energyTableLengthResolutionInterpolator%linearFactors(self%lengthResolutionScaleFree,jLengthResolution(0),hLengthResolution) + jLengthResolution(1)=jLengthResolution(0)+1 + self%energyPrevious=0.0d0 + do iLengthResolution=0,1 + self%energyPrevious=+self%energyPrevious & + & +self%energyTableRadiusOuterInterpolator%interpolate(radiusOuter/self%radiusScale,self%energyTable(:,jLengthResolution(iLengthResolution))) & + & * hLengthResolution(iLengthResolution) + end do + self%energyPrevious=+self %energyPrevious & + & *gravitationalConstantGalacticus & + & *self %densityNormalization**2 & + & *self %radiusScale **5 + end if + energy=self%energyPrevious + return + end function sphericalFiniteResolutionNFWEnergy + + subroutine sphericalFiniteResolutionNFWEnergyTabulate(self,lengthResolution,radiusOuter) + !!{ + Tabulates the energy for finite resolution NFW mass profiles. + !!} + use :: Numerical_Constants_Math, only : Pi + use :: Numerical_Integration , only : integrator + use :: Numerical_Ranges , only : Make_Range, rangeTypeLogarithmic + implicit none + class (massDistributionSphericalFiniteResolutionNFW), intent(inout), target :: self + double precision , intent(in ) :: radiusOuter , lengthResolution + double precision , parameter :: multiplierRadius =100.0d0 + type (integrator ) :: integratorPotential , integratorKinetic, & + & integratorPressure + double precision :: pseudoPressure , energyKinetic , & + & energyPotential , radiusOuter_ + logical :: retabulate + integer :: iLengthResolution , iRadiusOuter , & + & i + + do i=1,2 + retabulate=.false. + if (.not.self%energyTableInitialized) then + retabulate=.true. + else if ( & + & radiusOuter < self%energyRadiusOuterMinimum & + & .or. & + & radiusOuter > self%energyRadiusOuterMaximum & + & .or. & + & lengthResolution < self%energyLengthResolutionMinimum & + & .or. & + & lengthResolution > self%energyLengthResolutionMaximum & + & ) then + retabulate=.true. + end if + if ( retabulate.and.i==1) call self%restoreEnergyTable() + if (.not.retabulate ) exit + end do + if (retabulate) then + ! Decide how many points to tabulate and allocate table arrays. + self%energyRadiusOuterMinimum =min(0.5d0*radiusOuter ,self%energyRadiusOuterMinimum ) + self%energyRadiusOuterMaximum =max(2.0d0*radiusOuter ,self%energyRadiusOuterMaximum ) + self%energyLengthResolutionMinimum =min(0.5d0*lengthResolution,self%energyLengthResolutionMinimum) + self%energyLengthResolutionMaximum =max(2.0d0*lengthResolution,self%energyLengthResolutionMaximum) + self%energyTableRadiusOuterCount =int(log10(self%energyRadiusOuterMaximum /self%energyRadiusOuterMinimum )*dble(energyTableRadiusOuterPointsPerDecade ))+1 + self%energyTableLengthResolutionCount=int(log10(self%energyLengthResolutionMaximum/self%energyLengthResolutionMinimum)*dble(energyTableLengthResolutionPointsPerDecade))+1 + if (allocated(self%energyTableRadiusOuter)) then + deallocate(self%energyTableLengthResolution) + deallocate(self%energyTableRadiusOuter ) + deallocate(self%energyTable ) + end if + allocate(self%energyTableLengthResolution( self%energyTableLengthResolutionCount)) + allocate(self%energyTableRadiusOuter (self%energyTableRadiusOuterCount )) + allocate(self%energyTable (self%energyTableradiusOuterCount,self%energyTableLengthResolutionCount)) + ! Create a range of radii and core radii. + self%energyTableRadiusOuter =Make_Range(self%energyRadiusOuterMinimum ,self%energyRadiusOuterMaximum ,self%energyTableRadiusOuterCount ,rangeType=rangeTypeLogarithmic) + self%energyTableLengthResolution=Make_Range(self%energyLengthResolutionMinimum,self%energyLengthResolutionMaximum,self%energyTableLengthResolutionCount,rangeType=rangeTypeLogarithmic) + ! Initialize integrators. + integratorPotential=integrator(integrandEnergyPotential,toleranceRelative=1.0d-3) + integratorKinetic =integrator(integrandEnergyKinetic ,toleranceRelative=1.0d-3) + integratorPressure =integrator(integrandPseudoPressure ,toleranceRelative=1.0d-3) + ! Loop over radiusOuter and core radius and populate tables. + self_ => self + do iLengthResolution=1,self%energyTableLengthResolutionCount + iLengthResolution_=iLengthResolution + do iRadiusOuter=1,self%energyTableRadiusOuterCount + radiusOuter_ =self%energyTableRadiusOuter(iRadiusOuter) + energyPotential =+integratorPotential%integrate( 0.0d0, radiusOuter_) + energyKinetic =+integratorKinetic %integrate( 0.0d0, radiusOuter_) + pseudoPressure =+integratorPressure %integrate(radiusOuter_,multiplierRadius*radiusOuter_) + self%energyTable(iRadiusOuter,iLengthResolution)=-0.5d0 & + & *( & + & +energyPotential & + & +self%massEnclosedScaleFree(radiusOuter_,self%energyTableLengthResolution(iLengthResolution))**2 & + & /radiusOuter_ & + & ) & + & +2.0d0 & + & *Pi & + & *( & + & +radiusOuter_ **3 & + & *pseudoPressure & + & +energyKinetic & + & ) + end do + end do + ! Build interpolators. + if (allocated(self%energyTableLengthResolutionInterpolator)) deallocate(self%energyTableLengthResolutionInterpolator) + if (allocated(self%energyTableRadiusOuterInterpolator )) deallocate(self%energyTableRadiusOuterInterpolator ) + allocate(self%energyTableLengthResolutionInterpolator) + allocate(self%energyTableRadiusOuterInterpolator ) + self%energyTableLengthResolutionInterpolator=interpolator(self%energyTableLengthResolution) + self%energyTableRadiusOuterInterpolator =interpolator(self%energyTableRadiusOuter ) + ! Specify that tabulation has been made. + self%energyTableInitialized=.true. + call self%storeEnergyTable() + end if + return + end subroutine sphericalFiniteResolutionNFWEnergyTabulate + + double precision function integrandEnergyPotential(radius) + !!{ + Integrand for potential energy of the halo. + !!} + implicit none + double precision, intent(in ) :: radius + + if (radius > 0.0d0) then + integrandEnergyPotential=( & + & +self_%massEnclosedScaleFree(radius,self_%energyTableLengthResolution(iLengthResolution_)) & + & / radius & + & )**2 + else + integrandEnergyPotential=+0.0d0 + end if + return + end function integrandEnergyPotential + + double precision function integrandEnergyKinetic(radius) + !!{ + Integrand for kinetic energy of the halo. + !!} + implicit none + double precision, intent(in ) :: radius + + if (radius > 0.0d0) then + integrandEnergyKinetic=+self_%massEnclosedScaleFree(radius,self_%energyTableLengthResolution(iLengthResolution_)) & + & *self_%densityScaleFree (radius,self_%energyTableLengthResolution(iLengthResolution_)) & + & * radius + else + integrandEnergyKinetic=+0.0d0 + end if + return + end function integrandEnergyKinetic + + double precision function integrandPseudoPressure(radius) + !!{ + Integrand for pseudo-pressure ($\rho(r) \sigma^2(r)$) of the halo. + !!} + implicit none + double precision, intent(in ) :: radius + + if (radius > 0.0d0) then + integrandPseudoPressure=+self_%massEnclosedScaleFree(radius,self_%energyTableLengthResolution(iLengthResolution_)) & + & *self_%densityScaleFree (radius,self_%energyTableLengthResolution(iLengthResolution_)) & + & / radius **2 + else + integrandPseudoPressure=+0.0d0 + end if + return + end function integrandPseudoPressure + + double precision function sphericalFiniteResolutionNFWDensityScaleFree(self,radius,radiusCore) result(densityScaleFree) + !!{ + Returns the scale-free density in the dark matter profile at the given {\normalfont \ttfamily radius}. + !!} + implicit none + class (massDistributionSphericalFiniteResolutionNFW), intent(inout) :: self + double precision , intent(in ) :: radius, radiusCore + !$GLC attributes unused :: self + + densityScaleFree=1.0d0/(1.0d0+radius)**2/sqrt(radius**2+radiusCore**2) + return + end function sphericalFiniteResolutionNFWDensityScaleFree + + subroutine sphericalFiniteResolutionNFWStoreEnergyTable(self) + !!{ + Store the tabulated energy data to file. + !!} + use :: File_Utilities , only : File_Lock , File_Unlock , lockDescriptor, Directory_Make, & + & File_Path + use :: HDF5_Access , only : hdf5Access + use :: IO_HDF5 , only : hdf5Object + use :: Input_Paths , only : inputPath , pathTypeDataDynamic + use :: ISO_Varying_String, only : varying_string, operator(//) , char + implicit none + class(massDistributionSphericalFiniteResolutionNFW), intent(inout) :: self + type (lockDescriptor ) :: fileLock + type (hdf5Object ) :: file + type (varying_string ) :: fileName + + fileName=inputPath(pathTypeDataDynamic) // & + & 'darkMatter/' // & + & self%objectType ( )// & + & 'Energy_' // & + & self%hashedDescriptor(includeSourceDigest=.true.)// & + & '.hdf5' + call Directory_Make(char(File_Path(char(fileName)))) + ! Always obtain the file lock before the hdf5Access lock to avoid deadlocks between OpenMP threads. + call File_Lock(char(fileName),fileLock,lockIsShared=.false.) + !$ call hdf5Access%set() + call file%openFile(char(fileName),overWrite=.true.,objectsOverwritable=.true.,readOnly=.false.) + call file%writeDataset(self%energyTableLengthResolution,'lengthResolution') + call file%writeDataset(self%energyTableRadiusOuter ,'radiusOuter' ) + call file%writeDataset(self%energyTable ,'energy' ) + call file%close() + !$ call hdf5Access%unset() + call File_Unlock(fileLock) + return + end subroutine sphericalFiniteResolutionNFWStoreEnergyTable + + subroutine sphericalFiniteResolutionNFWRestoreEnergyTable(self) + !!{ + Restore the tabulated radius-enclosing-mass data from file, returning true if successful. + !!} + use :: File_Utilities , only : File_Exists , File_Lock , File_Unlock, lockDescriptor + use :: HDF5_Access , only : hdf5Access + use :: IO_HDF5 , only : hdf5Object + use :: Input_Paths , only : inputPath , pathTypeDataDynamic + use :: ISO_Varying_String, only : varying_string, operator(//) + implicit none + class(massDistributionSphericalFiniteResolutionNFW), intent(inout) :: self + type (lockDescriptor ) :: fileLock + type (hdf5Object ) :: file + type (varying_string ) :: fileName + + fileName=inputPath(pathTypeDataDynamic) // & + & 'darkMatter/' // & + & self%objectType ( )// & + & 'Energy_' // & + & self%hashedDescriptor(includeSourceDigest=.true.)// & + & '.hdf5' + if (File_Exists(fileName)) then + if (allocated(self%energyTableRadiusOuter)) then + deallocate(self%energyTableLengthResolution ) + deallocate(self%energyTableRadiusOuter) + deallocate(self%energyTable ) + end if + ! Always obtain the file lock before the hdf5Access lock to avoid deadlocks between OpenMP threads. + call File_Lock(char(fileName),fileLock,lockIsShared=.true.) + !$ call hdf5Access%set() + call file%openFile(char(fileName)) + call file%readDataset('lengthResolution',self%energyTableLengthResolution) + call file%readDataset('radiusOuter' ,self%energyTableRadiusOuter ) + call file%readDataset('energy' ,self%energyTable ) + call file%close() + !$ call hdf5Access%unset() + call File_Unlock(fileLock) + self%energyTableRadiusOuterCount =size(self%energyTableRadiusOuter ) + self%energyTableLengthResolutionCount=size(self%energyTableLengthResolution) + self%energyRadiusOuterMinimum =self%energyTableRadiusOuter ( 1) + self%energyRadiusOuterMaximum =self%energyTableRadiusOuter (self%energyTableRadiusOuterCount ) + self%energyLengthResolutionMinimum =self%energyTableLengthResolution( 1) + self%energyLengthResolutionMaximum =self%energyTableLengthResolution(self%energyTableLengthResolutionCount) + if (allocated(self%energyTableLengthResolutionInterpolator)) deallocate(self%energyTableLengthResolutionInterpolator) + if (allocated(self%energyTableRadiusOuterInterpolator )) deallocate(self%energyTableRadiusOuterInterpolator ) + allocate(self%energyTableLengthResolutionInterpolator) + allocate(self%energyTableRadiusOuterInterpolator ) + self%energyTableLengthResolutionInterpolator=interpolator(self%energyTableLengthResolution) + self%energyTableRadiusOuterInterpolator =interpolator(self%energyTableRadiusOuter ) + self%energyTableInitialized =.true. + end if + return + end subroutine sphericalFiniteResolutionNFWRestoreEnergyTable diff --git a/source/mass_distributions.spherical.heated.F90 b/source/mass_distributions.spherical.heated.F90 new file mode 100644 index 0000000000..0d72765af1 --- /dev/null +++ b/source/mass_distributions.spherical.heated.F90 @@ -0,0 +1,438 @@ +!! Copyright 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, +!! 2019, 2020, 2021, 2022, 2023 +!! Andrew Benson +!! +!! This file is part of Galacticus. +!! +!! Galacticus is free software: you can redistribute it and/or modify +!! it under the terms of the GNU General Public License as published by +!! the Free Software Foundation, either version 3 of the License, or +!! (at your option) any later version. +!! +!! Galacticus is distributed in the hope that it will be useful, +!! but WITHOUT ANY WARRANTY; without even the implied warranty of +!! MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +!! GNU General Public License for more details. +!! +!! You should have received a copy of the GNU General Public License +!! along with Galacticus. If not, see . + + !!{ + Implements a heated spherical mass distribution. + !!} + + use :: Root_Finder, only : rootFinder + + !![ + + + A mass distribution class in which the distribution starts out with a density profile defined by another {\normalfont + \ttfamily massDistribution}. This profile is then modified by heating, under the assumption that the + energy of a shell of mass before and after heating are related by + \begin{equation} + -{ \mathrm{G} M^\prime(r^\prime) \over r^\prime } = -{ \mathrm{G} M(r) \over r } + 2 \epsilon(r), + \end{equation} + where $M(r)$ is the mass enclosed within a radius $r$, and $\epsilon(r)$ represents the specific heating in the shell + initially at radius $r$. Primes indicate values after heating, while unprimed variables indicate quantities prior to + heating. With the assumption of no shell crossing, $M^\prime(r^\prime)=M(r)$ and this equation can be solved for $r$ given + $r^\prime$ and $\epsilon(r)$. + + Not all methods have analytic solutions for this profile. If {\normalfont \ttfamily [nonAnalyticSolver]}$=${\normalfont + \ttfamily fallThrough} then attempts to call these methods in heated profiles will simply return the result from the + unheated profile, otherwise a numerical calculation is performed. + + + !!] + type, extends(massDistributionSphericalDecorator) :: massDistributionSphericalHeated + !!{ + Implementation of a heated spherical mass distribution. + !!} + !![ + + + + + + !!] + private + class (massDistributionHeatingClass), pointer :: massDistributionHeating_ => null() + double precision :: radiusFinalPrevious , radiusInitialPrevious + type (rootFinder ) :: finder + contains + !![ + + + + + !!] + final :: sphericalHeatedDestructor + procedure :: radiusInitial => sphericalHeatedRadiusInitial + procedure :: noShellCrossingIsValid => sphericalHeatedNoShellCrossingIsValid + procedure :: density => sphericalHeatedDensity + procedure :: massEnclosedBySphere => sphericalHeatedMassEnclosedBySphere + procedure :: radiusEnclosingMass => sphericalHeatedRadiusEnclosingMass + procedure :: useUndecorated => sphericalHeatedUseUndecorated + end type massDistributionSphericalHeated + + interface massDistributionSphericalHeated + !!{ + Constructors for the {\normalfont \ttfamily sphericalHeated} mass distribution class. + !!} + module procedure sphericalHeatedConstructorParameters + module procedure sphericalHeatedConstructorInternal + end interface massDistributionSphericalHeated + + ! Global variables used in root solving. + double precision :: radiusFinal_ + class (massDistributionSphericalHeated), pointer :: self_ + !$omp threadprivate(radiusFinal_,self_) + +contains + + function sphericalHeatedConstructorParameters(parameters) result(self) + !!{ + Constructor for the {\normalfont \ttfamily sphericalHeated} mass distribution class which builds the object from a parameter + set. + !!} + use :: Input_Parameters , only : inputParameters + use :: Galactic_Structure_Options, only : enumerationComponentTypeEncode, enumerationMassTypeEncode + implicit none + type (massDistributionSphericalHeated) :: self + type (inputParameters ), intent(inout) :: parameters + class(massDistributionClass ), pointer :: massDistribution_ + class(massDistributionHeatingClass ), pointer :: massDistributionHeating_ + type (varying_string ) :: nonAnalyticSolver , componentType, & + & massType + logical :: tolerateVelocityMaximumFailure + + !![ + + nonAnalyticSolver + var_str('fallThrough') + parameters + Selects how solutions are computed when no analytic solution is available. If set to ``{\normalfont \ttfamily fallThrough}'' then the solution ignoring heating is used, while if set to ``{\normalfont \ttfamily numerical}'' then numerical solvers are used to find solutions. + + + componentType + var_str('unknown') + The component type that this mass distribution represents. + parameters + + + massType + var_str('unknown') + The mass type that this mass distribution represents. + parameters + + + tolerateVelocityMaximumFailure + .false. + If true, tolerate failures to find the radius of the peak in the rotation curve. + parameters + + + + !!] + select type (massDistribution_) + class is (massDistributionSpherical) + self=massDistributionSphericalHeated(enumerationNonAnalyticSolversEncode(char(nonAnalyticSolver),includesPrefix=.false.),tolerateVelocityMaximumFailure,massDistribution_,massDistributionHeating_,enumerationComponentTypeEncode(componentType,includesPrefix=.false.),enumerationMassTypeEncode(massType,includesPrefix=.false.)) + class default + call Error_Report('a spherically-symmetric mass distribution is required'//{introspection:location}) + end select + !![ + + + + !!] + return + end function sphericalHeatedConstructorParameters + + function sphericalHeatedConstructorInternal(nonAnalyticSolver,tolerateVelocityMaximumFailure,massDistribution_,massDistributionHeating_,componentType,massType) result(self) + !!{ + Constructor for ``sphericalHeated'' mass distribution class. + !!} + implicit none + type (massDistributionSphericalHeated ) :: self + class (massDistributionSpherical ), intent(in ), target :: massDistribution_ + class (massDistributionHeatingClass ), intent(in ), target :: massDistributionHeating_ + type (enumerationNonAnalyticSolversType), intent(in ) :: nonAnalyticSolver + logical , intent(in ) :: tolerateVelocityMaximumFailure + type (enumerationComponentTypeType ), intent(in ), optional :: componentType + type (enumerationMassTypeType ), intent(in ), optional :: massType + double precision , parameter :: toleranceAbsolute =0.0d0, toleranceRelative=1.0d-6 + !![ + + !!] + + self% componentType=self%massDistribution_%componentType + self% massType=self%massDistribution_% massType + self%radiusFinalPrevious=-huge(0.0d0) + self%finder =rootFinder( & + & rootFunction =radiusInitialRoot, & + & toleranceAbsolute=toleranceAbsolute, & + & toleranceRelative=toleranceRelative & + & ) + self%dimensionless =.false. + return + end function sphericalHeatedConstructorInternal + + subroutine sphericalHeatedDestructor(self) + !!{ + Destructor for the ``sphericalHeated'' mass distribution class. + !!} + implicit none + type(massDistributionSphericalHeated), intent(inout) :: self + + !![ + + + !!] + return + end subroutine sphericalHeatedDestructor + + logical function sphericalHeatedUseUndecorated(self) result(useUndecorated) + !!{ + Determines whether to use the undecorated solution. + !!} + implicit none + class(massDistributionSphericalHeated), intent(inout) :: self + + useUndecorated=self%nonAnalyticSolver == nonAnalyticSolversFallThrough .or. self%massDistributionHeating_%specificEnergyIsEverywhereZero() + return + end function sphericalHeatedUseUndecorated + + double precision function sphericalHeatedDensity(self,coordinates) result(density) + !!{ + Return the density at the specified {\normalfont \ttfamily coordinates} in a scaled spherical mass distribution. + !!} + use :: Coordinates , only : coordinateSpherical , assignment(=) + use :: Numerical_Constants_Math , only : Pi + use :: Numerical_Constants_Astronomical, only : gravitationalConstantGalacticus + implicit none + class (massDistributionSphericalHeated), intent(inout) :: self + class (coordinate ), intent(in ) :: coordinates + type (coordinateSpherical ) :: coordinatesInitial + double precision :: radius , radiusInitial, & + & densityInitial , massEnclosed , & + & jacobian + + if (self%massDistributionHeating_%specificEnergyIsEverywhereZero()) then + ! No heating, the density is unchanged. + density=+self%massDistribution_%density(coordinates) + return + end if + radius =coordinates %rSpherical ( ) + radiusInitial =self %radiusInitial(radius ) + coordinatesInitial=[radiusInitial,0.0d0,0.0d0] + densityInitial =self%massDistribution_%density (coordinatesInitial) + if (radius == 0.0d0 .and. radiusInitial == 0.0d0) then + ! At zero radius, the density is unchanged. + density =+densityInitial + else if (.not.self%noShellCrossingIsValid(radiusInitial,radius)) then + ! Shell crossing assumption is broken - simply return the density unchanged. + density =+self%massDistribution_%density (coordinates ) + else + massEnclosed=+self%massDistribution_%massEnclosedBySphere(radiusInitial) + if (massEnclosed > 0.0d0) then + jacobian=+1.0d0 & + & /( & + & +( & + & +radius & + & /radiusInitial & + & ) **2 & + & +2.0d0 & + & *radius **2 & + & /gravitationalConstantGalacticus & + & /massEnclosed & + & *( & + & +self%massDistributionHeating_%specificEnergyGradient(radiusInitial,self%massDistribution_) & + & -4.0d0 & + & *Pi & + & *radiusInitial **2 & + & *densityInitial & + & *self%massDistributionHeating_%specificEnergy (radiusInitial,self%massDistribution_) & + & /massEnclosed & + & ) & + & ) + density =+densityInitial & + & *( & + & +radiusInitial & + & /radius & + & ) **2 & + & *jacobian + else + density =+densityInitial + end if + end if + return + end function sphericalHeatedDensity + + double precision function sphericalHeatedMassEnclosedBySphere(self,radius) result(mass) + !!{ + Computes the mass enclosed within a sphere of given {\normalfont \ttfamily radius} for a heated mass distributions. + !!} + implicit none + class (massDistributionSphericalHeated), intent(inout), target :: self + double precision , intent(in ) :: radius + + mass=self%massDistribution_%massEnclosedBySphere(self%radiusInitial(radius)) + return + end function sphericalHeatedMassEnclosedBySphere + + double precision function sphericalHeatedRadiusInitial(self,radiusFinal) result(radiusInitial) + !!{ + Find the initial radius corresponding to the given {\normalfont \ttfamily radiusFinal} in + the heated mass distribution. + !!} + use :: Root_Finder, only : rangeExpandMultiplicative, rangeExpandSignExpectNegative, rangeExpandSignExpectPositive + implicit none + class (massDistributionSphericalHeated), intent(inout), target :: self + double precision , intent(in ) :: radiusFinal + double precision , parameter :: epsilonExpand=1.0d-2 + double precision :: factorExpand + + ! If profile is unheated, the initial radius equals the final radius. + if (self%massDistributionHeating_%specificEnergyIsEverywhereZero()) then + radiusInitial=radiusFinal + return + end if + ! Zero radius always remains at zero. + if (radiusFinal <= 0.0d0) then + radiusInitial=0.0d0 + return + end if + ! Find the initial radius in the unheated profile. + if (radiusFinal /= self%radiusFinalPrevious) then + self_ => self + radiusFinal_ = radiusFinal + if (self%radiusFinalPrevious <= -huge(0.0d0) .or. radiusFinal < self%radiusInitialPrevious .or. radiusFinal > 10.0d0*self%radiusInitialPrevious) then + ! No previous solution is available, or the requested final radius is smaller than the previous initial radius, or the + ! final radius is much larger than the previous initial radius. In this case, our guess for the initial radius is the + ! final radius, and we expand the range downward to find a solution. + call self%finder%rangeExpand( & + & rangeExpandUpward =1.01d0 , & + & rangeExpandDownward =0.50d0 , & + & rangeExpandDownwardSignExpect=rangeExpandSignExpectNegative, & + & rangeExpandUpwardSignExpect =rangeExpandSignExpectPositive, & + & rangeExpandType =rangeExpandMultiplicative & + & ) + self%radiusInitialPrevious=self%finder%find(rootGuess=radiusFinal) + else + ! Previous solution exists, and the requested final radius is larger (but not too much larger) than the previous initial + ! radius. Use the previous initial radius as a guess for the solution, with range expansion in steps determined by the + ! relative values of the current and previous final radii. If the current final radius is close to the previous final + ! radius this should give a guess for the initial radius close to the actual solution. + if (radiusFinal > self%radiusFinalPrevious) then + factorExpand= radiusFinal /self%radiusFinalPrevious + else + factorExpand=self%radiusFinalPrevious/ radiusFinal + end if + factorExpand=max(factorExpand,1.0d0+epsilonExpand) + call self%finder%rangeExpand( & + & rangeExpandUpward =1.0d0*factorExpand , & + & rangeExpandDownward =1.0d0/factorExpand , & + & rangeExpandDownwardSignExpect=rangeExpandSignExpectNegative, & + & rangeExpandUpwardSignExpect =rangeExpandSignExpectPositive, & + & rangeExpandType =rangeExpandMultiplicative & + & ) + self%radiusInitialPrevious=self%finder%find(rootGuess=self%radiusInitialPrevious) + end if + self%radiusFinalPrevious=radiusFinal + end if + radiusInitial=self%radiusInitialPrevious + return + end function sphericalHeatedRadiusInitial + + double precision function radiusInitialRoot(radiusInitial) + !!{ + Root function used in finding initial radii in heated mass distributions. + !!} + use :: Numerical_Constants_Astronomical, only : gravitationalConstantGalacticus + implicit none + double precision, intent(in ) :: radiusInitial + double precision, parameter :: fractionRadiusSmall=1.0d-3 + double precision :: massEnclosed + + if (radiusInitial < fractionRadiusSmall*radiusFinal_) then + ! The initial radius is a small fraction of the final radius. Check if the assumption of no shell crossing is locally + ! broken. If the gradient of the heating term is less than that of the gravitational potential term then it is likely that + ! no root exists. In this case shell crossing is likely to be occurring. Simply return a value of zero, which places the + ! root at the current radius. + if (.not.self_%noShellCrossingIsValid(radiusInitial,radiusFinal_)) then + radiusInitialRoot=0.0d0 + return + end if + end if + massEnclosed =+self_%massDistribution_ %massEnclosedBySphere(radiusInitial ) + radiusInitialRoot=+self_ %massDistributionHeating_%specificEnergy (radiusInitial,self_%massDistribution_) & + & +0.5d0 & + & *gravitationalConstantGalacticus & + & *massEnclosed & + & *( & + & +1.0d0/radiusFinal_ & + & -1.0d0/radiusInitial & + & ) + return + end function radiusInitialRoot + + logical function sphericalHeatedNoShellCrossingIsValid(self,radiusInitial,radiusFinal) result(isValid) + !!{ + Determines if the no shell crossing assumption is valid. + !!} + use :: Numerical_Constants_Math , only : Pi + use :: Numerical_Constants_Astronomical, only : gravitationalConstantGalacticus + use :: Coordinates , only : coordinateSpherical , assignment(=) + implicit none + class (massDistributionSphericalHeated), intent(inout) :: self + double precision , intent(in ) :: radiusInitial , radiusFinal + double precision :: massEnclosed + type (coordinateSpherical ) :: coordinatesInitial + + coordinatesInitial= [radiusInitial,0.0d0,0.0d0] + massEnclosed = + self%massDistribution_ %massEnclosedBySphere ( radiusInitial ) + isValid = + self %massDistributionHeating_%specificEnergyGradient( radiusInitial,self%massDistribution_) & + & > & + & +0.5d0 & + & *gravitationalConstantGalacticus & + & *( & + & +4.0d0 & + & *Pi & + & *radiusInitial**2 & + & *self%massDistribution_%density (coordinatesInitial ) & + & *( & + & -1.0d0/radiusFinal & + & +1.0d0/radiusInitial & + & ) & + & -massEnclosed & + & /radiusInitial**2 & + & ) + return + end function sphericalHeatedNoShellCrossingIsValid + + double precision function sphericalHeatedRadiusEnclosingMass(self,mass,massFractional) result(radius) + !!{ + Computes the radius enclosing a given mass or mass fraction for heated spherical mass distributions. + !!} + use :: Galactic_Structure_Options , only : radiusLarge + use :: Numerical_Constants_Astronomical, only : gravitationalConstantGalacticus + implicit none + class (massDistributionSphericalHeated), intent(inout), target :: self + double precision , intent(in ), optional :: mass , massFractional + double precision :: radiusInitial + double precision :: energySpecific + + radiusInitial =self%massDistribution_ %radiusEnclosingMass(mass , massFractional ) + energySpecific=self%massDistributionHeating_%specificEnergy (radiusInitial,self%massDistribution_) + if (radiusInitial <= 0.0d0) then + radius=+radiusLarge + else + radius=+1.0d0 & + & /( & + & +1.0d0/radiusInitial & + & -2.0d0/gravitationalConstantGalacticus/mass*energySpecific & + & ) + ! If the radius found is negative, which means the initial shell has expanded to infinity, return the largest radius. + if (radius < 0.0d0) radius=radiusLarge + end if + return + end function sphericalHeatedRadiusEnclosingMass diff --git a/source/mass_distributions.spherical.heated.monotonic.F90 b/source/mass_distributions.spherical.heated.monotonic.F90 new file mode 100644 index 0000000000..495fb62fc1 --- /dev/null +++ b/source/mass_distributions.spherical.heated.monotonic.F90 @@ -0,0 +1,368 @@ +!! Copyright 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, +!! 2019, 2020, 2021, 2022, 2023 +!! Andrew Benson +!! +!! This file is part of Galacticus. +!! +!! Galacticus is free software: you can redistribute it and/or modify +!! it under the terms of the GNU General Public License as published by +!! the Free Software Foundation, either version 3 of the License, or +!! (at your option) any later version. +!! +!! Galacticus is distributed in the hope that it will be useful, +!! but WITHOUT ANY WARRANTY; without even the implied warranty of +!! MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +!! GNU General Public License for more details. +!! +!! You should have received a copy of the GNU General Public License +!! along with Galacticus. If not, see . + + !!{ + An implementation of heated dark matter halo profiles based on the energy ordering of shells. + !!} + + use :: Numerical_Interpolation, only : interpolator + + !![ + + + A mass distribution class in which dark matter halos start out with a density profile defined by another {\normalfont + \ttfamily massDistributionClass}. This profile is then modified by heating, under the assumption that the + energy of a shell of mass before and after heating are related by + \begin{equation} + -{ \mathrm{G} M^\prime(r^\prime) \over r^\prime } = -{ \mathrm{G} M(r) \over r } + 2 \epsilon(r), + \end{equation} + where $M(r)$ is the mass enclosed within a radius $r$, and $\epsilon(r)$ represents the specific heating in the shell + initially at radius $r$. Primes indicate values after heating, while unprimed variables indicate quantities prior to + heating. + + The above equation can be re-written as + \begin{equation} + -r^{\prime -1} = -r^{-1} + \xi(r), + \end{equation} + where $\xi(r) = 2 \epsilon(r)/[\mathrm{G} M(r)/r]$ measures the perturbation to the shell. To avoid shell crossing a + monotonicity relation $r_1 < r_2 \implies \xi(r_1) \le \xi(r_2)$ is enforced by starting at large radius and stepping inward, + enforcing the condition in the next innermost shell as necessary. + + Not all methods have analytic solutions for this profile. If {\normalfont \ttfamily [nonAnalyticSolver]}$=${\normalfont + \ttfamily fallThrough} then attempts to call these methods in heated profiles will simply return the result from the + unheated profile, otherwise a numerical calculation is performed. + + + !!] + type, extends(massDistributionSphericalDecorator) :: massDistributionSphericalHeatedMonotonic + !!{ + Implementation of a heated spherical mass distribution. + !!} + private + class (massDistributionHeatingClass), pointer :: massDistributionHeating_ => null() + double precision :: radiusInitialMinimum , radiusInitialMaximum, & + & radiusFinalMinimum , radiusFinalMaximum , & + & radiusVirial + type (interpolator ), allocatable :: massProfile + logical :: isBound + + contains + !![ + + + + !!] + final :: sphericalHeatedMonotonicDestructor + procedure :: computeSolution => sphericalHeatedMonotonicComputeSolution + procedure :: density => sphericalHeatedMonotonicDensity + procedure :: massEnclosedBySphere => sphericalHeatedMonotonicMassEnclosedBySphere + procedure :: useUndecorated => sphericalHeatedMonotonicUseUndecorated + end type massDistributionSphericalHeatedMonotonic + + interface massDistributionSphericalHeatedMonotonic + !!{ + Constructors for the {\normalfont \ttfamily sphericalHeatedMonotonic} mass distribution class. + !!} + module procedure sphericalHeatedMonotonicConstructorParameters + module procedure sphericalHeatedMonotonicConstructorInternal + end interface massDistributionSphericalHeatedMonotonic + +contains + + function sphericalHeatedMonotonicConstructorParameters(parameters) result(self) + !!{ + Constructor for the {\normalfont \ttfamily sphericalHeatedMonotonic} mass distribution class which builds the object from a parameter + set. + !!} + use :: Input_Parameters , only : inputParameters + use :: Galactic_Structure_Options, only : enumerationComponentTypeEncode, enumerationMassTypeEncode + implicit none + type (massDistributionSphericalHeatedMonotonic) :: self + type (inputParameters ), intent(inout) :: parameters + class (massDistributionClass ), pointer :: massDistribution_ + class (massDistributionHeatingClass ), pointer :: massDistributionHeating_ + type (varying_string ) :: nonAnalyticSolver , componentType, & + & massType + double precision :: radiusVirial + + !![ + + radiusVirial + parameters + The virial radius of the halo. + + + nonAnalyticSolver + var_str('fallThrough') + parameters + Selects how solutions are computed when no analytic solution is available. If set to ``{\normalfont \ttfamily fallThrough}'' then the solution ignoring heating is used, while if set to ``{\normalfont \ttfamily numerical}'' then numerical solvers are used to find solutions. + + + componentType + var_str('unknown') + The component type that this mass distribution represents. + parameters + + + massType + var_str('unknown') + The mass type that this mass distribution represents. + parameters + + + + !!] + select type (massDistribution_) + class is (massDistributionSpherical) + self=massDistributionSphericalHeatedMonotonic(radiusVirial,enumerationNonAnalyticSolversEncode(char(nonAnalyticSolver),includesPrefix=.false.),massDistribution_,massDistributionHeating_,enumerationComponentTypeEncode(componentType,includesPrefix=.false.),enumerationMassTypeEncode(massType,includesPrefix=.false.)) + class default + call Error_Report('a spherically-symmetric mass distribution is required'//{introspection:location}) + end select + !![ + + + + !!] + return + end function sphericalHeatedMonotonicConstructorParameters + + function sphericalHeatedMonotonicConstructorInternal(radiusVirial,nonAnalyticSolver,massDistribution_,massDistributionHeating_,componentType,massType) result(self) + !!{ + Constructor for ``sphericalHeatedMonotonic'' mass distribution class. + !!} + implicit none + type (massDistributionSphericalHeatedMonotonic) :: self + double precision , intent(in ) :: radiusVirial + class (massDistributionSpherical ), intent(in ), target :: massDistribution_ + class (massDistributionHeatingClass ), intent(in ), target :: massDistributionHeating_ + type (enumerationNonAnalyticSolversType ), intent(in ) :: nonAnalyticSolver + type (enumerationComponentTypeType ), intent(in ), optional :: componentType + type (enumerationMassTypeType ), intent(in ), optional :: massType + !![ + + !!] + + ! Construct the object. + self%radiusInitialMinimum=+huge(0.0d0) + self%radiusInitialMaximum=-huge(0.0d0) + self%radiusFinalMinimum =+huge(0.0d0) + self%radiusFinalMaximum =-huge(0.0d0) + self%isBound =.true. + self%dimensionless =.false. + return + end function sphericalHeatedMonotonicConstructorInternal + + subroutine sphericalHeatedMonotonicDestructor(self) + !!{ + Destructor for the ``sphericalHeatedMonotonic'' mass distribution class. + !!} + implicit none + type(massDistributionSphericalHeatedMonotonic), intent(inout) :: self + + !![ + + + !!] + return + end subroutine sphericalHeatedMonotonicDestructor + + logical function sphericalHeatedMonotonicUseUndecorated(self) result(useUndecorated) + !!{ + Determines whether to use the undecorated solution. + !!} + implicit none + class(massDistributionSphericalHeatedMonotonic), intent(inout) :: self + + useUndecorated=self%nonAnalyticSolver == nonAnalyticSolversFallThrough .or. self%massDistributionHeating_%specificEnergyIsEverywhereZero() + return + end function sphericalHeatedMonotonicUseUndecorated + + double precision function sphericalHeatedMonotonicMassEnclosedBySphere(self,radius) result(mass) + !!{ + Returns the enclosed mass (in $M_\odot$) in the dark matter profile of {\normalfont \ttfamily node} at the given {\normalfont \ttfamily radius} (given in + units of Mpc). + !!} + implicit none + class (massDistributionSphericalHeatedMonotonic), intent(inout), target :: self + double precision , intent(in ) :: radius + + if (self%massDistributionHeating_%specificEnergyIsEverywhereZero()) then + ! No heating - use the unheated solution. + mass=self%massDistribution_%massEnclosedBySphere(radius) + else if (radius <= 0.0d0) then + ! Non-positive radius, mass must be zero. + mass=0.0d0 + else + ! Compute the solution (as needed). + call self%computeSolution(radius) + ! For bound halos, interpolate to find the enclosed mass. For unbound halos the enclosed mass is zero. + if (self%isBound) then + if (radius < self%radiusFinalMinimum) then + ! Assume constant density below the minimum radius. + mass =+exp(self%massProfile%interpolate(log(self%radiusFinalMinimum))) & + & *( & + & + radius & + & /self%radiusFinalMinimum & + & )**3 + else + mass=+exp(self%massProfile%interpolate(log(radius))) + end if + else + mass=0.0d0 + end if + end if + return + end function sphericalHeatedMonotonicMassEnclosedBySphere + + double precision function sphericalHeatedMonotonicDensity(self,coordinates) result(density) + !!{ + Return the density at the specified {\normalfont \ttfamily coordinates} in a scaled spherical mass distribution. + !!} + use :: Numerical_Constants_Math, only : Pi + implicit none + class (massDistributionSphericalHeatedMonotonic), intent(inout) :: self + class (coordinate ), intent(in ) :: coordinates + double precision :: radius , radius_ + + if (self%massDistributionHeating_%specificEnergyIsEverywhereZero()) then + ! No heating, the density is unchanged. + density=+self%massDistribution_%density(coordinates) + return + end if + radius=coordinates%rSpherical() + call self%computeSolution(radius) + ! For bound halos, interpolate to find the density. For unbound halos the density is zero. + if (self%isBound) then + radius_=max(radius,self%radiusFinalMinimum) + density=+ self%massProfile%derivative (log(radius_)) & + & *exp(self%massProfile%interpolate(log(radius_))) & + & /4.0d0 & + & /Pi & + & /radius**3 + else + density=+0.0d0 + end if + return + end function sphericalHeatedMonotonicDensity + + subroutine sphericalHeatedMonotonicComputeSolution(self,radius) + !!{ + Compute the solution for the heated density profile. + !!} + use :: Numerical_Ranges , only : Make_Range , rangeTypeLogarithmic + use :: Numerical_Constants_Astronomical, only : gravitationalConstantGalacticus + use :: Table_Labels , only : extrapolationTypeFix , extrapolationTypeZero + implicit none + class (massDistributionSphericalHeatedMonotonic), intent(inout) :: self + double precision , intent(in ) :: radius + double precision , parameter :: radiusFractionMinimum=1.0d-6, radiusFractionMaximum=10.0d0 + integer , parameter :: countPerDecadeRadius =100 + double precision , allocatable, dimension(:) :: massEnclosed , massShell , & + & radiusInitial , radiusFinal , & + & energyFinal , perturbation + logical , allocatable, dimension(:) :: isBound + integer :: i , countRadii + + ! Nothing to do if profile is already tabulated. + if (allocated(self%massProfile)) return + ! Choose extent of radii at which to tabulate the initial profile. + self%radiusInitialMinimum=radiusFractionMinimum*self%radiusVirial + self%radiusInitialMaximum=radiusFractionMaximum*self%radiusVirial + ! Build grid of radii. + countRadii=int(log10(self%radiusInitialMaximum/self%radiusInitialMinimum)*dble(countPerDecadeRadius)+1.0d0) + if (allocated(radiusInitial)) then + deallocate(radiusInitial) + deallocate(radiusFinal ) + deallocate(massEnclosed ) + deallocate(massShell ) + deallocate(energyFinal ) + deallocate(perturbation ) + end if + allocate(radiusInitial(countRadii)) + allocate(radiusFinal (countRadii)) + allocate(massEnclosed (countRadii)) + allocate(massShell (countRadii)) + allocate(energyFinal (countRadii)) + allocate(perturbation (countRadii)) + radiusInitial=Make_Range(self%radiusInitialMinimum,self%radiusInitialMaximum,countRadii,rangeTypeLogarithmic) + ! Evaluate masses and energies of shells. + do i=countRadii,1,-1 + massEnclosed(i)=+self%massDistribution_ %massEnclosedBySphere(radiusInitial(i) ) + perturbation(i)=+2.0d0 & + & *self%massDistributionHeating_ %specificEnergy (radiusInitial(i),self%massDistribution_) & + & /gravitationalConstantGalacticus & + & / massEnclosed (i) & + & * radiusInitial(i) + ! Limit the perturbation to avoid shell-crossing. + if (i < countRadii) & + & perturbation(i)=min( & + & +perturbation (i ), & + & +1.0d0 & + & -radiusInitial (i ) & + & /radiusInitial (i+1) & + & *( & + & +massEnclosed(i ) & + & /massEnclosed(i+1) & + & )**(-1.0d0/3.0d0) & + & *( & + & +1.0d0 & + & -perturbation(i+1) & + & ) & + & ) + end do + ! Compute the final energy of the heated profile. + energyFinal=+gravitationalConstantGalacticus & + & *massEnclosed & + & /radiusInitial & + & *( & + & -1.0d0 & + & +perturbation & + & ) + ! Find shell masses. + massShell(1 )=+massEnclosed(1 ) + massShell(2:countRadii)=+massEnclosed(2:countRadii ) & + & -massEnclosed(1:countRadii-1) + ! Evaluation boundedness. + isBound= energyFinal < 0.0d0 & + & .and. & + & massShell > 0.0d0 + ! Find final radii. + where (isBound) + radiusFinal=-gravitationalConstantGalacticus & + & *massEnclosed & + & /energyFinal + elsewhere + radiusFinal=+huge(0.0d0) + end where + ! Build the final profile interpolator. + self%isBound=count(isBound) > 2 + if (self%isBound) then + self%radiusFinalMinimum =minval(radiusFinal ,mask=isBound) + self%radiusFinalMaximum =maxval(radiusFinal ,mask=isBound) + ! Construct the interpolator. + if (allocated(self%massProfile)) deallocate(self%massProfile) + allocate(self%massProfile) + self%massProfile=interpolator( & + & x =log(pack(radiusFinal ,mask=isBound)), & + & y =log(pack(massEnclosed,mask=isBound)), & + & extrapolationType=extrapolationTypeFix & + & ) + end if + return + end subroutine sphericalHeatedMonotonicComputeSolution diff --git a/source/mass_distributions.spherical.heating.impulsive_outflow.F90 b/source/mass_distributions.spherical.heating.impulsive_outflow.F90 new file mode 100644 index 0000000000..709e4c877c --- /dev/null +++ b/source/mass_distributions.spherical.heating.impulsive_outflow.F90 @@ -0,0 +1,247 @@ +!! Copyright 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, +!! 2019, 2020, 2021, 2022, 2023 +!! Andrew Benson +!! +!! This file is part of Galacticus. +!! +!! Galacticus is free software: you can redistribute it and/or modify +!! it under the terms of the GNU General Public License as published by +!! the Free Software Foundation, either version 3 of the License, or +!! (at your option) any later version. +!! +!! Galacticus is distributed in the hope that it will be useful, +!! but WITHOUT ANY WARRANTY; without even the implied warranty of +!! MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +!! GNU General Public License for more details. +!! +!! You should have received a copy of the GNU General Public License +!! along with Galacticus. If not, see . + + !!{ + Implements a mass distribution heating class that computes heating due to two-body relaxation. + !!} + + !![ + + + A mass distribution heating class that computes heating due to impulsive outflows---i.e. outflows occurring on + timescales that are small relative to the dynamical time of the halo. The model assumed is that the energy injection is given by + \begin{equation} + \dot{\epsilon}(r) = \alpha \frac{\mathrm{G} \dot{M}_\mathrm{outflow}(r)}{r} f\left( \frac{t_\phi}{t_\mathrm{dyn}} \right), + \end{equation} + where $\alpha$ is a normalization factor, $t_\phi = M_\mathrm{gas}/\dot{M}_\mathrm{outflow}$ is the timescale for the + outflow, and $t_\mathrm{dyn} = r_{1/2}/v_{1/2}$ is the dynamical time at the half-mass radius. + + The quantity + \begin{equation} + \dot{\epsilon}^\prime = \dot{M}_\mathrm{outflow} f\left( \frac{t_\phi}{t_\mathrm{dyn}} \right), + \end{equation} + if provided as an argument to the class constructor. + + + !!] + type, extends(massDistributionHeatingClass) :: massDistributionHeatingImpulsiveOutflow + !!{ + Implementation of a mass distribution heating class that computes heating due to impulsive outflows. + !!} + private + double precision :: energyImpulsiveOutflowDisk, energyImpulsiveOutflowSpheroid, & + & impulsiveEnergyFactor + contains + procedure :: specificEnergy => impulsiveOutflowSpecificEnergy + procedure :: specificEnergyGradient => impulsiveOutflowSpecificEnergyGradient + procedure :: specificEnergyIsEveryWhereZero => impulsiveOutflowSpecificEnergyIsEverywhereZero + end type massDistributionHeatingImpulsiveOutflow + + interface massDistributionHeatingImpulsiveOutflow + !!{ + Constructors for the {\normalfont \ttfamily impulsiveOutflow} mass distribution class. + !!} + module procedure impulsiveOutflowConstructorParameters + module procedure impulsiveOutflowConstructorInternal + end interface massDistributionHeatingImpulsiveOutflow + +contains + + function impulsiveOutflowConstructorParameters(parameters) result(self) + !!{ + Constructor for the {\normalfont \ttfamily impulsiveOutflow} mass distribution class which builds the object from a parameter + set. + !!} + use :: Input_Parameters, only : inputParameter, inputParameters + implicit none + type (massDistributionHeatingImpulsiveOutflow) :: self + type (inputParameters ), intent(inout) :: parameters + double precision :: energyImpulsiveOutflowDisk, energyImpulsiveOutflowSpheroid, & + & impulsiveEnergyFactor + + !![ + + energyImpulsiveOutflowDisk + parameters + The impulsive energy of outflows from the disk. + + + energyImpulsiveOutflowSpheroid + parameters + The impulsive energy of outflows from the spheroid. + + + impulsiveEnergyFactor + 1.0d0 + The parameter $\alpha$ appearing in the impulsive outflow heating rate. + parameters + + !!] + self=massDistributionHeatingImpulsiveOutflow(energyImpulsiveOutflowDisk,energyImpulsiveOutflowSpheroid,impulsiveEnergyFactor) + !![ + + !!] + return + end function impulsiveOutflowConstructorParameters + + function impulsiveOutflowConstructorInternal(energyImpulsiveOutflowDisk,energyImpulsiveOutflowSpheroid,impulsiveEnergyFactor) result(self) + !!{ + Constructor for ``impulsiveOutflow'' dark matter profile heating class. + !!} + implicit none + type (massDistributionHeatingImpulsiveOutflow) :: self + double precision , intent(in ) :: energyImpulsiveOutflowDisk, energyImpulsiveOutflowSpheroid, & + & impulsiveEnergyFactor + !![ + + !!] + + return + end function impulsiveOutflowConstructorInternal + + double precision function impulsiveOutflowSpecificEnergy(self,radius,massDistribution_) result(energySpecific) + !!{ + Returns the specific energy of heating in the given {\normalfont \ttfamily node}. + !!} + use :: Galactic_Structure_Options , only : componentTypeDisk , componentTypeSpheroid + use :: Numerical_Constants_Astronomical, only : gravitationalConstantGalacticus + implicit none + class (massDistributionHeatingImpulsiveOutflow), intent(inout) :: self + double precision , intent(in ) :: radius + class (massDistributionClass ), intent(inout) :: massDistribution_ + class (massDistributionClass ), pointer :: massDistributionDisk, massDistributionSpheroid + double precision :: massTotalDisk , massTotalSpheroid , & + & fractionMassDisk , fractionMassSpheroid + + massDistributionDisk => massDistribution_%subset(componentType=componentTypeDisk ) + massDistributionSpheroid => massDistribution_%subset(componentType=componentTypeSpheroid) + fractionMassDisk = 0.0d0 + fractionMassSpheroid = 0.0d0 + if (associated(massDistributionDisk )) then + massTotalDisk = massDistributionDisk %massTotal() + if (massTotalDisk > 0.0d0) then + fractionMassDisk =+massDistributionDisk %massEnclosedBySphere(radius) & + & / massTotalDisk + end if + end if + if (associated(massDistributionSpheroid)) then + massTotalSpheroid = massDistributionSpheroid%massTotal() + if (massTotalSpheroid > 0.0d0) then + fractionMassSpheroid=+massDistributionSpheroid%massEnclosedBySphere(radius) & + & / massTotalSpheroid + end if + end if + energySpecific=+ self%impulsiveEnergyFactor & + & *gravitationalConstantGalacticus & + & *( & + & +self%energyImpulsiveOutflowDisk & + & *fractionMassDisk & + & +self%energyImpulsiveOutflowSpheroid & + & *fractionMassSpheroid & + & ) & + & /radius + !![ + + + !!] + return + end function impulsiveOutflowSpecificEnergy + + double precision function impulsiveOutflowSpecificEnergyGradient(self,radius,massDistribution_) result(energySpecificGradient) + !!{ + Returns the gradient of the specific energy of heating. + !!} + use :: Coordinates , only : coordinateSpherical , assignment(=) + use :: Galactic_Structure_Options , only : componentTypeDisk , componentTypeSpheroid + use :: Numerical_Constants_Astronomical, only : gravitationalConstantGalacticus + use :: Numerical_Constants_Math , only : Pi + implicit none + class (massDistributionHeatingImpulsiveOutflow), intent(inout) :: self + double precision , intent(in ) :: radius + class (massDistributionClass ), intent(inout) :: massDistribution_ + class (massDistributionClass ), pointer :: massDistributionDisk, massDistributionSpheroid + double precision :: massTotalDisk , massTotalSpheroid , & + & fractionMassDisk , fractionMassSpheroid , & + & fractionDensityDisk , fractionDensitySpheroid + type (coordinatespherical ) :: coordinates + + massDistributionDisk => massDistribution_%subset(componentType=componentTypeDisk ) + massDistributionSpheroid => massDistribution_%subset(componentType=componentTypeSpheroid) + coordinates = [radius,0.0d0,0.0d0] + fractionMassDisk = 0.0d0 + fractionMassSpheroid = 0.0d0 + fractionDensityDisk = 0.0d0 + fractionDensitySpheroid = 0.0d0 + if (associated(massDistributionDisk )) then + massTotalDisk = massDistributionDisk %massTotal() + if (massTotalDisk > 0.0d0) then + fractionMassDisk =+massDistributionDisk %massEnclosedBySphere(radius ) & + & / massTotalDisk + fractionDensityDisk =+massDistributionDisk %density (coordinates) & + & / massTotalDisk + end if + end if + if (associated(massDistributionSpheroid)) then + massTotalSpheroid = massDistributionSpheroid%massTotal() + if (massTotalSpheroid > 0.0d0) then + fractionMassSpheroid =+massDistributionSpheroid%massEnclosedBySphere(radius ) & + & / massTotalSpheroid + fractionDensitySpheroid=+massDistributionSpheroid%density (coordinates) & + & / massTotalSpheroid + end if + end if + energySpecificGradient=+self%impulsiveEnergyFactor & + & *gravitationalConstantGalacticus & + & *( & + & +( & + & +self%energyImpulsiveOutflowDisk & + & *fractionDensityDisk & + & +self%energyImpulsiveOutflowSpheroid & + & *fractionDensitySpheroid & + & ) & + & *4.0d0 & + & *Pi & + & *radius & + & -( & + & +self%energyImpulsiveOutflowDisk & + & *fractionMassDisk & + & +self%energyImpulsiveOutflowSpheroid & + & *fractionMassSpheroid & + & ) & + & /radius**2 & + & ) + !![ + + + !!] + return + end function impulsiveOutflowSpecificEnergyGradient + + logical function impulsiveOutflowSpecificEnergyIsEverywhereZero(self) result(energySpecificIsEverywhereZero) + !!{ + Returns true if the specific energy is everywhere zero. + !!} + implicit none + class(massDistributionHeatingImpulsiveOutflow), intent(inout) :: self + + energySpecificIsEverywhereZero= self%energyImpulsiveOutflowDisk <= 0.0d0 & + & .and. & + & self%energyImpulsiveOutflowSpheroid <= 0.0d0 + return + end function impulsiveOutflowSpecificEnergyIsEverywhereZero diff --git a/source/mass_distributions.spherical.heating.monotonic.F90 b/source/mass_distributions.spherical.heating.monotonic.F90 new file mode 100644 index 0000000000..06d50adcad --- /dev/null +++ b/source/mass_distributions.spherical.heating.monotonic.F90 @@ -0,0 +1,320 @@ +!! Copyright 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, +!! 2019, 2020, 2021, 2022, 2023 +!! Andrew Benson +!! +!! This file is part of Galacticus. +!! +!! Galacticus is free software: you can redistribute it and/or modify +!! it under the terms of the GNU General Public License as published by +!! the Free Software Foundation, either version 3 of the License, or +!! (at your option) any later version. +!! +!! Galacticus is distributed in the hope that it will be useful, +!! but WITHOUT ANY WARRANTY; without even the implied warranty of +!! MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +!! GNU General Public License for more details. +!! +!! You should have received a copy of the GNU General Public License +!! along with Galacticus. If not, see . + + !+ Contributions to this file made by: Xiaolong Du. + + !!{ + Implements a mass distribution heating class which takes another heating source and enforces monotonic heating energy perturbation. + !!} + + !![ + + + A mass distribution heating class which takes another heating source and enforces monotonic heating energy perturbation. + + + !!] + type, extends(massDistributionHeatingClass) :: massDistributionHeatingMonotonic + !!{ + Implementation of a mass distribution heating class which takes another heating source and enforces monotonic heating energy perturbation. + !!} + private + class (massDistributionHeatingClass), pointer :: massDistributionHeating_ => null() + type (rootFinder ) :: finder + double precision :: radiusShellCrossing , energyPerturbationShellCrossing + contains + !![ + + + + + !!] + procedure :: specificEnergy => monotonicSpecificEnergy + procedure :: specificEnergyGradient => monotonicSpecificEnergyGradient + procedure :: specificEnergyIsEveryWhereZero => monotonicSpecificEnergyIsEverywhereZero + procedure :: noShellCrossingIsValid => monotonicNoShellCrossingIsValid + procedure :: computeRadiusShellCrossing => monotonicComputeRadiusShellCrossing + end type massDistributionHeatingMonotonic + + interface massDistributionHeatingMonotonic + !!{ + Constructors for the {\normalfont \ttfamily monotonic} mass distribution class. + !!} + module procedure monotonicConstructorParameters + module procedure monotonicConstructorInternal + end interface massDistributionHeatingMonotonic + + ! Global variables used in root solving. + type (massDistributionHeatingMonotonic), pointer :: self_ + class(massDistributionClass ), pointer :: massDistribution__ + !$omp threadprivate(self_,massDistribution__) + +contains + + function monotonicConstructorParameters(parameters) result(self) + !!{ + Constructor for the {\normalfont \ttfamily monotonic} mass distribution class which builds the object from a parameter + set. + !!} + use :: Input_Parameters, only : inputParameter, inputParameters + implicit none + type (massDistributionHeatingMonotonic) :: self + type (inputParameters ), intent(inout) :: parameters + class(massDistributionHeatingClass ), pointer :: massDistributionHeating_ + + !![ + + !!] + self=massDistributionHeatingMonotonic(massDistributionHeating_) + !![ + + + !!] + return + end function monotonicConstructorParameters + + function monotonicConstructorInternal(massDistributionHeating_) result(self) + !!{ + Constructor for ``monotonic'' mass distribution heating class. + !!} + implicit none + type (massDistributionHeatingMonotonic) :: self + class (massDistributionHeatingClass ), target, intent(in ) :: massDistributionHeating_ + double precision , parameter :: toleranceAbsolute =0.0d0, toleranceRelative=1.0d-6 + !![ + + !!] + + self%radiusShellCrossing =-1.0d0 + self%energyPerturbationShellCrossing=-1.0d0 + self%finder =rootFinder( & + & rootFunction =monotonicRadiusShellCrossingRoot, & + & toleranceAbsolute=toleranceAbsolute , & + & toleranceRelative=toleranceRelative & + & ) + return + end function monotonicConstructorInternal + + subroutine monotonicDestructor(self) + !!{ + Destructor for the ``monotonic'' mass distribution heating class. + !!} + implicit none + type(massDistributionHeatingMonotonic), intent(inout) :: self + + !![ + + !!] + return + end subroutine monotonicDestructor + + double precision function monotonicSpecificEnergy(self,radius,massDistribution_) result(energySpecific) + !!{ + Compute the specific energy in a monotonicly-heated mass distribution. + !!} + use :: Numerical_Constants_Astronomical, only : gravitationalConstantGalacticus + implicit none + class (massDistributionHeatingMonotonic), intent(inout) :: self + double precision , intent(in ) :: radius + class (massDistributionClass ), intent(inout) :: massDistribution_ + + if (self%noShellCrossingIsValid(radius,massDistribution_)) then + energySpecific=self%massDistributionHeating_%specificEnergy( & + & radius , & + & massDistribution_ & + & ) + else + if (self%energyPerturbationShellCrossing < 0.0d0) & + call self%computeRadiusShellCrossing( & + & radius , & + & massDistribution_ & + & ) + energySpecific=+self%energyPerturbationShellCrossing & + & *0.5d0 & + & *gravitationalConstantGalacticus & + & *massDistribution_ %massEnclosedBySphere(radius) & + & /radius + end if + return + end function monotonicSpecificEnergy + + double precision function monotonicSpecificEnergyGradient(self,radius,massDistribution_) result(energySpecificGradient) + !!{ + Returns the gradient of the specific energy of heating. + !!} + use :: Coordinates , only : coordinateSpherical , assignment(=) + use :: Numerical_Constants_Astronomical, only : gravitationalConstantGalacticus + use :: Numerical_Constants_Math , only : Pi + implicit none + class (massDistributionHeatingMonotonic), intent(inout) :: self + double precision , intent(in ) :: radius + class (massDistributionClass ), intent(inout) :: massDistribution_ + type (coordinateSpherical ) :: coordinates + + if (self%noShellCrossingIsValid(radius,massDistribution_)) then + energySpecificGradient=self%massDistributionHeating_%specificEnergyGradient( & + & radius , & + & massDistribution_ & + & ) + else + if (self%energyPerturbationShellCrossing < 0.0d0) & + & call self%computeRadiusShellCrossing( & + & radius , & + & massDistribution_ & + & ) + coordinates =[radius,0.0d0,0.0d0] + energySpecificGradient=+self%energyPerturbationShellCrossing & + & *0.5d0 & + & *gravitationalConstantGalacticus & + & *( & + & +4.0d0 & + & *Pi & + & * radius & + & *massDistribution_%density (coordinates) & + & -massDistribution_%massEnclosedBySphere(radius ) & + & /radius**2 & + & ) + end if + return + end function monotonicSpecificEnergyGradient + + logical function monotonicSpecificEnergyIsEverywhereZero(self) result(energySpecificIsEverywhereZero) + !!{ + Returns true if the specific energy is everywhere zero. + !!} + implicit none + class(massDistributionHeatingMonotonic), intent(inout) :: self + + energySpecificIsEverywhereZero=self%massDistributionHeating_%specificEnergyIsEverywhereZero() + return + end function monotonicSpecificEnergyIsEverywhereZero + + logical function monotonicNoShellCrossingIsValid(self,radius,massDistribution_) + !!{ + Determines if the no shell crossing assumption is valid. + !!} + use :: Coordinates , only : coordinateSpherical, assignment(=) + use :: Numerical_Constants_Math, only : Pi + implicit none + class (massDistributionHeatingMonotonic), intent(inout) :: self + class (massDistributionClass ), intent(inout) :: massDistribution_ + double precision , intent(in ) :: radius + double precision :: massEnclosed + type (coordinateSpherical ) :: coordinates + + massEnclosed = massDistribution_%massEnclosedBySphere(radius) + if (massEnclosed > 0.0d0) then + coordinates =[radius,0.0d0,0.0d0] + monotonicNoShellCrossingIsValid=+self%massDistributionHeating_%specificEnergyGradient( & + & radius , & + & massDistribution_ & + & ) & + & * radius & + & +self%massDistributionHeating_%specificEnergy ( & + & radius , & + & massDistribution_ & + & ) & + & *( & + & +1.0d0 & + & -4.0d0 & + & *Pi & + & * radius**3 & + & *massDistribution_ %density ( & + & coordinates & + & ) & + & /massEnclosed & + & ) & + & >=0.0d0 + else + monotonicNoShellCrossingIsValid=.true. + end if + return + end function monotonicNoShellCrossingIsValid + + subroutine monotonicComputeRadiusShellCrossing(self,radius,massDistribution_) + !!{ + Determines if the no shell crossing assumption is valid. + !!} + use :: Root_Finder , only : rangeExpandMultiplicative , rangeExpandSignExpectNegative, rangeExpandSignExpectPositive + use :: Numerical_Constants_Astronomical, only : gravitationalConstantGalacticus + implicit none + class (massDistributionHeatingMonotonic), intent(inout), target :: self + class (massDistributionClass ), intent(inout), target :: massDistribution_ + double precision , intent(in ) :: radius + + if (self%energyPerturbationShellCrossing < 0.0d0) then + self_ => self + massDistribution__ => massDistribution_ + call self%finder%rangeExpand( & + & rangeExpandUpward =2.0d0 , & + & rangeExpandDownward =1.0d0 , & + & rangeExpandDownwardSignExpect=rangeExpandSignExpectNegative, & + & rangeExpandUpwardSignExpect =rangeExpandSignExpectPositive, & + & rangeExpandType =rangeExpandMultiplicative & + & ) + self%radiusShellCrossing =+self%finder%find(rootGuess=radius) + self%energyPerturbationShellCrossing =+self%massDistributionHeating_%specificEnergy (self%radiusShellCrossing,massDistribution_) & + & /( & + & +0.5d0 & + & *gravitationalConstantGalacticus & + & *massDistribution_ %massEnclosedBySphere(self%radiusShellCrossing ) & + & / self%radiusShellCrossing & + & ) + end if + return + end subroutine monotonicComputeRadiusShellCrossing + + double precision function monotonicRadiusShellCrossingRoot(radius) + !!{ + Root function used in finding the radius where shell crossing happens. + !!} + use :: Coordinates , only : coordinateSpherical, assignment(=) + use :: Numerical_Constants_Math, only : Pi + implicit none + double precision , intent(in ) :: radius + double precision :: massEnclosed + type (coordinateSpherical) :: coordinates + + massEnclosed = massDistribution__%massEnclosedBySphere(radius) + if (massEnclosed > 0.0d0) then + coordinates =[radius,0.0d0,0.0d0] + monotonicRadiusShellCrossingRoot=+self_%massDistributionHeating_%specificEnergyGradient( & + & radius , & + & massDistribution__ & + & ) & + & * radius & + & +self_%massDistributionHeating_%specificEnergy ( & + & radius , & + & massDistribution__ & + & ) & + & *( & + & +1.0d0 & + & -4.0d0 & + & *Pi & + & * radius**3 & + & *massDistribution__ %density ( & + & coordinates & + & ) & + & /massEnclosed & + & ) + else + monotonicRadiusShellCrossingRoot=0.0d0 + end if + return + end function monotonicRadiusShellCrossingRoot diff --git a/source/mass_distributions.spherical.heating.null.F90 b/source/mass_distributions.spherical.heating.null.F90 new file mode 100644 index 0000000000..505ce2dbda --- /dev/null +++ b/source/mass_distributions.spherical.heating.null.F90 @@ -0,0 +1,103 @@ +!! Copyright 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, +!! 2019, 2020, 2021, 2022, 2023 +!! Andrew Benson +!! +!! This file is part of Galacticus. +!! +!! Galacticus is free software: you can redistribute it and/or modify +!! it under the terms of the GNU General Public License as published by +!! the Free Software Foundation, either version 3 of the License, or +!! (at your option) any later version. +!! +!! Galacticus is distributed in the hope that it will be useful, +!! but WITHOUT ANY WARRANTY; without even the implied warranty of +!! MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +!! GNU General Public License for more details. +!! +!! You should have received a copy of the GNU General Public License +!! along with Galacticus. If not, see . + + !!{ + Implements a null mass distribution heating class. + !!} + + !![ + + + A null mass distribution heating class. The heating energy is always zero. + + + !!] + type, extends(massDistributionHeatingClass) :: massDistributionHeatingNull + !!{ + Implementation of a null mass distribution heating class. + !!} + private + contains + procedure :: specificEnergy => nullSpecificEnergy + procedure :: specificEnergyGradient => nullSpecificEnergyGradient + procedure :: specificEnergyIsEveryWhereZero => nullSpecificEnergyIsEverywhereZero + end type massDistributionHeatingNull + + interface massDistributionHeatingNull + !!{ + Constructors for the {\normalfont \ttfamily null} mass distribution class. + !!} + module procedure nullConstructorParameters + end interface massDistributionHeatingNull + +contains + + function nullConstructorParameters(parameters) result(self) + !!{ + Constructor for the {\normalfont \ttfamily null} mass distribution class which builds the object from a parameter + set. + !!} + use :: Input_Parameters, only : inputParameter, inputParameters + implicit none + type(massDistributionHeatingNull) :: self + type(inputParameters ), intent(inout) :: parameters + + self=massDistributionHeatingNull() + !![ + + !!] + return + end function nullConstructorParameters + + double precision function nullSpecificEnergy(self,radius,massDistribution_) result(energySpecific) + !!{ + Compute the specific energy in a zero-heating mass distribution. + !!} + implicit none + class (massDistributionHeatingNull), intent(inout) :: self + double precision , intent(in ) :: radius + class (massDistributionClass ), intent(inout) :: massDistribution_ + + energySpecific=+0.0d0 + return + end function nullSpecificEnergy + + double precision function nullSpecificEnergyGradient(self,radius,massDistribution_) result(energySpecificGradient) + !!{ + Returns the gradient of the specific energy of heating. + !!} + implicit none + class (massDistributionHeatingNull), intent(inout) :: self + double precision , intent(in ) :: radius + class (massDistributionClass ), intent(inout) :: massDistribution_ + + energySpecificGradient=+0.0d0 + return + end function nullSpecificEnergyGradient + + logical function nullSpecificEnergyIsEverywhereZero(self) result(energySpecificIsEverywhereZero) + !!{ + Returns true if the specific energy is everywhere zero. + !!} + implicit none + class(massDistributionHeatingNull), intent(inout) :: self + + energySpecificIsEverywhereZero=.true. + return + end function nullSpecificEnergyIsEverywhereZero diff --git a/source/mass_distributions.spherical.heating.summation.F90 b/source/mass_distributions.spherical.heating.summation.F90 new file mode 100644 index 0000000000..9d4a2870cc --- /dev/null +++ b/source/mass_distributions.spherical.heating.summation.F90 @@ -0,0 +1,188 @@ +!! Copyright 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, +!! 2019, 2020, 2021, 2022, 2023 +!! Andrew Benson +!! +!! This file is part of Galacticus. +!! +!! Galacticus is free software: you can redistribute it and/or modify +!! it under the terms of the GNU General Public License as published by +!! the Free Software Foundation, either version 3 of the License, or +!! (at your option) any later version. +!! +!! Galacticus is distributed in the hope that it will be useful, +!! but WITHOUT ANY WARRANTY; without even the implied warranty of +!! MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +!! GNU General Public License for more details. +!! +!! You should have received a copy of the GNU General Public License +!! along with Galacticus. If not, see . + + !!{ + Implements a mass distribution heating class that sums heating over other classes. + !!} + + !![ + + A mass distribution heating class that sums heating over other classes. + + + !!] + + type, public :: massDistributionHeatingList + class(massDistributionHeatingClass), pointer :: massDistributionHeating_ => null() + type (massDistributionHeatingList ), pointer :: next => null() + end type massDistributionHeatingList + + type, extends(massDistributionHeatingClass) :: massDistributionHeatingSummation + !!{ + Implementation of a mass distribution heating class that sums heating over other classes. + !!} + private + type(massDistributionHeatingList), pointer :: massDistributionHeatings => null() + contains + procedure :: specificEnergy => summationSpecificEnergy + procedure :: specificEnergyGradient => summationSpecificEnergyGradient + procedure :: specificEnergyIsEveryWhereZero => summationSpecificEnergyIsEverywhereZero + end type massDistributionHeatingSummation + + interface massDistributionHeatingSummation + !!{ + Constructors for the {\normalfont \ttfamily summation} mass distribution class. + !!} + module procedure summationConstructorParameters + module procedure summationConstructorInternal + end interface massDistributionHeatingSummation + +contains + + function summationConstructorParameters(parameters) result(self) + !!{ + Constructor for the {\normalfont \ttfamily summation} mass distribution class which builds the object from a parameter + set. + !!} + use :: Input_Parameters, only : inputParameter, inputParameters + implicit none + type (massDistributionHeatingSummation) :: self + type (inputParameters ), intent(inout) :: parameters + type (massDistributionHeatingList ), pointer :: massDistributionHeating_ + integer :: i + + massDistributionHeating_ => null() + do i=1,parameters%copiesCount('massDistributionHeating',zeroIfNotPresent=.true.) + if (associated(massDistributionHeating_)) then + allocate(massDistributionHeating_%next) + massDistributionHeating_ => massDistributionHeating_%next + else + allocate(self%massDistributionHeatings) + massDistributionHeating_ => self %massDistributionHeatings + end if + !![ + + !!] + end do + !![ + + !!] + return + end function summationConstructorParameters + + function summationConstructorInternal(massDistributionHeatings) result(self) + !!{ + Constructor for ``summation'' dark matter profile heating class. + !!} + implicit none + type(massDistributionHeatingSummation) :: self + type(massDistributionHeatingList ), pointer, intent(in ) :: massDistributionHeatings + type(massDistributionHeatingList ), pointer :: massDistributionHeating_ + + self %massDistributionHeatings => massDistributionHeatings + massDistributionHeating_ => massDistributionHeatings + do while (associated(massDistributionHeating_)) + !![ + + !!] + massDistributionHeating_ => massDistributionHeating_%next + end do + return + end function summationConstructorInternal + + subroutine summationDestructor(self) + !!{ + Destructor for composite mass distributions. + !!} + implicit none + type(massDistributionHeatingSummation), intent(inout) :: self + type(massDistributionHeatingList ), pointer :: massDistributionHeating_, massDistributionHeatingNext + + if (associated(self%massDistributionHeatings)) then + massDistributionHeating_ => self%massDistributionHeatings + do while (associated(massDistributionHeating_)) + massDistributionHeatingNext => massDistributionHeating_%next + !![ + + !!] + deallocate(massDistributionHeating_) + massDistributionHeating_ => massDistributionHeatingNext + end do + end if + return + end subroutine summationDestructor + + double precision function summationSpecificEnergy(self,radius,massDistribution_) result(energySpecific) + !!{ + Returns the specific energy of heating in the given {\normalfont \ttfamily node}. + !!} + use :: Numerical_Constants_Astronomical, only : gravitationalConstantGalacticus + implicit none + class (massDistributionHeatingSummation), intent(inout) :: self + double precision , intent(in ) :: radius + class (massDistributionClass ), intent(inout) :: massDistribution_ + type (massDistributionHeatingList ), pointer :: massDistributionHeating_ + + energySpecific = 0.0d0 + massDistributionHeating_ => self%massDistributionHeatings + do while (associated(massDistributionHeating_)) + energySpecific = + energySpecific & + & +massDistributionHeating_%massDistributionHeating_%specificEnergy(radius,massDistribution_) + massDistributionHeating_ => massDistributionHeating_%next + end do + return + end function summationSpecificEnergy + + double precision function summationSpecificEnergyGradient(self,radius,massDistribution_) result(energySpecificGradient) + !!{ + Returns the gradient of the specific energy of heating. + !!} + implicit none + class (massDistributionHeatingSummation), intent(inout) :: self + double precision , intent(in ) :: radius + class (massDistributionClass ), intent(inout) :: massDistribution_ + type (massDistributionHeatingList ), pointer :: massDistributionHeating_ + + energySpecificGradient = 0.0d0 + massDistributionHeating_ => self%massDistributionHeatings + do while (associated(massDistributionHeating_)) + energySpecificGradient = + energySpecificGradient & + & +massDistributionHeating_%massDistributionHeating_%specificEnergyGradient(radius,massDistribution_) + massDistributionHeating_ => massDistributionHeating_%next + end do + return + end function summationSpecificEnergyGradient + + logical function summationSpecificEnergyIsEverywhereZero(self) result(energySpecificIsEverywhereZero) + !!{ + Returns true if the specific energy is everywhere zero. + !!} + implicit none + class(massDistributionHeatingSummation), intent(inout) :: self + type (massDistributionHeatingList ), pointer :: massDistributionHeating_ + + energySpecificIsEverywhereZero = .true. + massDistributionHeating_ => self%massDistributionHeatings + do while (associated(massDistributionHeating_)) + energySpecificIsEverywhereZero=massDistributionHeating_%massDistributionHeating_%specificEnergyIsEverywhereZero() + if (.not.energySpecificIsEverywhereZero) return + massDistributionHeating_ => massDistributionHeating_%next + end do + return + end function summationSpecificEnergyIsEverywhereZero diff --git a/source/mass_distributions.spherical.heating.tidal.F90 b/source/mass_distributions.spherical.heating.tidal.F90 new file mode 100644 index 0000000000..bbc1911fbe --- /dev/null +++ b/source/mass_distributions.spherical.heating.tidal.F90 @@ -0,0 +1,250 @@ +!! Copyright 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, +!! 2019, 2020, 2021, 2022, 2023 +!! Andrew Benson +!! +!! This file is part of Galacticus. +!! +!! Galacticus is free software: you can redistribute it and/or modify +!! it under the terms of the GNU General Public License as published by +!! the Free Software Foundation, either version 3 of the License, or +!! (at your option) any later version. +!! +!! Galacticus is distributed in the hope that it will be useful, +!! but WITHOUT ANY WARRANTY; without even the implied warranty of +!! MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +!! GNU General Public License for more details. +!! +!! You should have received a copy of the GNU General Public License +!! along with Galacticus. If not, see . + + !!{ + Implements a tidal mass distribution heating class. + !!} + + !![ + + + A mass distribution heating model which accounts for heating due to tidal shocking. The model follows the general + approach of \cite{gnedin_tidal_1999}. The change in the specific energy of particles at radius $r$ in a halo is given by + $\Delta \epsilon = \Delta \epsilon_1 + \Delta \epsilon_2$, where $\Delta \epsilon_1$, and $\Delta \epsilon_2$ are the first + and second order perturbations respectively. The first order term is given by $\Delta \epsilon_1 = Q r^2$ where $Q$ is the + tidal tensor integrated along the orbital path (see, for example, \citealt{taylor_dynamics_2001}), while the second order + term is given by $\Delta \epsilon_2 = (2/3) f \sigma_\mathrm{rms} (1+\chi_\mathrm{r,v}) \sqrt{\Delta \epsilon_1}$ + \citep[][eqn.~20, see also \protect\citealt{gnedin_self-consistent_1999}; eqn.~18a,b]{gnedin_tidal_1999}. For the particle + velocity dispersion, $v_\mathrm{rms}$, we use $\sqrt{3} \sigma_\mathrm{r}(r)$, the radial velocity dispersion in the dark + matter profile scaled to the total velocity dispersion assuming an isotropic velocity distribution. The position-velocity + correlation function, $\chi_\mathrm{r,v}$, is taken to be a constant given by the parameter {\normalfont \ttfamily + [correlationVelocityRadius]}. The coefficient, $f=${\normalfont \ttfamily [coefficientSecondOrder]} is introduced to allow + some freedom to adjust the contribution of the second order term. It is degenerate with the value of $\chi_\mathrm{r,v}$ + but is introduced to allow for possible future promotion of $\chi_\mathrm{r,v}$ from a constant to a function of the dark + matter profile potential \citep[see, for example,][appendix~B]{gnedin_self-consistent_1999}. + + + !!] + type, extends(massDistributionHeatingClass) :: massDistributionHeatingTidal + !!{ + Implementation of a tidal mass distribution heating class. + !!} + private + double precision :: correlationVelocityRadius, coefficientSecondOrder0, & + & coefficientSecondOrder1 , coefficientSecondOrder2, & + & heatSpecificNormalized + contains + !![ + + + + !!] + procedure :: specificEnergy => tidalSpecificEnergy + procedure :: specificEnergyGradient => tidalSpecificEnergyGradient + procedure :: specificEnergyIsEveryWhereZero => tidalSpecificEnergyIsEverywhereZero + procedure :: specificEnergyTerms => tidalSpecificEnergyTerms + end type massDistributionHeatingTidal + + interface massDistributionHeatingTidal + !!{ + Constructors for the {\normalfont \ttfamily tidal} mass distribution class. + !!} + module procedure tidalConstructorParameters + module procedure tidalConstructorInternal + end interface massDistributionHeatingTidal + +contains + + function tidalConstructorParameters(parameters) result(self) + !!{ + Constructor for the {\normalfont \ttfamily tidal} mass distribution class which builds the object from a parameter + set. + !!} + use :: Input_Parameters, only : inputParameter, inputParameters + implicit none + type (massDistributionHeatingTidal) :: self + type (inputParameters ), intent(inout) :: parameters + double precision :: heatSpecificNormalized , correlationVelocityRadius, & + & coefficientSecondOrder0, coefficientSecondOrder1 , & + & coefficientSecondOrder2 + + !![ + + heatSpecificNormalized + The normalized specific tidal heating, $Q = \epsilon / r^2$. + parameters + + + coefficientSecondOrder0 + 0.0d0 + parameters + The coefficient, $a_0$, appearing in the second-order heating term, $f_2 = a_0 + a_1 \mathrm{d}\log \rho/\mathrm{d} \log r + a_2 (\mathrm{d}\log \rho/\mathrm{d} \log r)^2$. + + + coefficientSecondOrder1 + 0.0d0 + parameters + The coefficient, $a_1$, appearing in the second-order heating term, $f_2 = a_0 + a_1 \mathrm{d}\log \rho/\mathrm{d} \log r + a_2 (\mathrm{d}\log \rho/\mathrm{d} \log r)^2$. + + + coefficientSecondOrder2 + 0.0d0 + parameters + The coefficient, $a_2$, appearing in the second-order heating term, $f_2 = a_0 + a_1 \mathrm{d}\log \rho/\mathrm{d} \log r + a_2 (\mathrm{d}\log \rho/\mathrm{d} \log r)^2$. + + + correlationVelocityRadius + -1.0d0 + parameters + The velocity-position correlation function, $\chi_\mathrm{r,v}$, as defined by \cite[][eqn.~B1]{gnedin_self-consistent_1999} which controls the strength of the second order heating term. + + !!] + self=massDistributionHeatingTidal(heatSpecificNormalized,coefficientSecondOrder0,coefficientSecondOrder1,coefficientSecondOrder2,correlationVelocityRadius) + !![ + + !!] + return + end function tidalConstructorParameters + + function tidalConstructorInternal(heatSpecificNormalized,coefficientSecondOrder0,coefficientSecondOrder1,coefficientSecondOrder2,correlationVelocityRadius) result(self) + !!{ + Constructor for ``tidal'' convergence class. + !!} + implicit none + type (massDistributionHeatingTidal) :: self + double precision , intent(in ) :: heatSpecificNormalized , coefficientSecondOrder0, & + & coefficientSecondOrder1 , coefficientSecondOrder2, & + & correlationVelocityRadius + !![ + + !!] + + return + end function tidalConstructorInternal + + double precision function tidalSpecificEnergy(self,radius,massDistribution_) result(energySpecific) + !!{ + Compute the specific energy in a tidally-heated mass distribution. + !!} + implicit none + class (massDistributionHeatingTidal), intent(inout) :: self + double precision , intent(in ) :: radius + class (massDistributionClass ), intent(inout) :: massDistribution_ + double precision :: energyPerturbationFirstOrder, energyPerturbationSecondOrder + + call self%specificEnergyTerms(radius,massDistribution_,energyPerturbationFirstOrder,energyPerturbationSecondOrder) + energySpecific=+energyPerturbationFirstOrder & + & +energyPerturbationSecondOrder + return + end function tidalSpecificEnergy + + double precision function tidalSpecificEnergyGradient(self,radius,massDistribution_) result(energySpecificGradient) + !!{ + Returns the gradient of the specific energy of heating. + !!} + use :: Coordinates , only : coordinateSpherical , assignment(=) + use :: Numerical_Constants_Astronomical, only : gravitationalConstantGalacticus + implicit none + class (massDistributionHeatingTidal), intent(inout) :: self + double precision , intent(in ) :: radius + class (massDistributionClass ), intent(inout) :: massDistribution_ + double precision :: energyPerturbationFirstOrder, energyPerturbationSecondOrder + type (coordinateSpherical ) :: coordinates + + if (radius > 0.0d0) then + call self%specificEnergyTerms(radius,massDistribution_,energyPerturbationFirstOrder,energyPerturbationSecondOrder) + if (energyPerturbationSecondOrder > 0.0d0) then + coordinates=[radius,0.0d0,0.0d0] + energySpecificGradient=+( & + & +energyPerturbationFirstOrder * 2.0d0 & ! dlog[r² ]/dlog(r) term + & +energyPerturbationSecondOrder*( & + & -0.5d0 & ! ⎧ dlog[σ_r(r)]/dlog[r] term + & *massDistribution_%densityGradientRadial (coordinates,logarithmic=.true. ) & ! ⎥ + & -0.5d0 & ! ⎥ Assumes the Jeans equation in + & *gravitationalConstantGalacticus & ! ⎥ spherical symmetry with anisotropy + & *massDistribution_%massEnclosedBySphere (radius ) & ! ⎥ parameter β=0. Would be better to + & / radius & ! ⎥ have this provided by the + & /massDistribution_%kinematicsDistribution_%velocityDispersion1D(coordinates, massDistribution_)**2 & ! ⎩ darkMatterProfileDMO class. + & +1.0d0 & ! dlog[r ]/dlog(r) term + & ) & + & ) & + & /radius + else + energySpecificGradient=+ energyPerturbationFirstOrder * 2.0d0 & ! dlog[r² ]/dlog(r) term + & /radius + end if + else + energySpecificGradient =+0.0d0 + end if + return + end function tidalSpecificEnergyGradient + + subroutine tidalSpecificEnergyTerms(self,radius,massDistribution_,energyPerturbationFirstOrder,energyPerturbationSecondOrder) + !!{ + Compute the first and second order perturbations to the energy. + !!} + use :: Coordinates, only : coordinateSpherical, assignment(=) + implicit none + class (massDistributionHeatingTidal), intent(inout) :: self + double precision , intent(in ) :: radius + class (massDistributionClass ), intent(inout) :: massDistribution_ + double precision , intent( out) :: energyPerturbationFirstOrder, energyPerturbationSecondOrder + double precision :: coefficientSecondOrder , densityLogSlope + type (coordinateSpherical ) :: coordinates + + energyPerturbationFirstOrder=+self%heatSpecificNormalized & + & *radius **2 + if ( & + & self%coefficientSecondOrder0 /= 0.0d0 & + & .or. & + & self%coefficientSecondOrder1 /= 0.0d0 & + & .or. & + & self%coefficientSecondOrder2 /= 0.0d0 & + & ) then + ! Compute the coefficient for the second order term. + coordinates=[radius,0.0d0,0.0d0] + densityLogSlope =+massDistribution_%densityGradientRadial(coordinates,logarithmic=.true.) + coefficientSecondOrder=+self %coefficientSecondOrder0 & + & +self %coefficientSecondOrder1*densityLogSlope & + & +self %coefficientSecondOrder2*densityLogSlope**2 + ! Compute the second order energy perturbation. + energyPerturbationSecondOrder=+sqrt(2.0d0) & + & *coefficientSecondOrder & + & *( & + & +1.0d0 & + & +self%correlationVelocityRadius & + & ) & + & *sqrt(energyPerturbationFirstOrder) & + & *massDistribution_%kinematicsDistribution_%velocityDispersion1D(coordinates,massDistribution_) + else + energyPerturbationSecondOrder=+0.0d0 + end if + return + end subroutine tidalSpecificEnergyTerms + + logical function tidalSpecificEnergyIsEverywhereZero(self) result(energySpecificIsEverywhereZero) + !!{ + Returns true if the specific energy is everywhere zero. + !!} + implicit none + class(massDistributionHeatingTidal), intent(inout) :: self + + energySpecificIsEverywhereZero=self%heatSpecificNormalized <= 0.0d0 + return + end function tidalSpecificEnergyIsEverywhereZero diff --git a/source/mass_distributions.spherical.heating.two_body_relaxation.F90 b/source/mass_distributions.spherical.heating.two_body_relaxation.F90 new file mode 100644 index 0000000000..9167e8b5d8 --- /dev/null +++ b/source/mass_distributions.spherical.heating.two_body_relaxation.F90 @@ -0,0 +1,244 @@ +!! Copyright 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, +!! 2019, 2020, 2021, 2022, 2023 +!! Andrew Benson +!! +!! This file is part of Galacticus. +!! +!! Galacticus is free software: you can redistribute it and/or modify +!! it under the terms of the GNU General Public License as published by +!! the Free Software Foundation, either version 3 of the License, or +!! (at your option) any later version. +!! +!! Galacticus is distributed in the hope that it will be useful, +!! but WITHOUT ANY WARRANTY; without even the implied warranty of +!! MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +!! GNU General Public License for more details. +!! +!! You should have received a copy of the GNU General Public License +!! along with Galacticus. If not, see . + + !!{ + Implements a mass distribution heating class that computes heating due to two-body relaxation. + !!} + + !![ + + A mass distribution heating class that computes heating due to two-body relaxation. + + !!] + type, extends(massDistributionHeatingClass) :: massDistributionHeatingTwoBodyRelaxation + !!{ + Implementation of a mass distribution heating class that computes heating due to two-body relaxation. + !!} + private + double precision :: massParticle, lengthSoftening, & + & timeRelaxing, efficiency + contains + procedure :: specificEnergy => twoBodyRelaxationSpecificEnergy + procedure :: specificEnergyGradient => twoBodyRelaxationSpecificEnergyGradient + procedure :: specificEnergyIsEveryWhereZero => twoBodyRelaxationSpecificEnergyIsEverywhereZero + end type massDistributionHeatingTwoBodyRelaxation + + interface massDistributionHeatingTwoBodyRelaxation + !!{ + Constructors for the {\normalfont \ttfamily twoBodyRelaxation} mass distribution class. + !!} + module procedure twoBodyRelaxationConstructorParameters + module procedure twoBodyRelaxationConstructorInternal + end interface massDistributionHeatingTwoBodyRelaxation + +contains + + function twoBodyRelaxationConstructorParameters(parameters) result(self) + !!{ + Constructor for the {\normalfont \ttfamily twoBodyRelaxation} mass distribution class which builds the object from a parameter + set. + !!} + use :: Input_Parameters, only : inputParameter, inputParameters + implicit none + type (massDistributionHeatingTwoBodyRelaxation) :: self + type (inputParameters ), intent(inout) :: parameters + double precision :: massParticle, lengthSoftening, & + & timeRelaxing, efficiency + + !![ + + massParticle + parameters + The particle mass to use for two-body relaxation calculations. + + + lengthSoftening + parameters + The softening length to use for two-body relaxation calculations. + + + timeRelaxing + parameters + The time for which the system has been relaxing. + + + efficiency + parameters + The fractional efficiency of two-body relaxation heating. + + !!] + self=massDistributionHeatingTwoBodyRelaxation(massParticle,lengthSoftening,timeRelaxing,efficiency) + !![ + + !!] + return + end function twoBodyRelaxationConstructorParameters + + function twoBodyRelaxationConstructorInternal(massParticle,lengthSoftening,timeRelaxing,efficiency) result(self) + !!{ + Constructor for ``twoBodyRelaxation'' dark matter profile heating class. + !!} + implicit none + type (massDistributionHeatingTwoBodyRelaxation) :: self + double precision , intent(in ) :: massParticle, lengthSoftening, & + & timeRelaxing, efficiency + !![ + + !!] + + return + end function twoBodyRelaxationConstructorInternal + + double precision function twoBodyRelaxationSpecificEnergy(self,radius,massDistribution_) result(energySpecific) + !!{ + Returns the specific energy of heating in the given {\normalfont \ttfamily node}. The assumption here is that the mean + fractional change in energy for a particle per crossing time is $8 \log \Lambda / N$ where $N$ is the number of particles + within radius $r=${\normalfont \ttfamily radius}. The crossing time is approximated by $r/V(r)$ where $V(r)$ is the + circular velocity at $r$. The Coulomb logarithm is given by $\log\Lambda=\hbox{max}(\epsilon,b_{90})$ where $\epsilon$ is + the softening length, $b_{90}=2\mathrm{G}m_\mathrm{p}/V^2(r)$, and $m_\mathrm{p}$ is the particle mass. Finally, the + specific energy is assumed to be $\sigma^2(r)/2\approx V^2(r)/4$. + !!} + use :: Numerical_Constants_Astronomical, only : gigaYear, megaParsec, gravitationalConstantGalacticus + use :: Numerical_Constants_Prefixes , only : kilo + implicit none + class (massDistributionHeatingTwoBodyRelaxation), intent(inout) :: self + double precision , intent(in ) :: radius + class (massDistributionClass ), intent(inout) :: massDistribution_ + double precision :: particleCount , velocity , & + & logarithmCoulomb , impactParameterCritical + + if (self%timeRelaxing > 0.0d0) then + velocity =+massDistribution_ %rotationCurve (radius) + impactParameterCritical=+2.0d0 & + & *gravitationalConstantGalacticus & + & *self %massParticle & + & /velocity **2 + logarithmCoulomb =+0.5d0 & + & *log( & + & +1.0d0 & + & +( & + & +radius & + & /max( & + & self %lengthSoftening , & + & impactParameterCritical & + & ) & + & ) **2 & + & ) + particleCount =+massDistribution_ %massEnclosedBySphere(radius) & + & /self %massParticle + energySpecific =+2.0d0 & + & *self %efficiency & + & *logarithmCoulomb & + & *self %timeRelaxing & + & *velocity **3 & + & /radius & + & /particleCount & + & *kilo & + & *gigaYear & + & /megaParsec + else + energySpecific =+0.0d0 + end if + return + end function twoBodyRelaxationSpecificEnergy + + double precision function twoBodyRelaxationSpecificEnergyGradient(self,radius,massDistribution_) result(energySpecificGradient) + !!{ + Returns the gradient of the specific energy of heating. + !!} + use :: Coordinates , only : coordinateSpherical , assignment(=) + use :: Numerical_Constants_Astronomical, only : gravitationalConstantGalacticus + implicit none + class (massDistributionHeatingTwoBodyRelaxation), intent(inout) :: self + double precision , intent(in ) :: radius + class (massDistributionClass ), intent(inout) :: massDistribution_ + double precision :: particleCount , velocity , & + & logarithmCoulomb , impactParameterCritical, & + & gradientCoulomb + type (coordinateSpherical ) :: coordinates + + if (self%timeRelaxing > 0.0d0) then + coordinates =[radius,0.0d0,0.0d0] + velocity =+massDistribution_ %rotationCurve (radius) + impactParameterCritical =+2.0d0 & + & *gravitationalConstantGalacticus & + & *self %massParticle & + & /velocity **2 + logarithmCoulomb =+0.5d0 & + & *log( & + & +1.0d0 & + & +( & + & +radius & + & /max( & + & self %lengthSoftening , & + & impactParameterCritical & + & ) & + & ) **2 & + & ) + particleCount =+massDistribution_ %massEnclosedBySphere(radius) & + & /self %massParticle + if (self%lengthSoftening > impactParameterCritical) then + gradientCoulomb=+radius & + & /self %lengthSoftening + else + gradientCoulomb=+2.0d0 & + & *radius & + & /impactParameterCritical & + & *( & + & -1.0d0 & + & +8.0d0 & + & *Pi & + & *gravitationalConstantGalacticus & + & *radius **2 & + & *massDistribution_ %density (coordinates) & + & /velocity **2 & + & ) + end if + energySpecificGradient=+self %specificEnergy(radius ,massDistribution_) & + & / radius & + & *( & + & -2.5d0 & + & +6.0d0 & + & *Pi & + & *gravitationalConstantGalacticus & + & *massDistribution_ %density (coordinates ) & + & *radius **2 & + & /velocity **2 & + & - radius & + & * gradientCoulomb & + & / logarithmCoulomb & + & *sqrt(exp(2.0d0*logarithmCoulomb)-1.0d0) & + & / exp(2.0d0*logarithmCoulomb) & + & ) + else + energySpecificGradient=+0.0d0 + end if + return + end function twoBodyRelaxationSpecificEnergyGradient + + logical function twoBodyRelaxationSpecificEnergyIsEverywhereZero(self) result(energySpecificIsEverywhereZero) + !!{ + Returns true if the specific energy is everywhere zero. + !!} + implicit none + class(massDistributionHeatingTwoBodyRelaxation), intent(inout) :: self + + energySpecificIsEverywhereZero=self%timeRelaxing <= 0.0d0 + return + end function twoBodyRelaxationSpecificEnergyIsEverywhereZero diff --git a/source/mass_distributions.spherical.isothermal.F90 b/source/mass_distributions.spherical.isothermal.F90 new file mode 100644 index 0000000000..dc435c1357 --- /dev/null +++ b/source/mass_distributions.spherical.isothermal.F90 @@ -0,0 +1,648 @@ +!! Copyright 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, +!! 2019, 2020, 2021, 2022, 2023 +!! Andrew Benson +!! +!! This file is part of Galacticus. +!! +!! Galacticus is free software: you can redistribute it and/or modify +!! it under the terms of the GNU General Public License as published by +!! the Free Software Foundation, either version 3 of the License, or +!! (at your option) any later version. +!! +!! Galacticus is distributed in the hope that it will be useful, +!! but WITHOUT ANY WARRANTY; without even the implied warranty of +!! MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +!! GNU General Public License for more details. +!! +!! You should have received a copy of the GNU General Public License +!! along with Galacticus. If not, see . + + !!{ + Implementation of an isothermal mass distribution class. + !!} + + !![ + + + An isothermal mass distribution class in which the density profile is given by: + \begin{equation} + \rho(r) \propto r^{-2}. + \end{equation} + + + !!] + type, public, extends(massDistributionSpherical) :: massDistributionIsothermal + !!{ + The isothermal mass distribution. + !!} + private + double precision :: densityNormalization, lengthReference, & + & velocityRotation + contains + procedure :: massTotal => isothermalMassTotal + procedure :: density => isothermalDensity + procedure :: densityGradientRadial => isothermalDensityGradientRadial + procedure :: densityRadialMoment => isothermalDensityRadialMoment + procedure :: massEnclosedBySphere => isothermalMassEnclosedBySphere + procedure :: rotationCurve => isothermalRotationCurve + procedure :: rotationCurveGradient => isothermalRotationCurveGradient + procedure :: velocityRotationCurveMaximum => isothermalVelocityRotationCurveMaximum + procedure :: radiusRotationCurveMaximum => isothermalRadiusRotationCurveMaximum + procedure :: radiusEnclosingMass => isothermalRadiusEnclosingMass + procedure :: radiusEnclosingDensity => isothermalRadiusEnclosingDensity + procedure :: radiusFromSpecificAngularMomentum => isothermalRadiusFromSpecificAngularMomentum + procedure :: fourierTransform => isothermalFourierTransform + procedure :: radiusFreefall => isothermalRadiusFreefall + procedure :: radiusFreefallIncreaseRate => isothermalRadiusFreefallIncreaseRate + procedure :: energyPotential => isothermalEnergyPotential + procedure :: energyKinetic => isothermalEnergyKinetic + procedure :: potentialIsAnalytic => isothermalPotentialIsAnalytic + procedure :: potential => isothermalPotential + procedure :: positionSample => isothermalPositionSample + procedure :: descriptor => isothermalDescriptor + end type massDistributionIsothermal + + interface massDistributionIsothermal + !!{ + Constructors for the {\normalfont \ttfamily isothermal} mass distribution class. + !!} + module procedure massDistributionIsothermalConstructorParameters + module procedure massDistributionIsothermalConstructorInternal + end interface massDistributionIsothermal + +contains + + function massDistributionIsothermalConstructorParameters(parameters) result(self) + !!{ + Constructor for the {\normalfont \ttfamily isothermal} mass distribution class which builds the object from a parameter + set. + !!} + use :: Input_Parameters , only : inputParameter , inputParameters + use :: Galactic_Structure_Options, only : enumerationComponentTypeEncode, enumerationMassTypeEncode + use :: Numerical_Constants_Math , only : Pi + implicit none + type (massDistributionIsothermal) :: self + type (inputParameters ), intent(inout) :: parameters + double precision :: mass , lengthReference, & + & densityNormalization + logical :: dimensionless + type (varying_string ) :: componentType + type (varying_string ) :: massType + + !![ + + densityNormalization + 0.25d0/Pi + The density normalization of the isothermal profile. + parameters + + + lengthReference + 1.0d0 + The scale radius of the isothermal profile. + parameters + + + mass + 1.0d0 + The mass of the isothermal profile. + parameters + + + dimensionless + .true. + If true the isothermal profile is considered to be dimensionless. + parameters + + + componentType + var_str('unknown') + The component type that this mass distribution represents. + parameters + + + massType + var_str('unknown') + The mass type that this mass distribution represents. + parameters + + + self=massDistributionIsothermal(componentType=enumerationComponentTypeEncode(componentType,includesPrefix=.false.),massType=enumerationMassTypeEncode(massType,includesPrefix=.false.){conditions}) + + + + + + + !!] + return + end function massDistributionIsothermalConstructorParameters + + function massDistributionIsothermalConstructorInternal(densityNormalization,mass,lengthReference,dimensionless,componentType,massType) result(self) + !!{ + Internal constructor for ``isothermal'' mass distribution class. + !!} + use :: Error , only : Error_Report + use :: Numerical_Comparison , only : Values_Differ + use :: Numerical_Constants_Math , only : Pi + use :: Numerical_Constants_Astronomical, only : gravitationalConstantGalacticus + implicit none + type (massDistributionIsothermal ) :: self + double precision , intent(in ), optional :: densityNormalization, mass, & + & lengthReference + logical , intent(in ), optional :: dimensionless + type (enumerationComponentTypeType ), intent(in ), optional :: componentType + type (enumerationMassTypeType ), intent(in ), optional :: massType + !![ + + !!] + + ! Determine if profile is dimensionless. + self%dimensionless=.false. + if (present(dimensionless)) self%dimensionless=dimensionless + ! If dimensionless, then set scale length and mass to unity. + if (self%dimensionless) then + if (present(lengthReference )) then + if (Values_Differ(lengthReference ,1.0d0 ,absTol=1.0d-6)) call Error_Report('scaleLength should be unity for a dimensionless profile (or simply do not specify a scale length)' //{introspection:location}) + end if + if (present(mass )) then + if (Values_Differ(mass ,1.0d0 ,absTol=1.0d-6)) call Error_Report('mass should be unity for a dimensionless profile (or simply do not specify a mass)' //{introspection:location}) + end if + if (present(densityNormalization)) then + if (Values_Differ(densityNormalization,0.25d0/Pi,absTol=1.0d-6)) call Error_Report('densityNormalization should be π/4 for a dimensionless profile (or simply do not specify a densityNormalization)'//{introspection:location}) + end if + self%lengthReference =1.00d0 + self%densityNormalization=0.25d0/Pi + else + if (present(lengthReference )) then + self%lengthReference=lengthReference + else + call Error_Report('"lengthReference" must be specified'//{introspection:location}) + end if + if (present(densityNormalization)) then + self%densityNormalization=densityNormalization + else if (present(mass )) then + self%densityNormalization=mass /4.0d0/Pi/lengthReference**3 + else + call Error_Report('one of "densityNormalization" or "mass" must be specified'//{introspection:location}) + end if + end if + ! Compute the rotation velocity. + if (self%isDimensionless()) then + self%velocityRotation=+1.0d0 + else + self%velocityRotation=+sqrt( & + & +4.0d0 & + & *Pi & + & *gravitationalConstantGalacticus & + & *self%lengthReference **2 & + & *self%densityNormalization & + & ) + end if + return + end function massDistributionIsothermalConstructorInternal + + double precision function isothermalMassTotal(self) + !!{ + Return the total mass in an isothermal mass distribution. + !!} + implicit none + class(massDistributionIsothermal), intent(inout) :: self + + isothermalMassTotal=huge(0.0d0) + return + end function isothermalMassTotal + + double precision function isothermalDensity(self,coordinates) + !!{ + Return the density at the specified {\normalfont \ttfamily coordinates} in an isothermal mass distribution. + !!} + use :: Coordinates, only : assignment(=), coordinateSpherical + implicit none + class(massDistributionIsothermal), intent(inout) :: self + class(coordinate ), intent(in ) :: coordinates + + isothermalDensity=+ self %densityNormalization & + & /( & + & +coordinates%rSpherical () & + & /self %lengthReference & + & )**2 + return + end function isothermalDensity + + double precision function isothermalDensityGradientRadial(self,coordinates,logarithmic) + !!{ + Return the density at the specified {\normalfont \ttfamily coordinates} in an isothermal mass distribution. + !!} + implicit none + class (massDistributionIsothermal), intent(inout), target :: self + class (coordinate ), intent(in ) :: coordinates + logical , intent(in ), optional :: logarithmic + double precision :: radius + logical :: logarithmicActual + + ! Set default options. + logarithmicActual=.false. + if (present(logarithmic)) logarithmicActual=logarithmic + ! Get position in spherical coordinate system. + radius=coordinates%rSpherical() + ! Compute density gradient. + if (logarithmicActual) then + isothermalDensityGradientRadial=-2.0d0 + else + isothermalDensityGradientRadial=-2.0d0 & + & *self%densityNormalization & + & *self%lengthReference **2 & + & /radius **3 + end if + return + end function isothermalDensityGradientRadial + + double precision function isothermalMassEnclosedBySphere(self,radius) + !!{ + Computes the mass enclosed within a sphere of given {\normalfont \ttfamily radius} for isothermal mass distributions. + !!} + use :: Numerical_Constants_Math, only : Pi + implicit none + class (massDistributionIsothermal), intent(inout), target :: self + double precision , intent(in ) :: radius + + isothermalMassEnclosedBySphere=+4.0d0 & + & *Pi & + & *self%densityNormalization & + & *self%lengthReference **2 & + & *radius + return + end function isothermalMassEnclosedBySphere + + double precision function isothermalRadiusEnclosingMass(self,mass,massFractional) result(radius) + !!{ + Computes the radius enclosing a given mass or mass fraction for isothermal mass distributions. + !!} + use :: Error , only : Error_Report + use :: Numerical_Constants_Math, only : Pi + implicit none + class (massDistributionIsothermal), intent(inout), target :: self + double precision , intent(in ), optional :: mass , massFractional + double precision :: mass_ + + mass_=0.0d0 + if (present(mass)) then + mass_=mass + else if (present(massFractional)) then + call Error_Report('mass is unbounded, so mass fraction is undefined'//{introspection:location}) + else + call Error_Report('either mass or massFractional must be supplied'//{introspection:location}) + end if + radius=+ mass_ & + & / 4.0d0 & + & / Pi & + & /self%densityNormalization & + & /self%lengthReference **2 + return + end function isothermalRadiusEnclosingMass + + double precision function isothermalRadiusEnclosingDensity(self,density,radiusGuess) result(radius) + !!{ + Computes the radius enclosing a given mean density for isothermal mass distributions. + !!} + use :: Numerical_Constants_Math, only : Pi + implicit none + class (massDistributionIsothermal), intent(inout), target :: self + double precision , intent(in ) :: density + double precision , intent(in ), optional :: radiusGuess + + radius=+ self%lengthReference & + & *sqrt( & + & +3.0d0 & + & *self%densityNormalization & + & / density & + & ) + return + end function isothermalRadiusEnclosingDensity + + double precision function isothermalRadiusFromSpecificAngularMomentum(self,angularMomentumSpecific) result(radius) + !!{ + Computes the radius corresponding to a given specific angular momentum for isothermal mass distributions. + !!} + use :: Numerical_Constants_Math , only : Pi + use :: Numerical_Constants_Astronomical, only : gravitationalConstantGalacticus + implicit none + class (massDistributionIsothermal), intent(inout), target :: self + double precision , intent(in ) :: angularMomentumSpecific + + radius=+angularMomentumSpecific & + & /sqrt( & + & +4.0d0 & + & *Pi & + & *self%densityNormalization & + & *self%lengthReference**2 & + & ) + if (.not.self%isDimensionless()) radius=+radius & + & /sqrt(gravitationalConstantGalacticus) + return + end function isothermalRadiusFromSpecificAngularMomentum + + double precision function isothermalDensityRadialMoment(self,moment,radiusMinimum,radiusMaximum,isInfinite) + !!{ + Returns a radial density moment for the Isothermal mass distribution. + !!} + use :: Error, only : Error_Report + implicit none + class (massDistributionIsothermal), intent(inout) :: self + double precision , intent(in ) :: moment + double precision , intent(in ), optional :: radiusMinimum, radiusMaximum + logical , intent( out), optional :: isInfinite + double precision :: momentMinimum, momentMaximum + + isothermalDensityRadialMoment=0.0d0 + if (present(isInfinite)) isInfinite=.false. + if (present(radiusMinimum)) then + if (moment == 1.0d0) then + momentMinimum=+log(+radiusMinimum ) + else + momentMinimum= +radiusMinimum**(moment-1.0d0) & + & / (moment-1.0d0) + end if + else if (moment <= 1.0d0) then + momentMinimum=+0.0d0 + if (present(isInfinite)) then + isInfinite=.true. + return + else + call Error_Report('radial moment is infinite'//{introspection:location}) + end if + else + momentMinimum=0.0d0 + end if + if (present(radiusMaximum)) then + if (moment == 1.0d0) then + momentMaximum=+log(+radiusMaximum ) + else + momentMaximum= +radiusMaximum**(moment-1.0d0) & + & / (moment-1.0d0) + end if + else if (moment >= 1.0d0) then + momentMaximum=+0.0d0 + if (present(isInfinite)) then + isInfinite=.true. + return + else + call Error_Report('radial moment is infinite'//{introspection:location}) + end if + else + momentMaximum=0.0d0 + end if + isothermalDensityRadialMoment=+self%densityNormalization & + & *self%lengthReference **2 & + & *( & + & +momentMaximum & + & -momentMinimum & + & ) + return + end function isothermalDensityRadialMoment + + double precision function isothermalRotationCurve(self,radius) result(rotationCurve) + !!{ + Return the rotation curve for an isothermal mass distribution. + !!} + implicit none + class (massDistributionIsothermal), intent(inout) :: self + double precision , intent(in ) :: radius + + rotationCurve=self%velocityRotation + return + end function isothermalRotationCurve + + double precision function isothermalRotationCurveGradient(self,radius) result(rotationCurveGradient) + !!{ + Return the rotation curve gradient (specifically, $\mathrm{d}V^2_\mathrm{c}/\mathrm{d}r$) for an isothermal mass distribution. + !!} + implicit none + class (massDistributionIsothermal), intent(inout) :: self + double precision , intent(in ) :: radius + !$GLC attributes unused :: self, radius + + rotationCurveGradient=0.0d0 + return + end function isothermalRotationCurveGradient + + double precision function isothermalVelocityRotationCurveMaximum(self) result(velocity) + !!{ + Return the peak velocity in the rotation curve for an isothermal mass distribution. + !!} + implicit none + class(massDistributionIsothermal), intent(inout) :: self + + velocity=self%velocityRotation + return + end function isothermalVelocityRotationCurveMaximum + + double precision function isothermalRadiusRotationCurveMaximum(self) result(radius) + !!{ + Return the peak velocity in the rotation curve for an isothermal mass distribution. + !!} + implicit none + class(massDistributionIsothermal), intent(inout), target :: self + !$GLC attributes unused :: self + + radius=1.0d0 + return + end function isothermalRadiusRotationCurveMaximum + + logical function isothermalPotentialIsAnalytic(self) result(isAnalytic) + !!{ + Return that the potential has an analytic form. + !!} + implicit none + class(massDistributionIsothermal), intent(inout) :: self + + isAnalytic=.true. + return + end function isothermalPotentialIsAnalytic + + double precision function isothermalPotential(self,coordinates,status) + !!{ + Return the potential at the specified {\normalfont \ttfamily coordinates} in an isothermal mass distribution. + !!} + use :: Coordinates , only : assignment(=) + use :: Galactic_Structure_Options , only : structureErrorCodeSuccess , structureErrorCodeInfinite + use :: Numerical_Constants_Astronomical, only : gravitationalConstantGalacticus + use :: Numerical_Constants_Math , only : Pi + use :: Error , only : Error_Report + implicit none + class(massDistributionIsothermal ), intent(inout), target :: self + class(coordinate ), intent(in ) :: coordinates + type (enumerationStructureErrorCodeType), intent( out), optional :: status + + if (present(status)) status=structureErrorCodeSuccess + ! Compute the potential at this position. + if (coordinates%rSpherical() <= 0.0d0) then + isothermalPotential=0.0d0 + if (present(status)) then + status=structureErrorCodeInfinite + return + else + call Error_Report('potential is divergent at zero radius'//{introspection:location}) + end if + end if + isothermalPotential=+ 4.0d0 & + & *Pi & + & * self %densityNormalization & + & * self %lengthReference **2 & + & *log( & + & +coordinates%rSpherical () & + & /self %lengthReference & + & ) + if (.not.self%isDimensionless()) isothermalPotential=+gravitationalConstantGalacticus & + & *isothermalPotential + return + end function isothermalPotential + + double precision function isothermalFourierTransform(self,radiusOuter,wavenumber) result(fourierTransform) + !!{ + Compute the Fourier transform of the density profile at the given {\normalfont \ttfamily wavenumber} in an isothermal mass distribution. + !!} + use :: Exponential_Integrals, only : Sine_Integral + implicit none + class (massDistributionIsothermal), intent(inout) :: self + double precision , intent(in ) :: radiusOuter , wavenumber + double precision :: wavenumberScaleFree + + waveNumberScaleFree=+waveNumber & + & *radiusOuter + fourierTransform =+Sine_Integral(waveNumberScaleFree) & + & / waveNumberScaleFree + return + end function isothermalFourierTransform + + double precision function isothermalRadiusFreefall(self,time) result(radius) + !!{ + Compute the freefall radius at the given {\normalfont \ttfamily time} in an isothermal mass distribution. For an isothermal + potential, the freefall radius, $r_\mathrm{ff}(t)$, is: + \begin{equation} + r_\mathrm{ff}(t) = \sqrt{{2 \over \pi}} V_\mathrm{virial} t. + \end{equation} + !!} + use :: Numerical_Constants_Math , only : Pi + use :: Numerical_Constants_Astronomical, only : Mpc_per_km_per_s_To_Gyr + implicit none + class (massDistributionIsothermal), intent(inout) :: self + double precision , intent(in ) :: time + + radius=+sqrt( & + & +2.0d0 & + & /Pi & + & ) & + & *self%velocityRotation & + & * time & + & /Mpc_per_km_per_s_To_Gyr + return + end function isothermalRadiusFreefall + + double precision function isothermalRadiusFreefallIncreaseRate(self,time) result(radiusIncreaseRate) + !!{ + Compute the rate of increase of the freefall radius at the given {\normalfont \ttfamily time} in an isothermal mass + distribution. For an isothermal potential, the rate of increase of the freefall radius, $\dot{r}_\mathrm{ff}(t)$, is: + \begin{equation} + \dot{r}_\mathrm{ff}(t) = \sqrt{{2 \over \pi}} V_\mathrm{virial}. + \end{equation} + !!} + use :: Numerical_Constants_Math , only : Pi + use :: Numerical_Constants_Astronomical, only : Mpc_per_km_per_s_To_Gyr + implicit none + class (massDistributionIsothermal), intent(inout) :: self + double precision , intent(in ) :: time + !$GLC attributes unused :: time + + radiusIncreaseRate=+sqrt( & + & +2.0d0 & + & /Pi & + & ) & + & *self%velocityRotation & + & /Mpc_per_km_per_s_To_Gyr + return + end function isothermalRadiusFreefallIncreaseRate + + double precision function isothermalEnergyPotential(self,radiusOuter) result(energy) + !!{ + Compute the potential energy within a given {\normalfont \ttfamily radius} in an isothermal mass distribution. + !!} + use :: Numerical_Constants_Astronomical, only : gravitationalConstantGalacticus + use :: Numerical_Constants_Math , only : Pi + implicit none + class (massDistributionIsothermal), intent(inout) :: self + double precision , intent(in ) :: radiusOuter + + energy=-16.0d0 & + & *Pi **2 & + & * gravitationalConstantGalacticus & + & *self%lengthReference **4 & + & *self%densityNormalization **2 & + & * radiusOuter + return + end function isothermalEnergyPotential + + double precision function isothermalEnergyKinetic(self,radiusOuter,massDistributionEmbedding) result(energy) + !!{ + Compute the kinetic energy within a given {\normalfont \ttfamily radius} in an isothermal mass distribution. + !!} + use :: Coordinates, only : assignment(=), coordinateSpherical + implicit none + class (massDistributionIsothermal), intent(inout) :: self + double precision , intent(in ) :: radiusOuter + class (massDistributionClass ), intent(inout) :: massDistributionEmbedding + logical :: analytic + type (coordinateSpherical ) :: coordinates + + analytic=.false. + select type (massDistributionEmbedding) + class is (massDistributionIsothermal) + select type (kinematicsDistribution_ => massDistributionEmbedding%kinematicsDistribution_) + class is (kinematicsDistributionIsothermal) + analytic =.true. + coordinates=[radiusOuter,0.0d0,0.0d0] + energy =+1.5d0 & + & *self %massEnclosedBySphere(radiusOuter ) & + & *kinematicsDistribution_%velocityDispersion1D(coordinates,massDistributionEmbedding) + end select + end select + if (.not.analytic) energy=self%energyKineticNumerical(radiusOuter,massDistributionEmbedding) + return + end function isothermalEnergyKinetic + + function isothermalPositionSample(self,randomNumberGenerator_) result(position) + !!{ + Computes the half-mass radius of a spherically symmetric mass distribution using numerical root finding. + !!} + use :: Numerical_Constants_Math, only : Pi + implicit none + double precision , dimension(3) :: position + class (massDistributionIsothermal), intent(inout) :: self + class (randomNumberGeneratorClass), intent(inout) :: randomNumberGenerator_ + + position=0.0d0 + call Error_Report('can not sample positions, mass is unbounded'//{introspection:location}) + return + end function isothermalPositionSample + + subroutine isothermalDescriptor(self,descriptor,includeClass,includeFileModificationTimes) + !!{ + Return an input parameter list descriptor which could be used to recreate this object. + !!} + use :: Input_Parameters, only : inputParameters + implicit none + class (massDistributionIsothermal), intent(inout) :: self + type (inputParameters ), intent(inout) :: descriptor + logical , intent(in ), optional :: includeClass , includeFileModificationTimes + character(len=18 ) :: parameterLabel + type (inputParameters ) :: parameters + + if (.not.present(includeClass).or.includeClass) call descriptor%addParameter('massDistribution','isothermal') + parameters=descriptor%subparameters('massDistribution') + write (parameterLabel,'(e17.10)') self%densityNormalization + call parameters%addParameter('densityNormalization',trim(adjustl(parameterLabel))) + write (parameterLabel,'(e17.10)') self%lengthReference + call parameters%addParameter('lengthReference' ,trim(adjustl(parameterLabel))) + return + end subroutine isothermalDescriptor diff --git a/source/mass_distributions.spherical.null.F90 b/source/mass_distributions.spherical.null.F90 new file mode 100644 index 0000000000..2fa7e680e5 --- /dev/null +++ b/source/mass_distributions.spherical.null.F90 @@ -0,0 +1,217 @@ +!! Copyright 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, +!! 2019, 2020, 2021, 2022, 2023 +!! Andrew Benson +!! +!! This file is part of Galacticus. +!! +!! Galacticus is free software: you can redistribute it and/or modify +!! it under the terms of the GNU General Public License as published by +!! the Free Software Foundation, either version 3 of the License, or +!! (at your option) any later version. +!! +!! Galacticus is distributed in the hope that it will be useful, +!! but WITHOUT ANY WARRANTY; without even the implied warranty of +!! MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +!! GNU General Public License for more details. +!! +!! You should have received a copy of the GNU General Public License +!! along with Galacticus. If not, see . + + !!{ + Implementation of a zero mass distribution class. + !!} + + !![ + + A zero mass distribution class. + + !!] + type, public, extends(massDistributionSpherical) :: massDistributionZero + !!{ + A zero mass distribution. + !!} + contains + procedure :: massTotal => zeroMassTotal + procedure :: density => zeroDensity + procedure :: densityGradientRadial => zeroDensityGradientRadial + procedure :: densityRadialMoment => zeroDensityRadialMoment + procedure :: massEnclosedBySphere => zeroMassEnclosedBySphere + procedure :: rotationCurve => zeroRotationCurve + procedure :: rotationCurveGradient => zeroRotationCurveGradient + procedure :: potentialIsAnalytic => zeroPotentialIsAnalytic + procedure :: potential => zeroPotential + end type massDistributionZero + + interface massDistributionZero + !!{ + Constructors for the {\normalfont \ttfamily zero} mass distribution class. + !!} + module procedure zeroConstructorParameters + module procedure zeroConstructorInternal + end interface massDistributionZero + +contains + + function zeroConstructorParameters(parameters) result(self) + !!{ + Constructor for the {\normalfont \ttfamily zero} mass distribution class which builds the object from a parameter + set. + !!} + use :: Input_Parameters, only : inputParameters + implicit none + type (massDistributionZero) :: self + type (inputParameters ), intent(inout) :: parameters + logical :: dimensionless + + !![ + + dimensionless + .true. + If true the null profile is considered to be dimensionless. + parameters + + !!] + self=massDistributionZero(dimensionless) + !![ + + !!] + return + end function zeroConstructorParameters + + function zeroConstructorInternal(dimensionless) result(self) + !!{ + Constructor for {\normalfont \ttfamily zero} mass distribution class. + !!} + use :: Galactic_Structure_Options, only : componentTypeUnknown, massTypeUnknown + implicit none + type (massDistributionZero) :: self + logical , intent(in ) :: dimensionless + !![ + + !!] + + self%componentType=componentTypeUnknown + self%massType =massTypeUnknown + return + end function zeroConstructorInternal + + double precision function zeroMassTotal(self) + !!{ + Return the total mass in the zero distribution. + !!} + implicit none + class(massDistributionZero), intent(inout) :: self + !$GLC attributes unused :: self + + zeroMassTotal=0.0d0 + return + end function zeroMassTotal + + double precision function zeroDensity(self,coordinates) + !!{ + Return the density at the specified {\normalfont \ttfamily coordinates} in a zero distribution. + !!} + implicit none + class(massDistributionZero), intent(inout) :: self + class(coordinate ), intent(in ) :: coordinates + !$GLC attributes unused :: self, coordinates + + zeroDensity=0.0d0 + return + end function zeroDensity + + double precision function zeroDensityGradientRadial(self,coordinates,logarithmic) + !!{ + Return the density gradient in the radial direction for a point mass. + !!} + implicit none + class (massDistributionZero), intent(inout), target :: self + class (coordinate ), intent(in ) :: coordinates + logical , intent(in ), optional :: logarithmic + !$GLC attributes unused :: self, coordinates, logarithmic + + zeroDensityGradientRadial=0.0d0 + return + end function zeroDensityGradientRadial + + double precision function zeroMassEnclosedBySphere(self,radius) + !!{ + Computes the mass enclosed within a sphere of given {\normalfont \ttfamily radius} for a zero distribution. + !!} + implicit none + class (massDistributionZero), intent(inout), target :: self + double precision , intent(in ) :: radius + !$GLC attributes unused :: self, radius + + zeroMassEnclosedBySphere=0.0d0 + return + end function zeroMassEnclosedBySphere + + double precision function zeroRotationCurve(self,radius) + !!{ + Return the rotation curve for a zero mass distribution. + !!} + implicit none + class (massDistributionZero), intent(inout) :: self + double precision , intent(in ) :: radius + !$GLC attributes unused :: self, radius + + zeroRotationCurve=0.0d0 + return + end function zeroRotationCurve + + double precision function zeroRotationCurveGradient(self,radius) + !!{ + Return the rotation curve gradient for a spherical mass distribution. + !!} + implicit none + class (massDistributionZero), intent(inout) :: self + double precision , intent(in ) :: radius + !$GLC attributes unused :: self, radius + + zeroRotationCurveGradient=0.0d0 + return + end function zeroRotationCurveGradient + + logical function zeroPotentialIsAnalytic(self) result(isAnalytic) + !!{ + Return that the potential has an analytic form. + !!} + implicit none + class(massDistributionZero), intent(inout) :: self + + isAnalytic=.true. + return + end function zeroPotentialIsAnalytic + + double precision function zeroPotential(self,coordinates,status) + !!{ + Return the potential at the specified {\normalfont \ttfamily coordinates} for a point mass. + !!} + use :: Galactic_Structure_Options, only : structureErrorCodeSuccess + implicit none + class(massDistributionZero ), intent(inout), target :: self + class(coordinate ), intent(in ) :: coordinates + type (enumerationStructureErrorCodeType), intent( out), optional :: status + !$GLC attributes unused :: self, coordinates + + if (present(status)) status=structureErrorCodeSuccess + zeroPotential=0.0d0 + return + end function zeroPotential + + double precision function zeroDensityRadialMoment(self,moment,radiusMinimum,radiusMaximum,isInfinite) + !!{ + Computes radial moments of the density for a point mass. + !!} + implicit none + class (massDistributionZero), intent(inout) :: self + double precision , intent(in ) :: moment + double precision , intent(in ), optional :: radiusMinimum, radiusMaximum + logical , intent( out), optional :: isInfinite + !$GLC attributes unused :: self, moment, radiusMinimum, radiusMaximum + + zeroDensityRadialMoment=0.0d0 + if (present(isInfinite)) isInfinite=.false. + return + end function zeroDensityRadialMoment diff --git a/source/mass_distributions.spherical.scaler.F90 b/source/mass_distributions.spherical.scaler.F90 index 1b0abb8111..8a19b96698 100644 --- a/source/mass_distributions.spherical.scaler.F90 +++ b/source/mass_distributions.spherical.scaler.F90 @@ -27,7 +27,7 @@ A mass distribution class for scaling spherical mass distributions. Specifically, the density at position $\mathbf{x}$ is given by \begin{equation} - \rho(\mathbf{x}) = f_\mathrm{M} \rho^\prime(\mathbf{x}/f_\mathrm{r}), + \rho(\mathbf{x}) = \frac{f_\mathrm{M}}{f_\mathrm{r}^3} \rho^\prime(\mathbf{x}/f_\mathrm{r}), \end{equation} where $\rho^\prime(\mathbf{x})$ is the original mass distribution, and $f_\mathrm{r}=${\normalfont \ttfamily [factorScalingLength]}, and $f_\mathrm{M}=${\normalfont \ttfamily [factorScalingMass]}. @@ -38,20 +38,36 @@ !!{ A mass distribution class for scaling spherical mass distributions. !!} - class (massDistributionSpherical), pointer :: massDistribution_ => null() - double precision :: factorScalingLength , factorScalingMass + class (massDistributionSpherical ), pointer :: massDistribution_ => null() + double precision :: factorScalingLength , factorScalingMass + ! Memoized results. + double precision , dimension(3) :: positionTidalTensorPrevious + type (tensorRank2Dimension3Symmetric) :: tidalTensorPrevious contains - final :: sphericalScalerDestructor - procedure :: density => sphericalScalerDensity - procedure :: densityGradientRadial => sphericalScalerDensityGradientRadial - procedure :: densityRadialMoment => sphericalScalerDensityRadialMoment - procedure :: massEnclosedBySphere => sphericalScalerMassEnclosedBySphere - procedure :: potential => sphericalScalerPotential - procedure :: radiusHalfMass => sphericalScalerRadiusHalfMass - procedure :: tidalTensor => sphericalScalerTidalTensor - procedure :: radiusEnclosingMass => sphericalScalerRadiusEnclosingMass - procedure :: positionSample => sphericalScalerPositionSample - procedure :: isDimensionless => sphericalScalerIsDimensionless + final :: sphericalScalerDestructor + procedure :: massTotal => sphericalScalerMassTotal + procedure :: density => sphericalScalerDensity + procedure :: densityGradientRadial => sphericalScalerDensityGradientRadial + procedure :: densityRadialMoment => sphericalScalerDensityRadialMoment + procedure :: massEnclosedBySphere => sphericalScalerMassEnclosedBySphere + procedure :: velocityRotationCurveMaximum => sphericalScalerVelocityRotationCurveMaximum + procedure :: radiusRotationCurveMaximum => sphericalScalerRadiusRotationCurveMaximum + procedure :: radiusEnclosingMass => sphericalScalerRadiusEnclosingMass + procedure :: radiusEnclosingDensity => sphericalScalerRadiusEnclosingDensity + procedure :: radiusFromSpecificAngularMomentum => sphericalScalerRadiusFromSpecificAngularMomentum + procedure :: fourierTransform => sphericalScalerFourierTransform + procedure :: radiusFreefall => sphericalScalerRadiusFreefall + procedure :: radiusFreefallIncreaseRate => sphericalScalerRadiusFreefallIncreaseRate + procedure :: energyPotential => sphericalScalerEnergyPotential + procedure :: densitySphericalAverage => sphericalScalerDensitySphericalAverage + procedure :: rotationCurve => sphericalScalerRotationCurve + procedure :: rotationCurveGradient => sphericalScalerRotationCurveGradient + procedure :: potentialIsAnalytic => sphericalScalerPotentialIsAnalytic + procedure :: potential => sphericalScalerPotential + procedure :: radiusHalfMass => sphericalScalerRadiusHalfMass + procedure :: tidalTensor => sphericalScalerTidalTensor + procedure :: acceleration => sphericalScalerAcceleration + procedure :: positionSample => sphericalScalerPositionSample end type massDistributionSphericalScaler interface massDistributionSphericalScaler @@ -115,8 +131,10 @@ function sphericalScalerConstructorInternal(factorScalingLength,factorScalingMas !!] - self%componentType=self%massDistribution_%componentType - self% massType=self%massDistribution_% massType + self%componentType =self%massDistribution_%componentType + self% massType =self%massDistribution_% massType + self%dimensionless =.false. + self%positionTidalTensorPrevious=-huge(0.0d0) return end function sphericalScalerConstructorInternal @@ -133,58 +151,55 @@ subroutine sphericalScalerDestructor(self) return end subroutine sphericalScalerDestructor - logical function sphericalScalerIsDimensionless(self) + double precision function sphericalScalerMassTotal(self) !!{ - Return the dimensional status. + Return the total mass in a scaled spherical distribution. !!} implicit none class(massDistributionSphericalScaler), intent(inout) :: self - sphericalScalerIsDimensionless=.false. + sphericalScalerMassTotal=+self%massDistribution_%massTotal () & + & *self %factorScalingMass return - end function sphericalScalerIsDimensionless + end function sphericalScalerMassTotal - double precision function sphericalScalerDensity(self,coordinates,componentType,massType) + double precision function sphericalScalerDensity(self,coordinates) !!{ Return the density at the specified {\normalfont \ttfamily coordinates} in a scaled spherical mass distribution. !!} implicit none - class(massDistributionSphericalScaler), intent(inout) :: self - class(coordinate ), intent(in ) :: coordinates - type (enumerationComponentTypeType ), intent(in ), optional :: componentType - type (enumerationMassTypeType ), intent(in ), optional :: massType - - sphericalScalerDensity=+self%massDistribution_%density ( & - & coordinates & - & /self%factorScalingLength, & - & componentType , & - & massType & - & ) & - & *self %factorScalingMass & - & /self %factorScalingLength**3 + class(massDistributionSphericalScaler), intent(inout) :: self + class(coordinate ), intent(in ) :: coordinates + class(coordinate ), allocatable :: coordinatesScaled + + if (self%factorScalingMass > 0.0d0) then + call coordinates%scale(1.0d0/self%factorScalingLength,coordinatesScaled) + sphericalScalerDensity=+self%massDistribution_%density (coordinatesScaled) & + & *self %factorScalingMass & + & /self %factorScalingLength**3 + else + sphericalScalerDensity=+0.0d0 + end if return end function sphericalScalerDensity - double precision function sphericalScalerDensityGradientRadial(self,coordinates,logarithmic,componentType,massType) + double precision function sphericalScalerDensityGradientRadial(self,coordinates,logarithmic) !!{ Return the density gradient in the radial direction in a scaled spherical mass distribution. !!} implicit none - class (massDistributionSphericalScaler), intent(inout) :: self - class (coordinate ), intent(in ) :: coordinates - logical , intent(in ), optional :: logarithmic - type (enumerationComponentTypeType ), intent(in ), optional :: componentType - type (enumerationMassTypeType ), intent(in ), optional :: massType + class (massDistributionSphericalScaler), intent(inout), target :: self + class (coordinate ), intent(in ) :: coordinates + logical , intent(in ), optional :: logarithmic + class (coordinate ) , allocatable :: coordinatesScaled !![ !!] - sphericalScalerDensityGradientRadial=+self%massDistribution_%densityGradientRadial( & - & coordinates & - & /self%factorScalingLength, & - & logarithmic , & - & componentType , & - & massType & + call coordinates%scale(1.0d0/self%factorScalingLength,coordinatesScaled) + sphericalScalerDensityGradientRadial=+self%massDistribution_%densityGradientRadial( & + & coordinatesScaled, & + & logarithmic & & ) if (.not.logarithmic) & & sphericalScalerDensityGradientRadial=+sphericalScalerDensityGradientRadial & @@ -193,48 +208,58 @@ double precision function sphericalScalerDensityGradientRadial(self,coordinates, return end function sphericalScalerDensityGradientRadial - double precision function sphericalScalerMassEnclosedBySphere(self,radius,componentType,massType) + double precision function sphericalScalerMassEnclosedBySphere(self,radius) !!{ Computes the mass enclosed within a sphere of given {\normalfont \ttfamily radius} for a scaled spherical mass distribution. !!} implicit none - class (massDistributionSphericalScaler), intent(inout), target :: self - double precision , intent(in ) :: radius - type (enumerationComponentTypeType ), intent(in ), optional :: componentType - type (enumerationMassTypeType ), intent(in ), optional :: massType - - sphericalScalerMassEnclosedBySphere=+self%massDistribution_%massEnclosedBySphere( & - & radius & - & /self%factorScalingLength, & - & componentType , & - & massType & - & ) & + class (massDistributionSphericalScaler), intent(inout), target :: self + double precision , intent(in ) :: radius + + sphericalScalerMassEnclosedBySphere=+self%massDistribution_%massEnclosedBySphere( & + & radius & + & /self%factorScalingLength & + & ) & & *self %factorScalingMass return end function sphericalScalerMassEnclosedBySphere - double precision function sphericalScalerPotential(self,coordinates,componentType,massType) + logical function sphericalScalerPotentialIsAnalytic(self) result(isAnalytic) + !!{ + Return that the potential has an analytic form. + !!} + implicit none + class(massDistributionSphericalScaler), intent(inout) :: self + + isAnalytic=self%massDistribution_%potentialIsAnalytic() + return + end function sphericalScalerPotentialIsAnalytic + + double precision function sphericalScalerPotential(self,coordinates,status) !!{ Return the potential at the specified {\normalfont \ttfamily coordinates} in a scaled spherical mass distribution. !!} + use :: Numerical_Constants_Astronomical, only : gravitationalConstantGalacticus implicit none - class(massDistributionSphericalScaler), intent(inout) :: self - class(coordinate ), intent(in ) :: coordinates - type (enumerationComponentTypeType ), intent(in ), optional :: componentType - type (enumerationMassTypeType ), intent(in ), optional :: massType - - sphericalScalerPotential=+self%massDistribution_%potential ( & - & coordinates & - & /self%factorScalingLength, & - & componentType , & - & massType & - & ) & - & *self %factorScalingMass & + class(massDistributionSphericalScaler ), intent(inout), target :: self + class(coordinate ), intent(in ) :: coordinates + type (enumerationStructureErrorCodeType), intent( out), optional :: status + class(coordinate ) , allocatable :: coordinatesScaled + + call coordinates%scale(1.0d0/self%factorScalingLength,coordinatesScaled) + sphericalScalerPotential=+self%massDistribution_%potential ( & + & coordinatesScaled, & + & status & + & ) & + & *self %factorScalingMass & & /self %factorScalingLength + if (self%massDistribution_%isDimensionless()) & + & sphericalScalerPotential=+sphericalScalerPotential & + & *gravitationalConstantGalacticus return end function sphericalScalerPotential - double precision function sphericalScalerDensityRadialMoment(self,moment,radiusMinimum,radiusMaximum,isInfinite,componentType,massType) + double precision function sphericalScalerDensityRadialMoment(self,moment,radiusMinimum,radiusMaximum,isInfinite) !!{ Computes radial moments of the density in a scaled spherical mass distribution. !!} @@ -243,13 +268,11 @@ double precision function sphericalScalerDensityRadialMoment(self,moment,radiusM double precision , intent(in ) :: moment double precision , intent(in ), optional :: radiusMinimum, radiusMaximum logical , intent( out), optional :: isInfinite - type (enumerationComponentTypeType ), intent(in ), optional :: componentType - type (enumerationMassTypeType ), intent(in ), optional :: massType sphericalScalerDensityRadialMoment=0.0d0 !![ - sphericalScalerDensityRadialMoment=self%massDistribution_%densityRadialMoment(moment=moment,isInfinite=isInfinite,componentType=componentType,massType=massType{conditions}) + sphericalScalerDensityRadialMoment=self%massDistribution_%densityRadialMoment(moment=moment,isInfinite=isInfinite{conditions}) @@ -260,97 +283,320 @@ double precision function sphericalScalerDensityRadialMoment(self,moment,radiusM return end function sphericalScalerDensityRadialMoment - double precision function sphericalScalerRadiusEnclosingMass(self,mass,componentType,massType) - !!{ - Computes the radius enclosing a given mass in a scaled spherical mass distribution. - !!} - implicit none - class (massDistributionSphericalScaler), intent(inout), target :: self - double precision , intent(in ) :: mass - type (enumerationComponentTypeType ), intent(in ), optional :: componentType - type (enumerationMassTypeType ), intent(in ), optional :: massType - - sphericalScalerRadiusEnclosingMass=+self%massDistribution_%radiusEnclosingMass( & - & + mass & - & /self%factorScalingMass, & - & componentType , & - & massType & - & ) & - & *self %factorScalingLength - return - end function sphericalScalerRadiusEnclosingMass - - double precision function sphericalScalerRadiusHalfMass(self,componentType,massType) + double precision function sphericalScalerRadiusHalfMass(self) !!{ Computes the half-mass radius in a scaled spherical mass distribution. !!} implicit none - class(massDistributionSphericalScaler), intent(inout) :: self - type (enumerationComponentTypeType ), intent(in ), optional :: componentType - type (enumerationMassTypeType ), intent(in ), optional :: massType + class(massDistributionSphericalScaler), intent(inout) :: self - sphericalScalerRadiusHalfMass=+self%massDistribution_%radiusHalfMass (componentType,massType) & + sphericalScalerRadiusHalfMass=+self%massDistribution_%radiusHalfMass () & & *self %factorScalingLength return end function sphericalScalerRadiusHalfMass - function sphericalScalerAcceleration(self,coordinates,componentType,massType) + function sphericalScalerAcceleration(self,coordinates) !!{ Computes the gravitational acceleration at {\normalfont \ttfamily coordinates} for spherically-symmetric mass distributions. !!} + use :: Numerical_Constants_Astronomical, only : gigaYear, gravitationalConstantGalacticus, megaParsec + use :: Numerical_Constants_Prefixes , only : kilo implicit none double precision , dimension(3) :: sphericalScalerAcceleration class (massDistributionSphericalScaler), intent(inout) :: self class (coordinate ), intent(in ) :: coordinates - type (enumerationComponentTypeType), intent(in ), optional :: componentType - type (enumerationMassTypeType ), intent(in ), optional :: massType - - sphericalScalerAcceleration=+self%massDistribution_%acceleration ( & - & coordinates & - & /self%factorScalingLength, & - & componentType , & - & massType & - & ) & - & *self %factorScalingMass & - & /self %factorScalingLength**2 + class (coordinate ), allocatable :: coordinatesScaled + + call coordinates%scale(1.0d0/self%factorScalingLength,coordinatesScaled) + sphericalScalerAcceleration=+self%massDistribution_%acceleration ( & + & coordinatesScaled & + & ) & + & *self %factorScalingMass & + & /self %factorScalingLength**2 + if (self%massDistribution_%isDimensionless()) & + & sphericalScalerAcceleration=+sphericalScalerAcceleration & + & *kilo & + & *gigaYear & + & /megaParsec & + & *gravitationalConstantGalacticus return end function sphericalScalerAcceleration - function sphericalScalerTidalTensor(self,coordinates,componentType,massType) + double precision function sphericalScalerDensitySphericalAverage(self,radius) + !!{ + Return the spherically-averaged density at the specified {\normalfont \ttfamily coordinates} in a scaled spherical mass + distribution. + !!} + implicit none + class (massDistributionSphericalScaler), intent(inout) :: self + double precision , intent(in ) :: radius + + sphericalScalerDensitySphericalAverage=+self%massDistribution_%densitySphericalAverage( & + & + radius & + & /self%factorScalingLength & + & ) & + & *self %factorScalingMass & + & /self %factorScalingLength**3 + return + end function sphericalScalerDensitySphericalAverage + + double precision function sphericalScalerRotationCurve(self,radius) + !!{ + Return the mid-plane rotation curve for a scaled spherical distribution. + !!} + use :: Numerical_Constants_Astronomical, only : gravitationalConstantGalacticus + implicit none + class (massDistributionSphericalScaler), intent(inout) :: self + double precision , intent(in ) :: radius + + sphericalScalerRotationCurve=+ self%massDistribution_%rotationCurve ( & + & + radius & + & /self%factorScalingLength & + & ) & + & *sqrt( & + & +self %factorScalingMass & + & /self %factorScalingLength & + & ) + if (self%massDistribution_%isDimensionless()) & + & sphericalScalerRotationCurve=+sphericalScalerRotationCurve & + & *sqrt(gravitationalConstantGalacticus) + return + end function sphericalScalerRotationCurve + + double precision function sphericalScalerRotationCurveGradient(self,radius) + !!{ + Return the mid-plane rotation curve gradient (specifically, $\mathrm{d}V^2_\mathrm{c}/\mathrm{d}r$) for a scaled spherical distribution. + !!} + use :: Numerical_Constants_Astronomical, only : gravitationalConstantGalacticus + implicit none + class (massDistributionSphericalScaler), intent(inout) :: self + double precision , intent(in ) :: radius + + sphericalScalerRotationCurveGradient=+self%massDistribution_%rotationCurveGradient( & + & + radius & + & /self%factorScalingLength & + & ) & + & *self%factorScalingMass & + & /self%factorScalingLength**2 + if (self%massDistribution_%isDimensionless()) & + & sphericalScalerRotationCurveGradient=+sphericalScalerRotationCurveGradient & + & *gravitationalConstantGalacticus + return + end function sphericalScalerRotationCurveGradient + + function sphericalScalerTidalTensor(self,coordinates) result(tidalTensor) !!{ Computes the gravitational tidal tensor at {\normalfont \ttfamily coordinates} in a scaled spherical mass distribution. !!} + use :: Numerical_Constants_Astronomical, only : gravitationalConstantGalacticus + use :: Coordinates , only : coordinateCartesian , assignment(=) implicit none - type (tensorRank2Dimension3Symmetric ) :: sphericalScalerTidalTensor - class(massDistributionSphericalScaler), intent(inout) :: self - class(coordinate ), intent(in ) :: coordinates - type (enumerationComponentTypeType ), intent(in ), optional :: componentType - type (enumerationMassTypeType ), intent(in ), optional :: massType - - sphericalScalerTidalTensor=+self%massDistribution_%tidalTensor ( & - & coordinates & - & /self%factorScalingLength, & - & componentType , & - & massType & - & ) & - & *self %factorScalingMass & - & /self %factorScalingLength**3 + type (tensorRank2Dimension3Symmetric ) :: tidalTensor + class(massDistributionSphericalScaler), intent(inout) :: self + class(coordinate ), intent(in ) :: coordinates + class(coordinate ), allocatable :: coordinatesScaled + type (coordinateCartesian ) :: position + + position=coordinates + if (any(position%position /= self%positionTidalTensorPrevious)) then + call coordinates%scale(1.0d0/self%factorScalingLength,coordinatesScaled) + self%tidalTensorPrevious=+self%massDistribution_%tidalTensor ( & + & coordinatesScaled & + & ) & + & *self %factorScalingMass & + & /self %factorScalingLength**3 + if (self%massDistribution_%isDimensionless()) & + & self%tidalTensorPrevious=+self%tidalTensorPrevious & + & *gravitationalConstantGalacticus + self%positionTidalTensorPrevious=position%position + end if + tidalTensor=self%tidalTensorPrevious return end function sphericalScalerTidalTensor - function sphericalScalerPositionSample(self,randomNumberGenerator_,componentType,massType) + function sphericalScalerPositionSample(self,randomNumberGenerator_) !!{ - Computes the half-mass radius of a spherically symmetric mass distribution in a scaled spherical mass distribution. + Sample a position from a scaled spherical mass distribution. !!} implicit none - double precision , dimension(3) :: sphericalScalerPositionSample - class (massDistributionSphericalScaler), intent(inout) :: self - class (randomNumberGeneratorClass ), intent(inout) :: randomNumberGenerator_ - type (enumerationComponentTypeType ), intent(in ), optional :: componentType - type (enumerationMassTypeType ), intent(in ), optional :: massType + double precision , dimension(3) :: sphericalScalerPositionSample + class (massDistributionSphericalScaler), intent(inout) :: self + class (randomNumberGeneratorClass ), intent(inout) :: randomNumberGenerator_ - sphericalScalerPositionSample=+self%massDistribution_%positionSample (randomNumberGenerator_,componentType,massType) & + sphericalScalerPositionSample=+self%massDistribution_%positionSample (randomNumberGenerator_) & & *self %factorScalingLength return end function sphericalScalerPositionSample + + double precision function sphericalScalerFourierTransform(self,radiusOuter,wavenumber) result(fourierTransform) + !!{ + Compute the Fourier transform of the density profile at the given {\normalfont \ttfamily wavenumber} in a spherical, scaled mass distribution. + !!} + implicit none + class (massDistributionSphericalScaler), intent(inout) :: self + double precision , intent(in ) :: radiusOuter , wavenumber + + fourierTransform=self%massDistribution_%fourierTransform(radiusOuter/self%factorScalingLength,wavenumber*self%factorScalingLength) + return + end function sphericalScalerFourierTransform + + double precision function sphericalScalerRadiusFreefall(self,time) result(radius) + !!{ + Compute the freefall radius at the given {\normalfont \ttfamily time} in a spherical mass distribution. + !!} + implicit none + class (massDistributionSphericalScaler), intent(inout) :: self + double precision , intent(in ) :: time + + radius=+self%massDistribution_%radiusFreefall( & + & +time & + & *sqrt( & + & +self%factorScalingMass & + & /self%factorScalingLength**3 & + & ) & + & ) & + & * self%factorScalingLength + return + end function sphericalScalerRadiusFreefall + + double precision function sphericalScalerRadiusFreefallIncreaseRate(self,time) result(radiusIncreaseRate) + !!{ + Compute the rate of increase of the freefall radius at the given {\normalfont \ttfamily time} in an spherical mass + distribution. + !!} + implicit none + class (massDistributionSphericalScaler), intent(inout) :: self + double precision , intent(in ) :: time + + radiusIncreaseRate=+self%massDistribution_%radiusFreefallIncreaseRate( & + & +time & + & *sqrt( & + & +self%factorScalingMass & + & /self%factorScalingLength**3 & + & ) & + & ) & + & * sqrt( & + & +self%factorScalingLength**5 & + & /self%factorScalingMass & + & ) + return + end function sphericalScalerRadiusFreefallIncreaseRate + + double precision function sphericalScalerEnergyPotential(self,radiusOuter) result(energy) + !!{ + Compute the potential energy within a given {\normalfont \ttfamily radius} in a spherical mass distribution. + !!} + implicit none + class (massDistributionSphericalScaler), intent(inout) :: self + double precision , intent(in ) :: radiusOuter + + energy =+self%massDistribution_%energyPotential(radiusOuter/self%factorScalingLength ) & + & * self%factorScalingMass **2 & + & / self%factorScalingLength + return + end function sphericalScalerEnergyPotential + + double precision function sphericalScalerVelocityRotationCurveMaximum(self) result(velocity) + !!{ + Return the peak velocity in the rotation curve for an spherical scaled mass distribution. + !!} + implicit none + class(massDistributionSphericalScaler), intent(inout) :: self + + velocity=+self%massDistribution_%velocityRotationCurveMaximum() & + & *sqrt( & + & +self%factorScalingMass & + & /self%factorScalingLength & + & ) + return + end function sphericalScalerVelocityRotationCurveMaximum + + double precision function sphericalScalerRadiusRotationCurveMaximum(self) result(radius) + !!{ + Return the peak velocity in the rotation curve for an spherical scaled mass distribution. + !!} + implicit none + class(massDistributionSphericalScaler), intent(inout), target :: self + + radius=+self%massDistribution_%radiusRotationCurveMaximum() & + & *self%factorScalingLength + return + end function sphericalScalerRadiusRotationCurveMaximum + + double precision function sphericalScalerRadiusEnclosingMass(self,mass,massFractional) result(radius) + !!{ + Computes the radius enclosing a given mass or mass fraction for spherical scaled mass distributions. + !!} + implicit none + class (massDistributionSphericalScaler), intent(inout), target :: self + double precision , intent(in ), optional :: mass, massFractional + + if (present(massFractional)) then + if (massFractional <= 0.0d0) then + radius=+0.0d0 + else + radius=+self%massDistribution_%radiusEnclosingMass(massFractional=massFractional ) + end if + else if (present(mass)) then + if (mass <= 0.0d0) then + radius=+0.0d0 + else + radius=+self%massDistribution_%radiusEnclosingMass(mass =mass *self%factorScalingMass) + end if + else + radius=+0.0d0 + call Error_Report('either "mass" or "massFractional" must be provided'//{introspection:location}) + end if + radius=+radius & + & *self%factorScalingLength + return + end function sphericalScalerRadiusEnclosingMass + + double precision function sphericalScalerRadiusEnclosingDensity(self,density,radiusGuess) result(radius) + !!{ + Computes the radius enclosing a given mean density for spherical scaled mass distributions. + !!} + implicit none + class (massDistributionSphericalScaler), intent(inout), target :: self + double precision , intent(in ) :: density + double precision , intent(in ), optional :: radiusGuess + + if (present(radiusGuess)) then + radius=+self%massDistribution_%radiusEnclosingDensity( & + & +density & + & *self%factorScalingLength**3 & + & /self%factorScalingMass , & + & +radiusGuess & + & /self%factorScalingLength & + & ) & + & * self%factorScalingLength + else + radius=+self%massDistribution_%radiusEnclosingDensity( & + & +density & + & *self%factorScalingLength**3 & + & /self%factorScalingMass & + & ) & + & * self%factorScalingLength + end if + return + end function sphericalScalerRadiusEnclosingDensity + + double precision function sphericalScalerRadiusFromSpecificAngularMomentum(self,angularMomentumSpecific) result(radius) + !!{ + Computes the radius corresponding to a given specific angular momentum for sphericalScaler mass distributions. + !!} + implicit none + class (massDistributionSphericalScaler), intent(inout), target :: self + double precision , intent(in ) :: angularMomentumSpecific + + radius=+self%massDistribution_%radiusFromSpecificAngularMomentum( & + & +angularMomentumSpecific & + & /sqrt( & + & +self%factorScalingMass & + & *self%factorScalingLength & + & ) & + & ) & + & * self%factorScalingLength + return + end function sphericalScalerRadiusFromSpecificAngularMomentum diff --git a/source/mass_distributions.spherical.truncated.F90 b/source/mass_distributions.spherical.truncated.F90 new file mode 100644 index 0000000000..fdd7605aa0 --- /dev/null +++ b/source/mass_distributions.spherical.truncated.F90 @@ -0,0 +1,293 @@ +!! Copyright 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, +!! 2019, 2020, 2021, 2022, 2023 +!! Andrew Benson +!! +!! This file is part of Galacticus. +!! +!! Galacticus is free software: you can redistribute it and/or modify +!! it under the terms of the GNU General Public License as published by +!! the Free Software Foundation, either version 3 of the License, or +!! (at your option) any later version. +!! +!! Galacticus is distributed in the hope that it will be useful, +!! but WITHOUT ANY WARRANTY; without even the implied warranty of +!! MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +!! GNU General Public License for more details. +!! +!! You should have received a copy of the GNU General Public License +!! along with Galacticus. If not, see . + + !!{ + Implements a truncated spherical mass distribution. + !!} + + !![ + + + Implements a mass distribution in which the density is given by + \begin{equation} + \rho(r) = \rho^\prime(r) \left\{ \begin{array}{ll} 1 & \hbox{ if } r < r_\mathrm{min}, \\ 0 & \hbox{ if } r > r_\mathrm{max}, \\ 1-3 x^2 + 2x^3 & \hbox{otherwise,} \end{array} \right. + \end{equation} + where + \begin{equation} + x=\frac{r-r_\mathrm{min}}{r_\mathrm{max}-r_\mathrm{min}}, + \end{equation} + $\rho^\prime(r)$ is some other density profile, $r_\mathrm{min}=${\normalfont \ttfamily [radiusTruncateMinimum]}, and $r_\mathrm{max}=${\normalfont \ttfamily [radiusTruncateMaximum]}. + + + !!] + type, extends(massDistributionSphericalDecorator) :: massDistributionSphericalTruncated + !!{ + Implementation of a truncated spherical mass distribution. + !!} + private + double precision :: radiusTruncateMinimum, radiusTruncateMaximum, & + & massAtTruncation , massTotal_ + contains + !![ + + + + !!] + final :: sphericalTruncatedDestructor + procedure :: density => sphericalTruncatedDensity + procedure :: densityGradientRadial => sphericalTruncatedDensityGradientRadial + procedure :: massTotal => sphericalTruncatedMassTotal + procedure :: massEnclosedBySphere => sphericalTruncatedMassEnclosedBySphere + procedure :: radiusEnclosingMass => sphericalTruncatedRadiusEnclosingMass + procedure :: truncationFunction => sphericalTruncatedTruncationFunction + end type massDistributionSphericalTruncated + + interface massDistributionSphericalTruncated + !!{ + Constructors for the {\normalfont \ttfamily sphericalTruncated} mass distribution class. + !!} + module procedure sphericalTruncatedConstructorParameters + module procedure sphericalTruncatedConstructorInternal + end interface massDistributionSphericalTruncated + +contains + + function sphericalTruncatedConstructorParameters(parameters) result(self) + !!{ + Constructor for the {\normalfont \ttfamily sphericalTruncated} mass distribution class which builds the object from a parameter + set. + !!} + use :: Input_Parameters , only : inputParameters + use :: Galactic_Structure_Options, only : enumerationComponentTypeEncode, enumerationMassTypeEncode + implicit none + type (massDistributionSphericalTruncated) :: self + type (inputParameters ), intent(inout) :: parameters + class (massDistributionClass ), pointer :: massDistribution_ + type (varying_string ) :: nonAnalyticSolver + double precision :: radiusTruncateMinimum, radiusTruncateMaximum + type (varying_string ) :: componentType , massType + + !![ + + radiusTruncateMinimum + parameters + The minimum radius to begin truncating the density profile. + + + radiusTruncateMaximum + parameters + The maximum radius to finish truncating the density profile. + + + nonAnalyticSolver + var_str('fallThrough') + parameters + Selects how solutions are computed when no analytic solution is available. + + + componentType + var_str('unknown') + The component type that this mass distribution represents. + parameters + + + massType + var_str('unknown') + The mass type that this mass distribution represents. + parameters + + + !!] + select type (massDistribution_) + class is (massDistributionSpherical) + self=massDistributionSphericalTruncated(radiusTruncateMinimum,radiusTruncateMaximum,enumerationNonAnalyticSolversEncode(char(nonAnalyticSolver),includesPrefix=.false.),massDistribution_,enumerationComponentTypeEncode(componentType,includesPrefix=.false.),enumerationMassTypeEncode(massType,includesPrefix=.false.)) + class default + call Error_Report('a spherically-symmetric mass distribution is required'//{introspection:location}) + end select + !![ + + + !!] + return + end function sphericalTruncatedConstructorParameters + + function sphericalTruncatedConstructorInternal(radiusTruncateMinimum,radiusTruncateMaximum,nonAnalyticSolver,massDistribution_,componentType,massType) result(self) + !!{ + Constructor for ``sphericalTruncated'' mass distribution class. + !!} + implicit none + type (massDistributionSphericalTruncated) :: self + class (massDistributionSpherical ), intent(in ), target :: massDistribution_ + type (enumerationNonAnalyticSolversType ), intent(in ) :: nonAnalyticSolver + double precision , intent(in ) :: radiusTruncateMinimum, radiusTruncateMaximum + type (enumerationComponentTypeType ), intent(in ), optional :: componentType + type (enumerationMassTypeType ), intent(in ), optional :: massType + !![ + + !!] + + self%massAtTruncation=self%massDistribution_%massEnclosedBySphere(radiusTruncateMinimum) + self%massTotal_ =self %massEnclosedBySphere(radiusTruncateMaximum) + self%dimensionless =self%massDistribution_%isDimensionless ( ) + return + end function sphericalTruncatedConstructorInternal + + subroutine sphericalTruncatedDestructor(self) + !!{ + Destructor for the abstract {\normalfont \ttfamily massDistributionSphericalTruncated} class. + !!} + implicit none + type(massDistributionSphericalTruncated), intent(inout) :: self + + !![ + + !!] + return + end subroutine sphericalTruncatedDestructor + + subroutine sphericalTruncatedTruncationFunction(self,radius,x,multiplier,multiplierGradient) + !!{ + Return the scaled truncation radial coordinate, and the truncation multiplier. + !!} + implicit none + class (massDistributionSphericalTruncated), intent(inout) :: self + double precision , intent(in ) :: radius + double precision , intent( out), optional :: x , multiplier, & + & multiplierGradient + double precision :: x_ + + if (radius <= self%radiusTruncateMinimum) then + if (present(x )) x =+0.0d0 + if (present(multiplier )) multiplier =+1.0d0 + if (present(multiplierGradient)) multiplierGradient=+0.0d0 + else if (radius >= self%radiusTruncateMaximum) then + if (present(x )) x =+1.0d0 + if (present(multiplier )) multiplier =+0.0d0 + if (present(multiplierGradient)) multiplierGradient=+0.0d0 + else + x_ =+(+ radius -self%radiusTruncateMinimum) & + & /(+self%radiusTruncateMaximum-self%radiusTruncateMinimum) + if (present(x )) x =+x_ + if (present(multiplier )) multiplier = +1.0d0 & + & -3.0d0*x_**2 & + & +2.0d0*x_**3 + if (present(multiplierGradient)) multiplierGradient=+( & + & -6.0d0*x_ & + & +6.0d0*x_**2 & + & ) & + & /(+self%radiusTruncateMaximum-self%radiusTruncateMinimum) + end if + return + end subroutine sphericalTruncatedTruncationFunction + + double precision function sphericalTruncatedDensity(self,coordinates) result(density) + !!{ + Return the density at the specified {\normalfont \ttfamily coordinates} in a scaled spherical mass distribution. + !!} + implicit none + class (massDistributionSphericalTruncated), intent(inout) :: self + class (coordinate ), intent(in ) :: coordinates + double precision :: multiplier + + call self%truncationFunction(radius=coordinates%rSpherical(),multiplier=multiplier) + density=+self%massDistribution_%density(coordinates) & + & * multiplier + return + end function sphericalTruncatedDensity + + double precision function sphericalTruncatedDensityGradientRadial(self,coordinates,logarithmic) result(densityGradient) + !!{ + Return the density at the specified {\normalfont \ttfamily coordinates} in a truncated spherical mass distribution. + !!} + implicit none + class (massDistributionSphericalTruncated), intent(inout), target :: self + class (coordinate ), intent(in ) :: coordinates + logical , intent(in ), optional :: logarithmic + double precision :: multiplier , multiplierGradient + !![ + + !!] + + call self%truncationFunction(radius=coordinates%rSpherical(),multiplier=multiplier,multiplierGradient=multiplierGradient) + if (multiplier > 0.0d0) then + densityGradient=+self%massDistribution_%densityGradientRadial(coordinates,logarithmic=.false.) & + & * multiplier & + & +self%massDistribution_%density (coordinates ) & + & * multiplierGradient + if (logarithmic_) densityGradient=+ densityGradient & + & *coordinates%rSpherical ( ) & + & /self %density (coordinates) + else + densityGradient=+0.0d0 + end if + return + end function sphericalTruncatedDensityGradientRadial + + double precision function sphericalTruncatedMassTotal(self) result(mass) + !!{ + Return the total mass in a truncated mass distribution. + !!} + implicit none + class(massDistributionSphericalTruncated), intent(inout) :: self + + mass=self%massTotal_ + return + end function sphericalTruncatedMassTotal + + double precision function sphericalTruncatedMassEnclosedBySphere(self,radius) result(mass) + !!{ + Computes the mass enclosed within a sphere of given {\normalfont \ttfamily radius} for truncated mass distributions. + !!} + implicit none + class (massDistributionSphericalTruncated), intent(inout), target :: self + double precision , intent(in ) :: radius + + if (radius <= self%radiusTruncateMinimum) then + mass=self%massDistribution_%massEnclosedBySphere (radius) + else + mass=self %massEnclosedBySphereNonAnalytic(radius) + end if + return + end function sphericalTruncatedMassEnclosedBySphere + + double precision function sphericalTruncatedRadiusEnclosingMass(self,mass,massFractional) result(radius) + !!{ + Computes the radius enclosing a given mass or mass fraction for truncated spherical mass distributions. + !!} + implicit none + class (massDistributionSphericalTruncated), intent(inout), target :: self + double precision , intent(in ), optional :: mass , massFractional + double precision :: mass_ + + if (present(mass)) then + mass_ =+ mass + else if (present(massFractional)) then + mass_ =+ massFractional & + & *self%massTotal () + else + mass_ =+0.0d0 + call Error_Report('either `mass` or `massFractional` must be provided'//{introspection:location}) + end if + if (mass_ <= self%massAtTruncation) then + radius=self%massDistribution_%radiusEnclosingMass (mass=mass_) + else + radius=self %radiusEnclosingMassNonAnalytic(mass=mass_) + end if + return + end function sphericalTruncatedRadiusEnclosingMass diff --git a/source/mass_distributions.spherical_shell_overdensities.F90 b/source/mass_distributions.spherical_shell_overdensities.F90 index 92d36a4d5e..9ba1dd1bc9 100644 --- a/source/mass_distributions.spherical_shell_overdensities.F90 +++ b/source/mass_distributions.spherical_shell_overdensities.F90 @@ -238,25 +238,19 @@ subroutine sphericalShellOverdensitiesDestructor(self) return end subroutine sphericalShellOverdensitiesDestructor - double precision function sphericalShellOverdensitiesDensity(self,coordinates,componentType,massType) + double precision function sphericalShellOverdensitiesDensity(self,coordinates) !!{ Return the density at the specified {\normalfont \ttfamily coordinates} in a cloud overdensities mass distribution. !!} use :: Arrays_Search, only : searchArrayClosest use :: Coordinates , only : assignment(=) , coordinateSpherical implicit none - class (massDistributionSphericalShellOverdensities), intent(inout) :: self - class (coordinate ), intent(in ) :: coordinates - type (enumerationComponentTypeType ), intent(in ), optional :: componentType - type (enumerationMassTypeType ), intent(in ), optional :: massType - integer (c_size_t ) :: i - type (coordinateSpherical ) :: position - double precision :: densityContrast, radius + class (massDistributionSphericalShellOverdensities), intent(inout) :: self + class (coordinate ), intent(in ) :: coordinates + integer (c_size_t ) :: i + type (coordinateSpherical ) :: position + double precision :: densityContrast, radius - if (.not.self%matches(componentType,massType)) then - sphericalShellOverdensitiesDensity=0.0d0 - return - end if ! Extract the position. position=coordinates radius =position %r() diff --git a/source/mass_distributions.truncated.exponential.F90 b/source/mass_distributions.truncated.exponential.F90 new file mode 100644 index 0000000000..61dfef3d4b --- /dev/null +++ b/source/mass_distributions.truncated.exponential.F90 @@ -0,0 +1,327 @@ +!! Copyright 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, +!! 2019, 2020, 2021, 2022, 2023 +!! Andrew Benson +!! +!! This file is part of Galacticus. +!! +!! Galacticus is free software: you can redistribute it and/or modify +!! it under the terms of the GNU General Public License as published by +!! the Free Software Foundation, either version 3 of the License, or +!! (at your option) any later version. +!! +!! Galacticus is distributed in the hope that it will be useful, +!! but WITHOUT ANY WARRANTY; without even the implied warranty of +!! MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +!! GNU General Public License for more details. +!! +!! You should have received a copy of the GNU General Public License +!! along with Galacticus. If not, see . + +!+ Contributions to this file made by: Xiaolong Du, Andrew Benson. + + !!{ + Implements an exponentially truncated spherical mass distribution \cite{kazantzidis_2006}. + !!} + + !![ + + + Implements an exponentially truncated mass distribution \cite{kazantzidis_2006} in which the density is given by + \begin{equation} + \rho(r) = \rho^\prime(r_\mathrm{min}) \left\{ \begin{array}{ll} 1 & \hbox{ if } r < r_\mathrm{min}, \\ \rho^\prime(r_\mathrm{min} x^\kappa \exp\left(-\frac{x-1}{x_\mathrm{max}}\right) & \hbox{otherwise,} \end{array} \right. + \end{equation} + where $x = r/r_\mathrm{min}$, $x_\mathrm{decay} = r_\mathrm{decay}/r_\mathrm{min}$, $\rho^\prime(r)$ is some other density + profile, $r_\mathrm{min}=${\normalfont \ttfamily [radiusTruncateMinimum]}, $r_\mathrm{decay}=${\normalfont \ttfamily + [radiusTruncateDecay]}, and + \begin{equation} + \kappa = \frac{r_\mathrm{min}}{r_\mathrm{decay}} + \frac{\mathrm{d}\log \rho^\prime}{\mathrm{d}\log r}(r_\mathrm{min}) + \end{equation} + is chosen to ensure that the logarithmic gradient of the density profile is continuous across $r=r_\mathrm{min}$. + + + !!] + type, extends(massDistributionSphericalDecorator) :: massDistributionSphericalTruncatedExponential + !!{ + Implementation of an exponentially-truncated spherical mass distribution. + !!} + private + double precision :: radiusTruncateMinimum , radiusTruncateDecay , & + & massAtTruncation , massTotal_ , & + & densityAtTruncation , kappa , & + & massEnclosedExponentialTerm, massEnclosedGammaFunctionTerm + contains + final :: sphericalTruncatedExponentialDestructor + procedure :: density => sphericalTruncatedExponentialDensity + procedure :: densityGradientRadial => sphericalTruncatedExponentialDensityGradientRadial + procedure :: massTotal => sphericalTruncatedExponentialMassTotal + procedure :: massEnclosedBySphere => sphericalTruncatedExponentialMassEnclosedBySphere + procedure :: radiusEnclosingMass => sphericalTruncatedExponentialRadiusEnclosingMass + end type massDistributionSphericalTruncatedExponential + + interface massDistributionSphericalTruncatedExponential + !!{ + Constructors for the {\normalfont \ttfamily sphericalTruncatedExponential} mass distribution class. + !!} + module procedure sphericalTruncatedExponentialConstructorParameters + module procedure sphericalTruncatedExponentialConstructorInternal + end interface massDistributionSphericalTruncatedExponential + +contains + + function sphericalTruncatedExponentialConstructorParameters(parameters) result(self) + !!{ + Constructor for the {\normalfont \ttfamily sphericalTruncatedExponential} mass distribution class which builds the object from a parameter + set. + !!} + use :: Input_Parameters , only : inputParameters + use :: Galactic_Structure_Options, only : enumerationComponentTypeEncode, enumerationMassTypeEncode + implicit none + type (massDistributionSphericalTruncatedExponential) :: self + type (inputParameters ), intent(inout) :: parameters + class (massDistributionClass ), pointer :: massDistribution_ + type (varying_string ) :: nonAnalyticSolver + double precision :: radiusTruncateMinimum, radiusTruncateDecay + type (varying_string ) :: componentType , massType + + !![ + + radiusTruncateMinimum + parameters + The minimum radius to begin truncating the density profile. + + + radiusTruncateDecay + parameters + The exponential decay scale for truncating the density profile. + + + nonAnalyticSolver + var_str('fallThrough') + parameters + Selects how solutions are computed when no analytic solution is available. + + + componentType + var_str('unknown') + The component type that this mass distribution represents. + parameters + + + massType + var_str('unknown') + The mass type that this mass distribution represents. + parameters + + + !!] + select type (massDistribution_) + class is (massDistributionSpherical) + self=massDistributionSphericalTruncatedExponential(radiusTruncateMinimum,radiusTruncateDecay,enumerationNonAnalyticSolversEncode(char(nonAnalyticSolver),includesPrefix=.false.),massDistribution_,enumerationComponentTypeEncode(componentType,includesPrefix=.false.),enumerationMassTypeEncode(massType,includesPrefix=.false.)) + class default + call Error_Report('a spherically-symmetric mass distribution is required'//{introspection:location}) + end select + !![ + + + !!] + return + end function sphericalTruncatedExponentialConstructorParameters + + function sphericalTruncatedExponentialConstructorInternal(radiusTruncateMinimum,radiusTruncateDecay,nonAnalyticSolver,massDistribution_,componentType,massType) result(self) + !!{ + Constructor for ``sphericalTruncatedExponential'' mass distribution class. + !!} + use :: Coordinates , only : coordinateSpherical , assignment(=) + use :: Gamma_Functions, only : Gamma_Function_Incomplete_Unnormalized + implicit none + type (massDistributionSphericalTruncatedExponential) :: self + class (massDistributionSpherical ), intent(in ), target :: massDistribution_ + type (enumerationNonAnalyticSolversType ), intent(in ) :: nonAnalyticSolver + double precision , intent(in ) :: radiusTruncateMinimum , radiusTruncateDecay + type (enumerationComponentTypeType ), intent(in ), optional :: componentType + type (enumerationMassTypeType ), intent(in ), optional :: massType + double precision , parameter :: fractionRadialDecayMaximum=50.0d0 + type (coordinateSpherical ) :: coordinatesTruncateMinimum + !![ + + !!] + + coordinatesTruncateMinimum =[self%radiusTruncateMinimum,0.0d0,0.0d0] + self%kappa =+self%radiusTruncateMinimum & + & /self%radiusTruncateDecay & + & +self%massDistribution_%densityGradientRadial( & + & coordinatesTruncateMinimum , & + & logarithmic=.true. & + & ) + self%massEnclosedExponentialTerm =+exp ( self%radiusTruncateMinimum/self%radiusTruncateDecay) & + & / ( self%radiusTruncateMinimum/self%radiusTruncateDecay)**self%kappa + self%massEnclosedGammaFunctionTerm=+Gamma_Function_Incomplete_Unnormalized(3.0d0+self%kappa,self%radiusTruncateMinimum/self%radiusTruncateDecay) + self%densityAtTruncation =+self%massDistribution_%density ( & + & coordinatesTruncateMinimum & + & ) + self%massAtTruncation =+self%massDistribution_%massEnclosedBySphere ( & + & +self%radiusTruncateMinimum & + & ) + self%massTotal_ =+self %massEnclosedBySphere ( & + & +self%radiusTruncateMinimum & + & + fractionRadialDecayMaximum & + & *self%radiusTruncateDecay & + & ) + self%dimensionless = self%massDistribution_%isDimensionless ( & + & ) + self%componentType = self%massDistribution_%componentType + self%massType = self%massDistribution_%massType + return + end function sphericalTruncatedExponentialConstructorInternal + + subroutine sphericalTruncatedExponentialDestructor(self) + !!{ + Destructor for the abstract {\normalfont \ttfamily massDistributionSphericalTruncatedExponential} class. + !!} + implicit none + type(massDistributionSphericalTruncatedExponential), intent(inout) :: self + + !![ + + !!] + return + end subroutine sphericalTruncatedExponentialDestructor + + double precision function sphericalTruncatedExponentialDensity(self,coordinates) result(density) + !!{ + Return the density at the specified {\normalfont \ttfamily coordinates} in an exponentially-truncated spherical mass distribution. + !!} + implicit none + class(massDistributionSphericalTruncatedExponential), intent(inout) :: self + class(coordinate ), intent(in ) :: coordinates + + if (coordinates%rSpherical() <= self%radiusTruncateMinimum) then + density=+self%massDistribution_%density(coordinates) + else + density=+self%densityAtTruncation & + & *( & + & +coordinates%rSpherical () & + & /self %radiusTruncateMinimum & + & )**self%kappa & + & *exp( & + & -( & + & +coordinates%rSpherical () & + & -self %radiusTruncateMinimum & + & ) & + & / self %radiusTruncateDecay & + & ) + end if + return + end function sphericalTruncatedExponentialDensity + + double precision function sphericalTruncatedExponentialDensityGradientRadial(self,coordinates,logarithmic) result(densityGradient) + !!{ + Return the density gradient at the specified {\normalfont \ttfamily coordinates} in an exponentially-truncated spherical mass distribution. + !!} + use :: Error, only : Error_Report + implicit none + class (massDistributionSphericalTruncatedExponential), intent(inout), target :: self + class (coordinate ), intent(in ) :: coordinates + logical , intent(in ), optional :: logarithmic + double precision :: density + !![ + + !!] + + if (coordinates%rSpherical() <= self%radiusTruncateMinimum) then + densityGradient=+self%massDistribution_%densityGradientRadial(coordinates,logarithmic) + else + densityGradient=+self%densityAtTruncation & + & *( & + & +coordinates%rSpherical () & + & /self %radiusTruncateMinimum & + & )**self%kappa & + & *exp( & + & -( & + & +coordinates%rSpherical () & + & -self %radiusTruncateMinimum & + & ) & + & / self %radiusTruncateDecay & + & ) & + & *( & + & +self %kappa & + & -coordinates%rSpherical () & + & /self %radiusTruncateDecay & + & ) & + & / coordinates%rSpherical () + if (logarithmic_) then + density=self%density(coordinates) + if (density > 0.0d0) then + densityGradient=+ densityGradient & + & *coordinates%rSpherical ( ) & + & /self %density (coordinates) + else if (densityGradient /= 0.0d0) then + call Error_Report('density is zero, but gradient is non-zero - logarithmic gradient is undefined'//{introspection:location}) + end if + end if + end if + return + end function sphericalTruncatedExponentialDensityGradientRadial + + double precision function sphericalTruncatedExponentialMassTotal(self) result(mass) + !!{ + Return the total mass in a truncated mass distribution. + !!} + implicit none + class(massDistributionSphericalTruncatedExponential), intent(inout) :: self + + mass=self%massTotal_ + return + end function sphericalTruncatedExponentialMassTotal + + double precision function sphericalTruncatedExponentialMassEnclosedBySphere(self,radius) result(mass) + !!{ + Computes the mass enclosed within a sphere of given {\normalfont \ttfamily radius} for truncatedExponential mass distributions. + !!} + use :: Gamma_Functions, only : Gamma_Function_Incomplete_Unnormalized + implicit none + class (massDistributionSphericalTruncatedExponential), intent(inout), target :: self + double precision , intent(in ) :: radius + + if (radius <= self%radiusTruncateMinimum) then + mass =+self%massDistribution_%massEnclosedBySphere(radius) + else + mass =+self%massAtTruncation & + & +4.0d0 & + & *Pi & + & *self%densityAtTruncation & + & *self%radiusTruncateDecay**3 & + & *self%massEnclosedExponentialTerm & + & *( & + & +self%massEnclosedGammaFunctionTerm & + & -Gamma_Function_Incomplete_Unnormalized(3.0d0+self%kappa,radius/self%radiusTruncateDecay) & + & ) + end if + return + end function sphericalTruncatedExponentialMassEnclosedBySphere + + double precision function sphericalTruncatedExponentialRadiusEnclosingMass(self,mass,massFractional) result(radius) + !!{ + Computes the radius enclosing a given mass or mass fraction for truncatedExponential spherical mass distributions. + !!} + implicit none + class (massDistributionSphericalTruncatedExponential), intent(inout), target :: self + double precision , intent(in ), optional :: mass , massFractional + double precision :: mass_ + + if (present(mass)) then + mass_ =+ mass + else if (present(massFractional)) then + mass_ =+ massFractional & + & *self%massTotal () + else + mass_ =+0.0d0 + call Error_Report('either `mass` or `massFractional` must be provided'//{introspection:location}) + end if + if (mass_ <= self%massAtTruncation) then + radius=self%massDistribution_%radiusEnclosingMass (mass=mass_) + else + radius=self %radiusEnclosingMassNonAnalytic(mass=mass_) + end if + return + end function sphericalTruncatedExponentialRadiusEnclosingMass diff --git a/source/math.dilogarithm.F90 b/source/math.dilogarithm.F90 index 94f573fb28..750439bf5d 100644 --- a/source/math.dilogarithm.F90 +++ b/source/math.dilogarithm.F90 @@ -28,7 +28,8 @@ module Dilogarithms !!{ Implements dilogarithms. !!} - use, intrinsic :: ISO_C_Binding, only : c_double + use, intrinsic :: ISO_C_Binding, only : c_double, c_int + use :: Interface_GSL, only : gsl_sf_result implicit none private public :: Dilogarithm @@ -42,19 +43,51 @@ function gsl_sf_dilog(x) bind(c,name='gsl_sf_dilog') real(c_double) :: gsl_sf_dilog real(c_double), value :: x end function gsl_sf_dilog + + function gsl_sf_complex_dilog_e(x,y,a,b) bind(c,name='gsl_sf_complex_dilog_e') + !!{ + Template for the GSL complex dilogarithm C function. + !!} + import + integer(c_int ) :: gsl_sf_complex_dilog_e + real (c_double ), value :: x , y + type (gsl_sf_result) :: a , b + end function gsl_sf_complex_dilog_e end interface + interface Dilogarithm + module procedure Dilogarithm_Real + module procedure Dilogarithm_Complex + end interface Dilogarithm + contains - double precision function Dilogarithm(x) + double precision function Dilogarithm_Real(x) !!{ Evaluate the $\hbox{Si}(x)\equiv\int_0^x \d t \sin(t)/t$ sine integral. !!} implicit none double precision, intent(in ) :: x - Dilogarithm=GSL_SF_Dilog(x) + Dilogarithm_Real=GSL_SF_Dilog(x) + return + end function Dilogarithm_Real + + double complex function Dilogarithm_Complex(x) + !!{ + Evaluate the dilogarithm for complex argument. + !!} + implicit none + double complex , intent(in ) :: x + type (gsl_sf_result) :: a , b + real (c_double ) :: r , theta + integer (c_int ) :: status + + r =sqrt (imag(x)**2+real(x)**2) + theta =atan2(imag(x) ,real(x) ) + status =GSL_SF_Complex_Dilog_E(r,theta,a,b) + Dilogarithm_Complex=dcmplx(a%val,b%val) return - end function Dilogarithm + end function Dilogarithm_Complex end module Dilogarithms diff --git a/source/math.gamma_function.F90 b/source/math.gamma_function.F90 index 5f4c973360..8fc929fa7d 100644 --- a/source/math.gamma_function.F90 +++ b/source/math.gamma_function.F90 @@ -114,25 +114,25 @@ double precision function Gamma_Function_Incomplete_Complementary(exponent,argum return end function Gamma_Function_Incomplete_Complementary - double precision function Gamma_Function(argument) + double precision function Gamma_Function(exponent) !!{ Computes the Gamma function. !!} implicit none - double precision, intent(in ) :: argument + double precision, intent(in ) :: exponent - Gamma_Function=GSL_SF_Gamma(argument) + Gamma_Function=GSL_SF_Gamma(exponent) return end function Gamma_Function - double precision function Gamma_Function_Logarithmic(argument) + double precision function Gamma_Function_Logarithmic(exponent) !!{ Computes the logarithm of the Gamma function. !!} implicit none - double precision, intent(in ) :: argument + double precision, intent(in ) :: exponent - Gamma_Function_Logarithmic=GSL_SF_lnGamma(argument) + Gamma_Function_Logarithmic=GSL_SF_lnGamma(exponent) return end function Gamma_Function_Logarithmic diff --git a/source/math.hypergeometric_functions.F90 b/source/math.hypergeometric_functions.F90 index c5d7f9ddc3..43195b7564 100644 --- a/source/math.hypergeometric_functions.F90 +++ b/source/math.hypergeometric_functions.F90 @@ -35,7 +35,8 @@ module Hypergeometric_Functions use :: Interface_GSL, only : gsl_sf_result, gsl_success implicit none private - public :: Hypergeometric_1F1, Hypergeometric_2F1, Hypergeometric_pFq, Hypergeometric_pFq_Regularized + public :: Hypergeometric_1F1 , Hypergeometric_2F1, Hypergeometric_pFq, Hypergeometric_pFq_Regularized, & + & Hypergeometric_2F1_Regularized interface Hypergeometric_pFq module procedure :: Hypergeometric_pFq_Real @@ -222,6 +223,20 @@ double precision function Hypergeometric_2F1(a,b,x,status,error,toleranceRelativ return end function Hypergeometric_2F1 + double precision function Hypergeometric_2F1_Regularized(a,b,x) + !!{ + Evaluate the regularized generalized hypergeometric function + $_2F_1(a_1,a_2;b_1;x)/\Gamma(b_1)$ for real arguments. + !!} + implicit none + double precision, intent(in ), dimension(2) :: a + double precision, intent(in ), dimension(1) :: b + double precision, intent(in ) :: x + + Hypergeometric_2F1_Regularized=Hypergeometric_2F1(a,b,x)/Gamma(b(1)) + return + end function Hypergeometric_2F1_Regularized + double complex function Hypergeometric_pFq_Complex(a,b,x,toleranceRelative) !!{ Evaluate the generalized hypergeometric function $_pF_q(a_1,\ldots,a_p;b_1,\ldots,b_q;x)$, using the algorithm of diff --git a/source/merger_trees.construct.build.masses.fixed_mass.F90 b/source/merger_trees.construct.build.masses.fixed_mass.F90 index e64c9c3882..e5ec6737c2 100644 --- a/source/merger_trees.construct.build.masses.fixed_mass.F90 +++ b/source/merger_trees.construct.build.masses.fixed_mass.F90 @@ -22,7 +22,6 @@ !!} use :: Cosmology_Parameters , only : cosmologyParametersClass use :: Dark_Matter_Halo_Scales , only : darkMatterHaloScaleClass - use :: Galactic_Structure , only : galacticStructureClass use :: Nodes_Operators , only : nodeOperatorClass use :: Numerical_Random_Numbers , only : randomNumberGeneratorClass @@ -39,7 +38,6 @@ class (cosmologyParametersClass ), pointer :: cosmologyParameters_ => null() class (darkMatterHaloScaleClass ), pointer :: darkMatterHaloScale_ => null() class (nodeOperatorClass ), pointer :: nodeOperator_ => null() - class (galacticStructureClass ), pointer :: galacticStructure_ => null() class (randomNumberGeneratorClass), pointer :: randomNumberGenerator_ => null() double precision , allocatable, dimension(:) :: massTree , radiusTree integer , allocatable, dimension(:) :: treeCount @@ -72,7 +70,6 @@ function fixedMassConstructorParameters(parameters) result(self) class (darkMatterHaloScaleClass ), pointer :: darkMatterHaloScale_ class (nodeOperatorClass ), pointer :: nodeOperator_ class (randomNumberGeneratorClass ), pointer :: randomNumberGenerator_ - class (galacticStructureClass ), pointer :: galacticStructure_ double precision :: massIntervalFractional integer :: fixedHalosCount @@ -146,21 +143,19 @@ function fixedMassConstructorParameters(parameters) result(self) - !!] - self=mergerTreeBuildMassesFixedMass(massTree,radiusTree,treeCount,massIntervalFractional,cosmologyParameters_,darkMatterHaloScale_,nodeOperator_,randomNumberGenerator_,galacticStructure_) + self=mergerTreeBuildMassesFixedMass(massTree,radiusTree,treeCount,massIntervalFractional,cosmologyParameters_,darkMatterHaloScale_,nodeOperator_,randomNumberGenerator_) !![ - !!] return end function fixedMassConstructorParameters - function fixedMassConstructorInternal(massTree,radiusTree,treeCount,massIntervalFractional,cosmologyParameters_,darkMatterHaloScale_,nodeOperator_,randomNumberGenerator_,galacticStructure_) result(self) + function fixedMassConstructorInternal(massTree,radiusTree,treeCount,massIntervalFractional,cosmologyParameters_,darkMatterHaloScale_,nodeOperator_,randomNumberGenerator_) result(self) !!{ Internal constructor for the {\normalfont \ttfamily fixedMass} merger tree masses class. !!} @@ -173,9 +168,8 @@ function fixedMassConstructorInternal(massTree,radiusTree,treeCount,massInterval class (darkMatterHaloScaleClass ), intent(in ), target :: darkMatterHaloScale_ class (nodeOperatorClass ), intent(in ), target :: nodeOperator_ class (randomNumberGeneratorClass ), intent(in ), target :: randomNumberGenerator_ - class (galacticStructureClass ), intent(in ), target :: galacticStructure_ !![ - + !!] return @@ -193,7 +187,6 @@ subroutine fixedMassDestructor(self) - !!] return end subroutine fixedMassDestructor @@ -286,24 +279,26 @@ double precision function massEnclosed(massTree) Root finding function used to set the halo mass given the halo radius. !!} use :: Galactic_Structure_Options, only : massTypeDark + use :: Mass_Distributions , only : massDistributionClass implicit none - double precision, intent(in ) :: massTree + double precision , intent(in ) :: massTree + class (massDistributionClass), pointer :: massDistribution_ call basic %massSet (massTree) call self %nodeOperator_%nodeTreeInitialize(node ) call self %nodeOperator_%nodeInitialize (node ) call Calculations_Reset(node) - massEnclosed=+ self%galacticStructure_ %massEnclosed( & - & node , & - & self%radiusTree(i), & - & massType=massTypeDark & - & ) & - & * self%cosmologyParameters_%OmegaMatter() & - & /( & - & +self%cosmologyParameters_%OmegaMatter() & - & -self%cosmologyParameters_%OmegaBaryon() & - & ) & - & -self%massTree(i) + massDistribution_ => node %massDistribution (massType= massTypeDark ) + massEnclosed = + massDistribution_ %massEnclosedBySphere( self%radiusTree (i)) & + & * self %cosmologyParameters_%OmegaMatter() & + & /( & + & +self %cosmologyParameters_%OmegaMatter() & + & -self %cosmologyParameters_%OmegaBaryon() & + & ) & + & - self%massTree (i) + !![ + + !!] return end function massEnclosed diff --git a/source/merger_trees.construct.read.F90 b/source/merger_trees.construct.read.F90 index 0f2458e8c0..602f393417 100644 --- a/source/merger_trees.construct.read.F90 +++ b/source/merger_trees.construct.read.F90 @@ -2207,15 +2207,17 @@ subroutine readAssignAngularMomenta(self,nodes,nodeList) use :: Error , only : Error_Report use :: Galacticus_Nodes , only : nodeComponentBasic, nodeComponentSpin, treeNodeList use :: Merger_Tree_Read_Importers, only : nodeData - implicit none + use :: Mass_Distributions , only : massDistributionClass + implicit none class (mergerTreeConstructorRead) , intent(inout) :: self class (nodeData ) , dimension(:), intent(inout) :: nodes type (treeNodeList ) , dimension(:), intent(inout) :: nodeList class (nodeComponentBasic ), pointer :: basic class (nodeComponentSpin ), pointer :: spin + class (massDistributionClass ), pointer :: massDistribution_ integer :: iNode integer (c_size_t ) :: iIsolatedNode - double precision :: angularMomentum + double precision :: angularMomentum , radiusVirial double precision , dimension(3) :: angularMomentum3D do iNode=1,size(nodes) @@ -2223,8 +2225,10 @@ subroutine readAssignAngularMomenta(self,nodes,nodeList) if (nodes(iNode)%isolatedNodeIndex /= nodeReachabilityUnreachable%ID) then iIsolatedNode=nodes(iNode)%isolatedNodeIndex ! Get basic and spin components. - basic => nodeList(iIsolatedNode)%node%basic( ) - spin => nodeList(iIsolatedNode)%node%spin (autoCreate=.true.) + basic => nodeList(iIsolatedNode)%node%basic( ) + spin => nodeList(iIsolatedNode)%node%spin (autoCreate=.true.) + radiusVirial = self%darkMatterHaloScale_ %radiusVirial(nodeList(iIsolatedNode)%node ) + massDistribution_ => self%darkMatterProfileDMO_%get (nodeList(iIsolatedNode)%node ) if (self%presetAngularMomenta ) then if (self%mergerTreeImporter_%angularMomentaAvailable()) then ! If angular momenta are available directly, use them. @@ -2251,6 +2255,9 @@ subroutine readAssignAngularMomenta(self,nodes,nodeList) call Error_Report('no method exists to set vector angular momenta'//{introspection:location}) end if end if + !![ + + !!] end if end do return @@ -2263,9 +2270,9 @@ double precision function spinNormalization() !!} use :: Numerical_Constants_Astronomical, only : gravitationalConstantGalacticus implicit none - spinNormalization=+gravitationalConstantGalacticus & - & *basic%mass()**2.5d0 & - & /sqrt(abs(self%darkMatterProfileDMO_%energy(nodeList(iIsolatedNode)%node))) + spinNormalization=+gravitationalConstantGalacticus & + & *basic%mass()**2.5d0 & + & /sqrt(abs(massDistribution_%energy(radiusVirial,massDistribution_))) return end function spinNormalization @@ -2310,13 +2317,21 @@ double precision function readRadiusHalfMassRoot(radius) !!{ Function used to find scale radius of dark matter halos given their half-mass radius. !!} + use :: Calculations_Resets, only : Calculations_Reset + use :: Mass_Distributions , only : massDistributionClass implicit none - double precision, intent(in ) :: radius + double precision , intent(in ) :: radius + class (massDistributionClass), pointer :: massDistribution_ ! Set scale radius to current guess. call darkMatterProfile_%scaleSet(radius) + call Calculations_Reset(node_) ! Compute difference between mass fraction enclosed at half mass radius and one half. - readRadiusHalfMassRoot=self_%darkMatterProfileDMO_%enclosedMass(node_,radiusHalfMass_)/basic_%mass()-0.50d0 + massDistribution_ => self_ %darkMatterProfileDMO_%get(node_ ) + readRadiusHalfMassRoot = massDistribution_%massEnclosedBySphere (radiusHalfMass_)/basic_%mass()-0.5d0 + !![ + + !!] return end function readRadiusHalfMassRoot @@ -3363,6 +3378,7 @@ subroutine readTimeUntilMergingSubresolution(self,lastSeenNode,nodes,nodeList,iN use :: Merger_Tree_Read_Importers, only : nodeData use :: String_Handling , only : operator(//) use :: Vectors , only : Vector_Magnitude + use :: Calculations_Resets , only : Calculations_Reset implicit none class (mergerTreeConstructorRead) , intent(inout) :: self class (nodeData ) , intent(in ) :: lastSeenNode @@ -3457,6 +3473,10 @@ subroutine readTimeUntilMergingSubresolution(self,lastSeenNode,nodes,nodeList,iN end if satelliteNode%parent => hostNode hostNode %firstSatellite => satelliteNode + ! Perform a calculation reset as technically these nodes have changed. (Specifically, they may have the same unique + ! ID as the prior time this function was called, and yet be new copies. Not resetting calculations could result in + ! the old - now destroyed - copies of these nodes being accessed.) + call Calculations_Reset(satelliteNode) ! Determine the time until merging. timeUntilMerging=self%satelliteMergingTimescales_%timeUntilMerging(satelliteNode,orbit) ! Clean up. diff --git a/source/merger_trees.evolve.timesteps.history.F90 b/source/merger_trees.evolve.timesteps.history.F90 index 359572e913..f1a0e9ddb8 100644 --- a/source/merger_trees.evolve.timesteps.history.F90 +++ b/source/merger_trees.evolve.timesteps.history.F90 @@ -22,7 +22,6 @@ !!} use :: Cosmology_Functions , only : cosmologyFunctions , cosmologyFunctionsClass - use :: Galactic_Structure , only : galacticStructureClass use :: Numerical_Interpolation , only : interpolator use :: Star_Formation_Rates_Disks , only : starFormationRateDisksClass use :: Star_Formation_Rates_Spheroids, only : starFormationRateSpheroidsClass @@ -67,7 +66,6 @@ function of time. Timesteps are enforced such that: class (cosmologyFunctionsClass ), pointer :: cosmologyFunctions_ => null() class (starFormationRateDisksClass ), pointer :: starFormationRateDisks_ => null() class (starFormationRateSpheroidsClass), pointer :: starFormationRateSpheroids_ => null() - class (galacticStructureClass ), pointer :: galacticStructure_ => null() integer :: historyCount double precision :: timeBegin , timeEnd double precision , allocatable, dimension(:) :: rateStarFormationDisk , densityStellarDisk , & @@ -104,7 +102,6 @@ function historyConstructorParameters(parameters) result(self) class (cosmologyFunctionsClass ), pointer :: cosmologyFunctions_ class (starFormationRateDisksClass ), pointer :: starFormationRateDisks_ class (starFormationRateSpheroidsClass), pointer :: starFormationRateSpheroids_ - class (galacticStructureClass ), pointer :: galacticStructure_ integer :: historyCount double precision :: timeBegin , timeEnd, & & ageUniverse @@ -113,7 +110,6 @@ function historyConstructorParameters(parameters) result(self) - !!] ageUniverse=cosmologyFunctions_%cosmicTime(1.0d0) !![ @@ -136,18 +132,17 @@ function historyConstructorParameters(parameters) result(self) parameters !!] - self=mergerTreeEvolveTimestepHistory(historyCount,timeBegin,timeEnd,cosmologyFunctions_,starFormationRateDisks_,starFormationRateSpheroids_,galacticStructure_) + self=mergerTreeEvolveTimestepHistory(historyCount,timeBegin,timeEnd,cosmologyFunctions_,starFormationRateDisks_,starFormationRateSpheroids_) !![ - !!] return end function historyConstructorParameters - function historyConstructorInternal(historyCount,timeBegin,timeEnd,cosmologyFunctions_,starFormationRateDisks_,starFormationRateSpheroids_,galacticStructure_) result(self) + function historyConstructorInternal(historyCount,timeBegin,timeEnd,cosmologyFunctions_,starFormationRateDisks_,starFormationRateSpheroids_) result(self) !!{ Constructor for the {\normalfont \ttfamily history} merger tree evolution timestep class which takes a parameter set as input. !!} @@ -160,10 +155,9 @@ function historyConstructorInternal(historyCount,timeBegin,timeEnd,cosmologyFunc class (cosmologyFunctionsClass ), intent(in ), target :: cosmologyFunctions_ class (starFormationRateDisksClass ), intent(in ), target :: starFormationRateDisks_ class (starFormationRateSpheroidsClass), intent(in ), target :: starFormationRateSpheroids_ - class (galacticStructureClass ), intent(in ), target :: galacticStructure_ integer (c_size_t ) :: timeIndex !![ - + !!] ! Allocate storage arrays. @@ -221,7 +215,6 @@ subroutine historyDestructor(self) - !!] return end subroutine historyDestructor @@ -271,11 +264,12 @@ subroutine historyStore(self,tree,node,deadlockStatus) !!{ Store various properties in global arrays. !!} - use :: Galactic_Structure_Options, only : componentTypeDisk, componentTypeHotHalo, componentTypeSpheroid, massTypeGaseous , & + use :: Galactic_Structure_Options, only : componentTypeDisk , componentTypeHotHalo, componentTypeSpheroid, massTypeGaseous , & & massTypeStellar use :: Error , only : Error_Report - use :: Galacticus_Nodes , only : mergerTree , nodeComponentBasic , nodeComponentDisk , nodeComponentSpheroid, & - & treeNode + use :: Galacticus_Nodes , only : mergerTree , nodeComponentBasic , nodeComponentDisk , nodeComponentSpheroid, & + & treeNode + use :: Mass_Distributions , only : massDistributionClass use, intrinsic :: ISO_C_Binding , only : c_size_t implicit none class (* ), intent(inout) :: self @@ -285,9 +279,12 @@ subroutine historyStore(self,tree,node,deadlockStatus) class (nodeComponentBasic ) , pointer :: basic class (nodeComponentDisk ) , pointer :: disk class (nodeComponentSpheroid ) , pointer :: spheroid + class (massDistributionClass ) , pointer :: massDistributionHotHalo_ , massDistributionGaseous_ , & + & massDistributionStellarDisk_, massDistributionStellarSpheroid_, & + & massDistributionStellar_ integer (c_size_t ) :: timeIndex - double precision :: rateStarFormationDisk , massHotGas, & - & rateStarFormationSpheroid, time + double precision :: rateStarFormationDisk , massHotGas, & + & rateStarFormationSpheroid , time !$GLC attributes unused :: deadlockStatus select type (self) @@ -309,42 +306,57 @@ subroutine historyStore(self,tree,node,deadlockStatus) rateStarFormationSpheroid=self%starFormationRateSpheroids_%rate(node) ! Accumulate the properties. ! Star formation rate: - self%rateStarFormation (timeIndex)=+ self %rateStarFormation (timeIndex ) & - & +(+rateStarFormationDisk+rateStarFormationSpheroid) & - & * tree %volumeWeight - self%rateStarFormationDisk (timeIndex)=+ self %rateStarFormationDisk (timeIndex ) & - & + rateStarFormationDisk & - & * tree %volumeWeight - self%rateStarFormationSpheroid (timeIndex)=+ self %rateStarFormationSpheroid(timeIndex ) & - & + rateStarFormationSpheroid & - & * tree %volumeWeight + self%rateStarFormation (timeIndex) = + self %rateStarFormation (timeIndex ) & + & +(+rateStarFormationDisk+rateStarFormationSpheroid) & + & * tree %volumeWeight + self%rateStarFormationDisk (timeIndex) = + self %rateStarFormationDisk (timeIndex ) & + & + rateStarFormationDisk & + & * tree %volumeWeight + self%rateStarFormationSpheroid (timeIndex) = + self %rateStarFormationSpheroid(timeIndex ) & + & + rateStarFormationSpheroid & + & * tree %volumeWeight ! Stellar densities. - self%densityStellar (timeIndex)=+ self %densityStellar (timeIndex ) & - & + self%galacticStructure_%massEnclosed (node, massType=massTypeStellar) & - & * tree %volumeWeight - self%densityStellarDisk (timeIndex)=+ self %densityStellarDisk (timeIndex ) & - & + self%galacticStructure_%massEnclosed (node,componentType=componentTypeDisk ,massType=massTypeStellar) & - & * tree %volumeWeight - self%densityStellarSpheroid (timeIndex)=+ self %densityStellarSpheroid (timeIndex ) & - & + self%galacticStructure_%massEnclosed (node,componentType=componentTypeSpheroid,massType=massTypeStellar) & - & * tree %volumeWeight - + massDistributionStellar_ => node %massDistribution ( massType=massTypeStellar) + massDistributionStellarDisk_ => node %massDistribution (componentType=componentTypeDisk ,massType=massTypeStellar) + massDistributionStellarSpheroid_ => node %massDistribution (componentType=componentTypeSpheroid,massType=massTypeStellar) + self%densityStellar (timeIndex) = + self %densityStellar (timeIndex ) & + & + massDistributionStellar_ %massTotal ( ) & + & * tree %volumeWeight + self%densityStellarDisk (timeIndex) = + self %densityStellarDisk (timeIndex ) & + & + massDistributionStellarDisk_ %massTotal ( ) & + & * tree %volumeWeight + self%densityStellarSpheroid (timeIndex) = + self %densityStellarSpheroid(timeIndex ) & + & + massDistributionStellarSpheroid_%massTotal ( ) & + & * tree %volumeWeight + !![ + + + + !!] ! Hot gas density. - massHotGas =+ self%galacticStructure_%massEnclosed (node,componentType=componentTypeHotHalo ) - self%densityHotHaloGas (timeIndex)=+ self %densityHotHaloGas (timeIndex ) & - & +massHotGas & - & * tree %volumeWeight + massDistributionHotHalo_ => node %massDistribution (componentType=componentTypeHotHalo ) + massHotGas = + massDistributionHotHalo_ %massTotal ( ) + self%densityHotHaloGas (timeIndex) = + self %densityHotHaloGas (timeIndex ) & + & + massHotGas & + & * tree %volumeWeight + !![ + + !!] ! Galactic gas density. - self%densityColdGas (timeIndex)=+ self %densityColdGas (timeIndex ) & - & +( & - & +self%galacticStructure_%massEnclosed (node,massType=massTypeGaseous ) & - & -massHotGas & - & ) & - & *tree %volumeWeight + massDistributionGaseous_ => node %massDistribution ( massType=massTypeGaseous) + self%densityColdGas (timeIndex) = + self %densityColdGas (timeIndex ) & + & +( & + & +massDistributionGaseous_ %massTotal ( ) & + & -massHotGas & + & ) & + & * tree %volumeWeight + !![ + + !!] ! Node density - if (.not.node%isSatellite()) self%densityNode(timeIndex)=+ self %densityNode (timeIndex ) & - & + basic %mass ( ) & - & * tree %volumeWeight + if (.not.node%isSatellite()) self%densityNode(timeIndex)=+ self %densityNode (timeIndex ) & + & + basic %mass ( ) & + & * tree %volumeWeight class default call Error_Report('incorrect class'//{introspection:location}) end select diff --git a/source/merger_trees.evolve.timesteps.record_evolution.F90 b/source/merger_trees.evolve.timesteps.record_evolution.F90 index ea97fc416d..10fd25f4c1 100644 --- a/source/merger_trees.evolve.timesteps.record_evolution.F90 +++ b/source/merger_trees.evolve.timesteps.record_evolution.F90 @@ -23,7 +23,6 @@ !!} use :: Cosmology_Functions , only : cosmologyFunctions, cosmologyFunctionsClass - use :: Galactic_Structure , only : galacticStructureClass use :: Numerical_Interpolation, only : interpolator use :: Output_Times , only : outputTimes , outputTimesClass @@ -58,7 +57,6 @@ private class (cosmologyFunctionsClass), pointer :: cosmologyFunctions_ => null() class (outputTimesClass ), pointer :: outputTimes_ => null() - class (galacticStructureClass ), pointer :: galacticStructure_ => null() logical :: oneTimeDatasetsWritten integer :: countSteps double precision :: timeBegin , timeEnd @@ -97,7 +95,6 @@ function recordEvolutionConstructorParameters(parameters) result(self) type (inputParameters ), intent(inout) :: parameters class (cosmologyFunctionsClass ), pointer :: cosmologyFunctions_ class (outputTimesClass ), pointer :: outputTimes_ - class (galacticStructureClass ), pointer :: galacticStructure_ double precision :: timeBegin , timeEnd, & & ageUniverse integer :: countSteps @@ -105,7 +102,6 @@ function recordEvolutionConstructorParameters(parameters) result(self) !![ - !!] ageUniverse=cosmologyFunctions_%cosmicTime(1.0d0) !![ @@ -128,17 +124,16 @@ function recordEvolutionConstructorParameters(parameters) result(self) parameters !!] - self=mergerTreeEvolveTimestepRecordEvolution(timeBegin,timeEnd,countSteps,cosmologyFunctions_,outputTimes_,galacticStructure_) + self=mergerTreeEvolveTimestepRecordEvolution(timeBegin,timeEnd,countSteps,cosmologyFunctions_,outputTimes_) !![ - !!] return end function recordEvolutionConstructorParameters - function recordEvolutionConstructorInternal(timeBegin,timeEnd,countSteps,cosmologyFunctions_,outputTimes_,galacticStructure_) result(self) + function recordEvolutionConstructorInternal(timeBegin,timeEnd,countSteps,cosmologyFunctions_,outputTimes_) result(self) !!{ Internal constructor for the {\normalfont \ttfamily recordEvolution} merger tree evolution timestep class. !!} @@ -148,12 +143,11 @@ function recordEvolutionConstructorInternal(timeBegin,timeEnd,countSteps,cosmolo type (mergerTreeEvolveTimestepRecordEvolution) :: self class (cosmologyFunctionsClass ), intent(in ), target :: cosmologyFunctions_ class (outputTimesClass ), intent(in ), target :: outputTimes_ - class (galacticStructureClass ), intent(in ), target :: galacticStructure_ double precision , intent(in ) :: timeBegin , timeEnd integer , intent(in ) :: countSteps integer (c_size_t ) :: timeIndex !![ - + !!] allocate(self%time (self%countSteps)) @@ -193,7 +187,6 @@ subroutine recordEvolutionDestructor(self) !![ - !!] if (mergerTreeExtraOutputEvent%isAttached(self,recordEvolutionOutput)) call mergerTreeExtraOutputEvent%detach(self,recordEvolutionOutput) return @@ -246,9 +239,10 @@ subroutine recordEvolutionStore(self,tree,node,deadlockStatus) !!{ Store properties of the main progenitor galaxy. !!} - use :: Galactic_Structure_Options, only : massTypeGalactic, massTypeStellar + use :: Galactic_Structure_Options, only : massTypeGalactic , massTypeStellar use :: Error , only : Error_Report - use :: Galacticus_Nodes , only : mergerTree , nodeComponentBasic, treeNode + use :: Galacticus_Nodes , only : mergerTree , nodeComponentBasic, treeNode + use :: Mass_Distributions , only : massDistributionClass use, intrinsic :: ISO_C_Binding , only : c_size_t implicit none class (* ), intent(inout) :: self @@ -256,6 +250,7 @@ subroutine recordEvolutionStore(self,tree,node,deadlockStatus) type (treeNode ), intent(inout), pointer :: node type (enumerationDeadlockStatusType), intent(inout) :: deadlockStatus class (nodeComponentBasic ) , pointer :: basic + class (massDistributionClass ) , pointer :: massDistributionGalactic, massDistributionStellar integer (c_size_t ) :: indexTime double precision :: time !$GLC attributes unused :: deadlockStatus, tree @@ -269,8 +264,14 @@ subroutine recordEvolutionStore(self,tree,node,deadlockStatus) else indexTime=self%interpolator_%locate(time) end if - self%massStellar(indexTime)=self%galacticStructure_%massEnclosed(node,massType=massTypeStellar ) - self%massTotal (indexTime)=self%galacticStructure_%massEnclosed(node,massType=massTypeGalactic) + massDistributionStellar => node %massDistribution(massType=massTypeStellar ) + massDistributionGalactic => node %massDistribution(massType=massTypeGalactic) + self%massStellar (indexTime) = massDistributionStellar %massTotal ( ) + self%massTotal (indexTime) = massDistributionGalactic%massTotal ( ) + !![ + + + !!] class default call Error_Report('incorrect class'//{introspection:location}) end select diff --git a/source/merger_trees.evolver.standard.F90 b/source/merger_trees.evolver.standard.F90 index ed1eb4007e..97a85cfbce 100644 --- a/source/merger_trees.evolver.standard.F90 +++ b/source/merger_trees.evolver.standard.F90 @@ -29,7 +29,6 @@ use :: Merger_Tree_Initialization , only : mergerTreeInitializorClass use :: Merger_Tree_Timesteps , only : mergerTreeEvolveTimestep , mergerTreeEvolveTimestepClass use :: Merger_Trees_Evolve_Node , only : mergerTreeNodeEvolver , mergerTreeNodeEvolverClass - use :: Galactic_Structure , only : galacticStructureClass ! Structure used to store list of nodes for deadlock reporting. type :: deadlockList @@ -122,7 +121,6 @@ class (galacticStructureSolverClass ), pointer :: galacticStructureSolver_ => null() class (mergerTreeNodeEvolverClass ), pointer :: mergerTreeNodeEvolver_ => null() class (mergerTreeInitializorClass ), pointer :: mergerTreeInitializor_ => null() - class (galacticStructureClass ), pointer :: galacticStructure_ => null() class (mergerTreeEvolveProfilerClass), pointer :: mergerTreeEvolveProfiler_ => null() logical :: allTreesExistAtFinalTime , dumpTreeStructure , & & backtrackToSatellites , profileSteps @@ -171,7 +169,6 @@ function standardConstructorParameters(parameters) result(self) class (galacticStructureSolverClass ), pointer :: galacticStructureSolver_ class (mergerTreeNodeEvolverClass ), pointer :: mergerTreeNodeEvolver_ class (mergerTreeInitializorClass ), pointer :: mergerTreeInitializor_ - class (galacticStructureClass ), pointer :: galacticStructure_ class (mergerTreeEvolveProfilerClass), pointer :: mergerTreeEvolveProfiler_ logical :: allTreesExistAtFinalTime , dumpTreeStructure , & & backtrackToSatellites , profileSteps @@ -222,19 +219,14 @@ function standardConstructorParameters(parameters) result(self) Specifies whether or not to profile the ODE evolver. parameters - !!] - ! A galacticStructureSolver is built here. Even though this is not called explicitly by this mergerTreeEvolver, the - ! galacticStructureSolver is expected to hook itself to any events which will trigger a change in galactic structure. - !![ - !!] - self=mergerTreeEvolverStandard(allTreesExistAtFinalTime,dumpTreeStructure,timestepHostRelative,timestepHostAbsolute,fractionTimestepSatelliteMinimum,backtrackToSatellites,profileSteps,cosmologyFunctions_,mergerTreeNodeEvolver_,mergerTreeEvolveTimestep_,mergerTreeInitializor_,galacticStructureSolver_,galacticStructure_,mergerTreeEvolveProfiler_) + self=mergerTreeEvolverStandard(allTreesExistAtFinalTime,dumpTreeStructure,timestepHostRelative,timestepHostAbsolute,fractionTimestepSatelliteMinimum,backtrackToSatellites,profileSteps,cosmologyFunctions_,mergerTreeNodeEvolver_,mergerTreeEvolveTimestep_,mergerTreeInitializor_,galacticStructureSolver_,mergerTreeEvolveProfiler_) !![ @@ -242,13 +234,12 @@ function standardConstructorParameters(parameters) result(self) - !!] return end function standardConstructorParameters - function standardConstructorInternal(allTreesExistAtFinalTime,dumpTreeStructure,timestepHostRelative,timestepHostAbsolute,fractionTimestepSatelliteMinimum,backtrackToSatellites,profileSteps,cosmologyFunctions_,mergerTreeNodeEvolver_,mergerTreeEvolveTimestep_,mergerTreeInitializor_,galacticStructureSolver_,galacticStructure_,mergerTreeEvolveProfiler_) result(self) + function standardConstructorInternal(allTreesExistAtFinalTime,dumpTreeStructure,timestepHostRelative,timestepHostAbsolute,fractionTimestepSatelliteMinimum,backtrackToSatellites,profileSteps,cosmologyFunctions_,mergerTreeNodeEvolver_,mergerTreeEvolveTimestep_,mergerTreeInitializor_,galacticStructureSolver_,mergerTreeEvolveProfiler_) result(self) !!{ Internal constructor for the {\normalfont \ttfamily standard} merger tree evolver class. !!} @@ -259,14 +250,13 @@ function standardConstructorInternal(allTreesExistAtFinalTime,dumpTreeStructure, class (galacticStructureSolverClass ), intent(in ), target :: galacticStructureSolver_ class (mergerTreeNodeEvolverClass ), intent(in ), target :: mergerTreeNodeEvolver_ class (mergerTreeInitializorClass ), intent(in ), target :: mergerTreeInitializor_ - class (galacticStructureClass ), intent(in ), target :: galacticStructure_ class (mergerTreeEvolveProfilerClass), intent(in ), target :: mergerTreeEvolveProfiler_ logical , intent(in ) :: allTreesExistAtFinalTime , dumpTreeStructure , & & backtrackToSatellites , profileSteps double precision , intent(in ) :: timestepHostRelative , timestepHostAbsolute, & & fractionTimestepSatelliteMinimum !![ - + !!] self%deadlockHeadNode => null() @@ -286,7 +276,6 @@ subroutine standardDestructor(self) - !!] return diff --git a/source/merger_trees.node_evolver.standard.F90 b/source/merger_trees.node_evolver.standard.F90 index d855a86b60..c810dd1259 100644 --- a/source/merger_trees.node_evolver.standard.F90 +++ b/source/merger_trees.node_evolver.standard.F90 @@ -959,11 +959,11 @@ Function which evaluates the set of ODEs for the evolution of a specific node. propertyValues1 =+propertyValues0 propertyValues1(i)=+propertyValues1 (i) & & +propertyValueDelta - call self_%activeNode%deserializeValues (propertyValues1 ,self_%propertyTypeODE) - call self_%activeNode%odeStepRatesInitialize( ) - call self_%galacticStructureSolver_%revert (self_%activeNode ) - call standardDerivativesCompute (self_%activeNode ,interrupt,functionInterrupt,self_%propertyTypeODE) - call self_%activeNode%serializeRates (propertyRates1 ,self_%propertyTypeODE) + call self_%activeNode%deserializeValues (propertyValues1 ,self_%propertyTypeODE) + call self_%activeNode%odeStepRatesInitialize( ) + call self_%galacticStructureSolver_%revert (self_%activeNode ) + call standardDerivativesCompute (self_%activeNode,interrupt,functionInterrupt,self_%propertyTypeODE) + call self_%activeNode%serializeRates (propertyRates1 ,self_%propertyTypeODE) jacobian(i,:)=+( & & +propertyRates1 & & -propertyRates0 & diff --git a/source/merger_trees.operators.mass_accretion_history.F90 b/source/merger_trees.operators.mass_accretion_history.F90 index 14b03ecb8d..f5903a99cd 100644 --- a/source/merger_trees.operators.mass_accretion_history.F90 +++ b/source/merger_trees.operators.mass_accretion_history.F90 @@ -22,9 +22,9 @@ histories. !!} - use :: Cosmology_Functions , only : cosmologyFunctionsClass - use :: Dark_Matter_Profiles_DMO, only : darkMatterProfileDMOClass - use :: IO_HDF5 , only : hdf5Object + use :: Cosmology_Functions , only : cosmologyFunctionsClass + use :: Dark_Matter_Halo_Scales, only : darkMatterHaloScaleClass + use :: IO_HDF5 , only : hdf5Object !![ @@ -48,9 +48,9 @@ as a function of time). Histories are written into the \glc\ output file in a gr private type (hdf5Object ) :: outputGroup type (varying_string ) :: outputGroupName - class (cosmologyFunctionsClass ), pointer :: cosmologyFunctions_ => null() - class (darkMatterProfileDMOClass), pointer :: darkMatterProfileDMO_ => null() - logical :: includeSpin , includeSpinVector + class (cosmologyFunctionsClass ), pointer :: cosmologyFunctions_ => null() + class (darkMatterHaloScaleClass ), pointer :: darkMatterHaloScale_ => null() + logical :: includeSpin , includeSpinVector contains final :: massAccretionHistoryDestructor procedure :: operatePreEvolution => massAccretionHistoryOperatePreEvolution @@ -73,12 +73,12 @@ function massAccretionHistoryConstructorParameters(parameters) result(self) parameter set as input. !!} implicit none - type (mergerTreeOperatorMassAccretionHistory) :: self - type (inputParameters ), intent(inout) :: parameters - type (varying_string ) :: outputGroupName - class(darkMatterProfileDMOClass ), pointer :: darkMatterProfileDMO_ - class(cosmologyFunctionsClass ), pointer :: cosmologyFunctions_ - logical :: includeSpin , includeSpinVector + type (mergerTreeOperatorMassAccretionHistory) :: self + type (inputParameters ), intent(inout) :: parameters + type (varying_string ) :: outputGroupName + class (cosmologyFunctionsClass ), pointer :: cosmologyFunctions_ + class (darkMatterHaloScaleClass ), pointer :: darkMatterHaloScale_ + logical :: includeSpin , includeSpinVector !![ @@ -100,18 +100,18 @@ function massAccretionHistoryConstructorParameters(parameters) result(self) If true, include the spin vector of the halo in the output. - + !!] - self=mergerTreeOperatorMassAccretionHistory(char(outputGroupName),includeSpin,includeSpinVector,cosmologyFunctions_,darkMatterProfileDMO_) + self=mergerTreeOperatorMassAccretionHistory(char(outputGroupName),includeSpin,includeSpinVector,cosmologyFunctions_,darkMatterHaloScale_) !![ - - + + !!] return end function massAccretionHistoryConstructorParameters - function massAccretionHistoryConstructorInternal(outputGroupName,includeSpin,includeSpinVector,cosmologyFunctions_,darkMatterProfileDMO_) result(self) + function massAccretionHistoryConstructorInternal(outputGroupName,includeSpin,includeSpinVector,cosmologyFunctions_,darkMatterHaloScale_) result(self) !!{ Internal constructor for the mass accretion history merger tree operator class. !!} @@ -120,11 +120,11 @@ function massAccretionHistoryConstructorInternal(outputGroupName,includeSpin,inc implicit none type (mergerTreeOperatorMassAccretionHistory) :: self character(len=* ), intent(in ) :: outputGroupName - logical , intent(in ) :: includeSpin , includeSpinVector + logical , intent(in ) :: includeSpin , includeSpinVector class (cosmologyFunctionsClass ), intent(in ), target :: cosmologyFunctions_ - class (darkMatterProfileDMOClass ), intent(in ), target :: darkMatterProfileDMO_ + class (darkMatterHaloScaleClass ), intent(in ), target :: darkMatterHaloScale_ !![ - + !!] if (self%includeSpin .and..not.defaultSpinComponent%angularMomentumIsGettable ()) & @@ -158,8 +158,8 @@ Destructor for the mass accretion history merger tree operator function class. type(mergerTreeOperatorMassAccretionHistory), intent(inout) :: self !![ - - + + !!] return end subroutine massAccretionHistoryDestructor @@ -226,10 +226,10 @@ subroutine massAccretionHistoryOperatePreEvolution(self,tree) nodeTime (accretionHistoryCount ) = basic%time ( ) nodeMass (accretionHistoryCount ) = basic%mass ( ) nodeExpansionFactor (accretionHistoryCount ) = self%cosmologyFunctions_%expansionFactor(basic%time ( )) - if (self%includeSpin ) nodeSpin (accretionHistoryCount ) = spin %angularMomentum ( ) & - & /Dark_Matter_Halo_Angular_Momentum_Scale(node,self%darkMatterProfileDMO_) - if (self%includeSpinVector) nodeSpinVector(accretionHistoryCount,:) = spin %angularMomentumVector( ) & - & /Dark_Matter_Halo_Angular_Momentum_Scale(node,self%darkMatterProfileDMO_) + if (self%includeSpin ) nodeSpin (accretionHistoryCount ) = spin %angularMomentum ( ) & + & /Dark_Matter_Halo_Angular_Momentum_Scale(node,self%darkMatterHaloScale_) + if (self%includeSpinVector) nodeSpinVector(accretionHistoryCount,:) = spin %angularMomentumVector( ) & + & /Dark_Matter_Halo_Angular_Momentum_Scale(node,self%darkMatterHaloScale_) node => node %firstChild end do ! Create the output group if necessary. diff --git a/source/merger_trees.operators.particulate.F90 b/source/merger_trees.operators.particulate.F90 index 953ce35958..cfa48c9963 100644 --- a/source/merger_trees.operators.particulate.F90 +++ b/source/merger_trees.operators.particulate.F90 @@ -27,7 +27,6 @@ use :: Cosmology_Parameters , only : cosmologyParametersClass use :: Dark_Matter_Halo_Scales , only : darkMatterHaloScaleClass use :: Dark_Matter_Profiles_DMO, only : darkMatterProfileDMOClass - use :: Galactic_Structure , only : galacticStructureClass use :: Galacticus_Nodes , only : treeNode use :: HDF5 , only : hsize_t use :: ISO_Varying_String , only : varying_string @@ -75,12 +74,11 @@ class (cosmologyFunctionsClass ), pointer :: cosmologyFunctions_ => null() class (darkMatterHaloScaleClass ), pointer :: darkMatterHaloScale_ => null() class (darkMatterProfileDMOClass ), pointer :: darkMatterProfileDMO_ => null() - class (galacticStructureClass ), pointer :: galacticStructure_ => null() type (varying_string ) :: outputFileName double precision :: massParticle , radiusTruncateOverRadiusVirial , & & timeSnapshot , energyDistributionPointsPerDecade, & & lengthSoftening , toleranceRelativeSmoothing , & - & toleranceMass + & toleranceMass , tolerancePotential logical :: satelliteOffset , nonCosmological , & & positionOffset , addHubbleFlow , & & haloIdToParticleType , sampleParticleNumber , & @@ -138,7 +136,7 @@ function particulateConstructorParameters(parameters) result(self) double precision :: massParticle , radiusTruncateOverRadiusVirial , & & timeSnapshot , energyDistributionPointsPerDecade, & & lengthSoftening , toleranceRelativeSmoothing , & - & toleranceMass + & toleranceMass , tolerancePotential logical :: satelliteOffset , nonCosmological , & & positionOffset , addHubbleFlow , & & haloIdToParticleType , sampleParticleNumber , & @@ -150,7 +148,6 @@ function particulateConstructorParameters(parameters) result(self) class (cosmologyFunctionsClass ), pointer :: cosmologyFunctions_ class (darkMatterHaloScaleClass ), pointer :: darkMatterHaloScale_ class (darkMatterProfileDMOClass ), pointer :: darkMatterProfileDMO_ - class (galacticStructureClass ), pointer :: galacticStructure_ type (inputParameters ), pointer :: parametersRoot => null() type (varying_string ) :: selection , kernelSoftening @@ -217,6 +214,12 @@ function particulateConstructorParameters(parameters) result(self) 1.0d-8 The relative tolerance to use in the integrals over the mass distribution used in finding the smoothed density profile defined by \cite{barnes_gravitational_2012} to account for gravitational softening. + + tolerancePotential + parameters + 1.0d-9 + The relative tolerance to use in the integrals over the potential used in finding the smoothed density profile defined by \cite{barnes_gravitational_2012} to account for gravitational softening. + selection parameters @@ -274,16 +277,15 @@ function particulateConstructorParameters(parameters) result(self) - !!] if (associated(parameters%parent)) then parametersRoot => parameters%parent do while (associated(parametersRoot%parent)) parametersRoot => parametersRoot%parent end do - self=mergerTreeOperatorParticulate(outputFileName,idMultiplier,massParticle,radiusTruncateOverRadiusVirial,timeSnapshot,satelliteOffset,positionOffset,subtractRandomOffset,energyDistributionPointsPerDecade,selection_,nonCosmological,addHubbleFlow,haloIdToParticleType,sampleParticleNumber,kernelSoftening_,lengthSoftening,toleranceRelativeSmoothing,toleranceMass,chunkSize,cosmologyParameters_,cosmologyFunctions_,darkMatterHaloScale_,darkMatterProfileDMO_,galacticStructure_,parametersRoot) + self=mergerTreeOperatorParticulate(outputFileName,idMultiplier,massParticle,radiusTruncateOverRadiusVirial,timeSnapshot,satelliteOffset,positionOffset,subtractRandomOffset,energyDistributionPointsPerDecade,selection_,nonCosmological,addHubbleFlow,haloIdToParticleType,sampleParticleNumber,kernelSoftening_,lengthSoftening,toleranceRelativeSmoothing,toleranceMass,tolerancePotential,chunkSize,cosmologyParameters_,cosmologyFunctions_,darkMatterHaloScale_,darkMatterProfileDMO_,parametersRoot) else - self=mergerTreeOperatorParticulate(outputFileName,idMultiplier,massParticle,radiusTruncateOverRadiusVirial,timeSnapshot,satelliteOffset,positionOffset,subtractRandomOffset,energyDistributionPointsPerDecade,selection_,nonCosmological,addHubbleFlow,haloIdToParticleType,sampleParticleNumber,kernelSoftening_,lengthSoftening,toleranceRelativeSmoothing,toleranceMass,chunkSize,cosmologyParameters_,cosmologyFunctions_,darkMatterHaloScale_,darkMatterProfileDMO_,galacticStructure_,parameters ) + self=mergerTreeOperatorParticulate(outputFileName,idMultiplier,massParticle,radiusTruncateOverRadiusVirial,timeSnapshot,satelliteOffset,positionOffset,subtractRandomOffset,energyDistributionPointsPerDecade,selection_,nonCosmological,addHubbleFlow,haloIdToParticleType,sampleParticleNumber,kernelSoftening_,lengthSoftening,toleranceRelativeSmoothing,toleranceMass,tolerancePotential,chunkSize,cosmologyParameters_,cosmologyFunctions_,darkMatterHaloScale_,darkMatterProfileDMO_,parameters ) end if !![ @@ -291,12 +293,11 @@ function particulateConstructorParameters(parameters) result(self) - !!] return end function particulateConstructorParameters - function particulateConstructorInternal(outputFileName,idMultiplier,massParticle,radiusTruncateOverRadiusVirial,timeSnapshot,satelliteOffset,positionOffset,subtractRandomOffset,energyDistributionPointsPerDecade,selection,nonCosmological,addHubbleFlow,haloIdToParticleType,sampleParticleNumber,kernelSoftening,lengthSoftening,toleranceRelativeSmoothing,toleranceMass,chunkSize,cosmologyParameters_,cosmologyFunctions_,darkMatterHaloScale_,darkMatterProfileDMO_,galacticStructure_,parameters) result(self) + function particulateConstructorInternal(outputFileName,idMultiplier,massParticle,radiusTruncateOverRadiusVirial,timeSnapshot,satelliteOffset,positionOffset,subtractRandomOffset,energyDistributionPointsPerDecade,selection,nonCosmological,addHubbleFlow,haloIdToParticleType,sampleParticleNumber,kernelSoftening,lengthSoftening,toleranceRelativeSmoothing,toleranceMass,tolerancePotential,chunkSize,cosmologyParameters_,cosmologyFunctions_,darkMatterHaloScale_,darkMatterProfileDMO_,parameters) result(self) !!{ Internal constructor for the particulate merger tree operator class. !!} @@ -308,7 +309,7 @@ function particulateConstructorInternal(outputFileName,idMultiplier,massParticle double precision , intent(in ) :: massParticle , radiusTruncateOverRadiusVirial , & & timeSnapshot , energyDistributionPointsPerDecade, & & lengthSoftening , toleranceRelativeSmoothing , & - & toleranceMass + & toleranceMass , tolerancePotential logical , intent(in ) :: satelliteOffset , nonCosmological , & & positionOffset , addHubbleFlow , & & haloIdToParticleType , sampleParticleNumber , & @@ -320,10 +321,9 @@ function particulateConstructorInternal(outputFileName,idMultiplier,massParticle class (cosmologyFunctionsClass ), intent(in ), target :: cosmologyFunctions_ class (darkMatterHaloScaleClass ), intent(in ), target :: darkMatterHaloScale_ class (darkMatterProfileDMOClass ), intent(in ), target :: darkMatterProfileDMO_ - class (galacticStructureClass ), intent(in ), target :: galacticStructure_ type (inputParameters ), intent(in ), target :: parameters !![ - + !!] self%parameters=inputParameters(parameters) @@ -344,7 +344,6 @@ subroutine particulateDestructor(self) - !!] return end subroutine particulateDestructor @@ -365,6 +364,7 @@ subroutine particulateOperatePreEvolution(self,tree) use :: HDF5_Access , only : hdf5Access use :: IO_HDF5 , only : hdf5Object use :: ISO_Varying_String , only : varying_string , var_str + use :: Mass_Distributions , only : massDistributionClass use :: Merger_Tree_Walkers , only : mergerTreeWalkerAllNodes use :: Node_Components , only : Node_Components_Thread_Initialize, Node_Components_Thread_Uninitialize use :: Numerical_Comparison , only : Values_Agree @@ -377,6 +377,7 @@ subroutine particulateOperatePreEvolution(self,tree) class (nodeComponentBasic ), pointer :: basic class (nodeComponentPosition ), pointer :: position class (nodeComponentSatellite ), pointer :: satellite + class (massDistributionClass ), pointer :: massDistribution_ double precision , parameter :: tolerance =1.0d-06 double precision , parameter :: unitGadgetMass =1.0d+10 double precision , parameter :: unitGadgetLength =1.0d-03 @@ -477,11 +478,11 @@ subroutine particulateOperatePreEvolution(self,tree) radiusTruncate=+self%radiusTruncateOverRadiusVirial & & *radiusVirial ! Determine the mass within the truncation radius. - massTruncate=self%galacticStructure_%massEnclosed( & - & node , & - & radiusTruncate , & - & massType =massTypeDark & - & ) + massDistribution_ => node %massDistribution (massType=massTypeDark ) + massTruncate = massDistribution_%massEnclosedBySphere( radiusTruncate) + !![ + + !!] ! Determine the mean number of particles required to represent this node. particleCountMean =+massTruncate & & /self%massParticle @@ -541,16 +542,9 @@ subroutine particulateOperatePreEvolution(self,tree) end do end do !$omp end critical (mergerTreeOperatorParticulateSample) - call positionSpherical% phiSet( 2.0d0*Pi*randomDeviates(1) ) - call positionSpherical%thetaSet(acos(2.0d0 *randomDeviates(2)-1.0d0)) - call positionSpherical% rSet( & - & self_%galacticStructure_%radiusEnclosingMass( & - & node , & - & mass =+massTruncate & - & *randomDeviates(3), & - & massType =massTypeDark & - & ) & - & ) + call positionSpherical% phiSet( 2.0d0*Pi *randomDeviates(1) ) + call positionSpherical%thetaSet(acos(2.0d0 *randomDeviates(2)-1.0d0)) + call positionSpherical% rSet( radiusTruncate_*randomDeviates(3) ) ! Get the corresponding cartesian coordinates. positionCartesian=positionSpherical ! Construct the energy distribution function encompassing this radius. @@ -560,9 +554,12 @@ subroutine particulateOperatePreEvolution(self,tree) & positionSpherical%r() , & & table=energyDistributionTablePotential & & ) - speedEscape =+sqrt( & - & +2.0d0 & - & *energyPotential & + speedEscape =+sqrt( & + & +2.0d0 & + & *max( & + & energyPotential, & + & 0.0d0 & + & ) & & ) ! Estimate the maximum of the speed distribution function. distributionFunctionMaximum=+0.0d0 @@ -771,38 +768,42 @@ Construct the energy distribution function assuming a spherical dark matter halo \end{equation} which we can then take the derivative of numerically to obtain the distribution function. !!} + use :: Coordinates , only : coordinateSpherical , assignment(=) use :: Error , only : Error_Report use :: Galacticus_Nodes , only : nodeComponentBasic + use :: Mass_Distributions , only : massDistributionClass use :: Numerical_Integration, only : integrator use :: Table_Labels , only : extrapolationTypeFix implicit none - double precision , intent(in ) :: radius - double precision , intent(in ) :: energyDistributionPointsPerDecade - class (nodeComponentBasic), pointer :: basic - double precision , parameter :: toleranceTabulation =1.0d-6 - double precision , parameter :: toleranceGradient =1.0d-6 + double precision , intent(in ) :: radius + double precision , intent(in ) :: energyDistributionPointsPerDecade + class (nodeComponentBasic ), pointer :: basic + class (massDistributionClass), pointer :: massDistribution_ + double precision , parameter :: toleranceTabulation =1.0d-6 + double precision , parameter :: toleranceGradient =1.0d-6 ! The largest (absolute) logarithmic gradient dlog(Φ)/dlog(r) at which it is acceptable to have a non-monotonic distribution ! function. This allows for numerical inaccuracies that arise in cored density profiles where the central potential has the ! form Φ(r) = Φ₀ + k r², such that the potential is very weakly dependent on r at small radii. - double precision , parameter :: derivativeLogarithmicPotentialTolerance =1.0d-5 - double precision :: radiusMinimum , energyPotentialTruncate , & - & particulateSmoothingIntegrationRangeLower , particulateSmoothingIntegrationRangeUpper, & - & radiusFactorAsymptote , integralAsymptotic , & - & gradientDensityPotentialLower , gradientDensityPotentialUpper , & - & densitySmoothedIntegralLower , densitySmoothedIntegralUpper , & - & derivativeLogarithmicPotential - logical :: tableRebuild - integer :: i , j - integer :: radiusCount - type (integrator ) :: intergatorSmoothingZ , integratorMass , & - & integratorPotential , integratorEddington + double precision , parameter :: derivativeLogarithmicPotentialTolerance =1.0d-5 + double precision :: radiusMinimum , derivativeLogarithmicPotential , & + & particulateSmoothingIntegrationRangeLower , particulateSmoothingIntegrationRangeUpper, & + & radiusFactorAsymptote , integralAsymptotic , & + & gradientDensityPotentialLower , gradientDensityPotentialUpper , & + & densitySmoothedIntegralLower , densitySmoothedIntegralUpper + logical :: tableRebuild + integer :: i , j + integer :: radiusCount + type (coordinateSpherical ) :: coordinates , coordinatesTruncate + type (integrator ) :: intergatorSmoothingZ , integratorMass , & + & integratorPotential , integratorEddington ! Determine the minimum of the given radius and some small fraction of the virial radius. - basic => node_%basic() - radiusMinimum = min( & - & +0.5d0* radius , & - & + self_%darkMatterProfileDMO_%radiusEnclosingMass(node_ ,self_%massParticle) & - & ) + basic => node_%basic () + massDistribution_ => self_ %darkMatterProfileDMO_%get (node_ ) + radiusMinimum = min( & + & +0.5d0* radius , & + & + massDistribution_ %radiusEnclosingMass(self_%massParticle ) & + & ) ! Rebuild the density vs. potential table to have sufficient range if necessary. if (energyDistributionInitialized) then tableRebuild=(radiusMinimum < (1.0d0-toleranceTabulation)*energyDistribution%x(1)) @@ -821,45 +822,30 @@ Construct the energy distribution function assuming a spherical dark matter halo & tableCount = 5 , & & extrapolationType= [extrapolationTypeFix,extrapolationTypeFix] & & ) - select case (softeningKernel%ID) - case (particulateKernelDelta%ID) - energyPotentialTruncate=+self_%darkMatterProfileDMO_%potential( & - & node_ , & - & +radiusTruncate_ & - & ) - case default - ! Potential will be computed directly from the smoothed density profile in these cases. - energyPotentialTruncate=0.0d0 - end select do i=1,radiusCount - radius_=energyDistribution%x(i) - call energyDistribution%populate( & - & +self_%darkMatterProfileDMO_%density ( & - & node_ , & - & energyDistribution%x(i) & - & ) , & - & i , & - & table =energyDistributionTableDensity , & - & computeSpline=i==radiusCount & + radius_ =energyDistribution%x(i) + coordinates=[radius_,0.0d0,0.0d0] + call energyDistribution%populate( & + & +massDistribution_%density (coordinates) , & + & i , & + & table =energyDistributionTableDensity , & + & computeSpline=i==radiusCount & & ) select case (softeningKernel%ID) case (particulateKernelDelta%ID) ! No softening is applied, so use the actual density and potential. - call energyDistribution%populate( & - & energyDistribution%y(i,table=energyDistributionTableDensity) , & - & i , & - & table =energyDistributionTableDensitySmoothed , & - & computeSpline=i==radiusCount & + coordinatesTruncate=[radiusTruncate_,0.0d0,0.0d0] + call energyDistribution%populate( & + & energyDistribution%y(i,table=energyDistributionTableDensity) , & + & i , & + & table =energyDistributionTableDensitySmoothed , & + & computeSpline=i==radiusCount & & ) - call energyDistribution%populate( & - & -self_%darkMatterProfileDMO_%potential( & - & node_ , & - & energyDistribution%x(i) & - & ) & - & +energyPotentialTruncate , & - & i , & - & table =energyDistributionTablePotential , & - & computeSpline=i==radiusCount & + call energyDistribution%populate( & + & massDistribution_%potentialDifference(coordinatesTruncate,coordinates), & + & i , & + & table =energyDistributionTablePotential , & + & computeSpline=i==radiusCount & & ) case default ! Compute potential from a density field smoothed by the density distribution corresponding to the softened @@ -905,6 +891,9 @@ Construct the energy distribution function assuming a spherical dark matter halo & ) end select end do + !![ + + !!] ! If necessary, compute the potential from the smoothed density profile. if (softeningKernel /= particulateKernelDelta) then integratorMass=integrator(particulateMassIntegrand,toleranceRelative=self_%toleranceMass) @@ -929,7 +918,7 @@ Construct the energy distribution function assuming a spherical dark matter halo & computeSpline=i==radiusCount & & ) end do - integratorPotential=integrator(particulatePotentialIntegrand,toleranceRelative=1.0d-9) + integratorPotential=integrator(particulatePotentialIntegrand,toleranceRelative=self_%tolerancePotential) do i=1,radiusCount radius_=energyDistribution%x(i) call energyDistribution%populate( & @@ -1077,21 +1066,31 @@ double precision function particulateSmoothingIntegrandR(radiusCylindrical) The integrand over cylindrical coordinate $R$ used in finding the smoothed density profile defined by \cite{barnes_gravitational_2012} to account for gravitational softening. !!} + use :: Coordinates , only : coordinateSpherical , assignment(=) + use :: Mass_Distributions, only : massDistributionClass implicit none - double precision, intent(in ) :: radiusCylindrical - double precision :: radiusSplineKernel, lengthSplineKernel - - particulateSmoothingIntegrandR=+radiusCylindrical & - & *self_%darkMatterProfileDMO_%density( & - & node_ , & - & sqrt( & - & +radiusCylindrical**2 & - & +( & - & +height_ & - & -radius_ & - & ) **2 & - & ) & - & ) + double precision , intent(in ) :: radiusCylindrical + class (massDistributionClass), pointer :: massDistribution_ + double precision :: radiusSplineKernel, lengthSplineKernel + type (coordinateSpherical ) :: coordinates + + massDistribution_ => self_%darkMatterProfileDMO_%get(node_) + coordinates = [ & + & +sqrt( & + & +radiusCylindrical**2 & + & +( & + & +height_ & + & -radius_ & + & ) **2 & + & ) , & + & +0.0d0 , & + & +0.0d0 & + & ] + particulateSmoothingIntegrandR=+ radiusCylindrical & + & *massDistribution_%density (coordinates) + !![ + + !!] ! Apply the softening kernel density distribution. select case (softeningKernel%ID) case (particulateKernelPlummer%ID) diff --git a/source/merger_trees.outputter.halo_Fourier_profiles.F90 b/source/merger_trees.outputter.halo_Fourier_profiles.F90 index 6f81e12e31..bed420edfc 100644 --- a/source/merger_trees.outputter.halo_Fourier_profiles.F90 +++ b/source/merger_trees.outputter.halo_Fourier_profiles.F90 @@ -22,6 +22,7 @@ !!} use :: Cosmology_Functions , only : cosmologyFunctionsClass + use :: Dark_Matter_Halo_Scales , only : darkMatterHaloScaleClass use :: Dark_Matter_Profiles_DMO, only : darkMatterProfileDMOClass use :: Galactic_Filters , only : galacticFilterClass use :: IO_HDF5 , only : hdf5Object @@ -63,6 +64,7 @@ !!} private class (cosmologyFunctionsClass ), pointer :: cosmologyFunctions_ => null() + class (darkMatterHaloScaleClass ), pointer :: darkMatterHaloScale_ => null() class (darkMatterProfileDMOClass), pointer :: darkMatterProfileDMO_ => null() class (galacticFilterClass ), pointer :: galacticFilter_ => null() integer :: wavenumberPointsPerDecade , wavenumberCount @@ -96,6 +98,7 @@ function haloFourierProfilesConstructorParameters(parameters) result(self) type (inputParameters ), intent(inout) :: parameters class (cosmologyFunctionsClass ), pointer :: cosmologyFunctions_ class (darkMatterProfileDMOClass ), pointer :: darkMatterProfileDMO_ + class (darkMatterHaloScaleClass ), pointer :: darkMatterHaloScale_ class (galacticFilterClass ), pointer :: galacticFilter_ double precision :: wavenumberMinimum , wavenumberMaximum integer :: wavenumberPointsPerDecade @@ -122,18 +125,20 @@ function haloFourierProfilesConstructorParameters(parameters) result(self) + !!] - self=mergerTreeOutputterHaloFourierProfiles(wavenumberPointsPerDecade,wavenumberMinimum,wavenumberMaximum,cosmologyFunctions_,darkMatterProfileDMO_,galacticFilter_) + self=mergerTreeOutputterHaloFourierProfiles(wavenumberPointsPerDecade,wavenumberMinimum,wavenumberMaximum,cosmologyFunctions_,darkMatterHaloScale_,darkMatterProfileDMO_,galacticFilter_) !![ + !!] return end function haloFourierProfilesConstructorParameters - function haloFourierProfilesConstructorInternal(wavenumberPointsPerDecade,wavenumberMinimum,wavenumberMaximum,cosmologyFunctions_,darkMatterProfileDMO_,galacticFilter_) result(self) + function haloFourierProfilesConstructorInternal(wavenumberPointsPerDecade,wavenumberMinimum,wavenumberMaximum,cosmologyFunctions_,darkMatterHaloScale_,darkMatterProfileDMO_,galacticFilter_) result(self) !!{ Internal constructor for the {\normalfont \ttfamily haloFourierProfiles} merger tree outputter class. !!} @@ -141,12 +146,13 @@ function haloFourierProfilesConstructorInternal(wavenumberPointsPerDecade,wavenu implicit none type (mergerTreeOutputterHaloFourierProfiles) :: self class (cosmologyFunctionsClass ), intent(in ), target :: cosmologyFunctions_ + class (darkMatterHaloScaleClass ), intent(in ), target :: darkMatterHaloScale_ class (darkMatterProfileDMOClass ), intent(in ), target :: darkMatterProfileDMO_ class (galacticFilterClass ), intent(in ), target :: galacticFilter_ double precision , intent(in ) :: wavenumberMinimum , wavenumberMaximum integer , intent(in ) :: wavenumberPointsPerDecade !![ - + !!] ! Build a grid of wavenumbers. @@ -167,6 +173,7 @@ subroutine haloFourierProfilesDestructor(self) !![ + !!] return @@ -194,6 +201,7 @@ subroutine haloFourierProfilesOutputTree(self,tree,indexOutput,time) use :: Galacticus_Nodes , only : treeNode , nodeComponentBasic !$ use :: HDF5_Access , only : hdf5Access use :: ISO_Varying_String , only : var_str + use :: Mass_Distributions , only : massDistributionClass use :: Merger_Tree_Walkers , only : mergerTreeWalkerAllNodes use :: Numerical_Constants_Astronomical, only : megaParsec use :: String_Handling , only : operator(//) @@ -204,12 +212,13 @@ subroutine haloFourierProfilesOutputTree(self,tree,indexOutput,time) double precision , intent(in ) :: time type (treeNode ) , pointer :: node class (nodeComponentBasic ) , pointer :: basic + class (massDistributionClass ) , pointer :: massDistribution_ double precision , allocatable , dimension(:) :: fourierProfile type (mergerTreeWalkerAllNodes ) :: treeWalker - type (hdf5Object ) :: outputGroup , treeGroup, & + type (hdf5Object ) :: outputGroup , treeGroup , & & dataset integer (c_size_t ) :: treeIndexPrevious - double precision :: expansionFactor + double precision :: expansionFactor , radiusVirial integer :: i !$GLC attributes unused :: time @@ -236,11 +245,16 @@ subroutine haloFourierProfilesOutputTree(self,tree,indexOutput,time) end if basic => node%basic ( ) expansionFactor = self%cosmologyFunctions_%expansionFactor(basic%time()) - ! Construct profile. (Our wavenumbers are comoving, so we must convert them to physical coordinates before passing them to + ! Construct profile. (Our wavenumbers are comoving, so we must convert them to physical coordinates before passing them to ! the dark matter profile k-space routine.) + massDistribution_ => self%darkMatterProfileDMO_%get (node) + radiusVirial = self%darkMatterHaloScale_ %radiusVirial(node) do i=1,self%waveNumberCount - fourierProfile(i)=self%darkMatterProfileDMO_%kSpace(node,self%wavenumber(i)/expansionFactor) + fourierProfile(i)=massDistribution_%fourierTransform(radiusVirial,self%wavenumber(i)/expansionFactor) end do + !![ + + !!] !$ call hdf5Access%set () call treeGroup%writeDataset(fourierProfile,char(var_str('node')//node%index()),"The Fourier-space density profile.") !$ call hdf5Access%unset() diff --git a/source/models.likelihoods.spin_distribution.F90 b/source/models.likelihoods.spin_distribution.F90 index 5389862813..5166b4e1d5 100644 --- a/source/models.likelihoods.spin_distribution.F90 +++ b/source/models.likelihoods.spin_distribution.F90 @@ -52,7 +52,6 @@ class (cosmologyFunctionsClass ), pointer :: cosmologyFunctions_ => null() class (haloMassFunctionClass ), pointer :: haloMassFunction_ => null() class (nbodyHaloMassErrorClass ), pointer :: nbodyHaloMassError_ => null() - class (darkMatterProfileDMOClass ), pointer :: darkMatterProfileDMO_ => null() class (darkMatterHaloScaleClass ), pointer :: darkMatterHaloScale_ => null() class (darkMatterProfileScaleRadiusClass ), pointer :: darkMatterProfileScaleRadius_ => null() double precision , dimension(: ), allocatable :: spin , distribution , & @@ -92,7 +91,6 @@ function spinDistributionConstructorParameters(parameters) result(self) class (cosmologyFunctionsClass ), pointer :: cosmologyFunctions_ class (haloMassFunctionClass ), pointer :: haloMassFunction_ class (nbodyHaloMassErrorClass ), pointer :: nbodyHaloMassError_ - class (darkMatterProfileDMOClass ), pointer :: darkMatterProfileDMO_ class (darkMatterHaloScaleClass ), pointer :: darkMatterHaloScale_ class (darkMatterProfileScaleRadiusClass ), pointer :: darkMatterProfileScaleRadius_ type (varying_string ) :: fileName , distributionType @@ -147,24 +145,22 @@ function spinDistributionConstructorParameters(parameters) result(self) - !!] - self=posteriorSampleLikelihoodSpinDistribution(char(fileName),enumerationSpinDistributionTypeEncode(char(distributionType),includesPrefix=.false.),redshift,logNormalRange,massHaloMinimum,massParticle,particleCountMinimum,energyEstimateParticleCountMaximum,cosmologyFunctions_,haloMassFunction_,nbodyHaloMassError_,darkMatterProfileDMO_,darkMatterHaloScale_,darkMatterProfileScaleRadius_) + self=posteriorSampleLikelihoodSpinDistribution(char(fileName),enumerationSpinDistributionTypeEncode(char(distributionType),includesPrefix=.false.),redshift,logNormalRange,massHaloMinimum,massParticle,particleCountMinimum,energyEstimateParticleCountMaximum,cosmologyFunctions_,haloMassFunction_,nbodyHaloMassError_,darkMatterHaloScale_,darkMatterProfileScaleRadius_) !![ - !!] return end function spinDistributionConstructorParameters - function spinDistributionConstructorInternal(fileName,distributionType,redshift,logNormalRange,massHaloMinimum,massParticle,particleCountMinimum,energyEstimateParticleCountMaximum,cosmologyFunctions_,haloMassFunction_,nbodyHaloMassError_,darkMatterProfileDMO_,darkMatterHaloScale_,darkMatterProfileScaleRadius_) result(self) + function spinDistributionConstructorInternal(fileName,distributionType,redshift,logNormalRange,massHaloMinimum,massParticle,particleCountMinimum,energyEstimateParticleCountMaximum,cosmologyFunctions_,haloMassFunction_,nbodyHaloMassError_,darkMatterHaloScale_,darkMatterProfileScaleRadius_) result(self) !!{ Constructor for ``spinDistribution'' posterior sampling likelihood class. !!} @@ -181,14 +177,13 @@ function spinDistributionConstructorInternal(fileName,distributionType,redshift, class (cosmologyFunctionsClass ), intent(in ), target :: cosmologyFunctions_ class (haloMassFunctionClass ), intent(in ), target :: haloMassFunction_ class (nbodyHaloMassErrorClass ), intent(in ), target :: nbodyHaloMassError_ - class (darkMatterProfileDMOClass ), intent(in ), target :: darkMatterProfileDMO_ class (darkMatterHaloScaleClass ), intent(in ), target :: darkMatterHaloScale_ class (darkMatterProfileScaleRadiusClass ), intent(in ), target :: darkMatterProfileScaleRadius_ type (hdf5Object ) :: spinDistributionFile double precision :: spinIntervalLogarithmic integer :: i !![ - + !!] ! Convert redshift to time. @@ -234,7 +229,6 @@ subroutine spinDistributionDestructor(self) - !!] @@ -299,7 +293,7 @@ double precision function spinDistributionEvaluate(self,simulationState,modelPar distributionLogNormal=haloSpinDistributionLogNormal ( & & stateVector(1) , & & stateVector(2) , & - & self%darkMatterProfileDMO_ & + & self%darkMatterHaloScale_ & & ) distributionNbody =haloSpinDistributionNbodyErrors( & & distributionLogNormal , & @@ -312,7 +306,6 @@ double precision function spinDistributionEvaluate(self,simulationState,modelPar & self%cosmologyFunctions_ , & & self%haloMassFunction_ , & & self%darkMatterHaloScale_ , & - & self%darkMatterProfileDMO_ , & & self%darkMatterProfileScaleRadius_ & & ) end select @@ -328,7 +321,7 @@ double precision function spinDistributionEvaluate(self,simulationState,modelPar distributionBett2007=haloSpinDistributionBett2007 ( & & stateVector(1) , & & stateVector(2) , & - & self%darkMatterProfileDMO_ & + & self%darkMatterHaloScale_ & & ) distributionNbody =haloSpinDistributionNbodyErrors( & & distributionBett2007 , & @@ -341,7 +334,6 @@ double precision function spinDistributionEvaluate(self,simulationState,modelPar & self%cosmologyFunctions_ , & & self%haloMassFunction_ , & & self%darkMatterHaloScale_ , & - & self%darkMatterProfileDMO_ , & & self%darkMatterProfileScaleRadius_ & & ) end select @@ -379,7 +371,7 @@ Integrand function used to find cumulative spin distribution over a bin. implicit none double precision, intent(in ) :: spinPrime - call nodeSpin%angularMomentumSet(spinPrime*Dark_Matter_Halo_Angular_Momentum_Scale(node,self%darkMatterProfileDMO_)) + call nodeSpin%angularMomentumSet(spinPrime*Dark_Matter_Halo_Angular_Momentum_Scale(node,self%darkMatterHaloScale_)) spinDistributionIntegrate=distributionNbody%distributionAveraged(node,self%massHaloMinimum) return end function spinDistributionIntegrate diff --git a/source/nodes.operators.empirical.UniverseMachine.F90 b/source/nodes.operators.empirical.UniverseMachine.F90 index 695b3d906f..4b5a9a2e3d 100644 --- a/source/nodes.operators.empirical.UniverseMachine.F90 +++ b/source/nodes.operators.empirical.UniverseMachine.F90 @@ -25,10 +25,9 @@ assembly. !!} - use :: Cosmology_Parameters , only : cosmologyParametersClass - use :: Cosmology_Functions , only : cosmologyFunctions , cosmologyFunctionsClass - use :: Dark_Matter_Profiles_DMO, only : darkMatterProfileDMOClass - use :: Virial_Density_Contrast , only : virialDensityContrastClass, virialDensityContrastBryanNorman1998 + use :: Cosmology_Parameters , only : cosmologyParametersClass + use :: Cosmology_Functions , only : cosmologyFunctions , cosmologyFunctionsClass + use :: Virial_Density_Contrast, only : virialDensityContrastClass, virialDensityContrastBryanNorman1998 !![ @@ -62,7 +61,6 @@ logical :: setFinalStellarMass , hasDisk , hasSpheroid class (cosmologyParametersClass ), pointer :: cosmologyParameters_ => null() class (cosmologyFunctionsClass ), pointer :: cosmologyFunctions_ => null() - class (darkMatterProfileDMOClass ), pointer :: darkMatterProfileDMO_ => null() class (virialDensityContrastClass ), pointer :: virialDensityContrast_ => null() type (virialDensityContrastBryanNorman1998), pointer :: virialDensityContrastDefinition_ => null() contains @@ -110,7 +108,6 @@ function empiricalGalaxyUniverseMachineConstructorParameters(parameters) result( & delta_0 class (cosmologyParametersClass ), pointer :: cosmologyParameters_ class (cosmologyFunctionsClass ), pointer :: cosmologyFunctions_ - class (darkMatterProfileDMOClass ), pointer :: darkMatterProfileDMO_ class (virialDensityContrastClass ), pointer :: virialDensityContrast_ !![ @@ -274,18 +271,17 @@ function empiricalGalaxyUniverseMachineConstructorParameters(parameters) result( - !!] - self=nodeOperatorEmpiricalGalaxyUniverseMachine( & - & massStellarFinal ,fractionMassSpheroid, fractionMassDisk , & - & epsilon_0 ,epsilon_a ,epsilon_lna ,epsilon_z , & - & M_0 ,M_a ,M_lna ,M_z , & - & alpha_0 ,alpha_a ,alpha_lna ,alpha_z , & - & beta_0 ,beta_a ,beta_z , & - & gamma_0 ,gamma_a ,gamma_z , & - & delta_0 , & - & cosmologyParameters_,cosmologyFunctions_ ,darkMatterProfileDMO_,virialDensityContrast_ & + self=nodeOperatorEmpiricalGalaxyUniverseMachine( & + & massStellarFinal ,fractionMassSpheroid, fractionMassDisk , & + & epsilon_0 ,epsilon_a ,epsilon_lna ,epsilon_z, & + & M_0 ,M_a ,M_lna ,M_z , & + & alpha_0 ,alpha_a ,alpha_lna ,alpha_z , & + & beta_0 ,beta_a ,beta_z , & + & gamma_0 ,gamma_a ,gamma_z , & + & delta_0 , & + & cosmologyParameters_,cosmologyFunctions_ ,virialDensityContrast_ & & ) !![ @@ -294,15 +290,15 @@ function empiricalGalaxyUniverseMachineConstructorParameters(parameters) result( return end function empiricalGalaxyUniverseMachineConstructorParameters - function empiricalGalaxyUniverseMachineConstructorInternal( & - & massStellarFinal ,fractionMassSpheroid,fractionMassDisk , & - & epsilon_0 ,epsilon_a ,epsilon_lna ,epsilon_z , & - & M_0 ,M_a ,M_lna ,M_z , & - & alpha_0 ,alpha_a ,alpha_lna ,alpha_z , & - & beta_0 ,beta_a ,beta_z , & - & gamma_0 ,gamma_a ,gamma_z , & - & delta_0 , & - & cosmologyParameters_,cosmologyFunctions_ ,darkMatterProfileDMO_,virialDensityContrast_ & + function empiricalGalaxyUniverseMachineConstructorInternal( & + & massStellarFinal ,fractionMassSpheroid,fractionMassDisk , & + & epsilon_0 ,epsilon_a ,epsilon_lna ,epsilon_z, & + & M_0 ,M_a ,M_lna ,M_z , & + & alpha_0 ,alpha_a ,alpha_lna ,alpha_z , & + & beta_0 ,beta_a ,beta_z , & + & gamma_0 ,gamma_a ,gamma_z , & + & delta_0 , & + & cosmologyParameters_,cosmologyFunctions_ ,virialDensityContrast_ & & ) result(self) !!{ Internal constructor for the {\normalfont \ttfamily empiricalGalaxyUniverseMachine} {\normalfont \ttfamily nodeOperator} class. @@ -321,10 +317,9 @@ function empiricalGalaxyUniverseMachineConstructorInternal( & gamma_z class (cosmologyParametersClass ), intent(in ), target :: cosmologyParameters_ class (cosmologyFunctionsClass ), intent(in ), target :: cosmologyFunctions_ - class (darkMatterProfileDMOClass ), intent(in ), target :: darkMatterProfileDMO_ class (virialDensityContrastClass ), intent(in ), target :: virialDensityContrast_ !![ - + !!] self%setFinalStellarMass=massStellarFinal >= 0.0d0 @@ -352,7 +347,6 @@ subroutine empiricalGalaxyUniverseMachineDestructor(self) !![ - !!] @@ -458,7 +452,6 @@ subroutine empiricalGalaxyUniverseMachineUpdate(self,node) & ) , & & cosmologyParameters_ =self%cosmologyParameters_ , & & cosmologyFunctions_ =self%cosmologyFunctions_ , & - & darkMatterProfileDMO_ =self%darkMatterProfileDMO_ , & & virialDensityContrast_=self%virialDensityContrast_ & & ) massStellar = self %stellarMassHaloMassRelation (massHalo,redshift) @@ -475,7 +468,6 @@ subroutine empiricalGalaxyUniverseMachineUpdate(self,node) & ) , & & cosmologyParameters_ =self%cosmologyParameters_ , & & cosmologyFunctions_ =self%cosmologyFunctions_ , & - & darkMatterProfileDMO_ =self%darkMatterProfileDMO_ , & & virialDensityContrast_=self%virialDensityContrast_ & & ) redshiftRoot = self %cosmologyFunctions_%redshiftFromExpansionFactor( & diff --git a/source/nodes.operators.physics.Bertschinger_mass.interpolate.F90 b/source/nodes.operators.physics.Bertschinger_mass.interpolate.F90 index aa885c81ce..09d4471dd0 100644 --- a/source/nodes.operators.physics.Bertschinger_mass.interpolate.F90 +++ b/source/nodes.operators.physics.Bertschinger_mass.interpolate.F90 @@ -41,7 +41,6 @@ private class (cosmologyParametersClass ), pointer :: cosmologyParameters_ => null() class (cosmologyFunctionsClass ), pointer :: cosmologyFunctions_ => null() - class (darkMatterProfileDMOClass ), pointer :: darkMatterProfileDMO_ => null() class (virialDensityContrastClass), pointer :: virialDensityContrast_ => null() integer :: massBertschingerID , massBertschingerTargetID, & & accretionRateBertschingerID @@ -75,27 +74,24 @@ function bertschingerMassConstructorParameters(parameters) result(self) type (inputParameters ), intent(inout) :: parameters class(cosmologyParametersClass ), pointer :: cosmologyParameters_ class(cosmologyFunctionsClass ), pointer :: cosmologyFunctions_ - class(darkMatterProfileDMOClass ), pointer :: darkMatterProfileDMO_ class(virialDensityContrastClass ), pointer :: virialDensityContrast_ !![ - !!] - self=nodeOperatorBertschingerMass(cosmologyParameters_,cosmologyFunctions_,darkMatterProfileDMO_,virialDensityContrast_) + self=nodeOperatorBertschingerMass(cosmologyParameters_,cosmologyFunctions_,virialDensityContrast_) !![ - !!] return end function bertschingerMassConstructorParameters - function bertschingerMassConstructorInternal(cosmologyParameters_,cosmologyFunctions_,darkMatterProfileDMO_,virialDensityContrast_) result(self) + function bertschingerMassConstructorInternal(cosmologyParameters_,cosmologyFunctions_,virialDensityContrast_) result(self) !!{ Internal constructor for the {\normalfont \ttfamily bertschingerMass} node operator class. !!} @@ -103,10 +99,9 @@ function bertschingerMassConstructorInternal(cosmologyParameters_,cosmologyFunct type (nodeOperatorBertschingerMass) :: self class(cosmologyParametersClass ), intent(in ), target :: cosmologyParameters_ class(cosmologyFunctionsClass ), intent(in ), target :: cosmologyFunctions_ - class(darkMatterProfileDMOClass ), intent(in ), target :: darkMatterProfileDMO_ class(virialDensityContrastClass ), intent(in ), target :: virialDensityContrast_ !![ - + !!] !![ @@ -127,7 +122,6 @@ subroutine bertschingerMassDestructor(self) !![ - !!] return @@ -173,7 +167,6 @@ recursive subroutine bertschingerMassNodeInitialize(self,node) & ) , & & cosmologyParameters_ =self%cosmologyParameters_ , & & cosmologyFunctions_ =self%cosmologyFunctions_ , & - & darkMatterProfileDMO_ =self%darkMatterProfileDMO_ , & & virialDensityContrast_=self%virialDensityContrast_ & & ) & & ) @@ -228,7 +221,6 @@ recursive subroutine bertschingerMassNodeInitialize(self,node) & ) , & & cosmologyParameters_ =self%cosmologyParameters_ , & & cosmologyFunctions_ =self%cosmologyFunctions_ , & - & darkMatterProfileDMO_ =self%darkMatterProfileDMO_ , & & virialDensityContrast_=self%virialDensityContrast_ & & ) & & -massUnresolved diff --git a/source/nodes.operators.physics.CGM.chemistry.F90 b/source/nodes.operators.physics.CGM.chemistry.F90 index cf4d3afdfc..83e4e94a47 100644 --- a/source/nodes.operators.physics.CGM.chemistry.F90 +++ b/source/nodes.operators.physics.CGM.chemistry.F90 @@ -27,7 +27,6 @@ use :: Chemical_Reaction_Rates , only : chemicalReactionRateClass use :: Cosmology_Functions , only : cosmologyFunctionsClass use :: Dark_Matter_Halo_Scales , only : darkMatterHaloScaleClass - use :: Hot_Halo_Mass_Distributions , only : hotHaloMassDistributionClass use :: Radiation_Fields , only : radiationFieldClass , crossSectionFunctionTemplate use :: Numerical_Constants_Physical , only : plancksConstant , speedLight use :: Numerical_Constants_Units , only : angstromsPerMeter , electronVolt @@ -74,7 +73,6 @@ class (chemicalReactionRateClass ), pointer :: chemicalReactionRate_ => null() class (darkMatterHaloScaleClass ), pointer :: darkMatterHaloScale_ => null() class (cosmologyFunctionsClass ), pointer :: cosmologyFunctions_ => null() - class (hotHaloMassDistributionClass ), pointer :: hotHaloMassDistribution_ => null() class (radiationFieldClass ), pointer :: radiation_ => null() logical , allocatable, dimension(:) :: maskAnalytic integer :: atomicHydrogenIndex , atomicHydrogenCationIndex, & @@ -138,7 +136,6 @@ function cgmChemistryConstructorParameters(parameters) result(self) class (darkMatterHaloScaleClass ), pointer :: darkMatterHaloScale_ class (cosmologyFunctionsClass ), pointer :: cosmologyFunctions_ class (radiationFieldClass ), pointer :: radiation_ - class (hotHaloMassDistributionClass ), pointer :: hotHaloMassDistribution_ double precision :: fractionTimescaleEquilibrium !![ @@ -154,8 +151,8 @@ function cgmChemistryConstructorParameters(parameters) result(self) - !!] + radiation_ => null() if (parameters%isPresent('radiationFieldIntergalacticBackground',searchInParents=.true.)) then !![ @@ -168,8 +165,8 @@ function cgmChemistryConstructorParameters(parameters) result(self) !!] end select - end if - self=nodeOperatorCGMChemistry(fractionTimescaleEquilibrium,atomicIonizationRateCollisional_,atomicRecombinationRateRadiative_,atomicCrossSectionIonizationPhoto_,chemicalReactionRate_,darkMatterHaloScale_,cosmologyFunctions_,hotHaloMassDistribution_,radiation_) + end if + self=nodeOperatorCGMChemistry(fractionTimescaleEquilibrium,atomicIonizationRateCollisional_,atomicRecombinationRateRadiative_,atomicCrossSectionIonizationPhoto_,chemicalReactionRate_,darkMatterHaloScale_,cosmologyFunctions_,radiation_) !![ @@ -179,30 +176,27 @@ function cgmChemistryConstructorParameters(parameters) result(self) - !!] return end function cgmChemistryConstructorParameters - function cgmChemistryConstructorInternal(fractionTimescaleEquilibrium,atomicIonizationRateCollisional_,atomicRecombinationRateRadiative_,atomicCrossSectionIonizationPhoto_,chemicalReactionRate_,darkMatterHaloScale_,cosmologyFunctions_,hotHaloMassDistribution_,radiation_) result(self) + function cgmChemistryConstructorInternal(fractionTimescaleEquilibrium,atomicIonizationRateCollisional_,atomicRecombinationRateRadiative_,atomicCrossSectionIonizationPhoto_,chemicalReactionRate_,darkMatterHaloScale_,cosmologyFunctions_,radiation_) result(self) !!{ Internal constructor for the {\normalfont \ttfamily cgmChemistry} node operator class. !!} use :: Chemical_Abundances_Structure, only : Chemicals_Index, Chemicals_Property_Count implicit none - type (nodeOperatorCGMChemistry ) :: self - double precision , intent(in ) :: fractionTimescaleEquilibrium - class (atomicIonizationRateCollisionalClass ), intent(in ), target :: atomicIonizationRateCollisional_ - class (atomicRecombinationRateRadiativeClass ), intent(in ), target :: atomicRecombinationRateRadiative_ - class (atomicCrossSectionIonizationPhotoClass), intent(in ), target :: atomicCrossSectionIonizationPhoto_ - class (chemicalReactionRateClass ), intent(in ), target :: chemicalReactionRate_ - class (darkMatterHaloScaleClass ), intent(in ), target :: darkMatterHaloScale_ - class (cosmologyFunctionsClass ), intent(in ), target :: cosmologyFunctions_ - class (hotHaloMassDistributionClass ), intent(in ), target :: hotHaloMassDistribution_ - class (radiationFieldClass ), intent(in ), target :: radiation_ - !$GLC attributes initialized :: radiationIntergalacticBackground + type (nodeOperatorCGMChemistry ) :: self + double precision , intent(in ) :: fractionTimescaleEquilibrium + class (atomicIonizationRateCollisionalClass ), intent(in ), target :: atomicIonizationRateCollisional_ + class (atomicRecombinationRateRadiativeClass ), intent(in ), target :: atomicRecombinationRateRadiative_ + class (atomicCrossSectionIonizationPhotoClass), intent(in ), target :: atomicCrossSectionIonizationPhoto_ + class (chemicalReactionRateClass ), intent(in ), target :: chemicalReactionRate_ + class (darkMatterHaloScaleClass ), intent(in ), target :: darkMatterHaloScale_ + class (cosmologyFunctionsClass ), intent(in ), target :: cosmologyFunctions_ + class (radiationFieldClass ), intent(in ), pointer :: radiation_ !![ - + !!] ! Determine if chemicals are being solved for. @@ -236,7 +230,6 @@ subroutine cgmChemistryDestructor(self) - !!] return end subroutine cgmChemistryDestructor @@ -325,9 +318,11 @@ subroutine cgmChemistryDifferentialEvolution(self,node,interrupt,functionInterru !!} use :: Chemical_Abundances_Structure , only : chemicalAbundances use :: Galacticus_Nodes , only : nodeComponentHotHalo - use :: Numerical_Constants_Astronomical , only : gigaYear , megaParsec + use :: Numerical_Constants_Astronomical , only : gigaYear , megaParsec use :: Numerical_Constants_Prefixes , only : centi use :: Numerical_Constants_Math , only : Pi + use :: Mass_Distributions , only : massDistributionClass + use :: Galactic_Structure_Options , only : componentTypeHotHalo , massTypeGaseous implicit none class (nodeOperatorCGMChemistry), intent(inout), target :: self type (treeNode ), intent(inout), target :: node @@ -335,6 +330,7 @@ subroutine cgmChemistryDifferentialEvolution(self,node,interrupt,functionInterru procedure (interruptTask ), intent(inout), pointer :: functionInterrupt integer , intent(in ) :: propertyType class (nodeComponentHotHalo ) , pointer :: hotHalo + class (massDistributionClass ) , pointer :: massDistribution_ double precision , parameter :: massHotHaloTiny =1.0d-6 type (chemicalAbundances ), save :: chemicalDensitiesRates , chemicalMassesRates, & & chemicalDensities @@ -355,23 +351,27 @@ subroutine cgmChemistryDifferentialEvolution(self,node,interrupt,functionInterru ! Compute the column length through the halo (in cm). massHotHalo=hotHalo%mass() if (massHotHalo > massHotHaloTiny) then - radiusOuter =+hotHalo %outerRadius ( ) - factorBoostColumn=+self %hotHaloMassDistribution_%radialMoment (node,0.0d0,radiusOuter) & - & *4.0d0 & - & *Pi & - & *radiusOuter **2 & - & /3.0d0 & - & /massHotHalo - lengthColumn =+radiusOuter & - & *megaParsec & - & /centi - factorClumping =+self %hotHaloMassDistribution_%densitySquaredIntegral(node ,radiusOuter) & - & *4.0d0 & - & /3.0d0 & - & *Pi & - & *radiusOuter**3 & - & /massHotHalo**2 - else + massDistribution_ => node %massDistribution (componentTypeHotHalo,massTypeGaseous) + radiusOuter = +hotHalo %outerRadius ( ) + factorBoostColumn = +massDistribution_%densityRadialMoment (0.0d0,radiusOuter ) & + & *4.0d0 & + & *Pi & + & *radiusOuter **2 & + & /3.0d0 & + & /massHotHalo + lengthColumn = +radiusOuter & + & *megaParsec & + & /centi + factorClumping = +massDistribution_%densitySquareIntegral( radiusOuter ) & + & *4.0d0 & + & /3.0d0 & + & *Pi & + & *radiusOuter**3 & + & /massHotHalo**2 + !![ + + !!] + else lengthColumn =+0.0d0 factorClumping =+1.0d0 end if diff --git a/source/nodes.operators.physics.cooling_energy_radiated.F90 b/source/nodes.operators.physics.cooling_energy_radiated.F90 index dc3edf2240..8a50b1657c 100644 --- a/source/nodes.operators.physics.cooling_energy_radiated.F90 +++ b/source/nodes.operators.physics.cooling_energy_radiated.F90 @@ -22,13 +22,11 @@ following the model of \cite{benson_galaxy_2010-1}. !!} - use :: Hot_Halo_Temperature_Profiles, only : hotHaloTemperatureProfileClass - use :: Radiation_Fields , only : radiationFieldCosmicMicrowaveBackground - use :: Galactic_Structure , only : galacticStructureClass - use :: Cooling_Functions , only : coolingFunctionClass - use :: Cosmology_Functions , only : cosmologyFunctionsClass - use :: Chemical_States , only : chemicalStateClass - use :: Dark_Matter_Halo_Scales , only : darkMatterHaloScaleClass + use :: Radiation_Fields , only : radiationFieldCosmicMicrowaveBackground + use :: Cooling_Functions , only : coolingFunctionClass + use :: Cosmology_Functions , only : cosmologyFunctionsClass + use :: Chemical_States , only : chemicalStateClass + use :: Dark_Matter_Halo_Scales, only : darkMatterHaloScaleClass !![ @@ -46,13 +44,11 @@ A node operator class that accumulates an estimate of the energy radiated from the hot halo due to cooling following the model of \cite{benson_galaxy_2010-1}. !!} private - class (cosmologyFunctionsClass ), pointer :: cosmologyFunctions_ => null() - class (coolingFunctionClass ), pointer :: coolingFunction_ => null() - class (hotHaloTemperatureProfileClass ), pointer :: hotHaloTemperatureProfile_ => null() - class (darkMatterHaloScaleClass ), pointer :: darkMatterHaloScale_ => null() - class (chemicalStateClass ), pointer :: chemicalState_ => null() - class (galacticStructureClass ), pointer :: galacticStructure_ => null() - type (radiationFieldCosmicMicrowaveBackground), pointer :: radiation => null() + class (cosmologyFunctionsClass ), pointer :: cosmologyFunctions_ => null() + class (coolingFunctionClass ), pointer :: coolingFunction_ => null() + class (darkMatterHaloScaleClass ), pointer :: darkMatterHaloScale_ => null() + class (chemicalStateClass ), pointer :: chemicalState_ => null() + type (radiationFieldCosmicMicrowaveBackground), pointer :: radiation => null() integer :: energyRadiatedID contains final :: coolingEnergyRadiatedDestructor @@ -81,35 +77,29 @@ function coolingEnergyRadiatedConstructorParameters(parameters) result(self) implicit none type (nodeOperatorCoolingEnergyRadiated) :: self type (inputParameters ), intent(inout) :: parameters - class(hotHaloTemperatureProfileClass ), pointer :: hotHaloTemperatureProfile_ class(darkMatterHaloScaleClass ), pointer :: darkMatterHaloScale_ class(cosmologyFunctionsClass ), pointer :: cosmologyFunctions_ class(coolingFunctionClass ), pointer :: coolingFunction_ class(chemicalStateClass ), pointer :: chemicalState_ - class(galacticStructureClass ), pointer :: galacticStructure_ !![ - - - - - - + + + + !!] - self=nodeOperatorCoolingEnergyRadiated(cosmologyFunctions_,coolingFunction_,hotHaloTemperatureProfile_,chemicalState_,darkMatterHaloScale_,galacticStructure_) + self=nodeOperatorCoolingEnergyRadiated(cosmologyFunctions_,coolingFunction_,chemicalState_,darkMatterHaloScale_) !![ - - - - - - + + + + !!] return end function coolingEnergyRadiatedConstructorParameters - function coolingEnergyRadiatedConstructorInternal(cosmologyFunctions_,coolingFunction_,hotHaloTemperatureProfile_,chemicalState_,darkMatterHaloScale_,galacticStructure_) result(self) + function coolingEnergyRadiatedConstructorInternal(cosmologyFunctions_,coolingFunction_,chemicalState_,darkMatterHaloScale_) result(self) !!{ Internal constructor for the {\normalfont \ttfamily coolingEnergyRadiated} node operator class. !!} @@ -117,12 +107,10 @@ function coolingEnergyRadiatedConstructorInternal(cosmologyFunctions_,coolingFun type (nodeOperatorCoolingEnergyRadiated) :: self class(cosmologyFunctionsClass ), intent(in ), target :: cosmologyFunctions_ class(coolingFunctionClass ), intent(in ), target :: coolingFunction_ - class(hotHaloTemperatureProfileClass ), intent(in ), target :: hotHaloTemperatureProfile_ class(darkMatterHaloScaleClass ), intent(in ), target :: darkMatterHaloScale_ class(chemicalStateClass ), intent(in ), target :: chemicalState_ - class(galacticStructureClass ), intent(in ), target :: galacticStructure_ !![ - + !!] allocate(self%radiation) @@ -154,13 +142,11 @@ subroutine coolingEnergyRadiatedDestructor(self) type(nodeOperatorCoolingEnergyRadiated), intent(inout) :: self !![ - - - - - - - + + + + + !!] if (hotHaloMassEjectionEvent%isAttached(self,coolingEnergyRadiatedHotHaloMassEjection)) call hotHaloMassEjectionEvent%detach(self,coolingEnergyRadiatedHotHaloMassEjection) return @@ -204,8 +190,10 @@ subroutine coolingEnergyRadiatedDifferentialEvolution(self,node,interrupt,functi use :: Abundances_Structure , only : abundances use :: Chemical_Abundances_Structure , only : chemicalAbundances , Chemicals_Property_Count use :: Chemical_Reaction_Rates_Utilities, only : Chemicals_Mass_To_Density_Conversion - use :: Galactic_Structure_Options , only : radiusLarge , massTypeGalactic - use :: Numerical_Constants_Astronomical , only : gigaYear , massSolar , megaParsec + use :: Mass_Distributions , only : massDistributionClass , kinematicsDistributionClass + use :: Coordinates , only : coordinateSpherical , assignment(=) + use :: Galactic_Structure_Options , only : componentTypeHotHalo , massTypeGaseous , radiusLarge, massTypeGalactic + use :: Numerical_Constants_Astronomical , only : gigaYear , massSolar , megaParsec use :: Numerical_Constants_Atomic , only : massHydrogenAtom use :: Numerical_Constants_Physical , only : boltzmannsConstant use :: Numerical_Constants_Prefixes , only : hecto , centi @@ -219,6 +207,9 @@ subroutine coolingEnergyRadiatedDifferentialEvolution(self,node,interrupt,functi integer , intent(in ) :: propertyType class (nodeComponentBasic ) , pointer :: basic class (nodeComponentHotHalo ) , pointer :: hotHalo + class (massDistributionClass ) , pointer :: massDistribution_ + class (kinematicsDistributionClass ) , pointer :: kinematicsDistribution_ + type (coordinateSpherical ) :: coordinates double precision :: density , temperature , & & massToDensityConversion, numberDensityHydrogen, & & numberDensityAllSpecies, coolingFunction , & @@ -233,18 +224,29 @@ subroutine coolingEnergyRadiatedDifferentialEvolution(self,node,interrupt,functi type is (nodeComponentHotHalo) ! Hot halo does not exists - nothing to do here. class default - basic => node %basic ( ) - massNotional = +hotHalo %mass ( ) & - & +hotHalo %outflowedMass( ) & - & +self %galacticStructure_%massEnclosed (node,radiusLarge,massType=massTypeGalactic) + basic => node %basic ( ) + massDistribution_ => node %massDistribution(massType=massTypeGalactic) + massNotional = +hotHalo %mass ( ) & + & +hotHalo %outflowedMass ( ) & + & +massDistribution_%massTotal ( ) + !![ + + !!] if (massNotional <= 0.0d0) return ! Compute the mean density and temperature of the hot halo. + massDistribution_ => node %massDistribution (componentType=componentTypeHotHalo,massType=massTypeGaseous) + kinematicsDistribution_ => massDistribution_%kinematicsDistribution( ) density =+massNotional & & *3.0d0 & & /4.0d0 & & /Pi & & /hotHalo%outerRadius()**3 - temperature=self%hotHaloTemperatureProfile_%temperature(node,hotHalo%outerRadius()) + coordinates=[hotHalo%outerRadius(),0.0d0,0.0d0] + temperature=+kinematicsDistribution_%temperature(coordinates) + !![ + + + !!] ! Get the abundances for this node. abundances_=hotHalo%abundances() call abundances_%massToMassFraction(hotHalo%mass()) @@ -351,24 +353,30 @@ subroutine coolingEnergyRadiatedHotHaloMassEjection(self,hotHalo,massRate) Respond to mass ejection from the hot halo component. !!} use :: Error , only : Error_Report - use :: Galacticus_Nodes , only : nodeComponentBasic, nodeComponentHotHalo - use :: Galactic_Structure_Options, only : radiusLarge , massTypeGalactic + use :: Galacticus_Nodes , only : nodeComponentBasic , nodeComponentHotHalo + use :: Galactic_Structure_Options, only : massTypeGalactic + use :: Mass_Distributions , only : massDistributionClass implicit none - class (* ), intent(inout) :: self - class (nodeComponentHotHalo), intent(inout) :: hotHalo - double precision , intent(in ) :: massRate - class (nodeComponentBasic ), pointer :: basic - type (treeNode ), pointer :: node - double precision :: massNotional + class (* ), intent(inout) :: self + class (nodeComponentHotHalo ), intent(inout) :: hotHalo + double precision , intent(in ) :: massRate + class (nodeComponentBasic ), pointer :: basic + type (treeNode ), pointer :: node + class (massDistributionClass), pointer :: massDistribution_ + double precision :: massNotional select type (self) class is (nodeOperatorCoolingEnergyRadiated) ! Compute the mass in the notional hot halo. - node => hotHalo%hostNode - basic => node %basic ( ) - massNotional = +hotHalo %mass ( ) & - & +hotHalo %outflowedMass( ) & - & +self %galacticStructure_%massEnclosed (node,radiusLarge,massType=massTypeGalactic) + node => hotHalo %hostNode + basic => node %basic ( ) + massDistribution_ => node %massDistribution(massType=massTypeGalactic) + massNotional = +hotHalo %mass ( ) & + & +hotHalo %outflowedMass ( ) & + & +massDistribution_%massTotal ( ) + !![ + + !!] if (massNotional > 0.0d0) & & call hotHalo%floatRank0MetaPropertyRate(self%energyRadiatedID,-hotHalo%floatRank0MetaPropertyGet(self%energyRadiatedID)*massRate/massNotional) class default diff --git a/source/nodes.operators.physics.dark_matter_profile.initialize.F90 b/source/nodes.operators.physics.dark_matter_profile.initialize.F90 new file mode 100644 index 0000000000..186ca6581e --- /dev/null +++ b/source/nodes.operators.physics.dark_matter_profile.initialize.F90 @@ -0,0 +1,77 @@ +!! Copyright 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, +!! 2019, 2020, 2021, 2022, 2023, 2024 +!! Andrew Benson +!! +!! This file is part of Galacticus. +!! +!! Galacticus is free software: you can redistribute it and/or modify +!! it under the terms of the GNU General Public License as published by +!! the Free Software Foundation, either version 3 of the License, or +!! (at your option) any later version. +!! +!! Galacticus is distributed in the hope that it will be useful, +!! but WITHOUT ANY WARRANTY; without even the implied warranty of +!! MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +!! GNU General Public License for more details. +!! +!! You should have received a copy of the GNU General Public License +!! along with Galacticus. If not, see . + + !!{ + Implements a node operator class that simply initializes the dark matter profile. + !!} + + !![ + + + A node operator class that simply initializes the dark matter profile. + + + !!] + type, extends(nodeOperatorClass) :: nodeOperatorDarkMatterProfileInitialize + !!{ + A node operator class that simply initializes the dark matter profile in halos. + !!} + private + contains + procedure :: nodeTreeInitialize => darkMatterProfileInitializeNodeTreeInitialize + end type nodeOperatorDarkMatterProfileInitialize + + interface nodeOperatorDarkMatterProfileInitialize + !!{ + Constructors for the {\normalfont \ttfamily darkMatterProfileInitialize} node operator class. + !!} + module procedure darkMatterProfileInitializeConstructorParameters + end interface nodeOperatorDarkMatterProfileInitialize + +contains + + function darkMatterProfileInitializeConstructorParameters(parameters) result(self) + !!{ + Constructor for the {\normalfont \ttfamily darkMatterProfileInitialize} node operator class which takes a parameter set as input. + !!} + use :: Input_Parameters, only : inputParameters + implicit none + type (nodeOperatorDarkMatterProfileInitialize) :: self + type (inputParameters ), intent(inout) :: parameters + + self=nodeOperatorDarkMatterProfileInitialize() + !![ + + !!] + return + end function darkMatterProfileInitializeConstructorParameters + + subroutine darkMatterProfileInitializeNodeTreeInitialize(self,node) + !!{ + Initialize dark matter profile scale radii. + !!} + use :: Galacticus_Nodes, only : nodeComponentDarkMatterProfile + implicit none + class(nodeOperatorDarkMatterProfileInitialize), intent(inout), target :: self + type (treeNode ), intent(inout), target :: node + class(nodeComponentDarkMatterProfile ) , pointer :: darkMatterProfile + + darkMatterProfile => node%darkMatterProfile(autoCreate=.true.) + return + end subroutine darkMatterProfileInitializeNodeTreeInitialize diff --git a/source/nodes.operators.physics.halo_angular_momentum.Vitvitska2002.F90 b/source/nodes.operators.physics.halo_angular_momentum.Vitvitska2002.F90 index cc7a193500..62ba0620d8 100644 --- a/source/nodes.operators.physics.halo_angular_momentum.Vitvitska2002.F90 +++ b/source/nodes.operators.physics.halo_angular_momentum.Vitvitska2002.F90 @@ -24,7 +24,6 @@ use :: Dark_Matter_Halo_Scales , only : darkMatterHaloScaleClass use :: Dark_Matter_Profile_Scales , only : darkMatterProfileScaleRadiusClass - use :: Dark_Matter_Profiles_DMO , only : darkMatterProfileDMOClass use :: Halo_Spin_Distributions , only : haloSpinDistributionClass use :: Virial_Orbits , only : virialOrbitClass use :: Merger_Trees_Build_Mass_Resolution, only : mergerTreeMassResolutionClass @@ -63,7 +62,6 @@ !!} private class (haloSpinDistributionClass ), pointer :: haloSpinDistribution_ => null() - class (darkMatterProfileDMOClass ), pointer :: darkMatterProfileDMO_ => null() class (virialOrbitClass ), pointer :: virialOrbit_ => null() class (darkMatterHaloScaleClass ), pointer :: darkMatterHaloScale_ => null() class (darkMatterProfileScaleRadiusClass), pointer :: darkMatterProfileScaleRadius_ => null() @@ -93,7 +91,6 @@ function haloAngularMomentumVitvitska2002ConstructorParameters(parameters) resul type (nodeOperatorHaloAngularMomentumVitvitska2002) :: self type (inputParameters ), intent(inout) :: parameters class (haloSpinDistributionClass ), pointer :: haloSpinDistribution_ - class (darkMatterProfileDMOClass ), pointer :: darkMatterProfileDMO_ class (virialOrbitClass ), pointer :: virialOrbit_ class (darkMatterHaloScaleClass ), pointer :: darkMatterHaloScale_ class (darkMatterProfileScaleRadiusClass ), pointer :: darkMatterProfileScaleRadius_ @@ -115,16 +112,14 @@ function haloAngularMomentumVitvitska2002ConstructorParameters(parameters) resul - !!] - self=nodeOperatorHaloAngularMomentumVitvitska2002(exponentMass,angularMomentumVarianceSpecific,darkMatterProfileScaleRadius_,haloSpinDistribution_,darkMatterProfileDMO_,darkMatterHaloScale_,virialOrbit_,mergerTreeMassResolution_) + self=nodeOperatorHaloAngularMomentumVitvitska2002(exponentMass,angularMomentumVarianceSpecific,darkMatterProfileScaleRadius_,haloSpinDistribution_,darkMatterHaloScale_,virialOrbit_,mergerTreeMassResolution_) !![ - @@ -133,7 +128,7 @@ function haloAngularMomentumVitvitska2002ConstructorParameters(parameters) resul return end function haloAngularMomentumVitvitska2002ConstructorParameters - function haloAngularMomentumVitvitska2002ConstructorInternal(exponentMass,angularMomentumVarianceSpecific,darkMatterProfileScaleRadius_,haloSpinDistribution_,darkMatterProfileDMO_,darkMatterHaloScale_,virialOrbit_,mergerTreeMassResolution_) result(self) + function haloAngularMomentumVitvitska2002ConstructorInternal(exponentMass,angularMomentumVarianceSpecific,darkMatterProfileScaleRadius_,haloSpinDistribution_,darkMatterHaloScale_,virialOrbit_,mergerTreeMassResolution_) result(self) !!{ Internal constructor for the {\normalfont \ttfamily haloAngularMomentumVitvitska2002} node operator class. !!} @@ -142,14 +137,13 @@ function haloAngularMomentumVitvitska2002ConstructorInternal(exponentMass,angula implicit none type (nodeOperatorHaloAngularMomentumVitvitska2002) :: self class (haloSpinDistributionClass ), intent(in ), target :: haloSpinDistribution_ - class (darkMatterProfileDMOClass ), intent(in ), target :: darkMatterProfileDMO_ class (virialOrbitClass ), intent(in ), target :: virialOrbit_ class (darkMatterHaloScaleClass ), intent(in ), target :: darkMatterHaloScale_ class (darkMatterProfileScaleRadiusClass ), intent(in ), target :: darkMatterProfileScaleRadius_ class (mergerTreeMassResolutionClass ), intent(in ), target :: mergerTreeMassResolution_ double precision , intent(in ) :: exponentMass , angularMomentumVarianceSpecific !![ - + !!] ! Ensure that the spin component supports vector angular momentum. @@ -183,7 +177,6 @@ subroutine haloAngularMomentumVitvitska2002Destructor(self) !![ - @@ -229,7 +222,7 @@ subroutine haloAngularMomentumVitvitska2002NodeTreeInitialize(self,node) if (.not.associated(node%firstChild)) then theta =acos(2.0d0 *node%hostTree%randomNumberGenerator_%uniformSample()-1.0d0) phi = 2.0d0*Pi*node%hostTree%randomNumberGenerator_%uniformSample() - angularMomentumValue=self%haloSpinDistribution_%sample(node)*Dark_Matter_Halo_Angular_Momentum_Scale(node,self%darkMatterProfileDMO_) + angularMomentumValue=self%haloSpinDistribution_%sample(node)*Dark_Matter_Halo_Angular_Momentum_Scale(node,self%darkMatterHaloScale_) angularMomentumTotal=angularMomentumValue*[sin(theta)*cos(phi),sin(theta)*sin(phi),cos(theta)] else nodeChild => node %firstChild diff --git a/source/nodes.operators.physics.halo_angular_momentum_random.F90 b/source/nodes.operators.physics.halo_angular_momentum_random.F90 index a65f5695fe..885151a760 100644 --- a/source/nodes.operators.physics.halo_angular_momentum_random.F90 +++ b/source/nodes.operators.physics.halo_angular_momentum_random.F90 @@ -21,8 +21,8 @@ Implements a node operator class that initializes halo angular momenta using spins drawn at random from a distribution. !!} - use :: Dark_Matter_Profiles_DMO, only : darkMatterProfileDMOClass - use :: Halo_Spin_Distributions , only : haloSpinDistributionClass + use :: Dark_Matter_Halo_Scales, only : darkMatterHaloScaleClass + use :: Halo_Spin_Distributions, only : haloSpinDistributionClass !![ @@ -34,7 +34,7 @@ A node operator class that initializes halo spins to random values drawn from a distribution. !!} private - class (darkMatterProfileDMOClass), pointer :: darkMatterProfileDMO_ => null() + class (darkMatterHaloScaleClass ), pointer :: darkMatterHaloScale_ => null() class (haloSpinDistributionClass), pointer :: haloSpinDistribution_ => null() double precision :: factorReset contains @@ -60,7 +60,7 @@ function haloAngularMomentumRandomConstructorParameters(parameters) result(self) implicit none type (nodeOperatorHaloAngularMomentumRandom) :: self type (inputParameters ), intent(inout) :: parameters - class (darkMatterProfileDMOClass ), pointer :: darkMatterProfileDMO_ + class (darkMatterHaloScaleClass ), pointer :: darkMatterHaloScale_ class (haloSpinDistributionClass ), pointer :: haloSpinDistribution_ double precision :: factorReset @@ -72,28 +72,28 @@ function haloAngularMomentumRandomConstructorParameters(parameters) result(self) parameters - + !!] - self=nodeOperatorHaloAngularMomentumRandom(factorReset,haloSpinDistribution_,darkMatterProfileDMO_) + self=nodeOperatorHaloAngularMomentumRandom(factorReset,haloSpinDistribution_,darkMatterHaloScale_) !![ - + !!] return end function haloAngularMomentumRandomConstructorParameters - function haloAngularMomentumRandomConstructorInternal(factorReset,haloSpinDistribution_,darkMatterProfileDMO_) result(self) + function haloAngularMomentumRandomConstructorInternal(factorReset,haloSpinDistribution_,darkMatterHaloScale_) result(self) !!{ Internal constructor for the {\normalfont \ttfamily haloAngularMomentumRandom} node operator class. !!} implicit none type (nodeOperatorHaloAngularMomentumRandom) :: self - class (darkMatterProfileDMOClass ), intent(in ), target :: darkMatterProfileDMO_ + class (darkMatterHaloScaleClass ), intent(in ), target :: darkMatterHaloScale_ class (haloSpinDistributionClass ), intent(in ), target :: haloSpinDistribution_ double precision , intent(in ) :: factorReset !![ - + !!] return @@ -107,7 +107,7 @@ subroutine haloAngularMomentumRandomDestructor(self) type(nodeOperatorHaloAngularMomentumRandom), intent(inout) :: self !![ - + !!] return @@ -145,7 +145,8 @@ subroutine haloAngularMomentumRandomNodeInitialize(self,node) basicProgenitor => nodeProgenitor %basic ( ) spinPrevious = self %haloSpinDistribution_%sample( nodeProgenitor) massPrevious = basicProgenitor %mass ( ) - angularMomentum = spinPrevious*Dark_Matter_Halo_Angular_Momentum_Scale(nodeProgenitor,self%darkMatterProfileDMO_) + angularMomentum = spinPrevious*Dark_Matter_Halo_Angular_Momentum_Scale(nodeProgenitor,self%darkMatterHaloScale_) + call spinProgenitor%angularMomentumSet(spinPrevious*Dark_Matter_Halo_Angular_Momentum_Scale(nodeProgenitor,self%darkMatterHaloScale_)) call spinProgenitor%angularMomentumSet(angularMomentum) if (spinProgenitor%angularMomentumVectorIsSettable()) & & call spinProgenitor%angularMomentumVectorSet([angularMomentum,0.0d0,0.0d0]) @@ -156,8 +157,8 @@ subroutine haloAngularMomentumRandomNodeInitialize(self,node) spinPrevious=self %haloSpinDistribution_%sample(nodeProgenitor) massPrevious=basicProgenitor %mass ( ) end if - spinProgenitor => nodeProgenitor%spin(autoCreate=.true.) - angularMomentum = spinPrevious*Dark_Matter_Halo_Angular_Momentum_Scale(nodeProgenitor,self%darkMatterProfileDMO_) + spinProgenitor => nodeProgenitor%spin(autoCreate=.true.) + angularMomentum = spinPrevious*Dark_Matter_Halo_Angular_Momentum_Scale(nodeProgenitor,self%darkMatterHaloScale_) call spinProgenitor%angularMomentumSet ( angularMomentum ) if (spinProgenitor%angularMomentumVectorIsSettable()) & & call spinProgenitor%angularMomentumVectorSet([angularMomentum,0.0d0,0.0d0]) diff --git a/source/nodes.operators.physics.halo_angular_momentum_random_walk.F90 b/source/nodes.operators.physics.halo_angular_momentum_random_walk.F90 index 8bca79ee6f..c349450795 100644 --- a/source/nodes.operators.physics.halo_angular_momentum_random_walk.F90 +++ b/source/nodes.operators.physics.halo_angular_momentum_random_walk.F90 @@ -21,9 +21,8 @@ Implements a node operator class that initializes halo angular momenta using a random walk in angular momentum. !!} - use :: Halo_Spin_Distributions , only : haloSpinDistributionClass - use :: Dark_Matter_Profiles_DMO, only : darkMatterProfileDMOClass - use :: Dark_Matter_Halo_Scales , only : darkMatterHaloScaleClass + use :: Halo_Spin_Distributions, only : haloSpinDistributionClass + use :: Dark_Matter_Halo_Scales, only : darkMatterHaloScaleClass !![ @@ -47,7 +46,6 @@ !!} private class (haloSpinDistributionClass), pointer :: haloSpinDistribution_ => null() - class (darkMatterProfileDMOClass), pointer :: darkMatterProfileDMO_ => null() class (darkMatterHaloScaleClass ), pointer :: darkMatterHaloScale_ => null() double precision :: angularMomentumVarianceSpecific contains @@ -74,7 +72,6 @@ function haloAngularMomentumRandomWalkConstructorParameters(parameters) result(s type (nodeOperatorHaloAngularMomentumRandomWalk) :: self type (inputParameters ), intent(inout) :: parameters class (haloSpinDistributionClass ), pointer :: haloSpinDistribution_ - class (darkMatterProfileDMOClass ), pointer :: darkMatterProfileDMO_ class (darkMatterHaloScaleClass ), pointer :: darkMatterHaloScale_ double precision :: angularMomentumVarianceSpecific @@ -86,31 +83,28 @@ function haloAngularMomentumRandomWalkConstructorParameters(parameters) result(s 0.0029d0 - !!] - self=nodeOperatorHaloAngularMomentumRandomWalk(angularMomentumVarianceSpecific,haloSpinDistribution_,darkMatterProfileDMO_,darkMatterHaloScale_) + self=nodeOperatorHaloAngularMomentumRandomWalk(angularMomentumVarianceSpecific,haloSpinDistribution_,darkMatterHaloScale_) !![ - !!] return end function haloAngularMomentumRandomWalkConstructorParameters - function haloAngularMomentumRandomWalkConstructorInternal(angularMomentumVarianceSpecific,haloSpinDistribution_,darkMatterProfileDMO_,darkMatterHaloScale_) result(self) + function haloAngularMomentumRandomWalkConstructorInternal(angularMomentumVarianceSpecific,haloSpinDistribution_,darkMatterHaloScale_) result(self) !!{ Internal constructor for the {\normalfont \ttfamily haloAngularMomentumRandomWalk} node operator class. !!} implicit none type (nodeOperatorHaloAngularMomentumRandomWalk) :: self class (haloSpinDistributionClass ), intent(in ), target :: haloSpinDistribution_ - class (darkMatterProfileDMOClass ), intent(in ), target :: darkMatterProfileDMO_ class (darkMatterHaloScaleClass ), intent(in ), target :: darkMatterHaloScale_ double precision , intent(in ) :: angularMomentumVarianceSpecific !![ - + !!] return @@ -125,7 +119,6 @@ subroutine haloAngularMomentumRandomWalkDestructor(self) !![ - !!] return @@ -161,8 +154,8 @@ subroutine haloAngularMomentumRandomWalkNodeInitialize(self,node) ! Select a angular momentum for the initial halo using the spin distribution function. basicProgenitor => nodeProgenitor %basic ( ) spinProgenitor => nodeProgenitor %spin (autoCreate=.true. ) - angularMomentumScalar = +self %haloSpinDistribution_%sample( nodeProgenitor) & - & *Dark_Matter_Halo_Angular_Momentum_Scale(nodeProgenitor,self%darkMatterProfileDMO_) + angularMomentumScalar = +self %haloSpinDistribution_%sample( nodeProgenitor) & + & *Dark_Matter_Halo_Angular_Momentum_Scale(nodeProgenitor,self%darkMatterHaloScale_) call spinProgenitor%angularMomentumSet(angularMomentumScalar) ! Compute the initial angular momentum vector. We choose this to be aligned along the x-axis. As we only care about the ! magnitude of the angular momentum any choice of initial vector direction is equivalent. diff --git a/source/nodes.operators.physics.position.trace_dark_matter.F90 b/source/nodes.operators.physics.position.trace_dark_matter.F90 index 80639d9863..c44f7dd117 100644 --- a/source/nodes.operators.physics.position.trace_dark_matter.F90 +++ b/source/nodes.operators.physics.position.trace_dark_matter.F90 @@ -21,7 +21,6 @@ Implements a node operator class that sets the positions of subhalos to trace the dark matter component of their host halo. !!} - use :: Galactic_Structure , only : galacticStructureClass use :: Dark_Matter_Halo_Scales , only : darkMatterHaloScaleClass use :: Satellite_Oprhan_Distributions, only : satelliteOrphanDistributionTraceDarkMatter !![ @@ -41,7 +40,6 @@ !!} private class(darkMatterHaloScaleClass ), pointer :: darkMatterHaloScale_ => null() - class(galacticStructureClass ), pointer :: galacticStructure_ => null() type (satelliteOrphanDistributionTraceDarkMatter), pointer :: satelliteOrphanDistribution_ => null() contains !![ @@ -74,36 +72,32 @@ function positionTraceDarkMatterConstructorParameters(parameters) result(self) type (nodeOperatorPositionTraceDarkMatter) :: self type (inputParameters ), intent(inout) :: parameters class(darkMatterHaloScaleClass ), pointer :: darkMatterHaloScale_ - class(galacticStructureClass ), pointer :: galacticStructure_ !![ - !!] - self=nodeOperatorPositionTraceDarkMatter(darkMatterHaloScale_,galacticStructure_) + self=nodeOperatorPositionTraceDarkMatter(darkMatterHaloScale_) !![ - !!] return end function positionTraceDarkMatterConstructorParameters - function positionTraceDarkMatterConstructorInternal(darkMatterHaloScale_,galacticStructure_) result(self) + function positionTraceDarkMatterConstructorInternal(darkMatterHaloScale_) result(self) !!{ Internal constructor for the {\normalfont \ttfamily positionTraceDarkMatter} node operator class. !!} implicit none type (nodeOperatorPositionTraceDarkMatter) :: self class(darkMatterHaloScaleClass ), intent(in ), target :: darkMatterHaloScale_ - class(galacticStructureClass ), intent(in ), target :: galacticStructure_ !![ - + !!] allocate(self%satelliteOrphanDistribution_) !![ - + !!] return end function positionTraceDarkMatterConstructorInternal @@ -131,7 +125,6 @@ subroutine positionTraceDarkMatterDestructor(self) if (satelliteHostChangeEvent%isAttached(self,positionTraceDarkMatterSatelliteHostChange)) call satelliteHostChangeEvent%detach(self,positionTraceDarkMatterSatelliteHostChange) !![ - !!] return diff --git a/source/nodes.operators.physics.satellite.heating.tidal.F90 b/source/nodes.operators.physics.satellite.heating.tidal.F90 index 6e6f5314ad..7a8591aa4d 100644 --- a/source/nodes.operators.physics.satellite.heating.tidal.F90 +++ b/source/nodes.operators.physics.satellite.heating.tidal.F90 @@ -24,7 +24,6 @@ !!} use :: Satellite_Tidal_Heating, only : satelliteTidalHeatingRateClass - use :: Galactic_Structure , only : galacticStructureClass !![ @@ -37,7 +36,6 @@ !!} private class (satelliteTidalHeatingRateClass), pointer :: satelliteTidalHeatingRate_ => null() - class (galacticStructureClass ), pointer :: galacticStructure_ => null() double precision :: efficiencyDecay logical :: applyPreInfall contains @@ -65,7 +63,6 @@ function satelliteTidalHeatingRateConstructorParameters(parameters) result(self) type (nodeOperatorSatelliteTidalHeating) :: self type (inputParameters ), intent(inout) :: parameters class (satelliteTidalHeatingRateClass ), pointer :: satelliteTidalHeatingRate_ - class (galacticStructureClass ), pointer :: galacticStructure_ double precision :: efficiencyDecay logical :: applyPreInfall @@ -83,29 +80,26 @@ function satelliteTidalHeatingRateConstructorParameters(parameters) result(self) parameters - !!] - self=nodeOperatorSatelliteTidalHeating(efficiencyDecay,applyPreInfall,satelliteTidalHeatingRate_,galacticStructure_) + self=nodeOperatorSatelliteTidalHeating(efficiencyDecay,applyPreInfall,satelliteTidalHeatingRate_) !![ - !!] return end function satelliteTidalHeatingRateConstructorParameters - function satelliteTidalHeatingRateConstructorInternal(efficiencyDecay,applyPreInfall,satelliteTidalHeatingRate_,galacticStructure_) result(self) + function satelliteTidalHeatingRateConstructorInternal(efficiencyDecay,applyPreInfall,satelliteTidalHeatingRate_) result(self) !!{ Internal constructor for the {\normalfont \ttfamily satelliteTidalHeatingRate} node operator class. !!} implicit none type (nodeOperatorSatelliteTidalHeating) :: self class (satelliteTidalHeatingRateClass ), intent(in ), target :: satelliteTidalHeatingRate_ - class (galacticStructureClass ), intent(in ), target :: galacticStructure_ double precision , intent(in ) :: efficiencyDecay logical , intent(in ) :: applyPreInfall !![ - + !!] return @@ -120,7 +114,6 @@ subroutine satelliteTidalHeatingRateDestructor(self) !![ - !!] return end subroutine satelliteTidalHeatingRateDestructor @@ -129,7 +122,9 @@ subroutine satelliteTidalHeatingRateDifferentialEvolution(self,node,interrupt,fu !!{ Perform mass loss from a satellite due to tidal stripping. !!} + use :: Coordinates , only : coordinateCartesian , assignment(=) use :: Galacticus_Nodes , only : nodeComponentSatellite, nodeComponentBasic + use :: Mass_Distributions , only : massDistributionClass use :: Numerical_Constants_Astronomical, only : gigaYear , megaParsec use :: Numerical_Constants_Math , only : Pi use :: Numerical_Constants_Prefixes , only : kilo @@ -144,10 +139,12 @@ subroutine satelliteTidalHeatingRateDifferentialEvolution(self,node,interrupt,fu class (nodeComponentBasic ) , pointer :: basic , basicHost class (nodeComponentSatellite ) , pointer :: satellite type (treeNode ) , pointer :: nodeHost + class (massDistributionClass ) , pointer :: massDistribution_ double precision , dimension(3) :: position , velocity double precision :: radius , orbitalPeriod , & & radialFrequency , angularFrequency type (tensorRank2Dimension3Symmetric) :: tidalTensor , tidalTensorPathIntegrated + type (coordinateCartesian ) :: coordinates !$GLC attributes unused :: interrupt, functionInterrupt, propertyType if (.not.self%applyPreInfall.and..not.node%isSatellite()) return @@ -178,7 +175,12 @@ subroutine satelliteTidalHeatingRateDifferentialEvolution(self,node,interrupt,fu radius = Vector_Magnitude (position) if (radius <= 0.0d0) return ! Do not compute rates at zero radius. ! Calculate tidal tensor and rate of change of integrated tidal tensor. - tidalTensor = self%galacticStructure_%tidalTensor(nodeHost,position) + coordinates = position + massDistribution_ => nodeHost %massDistribution( ) + tidalTensor = massDistribution_%tidalTensor (coordinates) + !![ + + !!] ! Compute the orbital period. angularFrequency=+Vector_Magnitude(Vector_Product(position,velocity)) & & /radius**2 & diff --git a/source/nodes.operators.physics.satellite_merging.radius_trigger.F90 b/source/nodes.operators.physics.satellite_merging.radius_trigger.F90 index 809ff8d032..f9ccee935e 100644 --- a/source/nodes.operators.physics.satellite_merging.radius_trigger.F90 +++ b/source/nodes.operators.physics.satellite_merging.radius_trigger.F90 @@ -22,7 +22,6 @@ !!} use :: Dark_Matter_Halo_Scales, only : darkMatterHaloScaleClass - use :: Galactic_Structure , only : galacticStructureClass use :: Kepler_Orbits , only : keplerOrbitCount !![ @@ -36,7 +35,6 @@ !!} private class (darkMatterHaloScaleClass), pointer :: darkMatterHaloScale_ => null() - class (galacticStructureClass ), pointer :: galacticStructure_ => null() double precision :: radiusVirialFraction logical :: recordMergedSubhaloProperties , recordFirstLevelOnly integer :: mergedSubhaloIDs (keplerOrbitCount) , nodeHierarchyLevelMaximumID @@ -74,7 +72,6 @@ function satelliteMergingRadiusTriggerConstructorParameters(parameters) result(s type (nodeOperatorSatelliteMergingRadiusTrigger) :: self type (inputParameters ), intent(inout) :: parameters class (darkMatterHaloScaleClass ), pointer :: darkMatterHaloScale_ - class (galacticStructureClass ), pointer :: galacticStructure_ double precision :: radiusVirialFraction logical :: recordMergedSubhaloProperties, recordFirstLevelOnly @@ -98,18 +95,16 @@ function satelliteMergingRadiusTriggerConstructorParameters(parameters) result(s parameters - !!] - self=nodeOperatorSatelliteMergingRadiusTrigger(radiusVirialFraction,recordMergedSubhaloProperties,recordFirstLevelOnly,darkMatterHaloScale_,galacticStructure_) + self=nodeOperatorSatelliteMergingRadiusTrigger(radiusVirialFraction,recordMergedSubhaloProperties,recordFirstLevelOnly,darkMatterHaloScale_) !![ - !!] return end function satelliteMergingRadiusTriggerConstructorParameters - function satelliteMergingRadiusTriggerConstructorInternal(radiusVirialFraction,recordMergedSubhaloProperties,recordFirstLevelOnly,darkMatterHaloScale_,galacticStructure_) result(self) + function satelliteMergingRadiusTriggerConstructorInternal(radiusVirialFraction,recordMergedSubhaloProperties,recordFirstLevelOnly,darkMatterHaloScale_) result(self) !!{ Internal constructor for the {\normalfont \ttfamily satelliteMergingRadiusTrigger} node operator class. !!} @@ -120,20 +115,19 @@ function satelliteMergingRadiusTriggerConstructorInternal(radiusVirialFraction,r double precision , intent(in ) :: radiusVirialFraction logical , intent(in ) :: recordMergedSubhaloProperties, recordFirstLevelOnly class (darkMatterHaloScaleClass ), intent(in ), target :: darkMatterHaloScale_ - class (galacticStructureClass ), intent(in ), target :: galacticStructure_ !![ - + !!] if (recordMergedSubhaloProperties) then !![ - - - - - - - + + + + + + + !!] end if return @@ -148,7 +142,6 @@ subroutine satelliteMergingRadiusTriggerDestructor(self) !![ - !!] return end subroutine satelliteMergingRadiusTriggerDestructor @@ -266,45 +259,53 @@ double precision function satelliteMergingRadiusTriggerRadiusMerge(self,node) Compute the merging radius for a node. !!} use :: Galacticus_Nodes , only : treeNode - use :: Galactic_Structure_Options, only : massTypeGalactic, radiusLarge + use :: Galactic_Structure_Options, only : massTypeGalactic + use :: Mass_Distributions , only : massDistributionClass implicit none class (nodeOperatorSatelliteMergingRadiusTrigger), intent(inout) :: self type (treeNode ), intent(inout) :: node type (treeNode ), pointer :: nodeHost + class (massDistributionClass ), pointer :: massDistribution_ , massDistributionHost_ double precision :: radiusHalfMassCentral, radiusHalfMassSatellite ! Find the host node. nodeHost => node%mergesWith() - ! Get half-mass radii of central and satellite galaxies. We first check that the total mass in the galactic component - ! (found by setting the radius to "radiusLarge") is non-zero as we do not want to attempt to find the half-mass radius - ! of the galactic component, if no galactic component exists. To correctly handle the case that numerical errors lead - ! to a zero-size galactic component (the enclosed mass within zero radius is non-zero and equals to the total mass of - ! this component), we do a further check that the enclosed mass within zero radius is smaller than half of the total - ! mass in the galactic component. - if ( & - & self%galacticStructure_%massEnclosed(nodeHost,massType=massTypeGalactic,radius=radiusLarge) & - & > & - & max( & - & 0.0d0, & - & 2.0d0*self%galacticStructure_%massEnclosed(nodeHost,massType=massTypeGalactic,radius=0.0d0 ) & - & ) & + ! Get mass distributions. + massDistribution_ => node %massDistribution(massType=massTypeGalactic) + massDistributionHost_ => nodeHost%massDistribution(massType=massTypeGalactic) + ! Get half-mass radii of central and satellite galaxies. We first check that the total mass in the galactic component is + ! non-zero as we do not want to attempt to find the half-mass radius of the galactic component, if no galactic component + ! exists. To correctly handle the case that numerical errors lead to a zero-size galactic component (the enclosed mass + ! within zero radius is non-zero and equals to the total mass of this component), we do a further check that the enclosed + ! mass within zero radius is smaller than half of the total mass in the galactic component. + if ( & + & massDistributionHost_%massTotal() & + & > & + & max( & + & 0.0d0, & + & 2.0d0*massDistributionHost_%massEnclosedBySphere(radius=0.0d0) & + & ) & & ) then - radiusHalfMassCentral =self%galacticStructure_%radiusEnclosingMass(nodeHost,massFractional=0.5d0,massType=massTypeGalactic) + radiusHalfMassCentral =massDistributionHost_%radiusEnclosingMass(massFractional=0.5d0) else radiusHalfMassCentral =0.0d0 end if - if ( & - & self%galacticStructure_%massEnclosed(node ,massType=massTypeGalactic,radius=radiusLarge) & - & > & - & max( & - & 0.0d0, & - & 2.0d0*self%galacticStructure_%massEnclosed(node ,massType=massTypeGalactic,radius=0.0d0 ) & - & ) & + if ( & + & massDistribution_ %massTotal() & + & > & + & max( & + & 0.0d0, & + & 2.0d0*massDistribution_ %massEnclosedBySphere(radius=0.0d0) & + & ) & & ) then - radiusHalfMassSatellite=self%galacticStructure_%radiusEnclosingMass(node ,massFractional=0.5d0,massType=massTypeGalactic) + radiusHalfMassSatellite=massDistribution_ %radiusEnclosingMass(massFractional=0.5d0) else radiusHalfMassSatellite=0.0d0 - end if + end if + !![ + + + !!] satelliteMergingRadiusTriggerRadiusMerge=max( & & + radiusHalfMassSatellite & & + radiusHalfMassCentral , & diff --git a/source/nodes.operators.physics.satellite_orbits.F90 b/source/nodes.operators.physics.satellite_orbits.F90 index e6c729d353..cbb8888f45 100644 --- a/source/nodes.operators.physics.satellite_orbits.F90 +++ b/source/nodes.operators.physics.satellite_orbits.F90 @@ -23,9 +23,6 @@ Implements a node operator class that propagates satellite halos along their orbits. !!} - use :: Galactic_Structure , only : galacticStructureClass - use :: Dark_Matter_Profiles_DMO, only : darkMatterProfileDMOClass - !![ A node operator class that propagates satellite halos along their orbits. @@ -36,12 +33,9 @@ A node operator class that propagates satellite halos along their orbits. !!} private - class (galacticStructureClass ), pointer :: galacticStructure_ => null() - class (darkMatterProfileDMOClass), pointer :: darkMatterProfileDMO_ => null() - logical :: trackPreInfallOrbit - integer :: rateGrowthMassBoundID + logical :: trackPreInfallOrbit + integer :: rateGrowthMassBoundID contains - final :: satelliteOrbitDestructor procedure :: nodeInitialize => satelliteOrbitNodeInitialize procedure :: nodePromote => satelliteOrbitNodePromote procedure :: nodesMerge => satelliteOrbitNodeMerge @@ -74,8 +68,6 @@ function satelliteOrbitConstructorParameters(parameters) result(self) implicit none type (nodeOperatorSatelliteOrbit) :: self type (inputParameters ), intent(inout) :: parameters - class (galacticStructureClass ), pointer :: galacticStructure_ - class (darkMatterProfileDMOClass ), pointer :: darkMatterProfileDMO_ logical :: trackPreInfallOrbit !![ @@ -85,30 +77,24 @@ function satelliteOrbitConstructorParameters(parameters) result(self) If true, (approximately) track the orbits of halos prior to infall. parameters - - !!] - self=nodeOperatorSatelliteOrbit(trackPreInfallOrbit,galacticStructure_,darkMatterProfileDMO_) + self=nodeOperatorSatelliteOrbit(trackPreInfallOrbit) !![ - - !!] return end function satelliteOrbitConstructorParameters - function satelliteOrbitConstructorInternal(trackPreInfallOrbit,galacticStructure_,darkMatterProfileDMO_) result(self) + function satelliteOrbitConstructorInternal(trackPreInfallOrbit) result(self) !!{ Internal constructor for the {\normalfont \ttfamily satelliteOrbit} node operator class. !!} use :: Input_Parameters, only : inputParameters implicit none - type (nodeOperatorSatelliteOrbit) :: self - class (galacticStructureClass ), intent(in ), target :: galacticStructure_ - class (darkMatterProfileDMOClass ), intent(in ), target :: darkMatterProfileDMO_ - logical , intent(in ), :: trackPreInfallOrbit + type (nodeOperatorSatelliteOrbit) :: self + logical , intent(in ) :: trackPreInfallOrbit !![ - + !!] if (self%trackPreInfallOrbit) then @@ -119,20 +105,6 @@ function satelliteOrbitConstructorInternal(trackPreInfallOrbit,galacticStructure return end function satelliteOrbitConstructorInternal - subroutine satelliteOrbitDestructor(self) - !!{ - Destructor for the {\normalfont \ttfamily satelliteOrbit} node operator class. - !!} - implicit none - type(nodeOperatorSatelliteOrbit), intent(inout) :: self - - !![ - - - !!] - return - end subroutine satelliteOrbitDestructor - subroutine satelliteOrbitNodeInitialize(self,node) !!{ Estimate the position of nodes relative to their hosts prior to infall. @@ -219,21 +191,24 @@ integer function orbitalODEs(time,phaseSpaceCoordinates,phaseSpaceCoordinatesRat ODEs describing a halo orbit. !!} use :: Galacticus_Nodes , only : nodeComponentBasic + use :: Galactic_Structure_Options , only : componentTypeDarkMatterOnly, massTypeDark use :: Interface_GSL , only : GSL_Success - use :: Numerical_Constants_Astronomical, only : gigaYear , megaParsec, gravitationalConstantGalacticus, Mpc_per_km_per_s_To_Gyr + use :: Numerical_Constants_Astronomical, only : gigaYear , megaParsec, gravitationalConstantGalacticus, Mpc_per_km_per_s_To_Gyr use :: Numerical_Constants_Prefixes , only : kilo + use :: Mass_Distributions , only : massDistributionClass use :: Vectors , only : Vector_Magnitude implicit none - double precision , intent(in ) :: time - double precision , dimension(:), intent(in ) :: phaseSpaceCoordinates - double precision , dimension(:), intent( out) :: phaseSpaceCoordinatesRateOfChange - double precision , dimension(3) :: position , velocity , & - & acceleration - type (treeNode ), pointer :: nodeHost , nodeDescendent - class (nodeComponentBasic), pointer :: basicProgenitor , basicDescendent - double precision :: massSatellite , massHost , & - & factorInterpolate , radius , & - & massRatio + double precision , intent(in ) :: time + double precision , dimension(:), intent(in ) :: phaseSpaceCoordinates + double precision , dimension(:), intent( out) :: phaseSpaceCoordinatesRateOfChange + double precision , dimension(3) :: position , velocity , & + & acceleration + type (treeNode ), pointer :: nodeHost , nodeDescendent + class (nodeComponentBasic ), pointer :: basicProgenitor , basicDescendent + class (massDistributionClass), pointer :: massDistributionDescendent , massDistributionHost + double precision :: massSatellite , massHost , & + & factorInterpolate , radius , & + & massRatio ! Extract orbital position and velocity. orbitalODEs =GSL_Success @@ -264,26 +239,32 @@ integer function orbitalODEs(time,phaseSpaceCoordinates,phaseSpaceCoordinatesRat if (associated(nodeHost)) basicProgenitor => nodeHost%basic() end do if (associated(nodeHost)) then - nodeDescendent => nodeHost %parent - basicDescendent => nodeDescendent%basic () - radius = Vector_Magnitude(position) - factorInterpolate = +(+ time -basicProgenitor%time()) & - & /(+basicDescendent%time()-basicProgenitor%time()) - massHost = +self_%darkMatterProfileDMO_%enclosedMass(nodeDescendent,radius)* factorInterpolate & - & +self_%darkMatterProfileDMO_%enclosedMass(nodeHost ,radius)*(1.0d0-factorInterpolate) - massRatio =min( & - & +massRatioMaximum, & - & max( & - & +massRatioMinimum, & - & +massSatellite & - & /massHost & - & ) & - & ) - acceleration =-gravitationalConstantGalacticus & - & *massHost & - & *position & - & /radius **3 & - & /Mpc_per_km_per_s_To_Gyr + nodeDescendent => nodeHost %parent + basicDescendent => nodeDescendent%basic ( ) + massDistributionHost => nodeHost %massDistribution(componentTypeDarkMatterOnly,massTypeDark) + massDistributionDescendent => nodeDescendent%massDistribution(componentTypeDarkMatterOnly,massTypeDark) + radius = Vector_Magnitude(position) + factorInterpolate = +(+ time -basicProgenitor%time()) & + & /(+basicDescendent%time()-basicProgenitor%time()) + massHost = +massDistributionDescendent%massEnclosedBySphere(radius)* factorInterpolate & + & +massDistributionHost %massEnclosedBySphere(radius)*(1.0d0-factorInterpolate) + massRatio = min( & + & +massRatioMaximum, & + & max( & + & +massRatioMinimum, & + & +massSatellite & + & /massHost & + & ) & + & ) + acceleration = -gravitationalConstantGalacticus & + & *massHost & + & *position & + & /radius **3 & + & /Mpc_per_km_per_s_To_Gyr + !![ + + + !!] else ! No host exists at this time, assume zero acceleration. acceleration=0.0d0 @@ -353,22 +334,26 @@ subroutine satelliteOrbitDifferentialEvolution(self,node,interrupt,functionInter !!{ Perform evolution of a satellite orbit due to its velocity and the acceleration of its host's potential. !!} - use :: Galacticus_Nodes , only : nodeComponentSatellite , nodecomponentbasic + use :: Galacticus_Nodes , only : nodeComponentSatellite use :: Numerical_Constants_Astronomical, only : gigaYear , megaParsec use :: Numerical_Constants_Prefixes , only : kilo use :: Vectors , only : Vector_Magnitude + use :: Mass_Distributions , only : massDistributionClass + use :: Coordinates , only : coordinateCartesian , assignment(=) implicit none class (nodeOperatorSatelliteOrbit), intent(inout), target :: self type (treeNode ), intent(inout), target :: node logical , intent(inout) :: interrupt procedure (interruptTask ), intent(inout), pointer :: functionInterrupt integer , intent(in ) :: propertyType - type (treeNode ), pointer :: nodeHost + type (treeNode ) , pointer :: nodeHost class (nodeComponentSatellite ) , pointer :: satellite + class (massDistributionClass ) , pointer :: massDistribution_, massDistributionHost_ double precision , dimension(3) :: position , velocity , & & acceleration double precision :: massEnclosedHost , massEnclosedSatellite, & & radius , massRatio + type (coordinateCartesian ) :: coordinates !$GLC attributes unused :: interrupt, functionInterrupt, propertyType ! Ignore the main branch, and non-satellites unless we are tracking pre-infall orbits. @@ -399,15 +384,22 @@ subroutine satelliteOrbitDifferentialEvolution(self,node,interrupt,functionInter ! so the velocity remains constant between kicks). if (.not.node%isSatellite()) return if (radius <= 0.0d0) return ! If radius is non-positive, assume no acceleration. - massEnclosedSatellite=max( & - & 0.0d0 , & - & min( & - & self %galacticStructure_%massEnclosed(node ,radius ), & - & satellite %boundMass ( ) & - & ) & - & ) - massEnclosedHost = self %galacticStructure_%massEnclosed(nodeHost,radius ) - acceleration = self %galacticStructure_%acceleration(nodeHost,position) + coordinates = position + massDistribution_ => node %massDistribution() + massDistributionHost_ => nodeHost%massDistribution() + massEnclosedSatellite = max( & + & 0.0d0 , & + & min( & + & massDistribution_ %massEnclosedBySphere(radius ), & + & satellite %boundMass ( ) & + & ) & + & ) + massEnclosedHost = massDistributionHost_%massEnclosedBySphere(radius ) + acceleration = massDistributionHost_%acceleration (coordinates) + !![ + + + !!] ! Include a factor (1+m_{sat}/m_{host})=m_{sat}/µ (where µ is the reduced mass) to convert from the two-body problem of ! satellite and host orbiting their common center of mass to the equivalent one-body problem (since we're solving for the ! motion of the satellite relative to the center of the host which is held fixed). diff --git a/source/nodes.operators.physics.subsubhalo_promotion.F90 b/source/nodes.operators.physics.subsubhalo_promotion.F90 index 1214eeafd0..53604c6c3f 100644 --- a/source/nodes.operators.physics.subsubhalo_promotion.F90 +++ b/source/nodes.operators.physics.subsubhalo_promotion.F90 @@ -21,8 +21,6 @@ Implements a node operator class that promotes sub-sub-halos. !!} - use :: Dark_Matter_Profiles_DMO, only : darkMatterProfileDMOClass - !![ A node operator class that promotes sub-sub-halos. @@ -33,9 +31,7 @@ A node operator class that shifts node indices at node promotion. !!} private - class(darkMatterProfileDMOClass), pointer :: darkMatterProfileDMO_ => null() contains - final :: subsubhaloPromotionDestructor procedure :: differentialEvolution => subsubhaloPromotionDifferentialEvolution end type nodeOperatorSubsubhaloPromotion @@ -57,31 +53,22 @@ function subsubhaloPromotionConstructorParameters(parameters) result(self) implicit none type (nodeOperatorSubsubhaloPromotion) :: self type (inputParameters ), intent(inout) :: parameters - class(darkMatterProfileDMOClass ), pointer :: darkMatterProfileDMO_ - !![ - - !!] - self=nodeOperatorSubsubhaloPromotion(darkMatterProfileDMO_) + self=nodeOperatorSubsubhaloPromotion() !![ - !!] return end function subsubhaloPromotionConstructorParameters - function subsubhaloPromotionConstructorInternal(darkMatterProfileDMO_) result(self) + function subsubhaloPromotionConstructorInternal() result(self) !!{ Internal constructor for the {\normalfont \ttfamily subsubhaloPromotion} node operator class. !!} - use :: Error , only : Component_List , Error_Report - use :: Galacticus_Nodes , only : defaultSatelliteComponent + use:: Error , only : Component_List , Error_Report + use:: Galacticus_Nodes, only : defaultSatelliteComponent implicit none - type (nodeOperatorSubsubhaloPromotion) :: self - class(darkMatterProfileDMOClass ), intent(in ), target :: darkMatterProfileDMO_ - !![ - - !!] + type(nodeOperatorSubsubhaloPromotion) :: self if (.not.defaultSatelliteComponent%positionIsGettable()) & & call Error_Report & @@ -96,24 +83,13 @@ function subsubhaloPromotionConstructorInternal(darkMatterProfileDMO_) result(se return end function subsubhaloPromotionConstructorInternal - subroutine subsubhaloPromotionDestructor(self) - !!{ - Destructor for the {\normalfont \ttfamily subsubhaloPromotion} node operator class. - !!} - implicit none - type(nodeOperatorSubsubhaloPromotion), intent(inout) :: self - - !![ - - !!] - return - end subroutine subsubhaloPromotionDestructor - subroutine subsubhaloPromotionDifferentialEvolution(self,node,interrupt,functionInterrupt,propertyType) !!{ Determine if sub-sub-halos should be promoted. !!} - use :: Galacticus_Nodes, only : propertyInactive, nodeComponentSatellite + use :: Galacticus_Nodes , only : propertyInactive , nodeComponentSatellite + use :: Galactic_Structure_Options, only : componentTypeDarkMatterOnly, massTypeDark + use :: Mass_Distributions , only : massDistributionClass implicit none class (nodeOperatorSubsubhaloPromotion), intent(inout), target :: self type (treeNode ), intent(inout), target :: node @@ -121,6 +97,7 @@ subroutine subsubhaloPromotionDifferentialEvolution(self,node,interrupt,function procedure (interruptTask ), intent(inout), pointer :: functionInterrupt integer , intent(in ) :: propertyType class (nodeComponentSatellite ) , pointer :: satellite , satelliteHost + class (massDistributionClass ) , pointer :: massDistribution_ double precision , dimension(3) :: positionSatellite double precision :: radiusSatellite , massBoundHost, & & massEnclosedHost @@ -136,9 +113,13 @@ subroutine subsubhaloPromotionDifferentialEvolution(self,node,interrupt,function positionSatellite = satellite%position () radiusSatellite = sqrt(sum(positionSatellite**2)) ! Determine the mass enclosed by this orbit and the bound mass of the host. - satelliteHost => node %parent %satellite ( ) - massBoundHost = satelliteHost %boundMass ( ) - massEnclosedHost = self %darkMatterProfileDMO_%enclosedMass(node%parent,radiusSatellite) + massDistribution_ => node %parent%massDistribution (componentTypeDarkMatterOnly,massTypeDark) + satelliteHost => node %parent%satellite ( ) + massBoundHost = satelliteHost %boundMass ( ) + massEnclosedHost = massDistribution_ %massEnclosedBySphere(radiusSatellite ) + !![ + + !!] ! If the satellite is within the radius enclosing the total bound mass of the host it will not be promoted. if (massEnclosedHost <= massBoundHost) return ! The satellite is outside the current bound radius of the host, so should be promoted. Trigger an interrupt. diff --git a/source/nodes.property_extractor.CGM_cooling_function.F90 b/source/nodes.property_extractor.CGM_cooling_function.F90 index 4948d4b044..94cee95141 100644 --- a/source/nodes.property_extractor.CGM_cooling_function.F90 +++ b/source/nodes.property_extractor.CGM_cooling_function.F90 @@ -24,11 +24,7 @@ Implements a property extractor class for the CGM cooling function at a set of r use :: Cosmology_Functions , only : cosmologyFunctionsClass use :: Galactic_Structure_Radii_Definitions, only : radiusSpecifier use :: Cooling_Functions , only : coolingFunctionClass - use :: Hot_Halo_Mass_Distributions , only : hotHaloMassDistributionClass - use :: Hot_Halo_Temperature_Profiles , only : hotHaloTemperatureProfileClass use :: Radiation_Fields , only : radiationFieldCosmicMicrowaveBackground - use :: Galactic_Structure , only : galacticStructureClass - !![ A property extractor class for the CGM cooling function at a set of radii. @@ -47,10 +43,7 @@ A property extractor class for the CGM cooling function at a set of radii. private class (cosmologyFunctionsClass ), pointer :: cosmologyFunctions_ => null() class (darkMatterHaloScaleClass ), pointer :: darkMatterHaloScale_ => null() - class (galacticStructureClass ), pointer :: galacticStructure_ => null() class (coolingFunctionClass ), pointer :: coolingFunction_ => null() - class (hotHaloMassDistributionClass ), pointer :: hotHaloMassDistribution_ => null() - class (hotHaloTemperatureProfileClass ), pointer :: hotHaloTemperatureProfile_ => null() type (radiationFieldCosmicMicrowaveBackground), pointer :: radiation => null() integer :: radiiCount , elementCount_ , & & abundancesCount , chemicalsCount , & @@ -94,11 +87,8 @@ function cgmCoolingFunctionConstructorParameters(parameters) result(self) type (varying_string ), allocatable , dimension(:) :: radiusSpecifiers class (darkMatterHaloScaleClass ), pointer :: darkMatterHaloScale_ class (coolingFunctionClass ), pointer :: coolingFunction_ - class (hotHaloTemperatureProfileClass ), pointer :: hotHaloTemperatureProfile_ - class (hotHaloMassDistributionClass ), pointer :: hotHaloMassDistribution_ - class (galacticStructureClass ), pointer :: galacticStructure_ class (cosmologyFunctionsClass ), pointer :: cosmologyFunctions_ - logical :: includeRadii , includeDensity + logical :: includeRadii , includeDensity type (varying_string ) :: label allocate(radiusSpecifiers(parameters%count('radiusSpecifiers'))) @@ -126,27 +116,21 @@ function cgmCoolingFunctionConstructorParameters(parameters) result(self) A label to distinguish this cooling function from others. parameters - - - - - - + + + !!] - self=nodePropertyExtractorCGMCoolingFunction(radiusSpecifiers,includeRadii,includeDensity,label,cosmologyFunctions_,darkMatterHaloScale_,galacticStructure_,coolingFunction_,hotHaloTemperatureProfile_,hotHaloMassDistribution_) + self=nodePropertyExtractorCGMCoolingFunction(radiusSpecifiers,includeRadii,includeDensity,label,cosmologyFunctions_,darkMatterHaloScale_,coolingFunction_) !![ - - - - - - + + + !!] return end function cgmCoolingFunctionConstructorParameters - function cgmCoolingFunctionConstructorInternal(radiusSpecifiers,includeRadii,includeDensity,label,cosmologyFunctions_,darkMatterHaloScale_,galacticStructure_,coolingFunction_,hotHaloTemperatureProfile_,hotHaloMassDistribution_) result(self) + function cgmCoolingFunctionConstructorInternal(radiusSpecifiers,includeRadii,includeDensity,label,cosmologyFunctions_,darkMatterHaloScale_,coolingFunction_) result(self) !!{ Internal constructor for the {\normalfont \ttfamily cgmCoolingFunction} property extractor class. !!} @@ -159,14 +143,11 @@ function cgmCoolingFunctionConstructorInternal(radiusSpecifiers,includeRadii,inc type (varying_string ), intent(in ), dimension(:) :: radiusSpecifiers class (cosmologyFunctionsClass ), intent(in ), target :: cosmologyFunctions_ class (darkMatterHaloScaleClass ), intent(in ), target :: darkMatterHaloScale_ - class (galacticStructureClass ), intent(in ), target :: galacticStructure_ class (coolingFunctionClass ), intent(in ), target :: coolingFunction_ - class (hotHaloTemperatureProfileClass ), intent(in ), target :: hotHaloTemperatureProfile_ - class (hotHaloMassDistributionClass ), intent(in ), target :: hotHaloMassDistribution_ - logical , intent(in ) :: includeRadii , includeDensity + logical , intent(in ) :: includeRadii , includeDensity type (varying_string ), intent(in ) :: label !![ - + !!] ! Decode radii specifiers. @@ -210,13 +191,10 @@ subroutine cgmCoolingFunctionDestructor(self) type(nodePropertyExtractorCGMCoolingFunction), intent(inout) :: self !![ - - - - - - - + + + + !!] return end subroutine cgmCoolingFunctionDestructor @@ -255,12 +233,15 @@ function cgmCoolingFunctionExtract(self,node,time,instance) use :: Abundances_Structure , only : abundances use :: Chemical_Abundances_Structure , only : chemicalAbundances use :: Chemical_Reaction_Rates_Utilities , only : Chemicals_Mass_To_Fraction_Conversion - use :: Galactic_Structure_Options , only : componentTypeAll , massTypeGalactic , massTypeStellar + use :: Galactic_Structure_Options , only : componentTypeAll , componentTypeHotHalo , massTypeGaseous , massTypeGalactic , & + & massTypeStellar use :: Galactic_Structure_Radii_Definitions, only : radiusTypeDarkMatterScaleRadius , radiusTypeDiskHalfMassRadius, radiusTypeDiskRadius , radiusTypeGalacticLightFraction, & & radiusTypeGalacticMassFraction , radiusTypeRadius , radiusTypeSpheroidHalfMassRadius, radiusTypeSpheroidRadius , & & radiusTypeStellarMassFraction , radiusTypeVirialRadius use :: Galacticus_Nodes , only : nodeComponentDarkMatterProfile , nodeComponentDisk , nodeComponentSpheroid , treeNode , & & nodeComponentBasic , nodeComponentHotHalo + use :: Mass_Distributions , only : massDistributionClass , kinematicsDistributionClass + use :: Coordinates , only : coordinateSpherical , assignment(=) use :: Numerical_Constants_Astronomical , only : massSolar , megaParsec use :: Numerical_Constants_Atomic , only : massHydrogenAtom use :: Numerical_Constants_Prefixes , only : hecto @@ -276,6 +257,9 @@ function cgmCoolingFunctionExtract(self,node,time,instance) class (nodeComponentDisk ) , pointer :: disk class (nodeComponentSpheroid ) , pointer :: spheroid class (nodeComponentDarkMatterProfile ) , pointer :: darkMatterProfile + class (massDistributionClass ) , pointer :: massDistribution_ + class (kinematicsDistributionClass ) , pointer :: kinematicsDistribution_ + type (coordinateSpherical ) :: coordinates integer :: i double precision :: radius , radiusVirial , & & density , temperature , & @@ -309,27 +293,33 @@ function cgmCoolingFunctionExtract(self,node,time,instance) radius=+radius*spheroid %halfMassRadius() case (radiusTypeGalacticMassFraction %ID, & & radiusTypeGalacticLightFraction %ID) - radius=+radius & - & *self%galacticStructure_%radiusEnclosingMass & - & ( & - & node , & - & massFractional=self%radii(i)%fraction , & - & massType = massTypeGalactic, & - & componentType = componentTypeAll, & - & weightBy =self%radii(i)%weightBy , & - & weightIndex =self%radii(i)%weightByIndex & - & ) + massDistribution_ => node %massDistribution ( & + & massType = massTypeStellar , & + & componentType = componentTypeAll, & + & weightBy =self%radii(i)%weightBy , & + & weightIndex =self%radii(i)%weightByIndex & + & ) + radius = +radius & + & *massDistribution_%radiusEnclosingMass( & + & massFractional=self%radii(i)%fraction & + & ) + !![ + + !!] case (radiusTypeStellarMassFraction %ID) - radius=+radius & - & *self%galacticStructure_%radiusEnclosingMass & - & ( & - & node , & - & massFractional=self%radii(i)%fraction , & - & massType = massTypeStellar , & - & componentType = componentTypeAll, & - & weightBy =self%radii(i)%weightBy , & - & weightIndex =self%radii(i)%weightByIndex & - & ) + massDistribution_ => node %massDistribution ( & + & massType = massTypeStellar , & + & componentType = componentTypeAll, & + & weightBy =self%radii(i)%weightBy , & + & weightIndex =self%radii(i)%weightByIndex & + & ) + radius = +radius & + & *massDistribution_%radiusEnclosingMass( & + & massFractional=self%radii(i)%fraction & + & ) + !![ + + !!] case default call Error_Report('unrecognized radius type'//{introspection:location}) end select @@ -356,9 +346,16 @@ function cgmCoolingFunctionExtract(self,node,time,instance) ! Convert to number density per unit total mass density. call fractionsChemical%scale(massToDensityConversion) end if - ! Get density and temperature at the required radius. - density =self%hotHaloMassDistribution_ %density (node,radius) - temperature =self%hotHaloTemperatureProfile_%temperature(node,radius) + ! Get density and temperature. + coordinates = [radius,0.0d0,0.0d0] + massDistribution_ => node %massDistribution (componentTypeHotHalo,massTypeGaseous) + kinematicsDistribution_ => massDistribution_ %kinematicsDistribution( ) + density = massDistribution_ %density (coordinates ) + temperature = kinematicsDistribution_%temperature (coordinates ) + !![ + + + !!] ! Compute number density of hydrogen (in cm⁻³). numberDensityHydrogen=+density & & *abundancesGas %hydrogenMassFraction() & diff --git a/source/nodes.property_extractor.ICM_SZ.F90 b/source/nodes.property_extractor.ICM_SZ.F90 index abe37e884c..b974084dbe 100644 --- a/source/nodes.property_extractor.ICM_SZ.F90 +++ b/source/nodes.property_extractor.ICM_SZ.F90 @@ -20,13 +20,10 @@ !!{ Contains a module which implements an intracluster medium Sunyaev-Zeldovich Compton-y parameter property extractor class. !!} - use :: Chemical_States , only : chemicalState , chemicalStateClass - use :: Cosmology_Functions , only : cosmologyFunctions , cosmologyFunctionsClass , enumerationDensityCosmologicalType - use :: Cosmology_Parameters , only : cosmologyParameters , cosmologyParametersClass - use :: Dark_Matter_Halo_Scales , only : darkMatterHaloScale , darkMatterHaloScaleClass - use :: Galactic_Structure , only : galacticStructureClass - use :: Hot_Halo_Mass_Distributions , only : hotHaloMassDistribution , hotHaloMassDistributionClass - use :: Hot_Halo_Temperature_Profiles, only : hotHaloTemperatureProfile, hotHaloTemperatureProfileClass + use :: Chemical_States , only : chemicalState , chemicalStateClass + use :: Cosmology_Functions , only : cosmologyFunctions , cosmologyFunctionsClass , enumerationDensityCosmologicalType + use :: Cosmology_Parameters , only : cosmologyParameters, cosmologyParametersClass + use :: Dark_Matter_Halo_Scales, only : darkMatterHaloScale, darkMatterHaloScaleClass !![ @@ -60,10 +57,7 @@ class (cosmologyParametersClass ), pointer :: cosmologyParameters_ => null() class (cosmologyFunctionsClass ), pointer :: cosmologyFunctions_ => null() class (darkMatterHaloScaleClass ), pointer :: darkMatterHaloScale_ => null() - class (hotHaloMassDistributionClass ), pointer :: hotHaloMassDistribution_ => null() - class (hotHaloTemperatureProfileClass ), pointer :: hotHaloTemperatureProfile_ => null() class (chemicalStateClass ), pointer :: chemicalState_ => null() - class (galacticStructureClass ), pointer :: galacticStructure_ => null() type (nodePropertyExtractorDensityContrasts), pointer :: densityContrastExtractor_ => null() double precision :: densityContrast , distanceAngular logical :: useDensityContrast , useFixedDistance @@ -94,26 +88,20 @@ function icmSZConstructorParameters(parameters) result(self) use :: Input_Parameters , only : inputParameter , inputParameters use :: Cosmology_Functions, only : enumerationDensityCosmologicalEncode implicit none - type (nodePropertyExtractorICMSZ ) :: self - type (inputParameters ), intent(inout) :: parameters - class (cosmologyParametersClass ), pointer :: cosmologyParameters_ - class (cosmologyFunctionsClass ), pointer :: cosmologyFunctions_ - class (darkMatterHaloScaleClass ), pointer :: darkMatterHaloScale_ - class (hotHaloMassDistributionClass ), pointer :: hotHaloMassDistribution_ - class (hotHaloTemperatureProfileClass), pointer :: hotHaloTemperatureProfile_ - class (chemicalStateClass ), pointer :: chemicalState_ - class (galacticStructureClass ), pointer :: galacticStructure_ - double precision :: densityContrast , distanceAngular - type (varying_string ) :: densityContrastRelativeTo + type (nodePropertyExtractorICMSZ) :: self + type (inputParameters ), intent(inout) :: parameters + class (cosmologyParametersClass ), pointer :: cosmologyParameters_ + class (cosmologyFunctionsClass ), pointer :: cosmologyFunctions_ + class (darkMatterHaloScaleClass ), pointer :: darkMatterHaloScale_ + class (chemicalStateClass ), pointer :: chemicalState_ + double precision :: densityContrast , distanceAngular + type (varying_string ) :: densityContrastRelativeTo !![ - - - - - - - + + + + !!] if (parameters%isPresent('densityContrast')) then !![ @@ -141,24 +129,21 @@ function icmSZConstructorParameters(parameters) result(self) end if !![ - self=nodePropertyExtractorICMSZ(cosmologyParameters_,cosmologyFunctions_,darkMatterHaloScale_,hotHaloMassDistribution_,hotHaloTemperatureProfile_,chemicalState_,galacticStructure_{conditions}) + self=nodePropertyExtractorICMSZ(cosmologyParameters_,cosmologyFunctions_,darkMatterHaloScale_,chemicalState_{conditions}) - - - - - - - + + + + !!] return end function icmSZConstructorParameters - function icmSZConstructorInternal(cosmologyParameters_,cosmologyFunctions_,darkMatterHaloScale_,hotHaloMassDistribution_,hotHaloTemperatureProfile_,chemicalState_,galacticStructure_,densityContrast,densityContrastRelativeTo,distanceAngular) result(self) + function icmSZConstructorInternal(cosmologyParameters_,cosmologyFunctions_,darkMatterHaloScale_,chemicalState_,densityContrast,densityContrastRelativeTo,distanceAngular) result(self) !!{ Internal constructor for the {\normalfont \ttfamily icmSZ} property extractor class. !!} @@ -170,15 +155,12 @@ function icmSZConstructorInternal(cosmologyParameters_,cosmologyFunctions_,darkM class (cosmologyParametersClass ), intent(in ), target :: cosmologyParameters_ class (cosmologyFunctionsClass ), intent(in ), target :: cosmologyFunctions_ class (darkMatterHaloScaleClass ), intent(in ), target :: darkMatterHaloScale_ - class (hotHaloMassDistributionClass ), intent(in ), target :: hotHaloMassDistribution_ - class (hotHaloTemperatureProfileClass ), intent(in ), target :: hotHaloTemperatureProfile_ class (chemicalStateClass ), intent(in ), target :: chemicalState_ - class (galacticStructureClass ), intent(in ), target :: galacticStructure_ double precision , intent(in ), optional :: densityContrast , distanceAngular type (enumerationDensityCosmologicalType), intent(in ), optional :: densityContrastRelativeTo character (len=8 ) :: label !![ - + !!] self%useDensityContrast=present(densityContrast) @@ -195,8 +177,7 @@ function icmSZConstructorInternal(cosmologyParameters_,cosmologyFunctions_,darkM & densityContrastRelativeTo=densityContrastRelativeTo, & & cosmologyparameters_ =cosmologyParameters_ , & & cosmologyFunctions_ =cosmologyFunctions_ , & - & darkMatterHaloScale_ =darkMatterHaloScale_ , & - & galacticStructure_ =galacticStructure_ & + & darkMatterHaloScale_ =darkMatterHaloScale_ & & ) @@ -217,13 +198,10 @@ subroutine icmSZDestructor(self) type(nodePropertyExtractorICMSZ), intent(inout) :: self !![ - - - - - - - + + + + !!] if (self%useDensityContrast) then !![ @@ -295,21 +273,35 @@ double precision function integrandComptonY(radius) Integrand function used for computing ICM SZ properties. !!} use :: Abundances_Structure , only : abundances - use :: Numerical_Constants_Astronomical, only : massSolar , megaParsec + use :: Numerical_Constants_Astronomical, only : massSolar , megaParsec use :: Numerical_Constants_Atomic , only : massHydrogenAtom - use :: Numerical_Constants_Physical , only : boltzmannsConstant, electronMass, speedLight, thomsonCrossSection - use :: Numerical_Constants_Prefixes , only : centi , hecto + use :: Numerical_Constants_Physical , only : boltzmannsConstant , electronMass , speedLight, thomsonCrossSection + use :: Numerical_Constants_Prefixes , only : centi , hecto + use :: Mass_Distributions , only : massDistributionClass, kinematicsDistributionClass + use :: Coordinates , only : coordinateSpherical , assignment(=) + use :: Galactic_Structure_Options , only : componentTypeHotHalo , massTypeGaseous implicit none - double precision , intent(in ) :: radius - class (nodeComponentHotHalo), pointer :: hotHalo - double precision :: density , temperature, & - & numberDensityHydrogen, massICM - type (abundances ) :: abundancesICM + double precision , intent(in ) :: radius + class (nodeComponentHotHalo ), pointer :: hotHalo + class (massDistributionClass ), pointer :: massDistribution_ + class (kinematicsDistributionClass), pointer :: kinematicsDistribution_ + type (coordinateSpherical ) :: coordinates + double precision :: density , temperature, & + & numberDensityHydrogen , massICM + type (abundances ) :: abundancesICM + ! Get the mass distribution. + coordinates = [radius,0.0d0,0.0d0] + massDistribution_ => node %massDistribution (componentTypeHotHalo,massTypeGaseous) + kinematicsDistribution_ => massDistribution_ %kinematicsDistribution( ) ! Get the density of the ICM. - density = self %hotHaloMassDistribution_ %density (node,radius) + density = massDistribution_ %density (coordinates ) ! Get the temperature of the ICM. - temperature = self %hotHaloTemperatureProfile_%temperature(node,radius) + temperature = kinematicsDistribution_%temperature (coordinates ) + !![ + + + !!] ! Get abundances and chemistry of the ICM. hotHalo => node %hotHalo () massICM = hotHalo%mass () diff --git a/source/nodes.property_extractor.ICM_Xray_luminosity.F90 b/source/nodes.property_extractor.ICM_Xray_luminosity.F90 index 35a2695ea4..034aeda67a 100644 --- a/source/nodes.property_extractor.ICM_Xray_luminosity.F90 +++ b/source/nodes.property_extractor.ICM_Xray_luminosity.F90 @@ -21,11 +21,9 @@ Contains a module which implements an intracluster medium X-ray luminosity property extractor class. !!} - use :: Cooling_Functions , only : coolingFunction , coolingFunctionClass - use :: Cosmology_Functions , only : cosmologyFunctions , cosmologyFunctionsClass - use :: Dark_Matter_Halo_Scales , only : darkMatterHaloScale , darkMatterHaloScaleClass - use :: Hot_Halo_Mass_Distributions , only : hotHaloMassDistribution , hotHaloMassDistributionClass - use :: Hot_Halo_Temperature_Profiles, only : hotHaloTemperatureProfile, hotHaloTemperatureProfileClass + use :: Cooling_Functions , only : coolingFunction , coolingFunctionClass + use :: Cosmology_Functions , only : cosmologyFunctions , cosmologyFunctionsClass + use :: Dark_Matter_Halo_Scales, only : darkMatterHaloScale, darkMatterHaloScaleClass !![ @@ -37,11 +35,9 @@ A icmXRayLuminosity property extractor class. !!} private - class(darkMatterHaloScaleClass ), pointer :: darkMatterHaloScale_ => null() - class(hotHaloMassDistributionClass ), pointer :: hotHaloMassDistribution_ => null() - class(hotHaloTemperatureProfileClass), pointer :: hotHaloTemperatureProfile_ => null() - class(coolingFunctionClass ), pointer :: coolingFunction_ => null() - class(cosmologyFunctionsClass ), pointer :: cosmologyFunctions_ => null() + class(darkMatterHaloScaleClass), pointer :: darkMatterHaloScale_ => null() + class(coolingFunctionClass ), pointer :: coolingFunction_ => null() + class(cosmologyFunctionsClass ), pointer :: cosmologyFunctions_ => null() contains final :: icmXRayLuminosityDestructor procedure :: extract => icmXRayLuminosityExtract @@ -69,31 +65,25 @@ function icmXRayLuminosityConstructorParameters(parameters) result(self) type (nodePropertyExtractorICMXRayLuminosity) :: self type (inputParameters ), intent(inout) :: parameters class(darkMatterHaloScaleClass ), pointer :: darkMatterHaloScale_ - class(hotHaloMassDistributionClass ), pointer :: hotHaloMassDistribution_ - class(hotHaloTemperatureProfileClass ), pointer :: hotHaloTemperatureProfile_ class(coolingFunctionClass ), pointer :: coolingFunction_ class(cosmologyFunctionsClass ), pointer :: cosmologyFunctions_ !![ - - - - - + + + !!] - self=nodePropertyExtractorICMXRayLuminosity(cosmologyFunctions_,darkMatterHaloScale_,hotHaloMassDistribution_,hotHaloTemperatureProfile_,coolingFunction_) + self=nodePropertyExtractorICMXRayLuminosity(cosmologyFunctions_,darkMatterHaloScale_,coolingFunction_) !![ - - - - - + + + !!] return end function icmXRayLuminosityConstructorParameters - function icmXRayLuminosityConstructorInternal(cosmologyFunctions_,darkMatterHaloScale_,hotHaloMassDistribution_,hotHaloTemperatureProfile_,coolingFunction_) result(self) + function icmXRayLuminosityConstructorInternal(cosmologyFunctions_,darkMatterHaloScale_,coolingFunction_) result(self) !!{ Internal constructor for the {\normalfont \ttfamily icmXRayLuminosity} property extractor class. !!} @@ -101,11 +91,9 @@ function icmXRayLuminosityConstructorInternal(cosmologyFunctions_,darkMatterHalo type (nodePropertyExtractorICMXRayLuminosity) :: self class(cosmologyFunctionsClass ), intent(in ), target :: cosmologyFunctions_ class(darkMatterHaloScaleClass ), intent(in ), target :: darkMatterHaloScale_ - class(hotHaloMassDistributionClass ), intent(in ), target :: hotHaloMassDistribution_ - class(hotHaloTemperatureProfileClass ), intent(in ), target :: hotHaloTemperatureProfile_ class(coolingFunctionClass ), intent(in ), target :: coolingFunction_ !![ - + !!] return @@ -119,11 +107,9 @@ subroutine icmXRayLuminosityDestructor(self) type(nodePropertyExtractorICMXRayLuminosity), intent(inout) :: self !![ - - - - - + + + !!] return end subroutine icmXRayLuminosityDestructor @@ -138,11 +124,15 @@ double precision function icmXRayLuminosityExtract(self,node,instance) use :: Numerical_Constants_Units , only : electronVolt use :: Numerical_Integration , only : integrator use :: Radiation_Fields , only : radiationFieldCosmicMicrowaveBackground + use :: Mass_Distributions , only : massDistributionClass , kinematicsDistributionClass + use :: Galactic_Structure_Options , only : componentTypeHotHalo , massTypeGaseous implicit none class(nodePropertyExtractorICMXRayLuminosity ), intent(inout), target :: self type (treeNode ), intent(inout), target :: node type (multiCounter ), intent(inout), optional :: instance type (radiationFieldCosmicMicrowaveBackground), pointer :: radiation_ + class(massDistributionClass ), pointer :: massDistribution_ + class(kinematicsDistributionClass ), pointer :: kinematicsDistribution_ type (integrator ) :: integratorLuminosity !$GLC attributes unused :: self, instance @@ -151,11 +141,16 @@ double precision function icmXRayLuminosityExtract(self,node,instance) !![ !!] + ! Get the mass distribution. + massDistribution_ => node %massDistribution (componentTypeHotHalo,massTypeGaseous) + kinematicsDistribution_ => massDistribution_%kinematicsDistribution( ) ! Compute luminosity and temperature. integratorLuminosity =integrator (integrandLuminosityXray ,toleranceRelative =1.0d-3) icmXRayLuminosityExtract=integratorLuminosity %integrate(0.0d0 ,self%darkMatterHaloScale_%radiusVirial(node) ) !![ - + + + !!] return @@ -172,6 +167,7 @@ Integrand function used for computing ICM X-ray luminosities. use :: Numerical_Constants_Atomic , only : massHydrogenAtom use :: Numerical_Constants_Math , only : Pi use :: Numerical_Constants_Prefixes , only : centi , hecto + use :: Coordinates , only : coordinateSpherical , assignment(=) implicit none double precision , intent(in ) :: radius class (nodeComponentHotHalo), pointer :: hotHalo @@ -180,11 +176,14 @@ Integrand function used for computing ICM X-ray luminosities. & massToDensityConversion type (abundances ) :: abundancesICM type (chemicalAbundances ) :: massChemicalICM , fractionChemicalICM + type (coordinateSpherical ) :: coordinates - ! Get the density of the ICM. - density =self%hotHaloMassDistribution_ %density (node,radius) + ! Set the coordinates. + coordinates = [radius,0.0d0,0.0d0] + ! Get the density of the ICM. + density = massDistribution_ %density (coordinates) ! Get the temperature of the ICM. - temperature=self%hotHaloTemperatureProfile_%temperature(node,radius) + temperature = kinematicsDistribution_%temperature(coordinates) ! Get abundances and chemistry of the ICM. hotHalo => node %hotHalo () massICM = hotHalo%mass () diff --git a/source/nodes.property_extractor.ICM_Xray_temperature.F90 b/source/nodes.property_extractor.ICM_Xray_temperature.F90 index a60bb4faba..86f6048db7 100644 --- a/source/nodes.property_extractor.ICM_Xray_temperature.F90 +++ b/source/nodes.property_extractor.ICM_Xray_temperature.F90 @@ -21,11 +21,9 @@ Contains a module which implements an intracluster medium X-ray luminosity-weighted temperature property extractor class. !!} - use :: Cooling_Functions , only : coolingFunction , coolingFunctionClass - use :: Cosmology_Functions , only : cosmologyFunctions , cosmologyFunctionsClass - use :: Dark_Matter_Halo_Scales , only : darkMatterHaloScale , darkMatterHaloScaleClass - use :: Hot_Halo_Mass_Distributions , only : hotHaloMassDistribution , hotHaloMassDistributionClass - use :: Hot_Halo_Temperature_Profiles, only : hotHaloTemperatureProfile, hotHaloTemperatureProfileClass + use :: Cooling_Functions , only : coolingFunction , coolingFunctionClass + use :: Cosmology_Functions , only : cosmologyFunctions , cosmologyFunctionsClass + use :: Dark_Matter_Halo_Scales, only : darkMatterHaloScale, darkMatterHaloScaleClass !![ @@ -37,11 +35,9 @@ An ICM luminosity-weighted temperature property extractor class. !!} private - class(darkMatterHaloScaleClass ), pointer :: darkMatterHaloScale_ => null() - class(hotHaloMassDistributionClass ), pointer :: hotHaloMassDistribution_ => null() - class(hotHaloTemperatureProfileClass), pointer :: hotHaloTemperatureProfile_ => null() - class(coolingFunctionClass ), pointer :: coolingFunction_ => null() - class(cosmologyFunctionsClass ), pointer :: cosmologyFunctions_ => null() + class(darkMatterHaloScaleClass), pointer :: darkMatterHaloScale_ => null() + class(coolingFunctionClass ), pointer :: coolingFunction_ => null() + class(cosmologyFunctionsClass ), pointer :: cosmologyFunctions_ => null() contains final :: icmXRayTemperatureDestructor procedure :: extract => icmXRayTemperatureExtract @@ -69,31 +65,25 @@ function icmXRayTemperatureConstructorParameters(parameters) result(self) type (nodePropertyExtractorICMXRayTemperature) :: self type (inputParameters ), intent(inout) :: parameters class(darkMatterHaloScaleClass ), pointer :: darkMatterHaloScale_ - class(hotHaloMassDistributionClass ), pointer :: hotHaloMassDistribution_ - class(hotHaloTemperatureProfileClass ), pointer :: hotHaloTemperatureProfile_ class(coolingFunctionClass ), pointer :: coolingFunction_ class(cosmologyFunctionsClass ), pointer :: cosmologyFunctions_ !![ - - - - - + + + !!] - self=nodePropertyExtractorICMXRayTemperature(cosmologyFunctions_,darkMatterHaloScale_,hotHaloMassDistribution_,hotHaloTemperatureProfile_,coolingFunction_) + self=nodePropertyExtractorICMXRayTemperature(cosmologyFunctions_,darkMatterHaloScale_,coolingFunction_) !![ - - - - - + + + !!] return end function icmXRayTemperatureConstructorParameters - function icmXRayTemperatureConstructorInternal(cosmologyFunctions_,darkMatterHaloScale_,hotHaloMassDistribution_,hotHaloTemperatureProfile_,coolingFunction_) result(self) + function icmXRayTemperatureConstructorInternal(cosmologyFunctions_,darkMatterHaloScale_,coolingFunction_) result(self) !!{ Internal constructor for the {\normalfont \ttfamily icmXRayTemperature} property extractor class. !!} @@ -101,11 +91,9 @@ function icmXRayTemperatureConstructorInternal(cosmologyFunctions_,darkMatterHal type (nodePropertyExtractorICMXRayTemperature) :: self class(cosmologyFunctionsClass ), intent(in ), target :: cosmologyFunctions_ class(darkMatterHaloScaleClass ), intent(in ), target :: darkMatterHaloScale_ - class(hotHaloMassDistributionClass ), intent(in ), target :: hotHaloMassDistribution_ - class(hotHaloTemperatureProfileClass ), intent(in ), target :: hotHaloTemperatureProfile_ class(coolingFunctionClass ), intent(in ), target :: coolingFunction_ !![ - + !!] return @@ -119,11 +107,9 @@ subroutine icmXRayTemperatureDestructor(self) type(nodePropertyExtractorICMXRayTemperature), intent(inout) :: self !![ - - - - - + + + !!] return end subroutine icmXRayTemperatureDestructor @@ -138,13 +124,17 @@ double precision function icmXRayTemperatureExtract(self,node,instance) use :: Numerical_Constants_Units , only : electronVolt use :: Numerical_Integration , only : integrator use :: Radiation_Fields , only : radiationFieldCosmicMicrowaveBackground + use :: Mass_Distributions , only : massDistributionClass , kinematicsDistributionClass + use :: Galactic_Structure_Options , only : componentTypeHotHalo , massTypeGaseous implicit none class (nodePropertyExtractorICMXRayTemperature), intent(inout), target :: self type (treeNode ), intent(inout), target :: node type (multiCounter ), intent(inout), optional :: instance type (radiationFieldCosmicMicrowaveBackground), pointer :: radiation_ - type (integrator ) :: integratorLuminosity, integratorTemperature - double precision :: luminosity , temperature + type (integrator ) :: integratorLuminosity , integratorTemperature + class (massDistributionClass ), pointer :: massDistribution_ + class (kinematicsDistributionClass ), pointer :: kinematicsDistribution_ + double precision :: luminosity , temperature !$GLC attributes unused :: self, instance ! Initialize radiation field. @@ -152,6 +142,9 @@ double precision function icmXRayTemperatureExtract(self,node,instance) !![ !!] + ! Get the mass distribution. + massDistribution_ => node %massDistribution (componentTypeHotHalo,massTypeGaseous) + kinematicsDistribution_ => massDistribution_%kinematicsDistribution( ) ! Compute luminosity and temperature. integratorLuminosity =integrator (integrandLuminosityXray ,toleranceRelative =1.0d-3) integratorTemperature=integrator (integrandTemperatureXray,toleranceRelative =1.0d-3) @@ -168,7 +161,9 @@ double precision function icmXRayTemperatureExtract(self,node,instance) temperature=+0.0d0 end if !![ - + + + !!] icmXRayTemperatureExtract=temperature return @@ -186,19 +181,23 @@ Integrand function used for computing ICM X-ray luminosities. use :: Numerical_Constants_Atomic , only : massHydrogenAtom use :: Numerical_Constants_Math , only : Pi use :: Numerical_Constants_Prefixes , only : centi , hecto + use :: Coordinates , only : coordinateSpherical , assignment(=) implicit none double precision , intent(in ) :: radius class (nodeComponentHotHalo), pointer :: hotHalo + type (coordinateSpherical ) :: coordinates double precision :: density , temperature , & & numberDensityHydrogen , massICM , & & massToDensityConversion type (abundances ) :: abundancesICM type (chemicalAbundances ) :: massChemicalICM , fractionChemicalICM + ! Set the coordinates. + coordinates = [radius,0.0d0,0.0d0] ! Get the density of the ICM. - density =self%hotHaloMassDistribution_ %density (node,radius) + density = massDistribution_ %density (coordinates) ! Get the temperature of the ICM. - temperature=self%hotHaloTemperatureProfile_%temperature(node,radius) + temperature = kinematicsDistribution_%temperature(coordinates) ! Get abundances and chemistry of the ICM. hotHalo => node %hotHalo () massICM = hotHalo%mass () @@ -237,11 +236,14 @@ double precision function integrandTemperatureXray(radius) !!{ Integrand function used for computing ICM X-ray luminosity-weighted temperatures. !!} + use :: Coordinates, only : coordinateSpherical, assignment(=) implicit none - double precision, intent(in ) :: radius + double precision , intent(in ) :: radius + type (coordinateSpherical) :: coordinates - integrandTemperatureXray=+integrandLuminosityXray ( radius) & - & *self%hotHaloTemperatureProfile_%temperature(node,radius) + coordinates =[radius,0.0d0,0.0d0] + integrandTemperatureXray=+integrandLuminosityXray ( radius) & + & *kinematicsDistribution_%temperature(coordinates) return end function integrandTemperatureXray diff --git a/source/nodes.property_extractor.ICM_cooling_power_in_band.F90 b/source/nodes.property_extractor.ICM_cooling_power_in_band.F90 index d72a86823b..08b66aa020 100644 --- a/source/nodes.property_extractor.ICM_cooling_power_in_band.F90 +++ b/source/nodes.property_extractor.ICM_cooling_power_in_band.F90 @@ -21,11 +21,9 @@ Contains a module which implements an intracluster medium cooling power in band property extractor class. !!} - use :: Cooling_Functions , only : coolingFunction , coolingFunctionClass - use :: Cosmology_Functions , only : cosmologyFunctions , cosmologyFunctionsClass - use :: Dark_Matter_Halo_Scales , only : darkMatterHaloScale , darkMatterHaloScaleClass - use :: Hot_Halo_Mass_Distributions , only : hotHaloMassDistribution , hotHaloMassDistributionClass - use :: Hot_Halo_Temperature_Profiles, only : hotHaloTemperatureProfile, hotHaloTemperatureProfileClass + use :: Cooling_Functions , only : coolingFunction , coolingFunctionClass + use :: Cosmology_Functions , only : cosmologyFunctions , cosmologyFunctionsClass + use :: Dark_Matter_Halo_Scales, only : darkMatterHaloScale, darkMatterHaloScaleClass !![ @@ -37,13 +35,11 @@ A property extractor class which extracts the fraction of the ICM cooling power due to emission in a given energy band. !!} private - class (darkMatterHaloScaleClass ), pointer :: darkMatterHaloScale_ => null() - class (hotHaloMassDistributionClass ), pointer :: hotHaloMassDistribution_ => null() - class (hotHaloTemperatureProfileClass), pointer :: hotHaloTemperatureProfile_ => null() - class (coolingFunctionClass ), pointer :: coolingFunction_ => null() - class (cosmologyFunctionsClass ), pointer :: cosmologyFunctions_ => null() - double precision :: energyLow , energyHigh - type (varying_string ) :: label + class (darkMatterHaloScaleClass), pointer :: darkMatterHaloScale_ => null() + class (coolingFunctionClass ), pointer :: coolingFunction_ => null() + class (cosmologyFunctionsClass ), pointer :: cosmologyFunctions_ => null() + double precision :: energyLow , energyHigh + type (varying_string ) :: label contains final :: icmCoolingPowerInBandDestructor procedure :: extract => icmCoolingPowerInBandExtract @@ -71,11 +67,9 @@ function icmCoolingPowerInBandConstructorParameters(parameters) result(self) type (nodePropertyExtractorICMCoolingPowerInBand) :: self type (inputParameters ), intent(inout) :: parameters class (darkMatterHaloScaleClass ), pointer :: darkMatterHaloScale_ - class (hotHaloMassDistributionClass ), pointer :: hotHaloMassDistribution_ - class (hotHaloTemperatureProfileClass ), pointer :: hotHaloTemperatureProfile_ class (coolingFunctionClass ), pointer :: coolingFunction_ class (cosmologyFunctionsClass ), pointer :: cosmologyFunctions_ - double precision :: energyLow , energyHigh + double precision :: energyLow , energyHigh type (varying_string ) :: label @@ -95,39 +89,33 @@ function icmCoolingPowerInBandConstructorParameters(parameters) result(self) parameters A label to use as a suffix for this property. - - - - - + + + !!] - self=nodePropertyExtractorICMCoolingPowerInBand(energyLow,energyHigh,label,cosmologyFunctions_,darkMatterHaloScale_,hotHaloMassDistribution_,hotHaloTemperatureProfile_,coolingFunction_) + self=nodePropertyExtractorICMCoolingPowerInBand(energyLow,energyHigh,label,cosmologyFunctions_,darkMatterHaloScale_,coolingFunction_) !![ - - - - - + + + !!] return end function icmCoolingPowerInBandConstructorParameters - function icmCoolingPowerInBandConstructorInternal(energyLow,energyHigh,label,cosmologyFunctions_,darkMatterHaloScale_,hotHaloMassDistribution_,hotHaloTemperatureProfile_,coolingFunction_) result(self) + function icmCoolingPowerInBandConstructorInternal(energyLow,energyHigh,label,cosmologyFunctions_,darkMatterHaloScale_,coolingFunction_) result(self) !!{ Internal constructor for the {\normalfont \ttfamily icmCoolingPowerInBand} property extractor class. !!} implicit none type (nodePropertyExtractorICMCoolingPowerInBand) :: self - double precision , intent(in ) :: energyLow , energyHigh + double precision , intent(in ) :: energyLow , energyHigh type (varying_string ), intent(in ) :: label class (cosmologyFunctionsClass ), intent(in ), target :: cosmologyFunctions_ class (darkMatterHaloScaleClass ), intent(in ), target :: darkMatterHaloScale_ - class (hotHaloMassDistributionClass ), intent(in ), target :: hotHaloMassDistribution_ - class (hotHaloTemperatureProfileClass ), intent(in ), target :: hotHaloTemperatureProfile_ class (coolingFunctionClass ), intent(in ), target :: coolingFunction_ !![ - + !!] return @@ -141,11 +129,9 @@ subroutine icmCoolingPowerInBandDestructor(self) type(nodePropertyExtractorICMCoolingPowerInBand), intent(inout) :: self !![ - - - - - + + + !!] return end subroutine icmCoolingPowerInBandDestructor @@ -159,13 +145,17 @@ double precision function icmCoolingPowerInBandExtract(self,node,instance) use :: Numerical_Constants_Units , only : electronVolt use :: Numerical_Integration , only : integrator use :: Radiation_Fields , only : radiationFieldCosmicMicrowaveBackground + use :: Mass_Distributions , only : massDistributionClass , kinematicsDistributionClass + use :: Galactic_Structure_Options , only : componentTypeHotHalo , massTypeGaseous implicit none class (nodePropertyExtractorICMCoolingPowerInBand), intent(inout), target :: self type (treeNode ), intent(inout), target :: node type (multiCounter ), intent(inout), optional :: instance type (radiationFieldCosmicMicrowaveBackground ), pointer :: radiation_ - type (integrator ) :: integratorTotal, integratorInBand - double precision :: luminosityTotal, luminosityInBand + class (massDistributionClass ), pointer :: massDistribution_ + class (kinematicsDistributionClass ), pointer :: kinematicsDistribution_ + type (integrator ) :: integratorTotal , integratorInBand + double precision :: luminosityTotal , luminosityInBand !$GLC attributes unused :: instance ! Initialize radiation field. @@ -173,6 +163,9 @@ double precision function icmCoolingPowerInBandExtract(self,node,instance) !![ !!] + ! Get the mass distribution. + massDistribution_ => node %massDistribution (componentTypeHotHalo,massTypeGaseous) + kinematicsDistribution_ => massDistribution_%kinematicsDistribution( ) ! Compute luminosity and temperature. integratorTotal =integrator (integrandLuminosityTotal ,toleranceRelative =1.0d-3) integratorInBand=integrator (integrandLuminosityInBand,toleranceRelative =1.0d-3) @@ -185,7 +178,9 @@ double precision function icmCoolingPowerInBandExtract(self,node,instance) icmCoolingPowerInBandExtract=+0.0d0 end if !![ - + + + !!] return @@ -238,6 +233,7 @@ subroutine icmProperties(radius,numberDensityHydrogen,temperature,abundancesICM, use :: Numerical_Constants_Atomic , only : massHydrogenAtom use :: Numerical_Constants_Prefixes , only : hecto use :: Numerical_Constants_Astronomical , only : massSolar , megaParsec + use :: Coordinates , only : coordinateSpherical , assignment(=) implicit none double precision , intent(in ) :: radius double precision , intent( out) :: numberDensityHydrogen , temperature @@ -245,13 +241,16 @@ subroutine icmProperties(radius,numberDensityHydrogen,temperature,abundancesICM, type (chemicalAbundances ), intent( out) :: densityChemicalICM class (nodeComponentHotHalo), pointer :: hotHalo type (chemicalAbundances ) :: massChemicalICM + type (coordinateSpherical ) :: coordinates double precision :: density , massICM , & & massToDensityConversion + ! Set the coordinates. + coordinates = [radius,0.0d0,0.0d0] ! Get the density of the ICM. - density =self%hotHaloMassDistribution_ %density (node,radius) + density = massDistribution_ %density (coordinates) ! Get the temperature of the ICM. - temperature=self%hotHaloTemperatureProfile_%temperature(node,radius) + temperature = kinematicsDistribution_%temperature(coordinates) ! Get abundances and chemistry of the ICM. hotHalo => node %hotHalo () massICM = hotHalo%mass () diff --git a/source/nodes.property_extractor.ICM_optical_depth_LymanAlpha.F90 b/source/nodes.property_extractor.ICM_optical_depth_LymanAlpha.F90 index 577c8d729f..d280adcd18 100644 --- a/source/nodes.property_extractor.ICM_optical_depth_LymanAlpha.F90 +++ b/source/nodes.property_extractor.ICM_optical_depth_LymanAlpha.F90 @@ -19,22 +19,20 @@ !% Contains a module which implements an intracluster medium cooling power in band property extractor class. - use :: Cosmology_Functions , only : cosmologyFunctions , cosmologyFunctionsClass - use :: Dark_Matter_Halo_Scales , only : darkMatterHaloScale , darkMatterHaloScaleClass - use :: Hot_Halo_Mass_Distributions , only : hotHaloMassDistribution , hotHaloMassDistributionClass - use :: Hot_Halo_Temperature_Profiles, only : hotHaloTemperatureProfile, hotHaloTemperatureProfileClass - - !# - !# An intracluster medium cooling power in band property extractor class. - !# + use :: Cosmology_Functions , only : cosmologyFunctions , cosmologyFunctionsClass + use :: Dark_Matter_Halo_Scales, only : darkMatterHaloScale, darkMatterHaloScaleClass + + !![ + + An intracluster medium cooling power in band property extractor class. + + !!] type, extends(nodePropertyExtractorScalar) :: nodePropertyExtractorICMOpticalDepthLymanAlpha !% A property extractor class which extracts the fraction of the ICM cooling power due to emission in a given energy band. private - class (darkMatterHaloScaleClass ), pointer :: darkMatterHaloScale_ => null() - class (hotHaloMassDistributionClass ), pointer :: hotHaloMassDistribution_ => null() - class (hotHaloTemperatureProfileClass), pointer :: hotHaloTemperatureProfile_ => null() - class (cosmologyFunctionsClass ), pointer :: cosmologyFunctions_ => null() - integer :: speciesHydrogenNeutral , speciesHydrogenIonized + class (darkMatterHaloScaleClass), pointer :: darkMatterHaloScale_ => null() + class (cosmologyFunctionsClass ), pointer :: cosmologyFunctions_ => null() + integer :: speciesHydrogenNeutral , speciesHydrogenIonized contains final :: icmOpticalDepthLymanAlphaDestructor procedure :: extract => icmOpticalDepthLymanAlphaExtract @@ -58,34 +56,31 @@ function icmOpticalDepthLymanAlphaConstructorParameters(parameters) result(self) type (nodePropertyExtractorICMOpticalDepthLymanAlpha) :: self type (inputParameters ), intent(inout) :: parameters class(darkMatterHaloScaleClass ), pointer :: darkMatterHaloScale_ - class(hotHaloMassDistributionClass ), pointer :: hotHaloMassDistribution_ - class(hotHaloTemperatureProfileClass ), pointer :: hotHaloTemperatureProfile_ class(cosmologyFunctionsClass ), pointer :: cosmologyFunctions_ - !# - !# - !# - !# - self=nodePropertyExtractorICMOpticalDepthLymanAlpha(cosmologyFunctions_,darkMatterHaloScale_,hotHaloMassDistribution_,hotHaloTemperatureProfile_) - !# - !# - !# - !# - !# + !![ + + + !!] + self=nodePropertyExtractorICMOpticalDepthLymanAlpha(cosmologyFunctions_,darkMatterHaloScale_) + !![ + + + + !!] return end function icmOpticalDepthLymanAlphaConstructorParameters - function icmOpticalDepthLymanAlphaConstructorInternal(cosmologyFunctions_,darkMatterHaloScale_,hotHaloMassDistribution_,hotHaloTemperatureProfile_) result(self) + function icmOpticalDepthLymanAlphaConstructorInternal(cosmologyFunctions_,darkMatterHaloScale_) result(self) !% Internal constructor for the {\normalfont \ttfamily icmOpticalDepthLymanAlpha} property extractor class. use :: Chemical_Abundances_Structure, only : Chemicals_Index implicit none type (nodePropertyExtractorICMOpticalDepthLymanAlpha) :: self class(cosmologyFunctionsClass ), intent(in ), target :: cosmologyFunctions_ class(darkMatterHaloScaleClass ), intent(in ), target :: darkMatterHaloScale_ - class(hotHaloMassDistributionClass ), intent(in ), target :: hotHaloMassDistribution_ - class(hotHaloTemperatureProfileClass ), intent(in ), target :: hotHaloTemperatureProfile_ - !# - + !![ + + !!] self%speciesHydrogenNeutral=Chemicals_Index("AtomicHydrogen" ) self%speciesHydrogenIonized=Chemicals_Index("AtomicHydrogenCation") @@ -97,10 +92,10 @@ subroutine icmOpticalDepthLymanAlphaDestructor(self) implicit none type(nodePropertyExtractorICMOpticalDepthLymanAlpha), intent(inout) :: self - !# - !# - !# - !# + !![ + + + !!] return end subroutine icmOpticalDepthLymanAlphaDestructor @@ -111,21 +106,34 @@ double precision function icmOpticalDepthLymanAlphaExtract(self,node,instance) use :: Numerical_Constants_Units , only : electronVolt use :: Numerical_Integration , only : integrator use :: Radiation_Fields , only : radiationFieldCosmicMicrowaveBackground + use :: Mass_Distributions , only : massDistributionClass , kinematicsDistributionClass + use :: Galactic_Structure_Options , only : componentTypeHotHalo , massTypeGaseous implicit none class(nodePropertyExtractorICMOpticalDepthLymanAlpha), intent(inout), target :: self type (treeNode ), intent(inout), target :: node type (multiCounter ), intent(inout), optional :: instance type (radiationFieldCosmicMicrowaveBackground ), pointer :: radiation_ + class(massDistributionClass ), pointer :: massDistribution_ + class(kinematicsDistributionClass ), pointer :: kinematicsDistribution_ type (integrator ) :: integrator_ !$GLC attributes unused :: instance ! Initialize radiation field. allocate(radiation_) - !# + !![ + + !!] + ! Get the mass distribution. + massDistribution_ => node %massDistribution (componentTypeHotHalo,massTypeGaseous) + kinematicsDistribution_ => massDistribution_%kinematicsDistribution( ) ! Compute luminosity and temperature. integrator_ =integrator (integrandOpticalDepth,toleranceRelative =1.0d-3) icmOpticalDepthLymanAlphaExtract=integrator_%integrate(0.0d0 ,self%darkMatterHaloScale_%radiusVirial(node) ) - !# + !![ + + + + !!] return contains @@ -182,6 +190,7 @@ subroutine icmProperties(radius,numberDensityHydrogen,temperature,abundancesICM, use :: Numerical_Constants_Atomic , only : massHydrogenAtom use :: Numerical_Constants_Prefixes , only : hecto use :: Numerical_Constants_Astronomical , only : massSolar , megaParsec + use :: Coordinates , only : coordinateSpherical , assignment(=) implicit none double precision , intent(in ) :: radius double precision , intent( out) :: numberDensityHydrogen , temperature @@ -189,13 +198,16 @@ subroutine icmProperties(radius,numberDensityHydrogen,temperature,abundancesICM, type (chemicalAbundances ), intent( out) :: densityChemicalICM class (nodeComponentHotHalo), pointer :: hotHalo type (chemicalAbundances ) :: massChemicalICM + type (coordinateSpherical ) :: coordinates double precision :: density , massICM , & & massToDensityConversion + ! Set the coordinates. + coordinates = [radius,0.0d0,0.0d0] ! Get the density of the ICM. - density =self%hotHaloMassDistribution_ %density (node,radius) + density = massDistribution_ %density (coordinates) ! Get the temperature of the ICM. - temperature=self%hotHaloTemperatureProfile_%temperature(node,radius) + temperature = kinematicsDistribution_%temperature(coordinates) ! Get abundances and chemistry of the ICM. hotHalo => node %hotHalo () massICM = hotHalo%mass () diff --git a/source/nodes.property_extractor.bound_mass_radius.F90 b/source/nodes.property_extractor.bound_mass_radius.F90 index 6ee17bcf66..cd05e19193 100644 --- a/source/nodes.property_extractor.bound_mass_radius.F90 +++ b/source/nodes.property_extractor.bound_mass_radius.F90 @@ -21,8 +21,6 @@ Contains a module which implements a property extractor class that extracts the radius enclosing the current bound mass. !!} - use :: Galactic_Structure, only : galacticStructureClass - !![ @@ -35,9 +33,7 @@ A property extractor class that extracts the radius enclosing the current bound mass. !!} private - class(galacticStructureClass), pointer :: galacticStructure_ => null() contains - final :: radiusBoundMassDestructor procedure :: extract => radiusBoundMassExtract procedure :: name => radiusBoundMassName procedure :: description => radiusBoundMassDescription @@ -49,7 +45,6 @@ Constructors for the ``radiusBoundMass'' output analysis class. !!} module procedure radiusBoundMassConstructorParameters - module procedure radiusBoundMassConstructorInternal end interface nodePropertyExtractorRadiusBoundMass contains @@ -62,63 +57,41 @@ function radiusBoundMassConstructorParameters(parameters) result(self) implicit none type (nodePropertyExtractorRadiusBoundMass) :: self type (inputParameters ), intent(inout) :: parameters - class(galacticStructureClass ), pointer :: galacticStructure_ - !![ - - !!] - self=nodePropertyExtractorRadiusBoundMass(galacticStructure_) + self=nodePropertyExtractorRadiusBoundMass() !![ - !!] return end function radiusBoundMassConstructorParameters - function radiusBoundMassConstructorInternal(galacticStructure_) result(self) - !!{ - Constructor for the {\normalfont \ttfamily radiusBoundMass} property extractor class which takes a parameter set as input. - !!} - use :: Input_Parameters, only : inputParameters - implicit none - type (nodePropertyExtractorRadiusBoundMass) :: self - class(galacticStructureClass ), intent(in ), target :: galacticStructure_ - !![ - - !!] - - return - end function radiusBoundMassConstructorInternal - - subroutine radiusBoundMassDestructor(self) - !!{ - Destructor for the {\normalfont \ttfamily radiusBoundMass} property extractor class which takes a parameter set as input. - !!} - implicit none - type(nodePropertyExtractorRadiusBoundMass), intent(inout) :: self - - !![ - - !!] - return - end subroutine radiusBoundMassDestructor - double precision function radiusBoundMassExtract(self,node,instance) !!{ Implement a bound mass radius property extractor. !!} - use :: Galacticus_Nodes, only : nodeComponentSatellite + use :: Galacticus_Nodes , only : nodeComponentSatellite + use :: Galactic_Structure_Options, only : radiusLarge + use :: Mass_Distributions , only : massDistributionClass implicit none class (nodePropertyExtractorRadiusBoundMass), intent(inout), target :: self type (treeNode ), intent(inout), target :: node type (multiCounter ), intent(inout), optional :: instance class (nodeComponentSatellite ) , pointer :: satellite + class (massDistributionClass ) , pointer :: massDistribution_ + double precision , parameter :: toleranceRelative=1.0d-6 double precision :: massTotal !$GLC attributes unused :: instance - satellite => node%satellite() - massTotal = self%galacticStructure_%massEnclosed (node ) - radiusBoundMassExtract = self%galacticStructure_%radiusEnclosingMass(node,mass=min(satellite%boundMass(),massTotal)) + ! Find the radius enclosing the bound mass. Limit the bound mass to that enclosed within some very large radius. Additionally, + ! seek the radius enclosing a fraction 1-10⁻⁶ of the bound mass to avoid problems with numerical precision when solving + ! numerically for this radius. + satellite => node %satellite ( ) + massDistribution_ => node %massDistribution ( ) + massTotal = massDistribution_%massEnclosedBySphere(radius= radiusLarge) + radiusBoundMassExtract = massDistribution_%radiusEnclosingMass (mass =(1.0d0-toleranceRelative)*min(satellite%boundMass(),massTotal )) + !![ + + !!] return end function radiusBoundMassExtract diff --git a/source/nodes.property_extractor.concentration.F90 b/source/nodes.property_extractor.concentration.F90 index d7e08665ba..52bef1b3dd 100644 --- a/source/nodes.property_extractor.concentration.F90 +++ b/source/nodes.property_extractor.concentration.F90 @@ -21,10 +21,9 @@ Contains a module which implements a concentration output analysis property extractor class. !!} - use :: Cosmology_Functions , only : cosmologyFunctionsClass - use :: Cosmology_Parameters , only : cosmologyParametersClass - use :: Dark_Matter_Profiles_DMO, only : darkMatterProfileDMOClass - use :: Virial_Density_Contrast , only : virialDensityContrastClass + use :: Cosmology_Functions , only : cosmologyFunctionsClass + use :: Cosmology_Parameters , only : cosmologyParametersClass + use :: Virial_Density_Contrast, only : virialDensityContrastClass !![ @@ -42,7 +41,6 @@ private class (cosmologyParametersClass ), pointer :: cosmologyParameters_ => null() class (cosmologyFunctionsClass ), pointer :: cosmologyFunctions_ => null() - class (darkMatterProfileDMOClass ), pointer :: darkMatterProfileDMO_ => null() class (virialDensityContrastClass), pointer :: virialDensityContrast_ => null(), virialDensityContrastDefinition_ => null() logical :: useLastIsolatedTime contains @@ -73,7 +71,6 @@ function concentrationConstructorParameters(parameters) result(self) type (inputParameters ), intent(inout) :: parameters class (cosmologyParametersClass ), pointer :: cosmologyParameters_ class (cosmologyFunctionsClass ), pointer :: cosmologyFunctions_ - class (darkMatterProfileDMOClass ), pointer :: darkMatterProfileDMO_ class (virialDensityContrastClass ), pointer :: virialDensityContrast_, virialDensityContrastDefinition_ logical :: useLastIsolatedTime @@ -86,23 +83,21 @@ function concentrationConstructorParameters(parameters) result(self) - !!] - self=nodePropertyExtractorConcentration(useLastIsolatedTime,cosmologyParameters_,cosmologyFunctions_,darkMatterProfileDMO_,virialDensityContrast_,virialDensityContrastDefinition_) + self=nodePropertyExtractorConcentration(useLastIsolatedTime,cosmologyParameters_,cosmologyFunctions_,virialDensityContrast_,virialDensityContrastDefinition_) !![ - !!] return end function concentrationConstructorParameters - function concentrationConstructorInternal(useLastIsolatedTime,cosmologyParameters_,cosmologyFunctions_,darkMatterProfileDMO_,virialDensityContrast_,virialDensityContrastDefinition_) result(self) + function concentrationConstructorInternal(useLastIsolatedTime,cosmologyParameters_,cosmologyFunctions_,virialDensityContrast_,virialDensityContrastDefinition_) result(self) !!{ Internal constructor for the ``concentration'' output analysis property extractor class. !!} @@ -110,11 +105,10 @@ function concentrationConstructorInternal(useLastIsolatedTime,cosmologyParameter type (nodePropertyExtractorConcentration) :: self class (cosmologyParametersClass ), intent(in ), target :: cosmologyParameters_ class (cosmologyFunctionsClass ), intent(in ), target :: cosmologyFunctions_ - class (darkMatterProfileDMOClass ), intent(in ), target :: darkMatterProfileDMO_ class (virialDensityContrastClass ), intent(in ), target :: virialDensityContrast_, virialDensityContrastDefinition_ logical , intent(in ) :: useLastIsolatedTime !![ - + !!] return @@ -130,7 +124,6 @@ subroutine concentrationDestructor(self) !![ - !!] @@ -166,7 +159,6 @@ double precision function concentrationExtract(self,node,instance) & radius = radiusHalo , & & cosmologyParameters_ =self%cosmologyParameters_ , & & cosmologyFunctions_ =self%cosmologyFunctions_ , & - & darkMatterProfileDMO_ =self%darkMatterProfileDMO_ , & & virialDensityContrast_=self%virialDensityContrast_ , & & useLastIsolatedTime =self%useLastIsolatedTime & & ) diff --git a/source/nodes.property_extractor.dark_matter_profile.SIDM_interaction_radius.F90 b/source/nodes.property_extractor.dark_matter_profile.SIDM_interaction_radius.F90 index 55713355de..a7dc989a59 100644 --- a/source/nodes.property_extractor.dark_matter_profile.SIDM_interaction_radius.F90 +++ b/source/nodes.property_extractor.dark_matter_profile.SIDM_interaction_radius.F90 @@ -103,23 +103,25 @@ double precision function darkMatterProfileRadiusInteractionSIDMExtract(self,nod !!{ Implement a {\normalfont \ttfamily darkMatterProfileRadiusInteractionSIDM} output analysis. !!} - use :: Dark_Matter_Profiles_DMO, only : darkMatterProfileDMOSIDM - use :: Galacticus_Nodes , only : nodeComponentBasic + use :: Mass_Distributions, only : massDistributionSphericalSIDM, massDistributionClass implicit none class(nodePropertyExtractorDarkMatterProfileRadiusInteractionSIDM), intent(inout), target :: self type (treeNode ), intent(inout), target :: node type (multiCounter ), intent(inout), optional :: instance - class(nodeComponentBasic ), pointer :: basic + class(massDistributionClass ), pointer :: massDistribution_ !$GLC attributes unused :: instance - select type (darkMatterProfileDMO_ => self%darkMatterProfileDMO_) - class is (darkMatterProfileDMOSIDM) - basic => node %basic ( ) - darkMatterProfileRadiusInteractionSIDMExtract = darkMatterProfileDMO_%radiusInteraction(node,basic%time()) + massDistribution_ => self%darkMatterProfileDMO_%get(node) + select type (massDistribution_) + class is (massDistributionSphericalSIDM) + darkMatterProfileRadiusInteractionSIDMExtract=massDistribution_%radiusInteraction() class default - darkMatterProfileRadiusInteractionSIDMExtract = 0.0d0 + darkMatterProfileRadiusInteractionSIDMExtract=0.0d0 end select return + !![ + + !!] end function darkMatterProfileRadiusInteractionSIDMExtract function darkMatterProfileRadiusInteractionSIDMName(self) diff --git a/source/nodes.property_extractor.density.F90 b/source/nodes.property_extractor.density.F90 index 2feae326a2..d275f04621 100644 --- a/source/nodes.property_extractor.density.F90 +++ b/source/nodes.property_extractor.density.F90 @@ -22,7 +22,6 @@ !!} use :: Dark_Matter_Halo_Scales , only : darkMatterHaloScale , darkMatterHaloScaleClass use :: Galactic_Structure_Radii_Definitions, only : radiusSpecifier - use :: Galactic_Structure , only : galacticStructureClass !![ @@ -35,7 +34,6 @@ !!} private class (darkMatterHaloScaleClass), pointer :: darkMatterHaloScale_ => null() - class (galacticStructureClass ), pointer :: galacticStructure_ => null() integer :: radiiCount , elementCount_ logical :: includeRadii type (varying_string ), allocatable, dimension(:) :: radiusSpecifiers @@ -74,7 +72,6 @@ function densityProfileConstructorParameters(parameters) result(self) type (inputParameters ), intent(inout) :: parameters type (varying_string ), allocatable , dimension(:) :: radiusSpecifiers class (darkMatterHaloScaleClass ), pointer :: darkMatterHaloScale_ - class (galacticStructureClass ), pointer :: galacticStructure_ logical :: includeRadii allocate(radiusSpecifiers(parameters%count('radiusSpecifiers'))) @@ -91,18 +88,16 @@ function densityProfileConstructorParameters(parameters) result(self) parameters - !!] - self=nodePropertyExtractorDensityProfile(radiusSpecifiers,includeRadii,darkMatterHaloScale_,galacticStructure_) + self=nodePropertyExtractorDensityProfile(radiusSpecifiers,includeRadii,darkMatterHaloScale_) !![ - !!] return end function densityProfileConstructorParameters - function densityProfileConstructorInternal(radiusSpecifiers,includeRadii,darkMatterHaloScale_,galacticStructure_) result(self) + function densityProfileConstructorInternal(radiusSpecifiers,includeRadii,darkMatterHaloScale_) result(self) !!{ Internal constructor for the {\normalfont \ttfamily densityProfile} property extractor class. !!} @@ -111,10 +106,9 @@ function densityProfileConstructorInternal(radiusSpecifiers,includeRadii,darkMat type (nodePropertyExtractorDensityProfile) :: self type (varying_string ), intent(in ), dimension(:) :: radiusSpecifiers class (darkMatterHaloScaleClass ), intent(in ), target :: darkMatterHaloScale_ - class (galacticStructureClass ), intent(in ), target :: galacticStructure_ logical , intent(in ) :: includeRadii !![ - + !!] if (includeRadii) then @@ -144,7 +138,6 @@ subroutine densityProfileDestructor(self) !![ - !!] return end subroutine densityProfileDestructor @@ -185,6 +178,8 @@ function densityProfileExtract(self,node,time,instance) & radiusTypeGalacticMassFraction , radiusTypeRadius , radiusTypeSpheroidHalfMassRadius, radiusTypeSpheroidRadius , & & radiusTypeStellarMassFraction , radiusTypeVirialRadius use :: Galacticus_Nodes , only : nodeComponentDarkMatterProfile , nodeComponentDisk , nodeComponentSpheroid , treeNode + use :: Mass_Distributions , only : massDistributionClass + use :: Coordinates , only : coordinateSpherical , assignment(=) use :: Numerical_Constants_Math , only : Pi use :: Error , only : Error_Report implicit none @@ -196,6 +191,8 @@ function densityProfileExtract(self,node,time,instance) class (nodeComponentDisk ), pointer :: disk class (nodeComponentSpheroid ), pointer :: spheroid class (nodeComponentDarkMatterProfile ), pointer :: darkMatterProfile + class (massDistributionClass ), pointer :: massDistribution_ + type (coordinateSpherical ) :: coordinates integer :: i double precision :: radius , radiusVirial !$GLC attributes unused :: time, instance @@ -225,42 +222,49 @@ function densityProfileExtract(self,node,time,instance) radius=+radius*spheroid %halfMassRadius() case (radiusTypeGalacticMassFraction %ID, & & radiusTypeGalacticLightFraction %ID) - radius=+radius & - & *self%galacticStructure_%radiusEnclosingMass & - & ( & - & node , & - & massFractional=self%radii(i)%fraction , & - & massType = massTypeGalactic, & - & componentType = componentTypeAll, & - & weightBy =self%radii(i)%weightBy , & - & weightIndex =self%radii(i)%weightByIndex & - & ) - case (radiusTypeStellarMassFraction %ID) - radius=+radius & - & *self%galacticStructure_%radiusEnclosingMass & - & ( & - & node , & - & massFractional=self%radii(i)%fraction , & - & massType = massTypeStellar , & - & componentType = componentTypeAll, & - & weightBy =self%radii(i)%weightBy , & - & weightIndex =self%radii(i)%weightByIndex & - & ) + massDistribution_ => node %massDistribution ( & + & massType = massTypeStellar , & + & componentType = componentTypeAll, & + & weightBy =self%radii(i)%weightBy , & + & weightIndex =self%radii(i)%weightByIndex & + & ) + radius = +radius & + & *massDistribution_%radiusEnclosingMass( & + & massFractional=self%radii(i)%fraction & + & ) + !![ + + !!] + case (radiusTypeStellarMassFraction %ID) + massDistribution_ => node %massDistribution ( & + & massType = massTypeStellar , & + & componentType = componentTypeAll, & + & weightBy =self%radii(i)%weightBy , & + & weightIndex =self%radii(i)%weightByIndex & + & ) + radius = +radius & + & *massDistribution_%radiusEnclosingMass( & + & massFractional=self%radii(i)%fraction & + & ) + !![ + + !!] case default call Error_Report('unrecognized radius type'//{introspection:location}) end select - densityProfileExtract (i,1)=self%galacticStructure_%density( & - & node , & - & [ & - & radius , & - & Pi/2.0d0 , & - & 0.0d0 & - & ] , & - & componentType=self%radii(i)%component, & - & massType =self%radii(i)%mass & - & ) - if (self%includeRadii) & - & densityProfileExtract(i,2)= radius + coordinates=[radius,Pi/2.0d0,0.0d0] + massDistribution_ => node %massDistribution(& + & componentType=self%radii(i)%component , & + & massType =self%radii(i)%mass & + & ) + densityProfileExtract (i,1) = massDistribution_%density ( & + & coordinates= coordinates & + & ) + if (self%includeRadii) & + & densityProfileExtract(i,2) = radius + !![ + + !!] end do return end function densityProfileExtract diff --git a/source/nodes.property_extractor.density_contrasts.F90 b/source/nodes.property_extractor.density_contrasts.F90 index 5c4795b27f..635ddade4e 100644 --- a/source/nodes.property_extractor.density_contrasts.F90 +++ b/source/nodes.property_extractor.density_contrasts.F90 @@ -24,9 +24,8 @@ use :: Cosmology_Functions , only : cosmologyFunctions , cosmologyFunctionsClass , enumerationDensityCosmologicalType use :: Cosmology_Parameters , only : cosmologyParameters , cosmologyParametersClass use :: Dark_Matter_Halo_Scales , only : darkMatterHaloScale , darkMatterHaloScaleClass - use :: Galacticus_Nodes , only : nodeComponentBasic , treeNode - use :: Galactic_Structure , only : galacticStructureClass use :: Galactic_Structure_Options, only : enumerationMassTypeType + use :: Mass_Distributions , only : massDistributionClass use :: Root_Finder , only : rootFinder !![ @@ -51,7 +50,6 @@ class (cosmologyParametersClass ), pointer :: cosmologyParameters_ => null() class (cosmologyFunctionsClass ), pointer :: cosmologyFunctions_ => null() class (darkMatterHaloScaleClass ), pointer :: darkMatterHaloScale_ => null() - class (galacticStructureClass ), pointer :: galacticStructure_ => null() type (rootFinder ) :: finder integer :: elementCount_ , countDensityContrasts type (enumerationMassTypeType ) :: massTypeSelected @@ -78,11 +76,9 @@ end interface nodePropertyExtractorDensityContrasts ! Module-scope variables used in root finding. - type (treeNode ), pointer :: node_ - class (nodeComponentBasic ), pointer :: basic_ - class (nodePropertyExtractorDensityContrasts), pointer :: self_ - double precision :: densityTarget - !$omp threadprivate(node_,basic_,self_,densityTarget) + class (massDistributionClass), pointer :: massDistribution_ + double precision :: densityTarget + !$omp threadprivate(massDistribution_,densityTarget) contains @@ -98,7 +94,6 @@ function densityContrastsConstructorParameters(parameters) result(self) class (cosmologyParametersClass ), pointer :: cosmologyParameters_ class (cosmologyFunctionsClass ), pointer :: cosmologyFunctions_ class (darkMatterHaloScaleClass ), pointer :: darkMatterHaloScale_ - class (galacticStructureClass ), pointer :: galacticStructure_ double precision , allocatable , dimension(:) :: densityContrasts logical :: darkMatterOnly type (varying_string ) :: densityContrastRelativeTo @@ -125,20 +120,18 @@ function densityContrastsConstructorParameters(parameters) result(self) - !!] - self=nodePropertyExtractorDensityContrasts(densityContrasts,darkMatterOnly,enumerationDensityCosmologicalEncode(char(densityContrastRelativeTo),includesPrefix=.false.),cosmologyParameters_,cosmologyFunctions_,darkMatterHaloScale_,galacticStructure_) + self=nodePropertyExtractorDensityContrasts(densityContrasts,darkMatterOnly,enumerationDensityCosmologicalEncode(char(densityContrastRelativeTo),includesPrefix=.false.),cosmologyParameters_,cosmologyFunctions_,darkMatterHaloScale_) !![ - !!] return end function densityContrastsConstructorParameters - function densityContrastsConstructorInternal(densityContrasts,darkMatterOnly,densityContrastRelativeTo,cosmologyParameters_,cosmologyFunctions_,darkMatterHaloScale_,galacticStructure_) result(self) + function densityContrastsConstructorInternal(densityContrasts,darkMatterOnly,densityContrastRelativeTo,cosmologyParameters_,cosmologyFunctions_,darkMatterHaloScale_) result(self) !!{ Internal constructor for the {\normalfont \ttfamily densityContrasts} property extractor class. !!} @@ -149,13 +142,12 @@ function densityContrastsConstructorInternal(densityContrasts,darkMatterOnly,den class (cosmologyFunctionsClass ), intent(in ), target :: cosmologyFunctions_ class (cosmologyParametersClass ), intent(in ), target :: cosmologyParameters_ class (darkMatterHaloScaleClass ), intent(in ), target :: darkMatterHaloScale_ - class (galacticStructureClass ), intent(in ), target :: galacticStructure_ double precision , intent(in ), dimension(:) :: densityContrasts logical , intent(in ) :: darkMatterOnly type (enumerationDensityCosmologicalType ), intent(in ) :: densityContrastRelativeTo double precision , parameter :: toleranceAbsolute =0.0d0, toleranceRelative=1.0d-3 !![ - + !!] self%countDensityContrasts=size(densityContrasts) @@ -190,7 +182,6 @@ subroutine densityContrastsDestructor(self) - !!] return end subroutine densityContrastsDestructor @@ -229,12 +220,14 @@ function densityContrastsExtract(self,node,time,instance) use :: Cosmology_Functions , only : densityCosmologicalMean, densityCosmologicalCritical use :: Error , only : Error_Report use :: Galactic_Structure_Options, only : componentTypeAll + use :: Galacticus_Nodes , only : nodeComponentBasic implicit none double precision , dimension(:,:), allocatable :: densityContrastsExtract class (nodePropertyExtractorDensityContrasts ), intent(inout) , target :: self type (treeNode ), intent(inout) , target :: node double precision , intent(in ) :: time type (multiCounter ), intent(inout) , optional :: instance + class (nodeComponentBasic ) , pointer :: basic double precision , parameter :: radiusTiny =1.0d-12 integer :: i double precision :: enclosedMass , radius, & @@ -242,19 +235,16 @@ function densityContrastsExtract(self,node,time,instance) !$GLC attributes unused :: time, instance allocate(densityContrastsExtract(self%countDensityContrasts,self%elementCount_)) - ! Make the self, node, and basic component available to the root finding routine. - self_ => self - node_ => node - basic_ => node%basic() ! Find the reference density at this epoch. - densityReference=self%cosmologyFunctions_%matterDensityEpochal(basic_%time()) + basic => node %basic ( ) + densityReference = self%cosmologyFunctions_%matterDensityEpochal(basic%time()) select case (self%densityContrastRelativeTo%ID) case (densityCosmologicalMean %ID) ! No modification required. case (densityCosmologicalCritical%ID) ! Modify reference density to be the critical density. densityReference=+densityReference & - & /self%cosmologyFunctions_%OmegaMatterEpochal(basic_%time()) + & /self%cosmologyFunctions_%OmegaMatterEpochal(basic%time()) case default call Error_Report('unknown cosmological density'//{introspection:location}) end select @@ -262,7 +252,8 @@ function densityContrastsExtract(self,node,time,instance) if (self%darkMatterOnly) densityReference=+ densityReference & & *(self%cosmologyParameters_%OmegaMatter()-self%cosmologyParameters_%OmegaBaryon()) & & / self%cosmologyParameters_%OmegaMatter() - ! Iterate over density contrasts. + ! Iterate over density contrasts. + massDistribution_ => node%massDistribution(massType=self%massTypeSelected) do i=1,self%countDensityContrasts densityTarget=self%densityContrasts(i)*densityReference if (densityContrastsRoot(radiusTiny*self%darkMatterHaloScale_%radiusVirial(node)) < 0.0d0) then @@ -272,16 +263,14 @@ function densityContrastsExtract(self,node,time,instance) enclosedMass=0.0d0 else ! The target density is reached, so find the exact radius at which it occurs. - radius =self%finder%find (rootGuess=self%darkMatterHaloScale_%radiusVirial(node)) - enclosedMass=self%galacticStructure_%massEnclosed( & - & node , & - & radius , & - & componentType= componentTypeAll, & - & massType =self%massTypeSelected & - & ) + radius =self %finder%find (rootGuess=self%darkMatterHaloScale_%radiusVirial(node)) + enclosedMass=massDistribution_ %massEnclosedBySphere( radius ) end if densityContrastsExtract(i,:)=[radius,enclosedMass] end do + !![ + + !!] return end function densityContrastsExtract @@ -368,19 +357,12 @@ Root function used in finding the radius that encloses a given density contrast. use :: Numerical_Constants_Math , only : Pi implicit none double precision, intent(in ) :: radius - double precision :: enclosedMass - enclosedMass =self_%galacticStructure_%massEnclosed( & - & node_ , & - & radius , & - & componentType= componentTypeAll, & - & massType =self_%massTypeSelected & - & ) - densityContrastsRoot=+3.0d0 & - & *enclosedMass & - & /4.0d0 & - & /Pi & - & /radius**3 & + densityContrastsRoot=+3.0d0 & + & *massDistribution_%massEnclosedBySphere(radius) & + & /4.0d0 & + & /Pi & + & /radius**3 & & -densityTarget return end function densityContrastsRoot diff --git a/source/nodes.property_extractor.half_light_properties.F90 b/source/nodes.property_extractor.half_light_properties.F90 index da9e7f7066..3ff29069ac 100644 --- a/source/nodes.property_extractor.half_light_properties.F90 +++ b/source/nodes.property_extractor.half_light_properties.F90 @@ -21,8 +21,6 @@ Contains a module which implements a half-light radii property extractor class. !!} - use :: Galactic_Structure, only : galacticStructureClass - !![ @@ -38,9 +36,7 @@ A half-light radii property extractor class. !!} private - class(galacticStructureClass), pointer :: galacticStructure_ => null() contains - final :: radiiHalfLightPropertiesDestructor procedure :: elementCount => radiiHalfLightPropertiesElementCount procedure :: extract => radiiHalfLightPropertiesExtract procedure :: names => radiiHalfLightPropertiesNames @@ -53,7 +49,6 @@ Constructors for the ``radiiHalfLightProperties'' output analysis class. !!} module procedure radiiHalfLightPropertiesConstructorParameters - module procedure radiiHalfLightPropertiesConstructorInternal end interface nodePropertyExtractorRadiiHalfLightProperties contains @@ -66,47 +61,14 @@ function radiiHalfLightPropertiesConstructorParameters(parameters) result(self) implicit none type (nodePropertyExtractorRadiiHalfLightProperties) :: self type (inputParameters ), intent(inout) :: parameters - class(galacticStructureClass ), pointer :: galacticStructure_ - !![ - - !!] - self=nodePropertyExtractorRadiiHalfLightProperties(galacticStructure_) + self=nodePropertyExtractorRadiiHalfLightProperties() !![ - !!] return end function radiiHalfLightPropertiesConstructorParameters - function radiiHalfLightPropertiesConstructorInternal(galacticStructure_) result(self) - !!{ - Internal constructor for the {\normalfont \ttfamily radiiHalfLightProperties} property extractor class. - !!} - use :: Input_Parameters, only : inputParameters - implicit none - type (nodePropertyExtractorRadiiHalfLightProperties) :: self - class(galacticStructureClass ), intent(in ), target :: galacticStructure_ - !![ - - !!] - - return - end function radiiHalfLightPropertiesConstructorInternal - - subroutine radiiHalfLightPropertiesDestructor(self) - !!{ - Destructor for the {\normalfont \ttfamily radiiHalfLightProperties} property extractor class. - !!} - implicit none - type(nodePropertyExtractorRadiiHalfLightProperties), intent(inout) :: self - - !![ - - !!] - return - end subroutine radiiHalfLightPropertiesDestructor - integer function radiiHalfLightPropertiesElementCount(self,time) !!{ Return the number of elements in the {\normalfont \ttfamily radiiHalfLightProperties} property extractor class. @@ -126,6 +88,7 @@ function radiiHalfLightPropertiesExtract(self,node,time,instance) Implement a {\normalfont \ttfamily radiiHalfLightProperties} property extractor. !!} use :: Galactic_Structure_Options , only : componentTypeAll , massTypeAll, massTypeStellar, weightByLuminosity + use :: Mass_Distributions , only : massDistributionClass use :: Stellar_Luminosities_Structure, only : unitStellarLuminosities implicit none double precision , dimension(:) , allocatable :: radiiHalfLightPropertiesExtract @@ -133,20 +96,29 @@ function radiiHalfLightPropertiesExtract(self,node,time,instance) type (treeNode ), intent(inout), target :: node double precision , intent(in ) :: time type (multiCounter ), intent(inout), optional :: instance + class (massDistributionClass ) , pointer :: massDistribution_ , lightDistribution_ integer :: i , j double precision :: halfLightRadius , massEnclosed !$GLC attributes unused :: self, instance allocate(radiiHalfLightPropertiesExtract(2*unitStellarLuminosities%luminosityOutputCount(time))) j=-1 + massDistribution_ => node%massDistribution(componentType=componentTypeAll,massType=massTypeAll) do i=1,unitStellarLuminosities%luminosityCount() if (unitStellarLuminosities%isOutput(i,time)) then - halfLightRadius=self%galacticStructure_%radiusEnclosingMass(node,massFractional=0.5d0 ,massType=massTypeStellar,weightBy=weightByLuminosity,weightIndex=i) - massEnclosed =self%galacticStructure_%massEnclosed (node, halfLightRadius,componentType=componentTypeAll,massType=massTypeAll ) - j=j+1 - radiiHalfLightPropertiesExtract(2*j+1:2*j+2)=[halfLightRadius,massEnclosed] - end if - end do + lightDistribution_ => node %massDistribution (massType =massTypeStellar,weightBy=weightByLuminosity,weightIndex=i) + halfLightRadius = lightDistribution_%radiusEnclosingMass (massFractional=0.5d0 ) + massEnclosed = massDistribution_ %massEnclosedBySphere(radius =halfLightRadius ) + j = j+1 + radiiHalfLightPropertiesExtract(2*j+1:2*j+2) = [halfLightRadius,massEnclosed] + !![ + + !!] + end if + end do + !![ + + !!] return end function radiiHalfLightPropertiesExtract diff --git a/source/nodes.property_extractor.luminosity_stellar.F90 b/source/nodes.property_extractor.luminosity_stellar.F90 index d6d40a2e64..a08b4faec5 100644 --- a/source/nodes.property_extractor.luminosity_stellar.F90 +++ b/source/nodes.property_extractor.luminosity_stellar.F90 @@ -23,7 +23,6 @@ use :: ISO_Varying_String, only : varying_string use :: Output_Times , only : outputTimesClass - use :: Galactic_Structure, only : galacticStructureClass !![ @@ -35,13 +34,12 @@ A stellar luminosity output analysis property extractor class. !!} private - type (varying_string ) :: filterName , filterType, & - & postprocessChain , name_ , & - & description_ - double precision :: redshiftBand - integer , allocatable, dimension(:) :: luminosityIndex - class (outputTimesClass ), pointer :: outputTimes_ => null() - class (galacticStructureClass), pointer :: galacticStructure_ => null() + type (varying_string ) :: filterName , filterType, & + & postprocessChain , name_ , & + & description_ + double precision :: redshiftBand + integer , allocatable, dimension(:) :: luminosityIndex + class (outputTimesClass), pointer :: outputTimes_ => null() contains final :: luminosityStellarDestructor procedure :: extract => luminosityStellarExtract @@ -70,7 +68,6 @@ function luminosityStellarConstructorParameters(parameters) result(self) type (nodePropertyExtractorLuminosityStellar) :: self type (inputParameters ), intent(inout) :: parameters class (outputTimesClass ), pointer :: outputTimes_ - class (galacticStructureClass ), pointer :: galacticStructure_ type (varying_string ) :: filterName , filterType , & & postprocessChain double precision :: redshiftBand @@ -109,31 +106,29 @@ function luminosityStellarConstructorParameters(parameters) result(self) !!] end if !![ - - + !!] if (redshiftBandIsPresent) then if (postprocessChainIsPresent) then - self=nodePropertyExtractorLuminosityStellar(char(filterName),char(filterType),outputTimes_,galacticStructure_,redshiftBand=redshiftBand,postprocessChain=char(postprocessChain)) + self=nodePropertyExtractorLuminosityStellar(char(filterName),char(filterType),outputTimes_,redshiftBand=redshiftBand,postprocessChain=char(postprocessChain)) else - self=nodePropertyExtractorLuminosityStellar(char(filterName),char(filterType),outputTimes_,galacticStructure_,redshiftBand=redshiftBand ) + self=nodePropertyExtractorLuminosityStellar(char(filterName),char(filterType),outputTimes_,redshiftBand=redshiftBand ) end if else if (postprocessChainIsPresent) then - self=nodePropertyExtractorLuminosityStellar(char(filterName),char(filterType),outputTimes_,galacticStructure_, postprocessChain=char(postprocessChain)) + self=nodePropertyExtractorLuminosityStellar(char(filterName),char(filterType),outputTimes_, postprocessChain=char(postprocessChain)) else - self=nodePropertyExtractorLuminosityStellar(char(filterName),char(filterType),outputTimes_,galacticStructure_ ) + self=nodePropertyExtractorLuminosityStellar(char(filterName),char(filterType),outputTimes_ ) end if end if !![ - - + !!] return end function luminosityStellarConstructorParameters - function luminosityStellarConstructorInternal(filterName,filterType,outputTimes_,galacticStructure_,redshiftBand,postprocessChain,outputMask) result(self) + function luminosityStellarConstructorInternal(filterName,filterType,outputTimes_,redshiftBand,postprocessChain,outputMask) result(self) !!{ Internal constructor for the ``luminosityStellar'' output analysis property extractor class. !!} @@ -143,14 +138,13 @@ function luminosityStellarConstructorInternal(filterName,filterType,outputTimes_ type (nodePropertyExtractorLuminosityStellar) :: self character (len=* ), intent(in ) :: filterName , filterType class (outputTimesClass ), intent(in ), target :: outputTimes_ - class (galacticStructureClass ), intent(in ), target :: galacticStructure_ character (len=* ), intent(in ), optional :: postprocessChain double precision , intent(in ), optional :: redshiftBand logical , intent(in ), dimension(:), optional :: outputMask integer (c_size_t ) :: i character (len=7 ) :: label !![ - + !!] allocate(self%luminosityIndex(self%outputTimes_%count())) @@ -183,8 +177,7 @@ subroutine luminosityStellarDestructor(self) type(nodePropertyExtractorLuminosityStellar), intent(inout) :: self !![ - - + !!] return end subroutine luminosityStellarDestructor @@ -193,20 +186,26 @@ double precision function luminosityStellarExtract(self,node,instance) !!{ Implement a stellar luminosity output analysis property extractor. !!} - use :: Galactic_Structure_Options, only : massTypeStellar , radiusLarge, weightByLuminosity - use :: Galacticus_Nodes , only : nodeComponentBasic, treeNode + use :: Galactic_Structure_Options, only : massTypeStellar , weightByLuminosity + use :: Galacticus_Nodes , only : nodeComponentBasic , treeNode + use :: Mass_Distributions , only : massDistributionClass use, intrinsic :: ISO_C_Binding , only : c_size_t implicit none class (nodePropertyExtractorLuminosityStellar), intent(inout), target :: self type (treeNode ), intent(inout), target :: node type (multiCounter ), intent(inout), optional :: instance + class (massDistributionClass ) , pointer :: massDistribution_ class (nodeComponentBasic ), pointer :: basic integer(c_size_t ) :: i !$GLC attributes unused :: instance - basic => node %basic ( ) - i = self%outputTimes_ %index (basic%time(),findClosest=.true. ) - luminosityStellarExtract = self%galacticStructure_%massEnclosed(node , radiusLarge,massType=massTypeStellar,weightBy=weightByLuminosity,weightIndex=self%luminosityIndex(i)) + basic => node %basic ( ) + i = self%outputTimes_%index (basic%time(),findClosest=.true. ) + massDistribution_ => node %massDistribution(massType=massTypeStellar,weightBy=weightByLuminosity,weightIndex=self%luminosityIndex(i)) + luminosityStellarExtract = massDistribution_%massTotal ( ) + !![ + + !!] return end function luminosityStellarExtract diff --git a/source/nodes.property_extractor.mass_ISM.F90 b/source/nodes.property_extractor.mass_ISM.F90 index f33bec5274..d7ebd7347b 100644 --- a/source/nodes.property_extractor.mass_ISM.F90 +++ b/source/nodes.property_extractor.mass_ISM.F90 @@ -21,8 +21,6 @@ Contains a module which implements an ISM mass output analysis property extractor class. !!} - use :: Galactic_Structure, only : galacticStructureClass - !![ An ISM mass output analysis property extractor class. @@ -33,9 +31,7 @@ A stellar mass output analysis class. !!} private - class(galacticStructureClass), pointer :: galacticStructure_ => null() contains - final :: massISMDestructor procedure :: extract => massISMExtract procedure :: quantity => massISMQuantity procedure :: name => massISMName @@ -48,7 +44,6 @@ Constructors for the ``massISM'' output analysis class. !!} module procedure massISMConstructorParameters - module procedure massISMConstructorInternal end interface nodePropertyExtractorMassISM contains @@ -61,60 +56,36 @@ function massISMConstructorParameters(parameters) result(self) implicit none type (nodePropertyExtractorMassISM) :: self type (inputParameters ), intent(inout) :: parameters - class(galacticStructureClass ), pointer :: galacticStructure_ - !![ - - !!] - self=nodePropertyExtractorMassISM(galacticStructure_) + + self=nodePropertyExtractorMassISM() !![ - !!] return end function massISMConstructorParameters - function massISMConstructorInternal(galacticStructure_) result(self) - !!{ - Internal constructor for the ``massISM'' output analysis property extractor class. - !!} - use :: Input_Parameters, only : inputParameters - implicit none - type (nodePropertyExtractorMassISM) :: self - class(galacticStructureClass ), intent(in ), target :: galacticStructure_ - !![ - - !!] - - return - end function massISMConstructorInternal - - subroutine massISMDestructor(self) - !!{ - Destructor for the ``massISM'' output analysis property extractor class. - !!} - implicit none - type(nodePropertyExtractorMassISM), intent(inout) :: self - - !![ - - !!] - return - end subroutine massISMDestructor - double precision function massISMExtract(self,node,instance) !!{ Implement a massISM output analysis. !!} - use :: Galactic_Structure_Options, only : componentTypeDisk, componentTypeSpheroid, massTypeGaseous, radiusLarge + use :: Galactic_Structure_Options, only : componentTypeDisk , componentTypeSpheroid, massTypeGaseous + use :: Mass_Distributions , only : massDistributionClass implicit none class(nodePropertyExtractorMassISM), intent(inout), target :: self type (treeNode ), intent(inout), target :: node type (multiCounter ), intent(inout), optional :: instance + class(massDistributionClass ) , pointer :: massDistributionDisk, massDistributionSpheroid !$GLC attributes unused :: self, instance - massISMExtract=+self%galacticStructure_%massEnclosed(node,radiusLarge,massType=massTypeGaseous,componentType=componentTypeDisk ) & - & +self%galacticStructure_%massEnclosed(node,radiusLarge,massType=massTypeGaseous,componentType=componentTypeSpheroid) + massDistributionDisk => node %massDistribution(massType=massTypeGaseous,componentType=componentTypeDisk ) + massDistributionSpheroid => node %massDistribution(massType=massTypeGaseous,componentType=componentTypeSpheroid) + massISMExtract = +massDistributionDisk %massTotal ( ) & + & +massDistributionSpheroid%massTotal ( ) + !![ + + + !!] return end function massISMExtract diff --git a/source/nodes.property_extractor.mass_halo.F90 b/source/nodes.property_extractor.mass_halo.F90 index d6a9427ec1..9a1d79194a 100644 --- a/source/nodes.property_extractor.mass_halo.F90 +++ b/source/nodes.property_extractor.mass_halo.F90 @@ -21,10 +21,9 @@ Contains a module which implements a halo mass output analysis property extractor class. !!} - use :: Cosmology_Parameters , only : cosmologyParametersClass - use :: Cosmology_Functions , only : cosmologyFunctionsClass - use :: Dark_Matter_Profiles_DMO, only : darkMatterProfileDMOClass - use :: Virial_Density_Contrast , only : virialDensityContrastClass + use :: Cosmology_Parameters , only : cosmologyParametersClass + use :: Cosmology_Functions , only : cosmologyFunctionsClass + use :: Virial_Density_Contrast, only : virialDensityContrastClass !![ @@ -39,7 +38,6 @@ time at which is was last isolated (as is used for standard definition of halo mass). !!} private - class (darkMatterProfileDMOClass ), pointer :: darkMatterProfileDMO_ => null() class (virialDensityContrastClass), pointer :: virialDensityContrast_ => null(), virialDensityContrastDefinition_ => null() class (cosmologyParametersClass ), pointer :: cosmologyParameters_ => null() class (cosmologyFunctionsClass ), pointer :: cosmologyFunctions_ => null() @@ -72,7 +70,6 @@ function massHaloConstructorParameters(parameters) result(self) type (inputParameters ), intent(inout) :: parameters class (cosmologyFunctionsClass ), pointer :: cosmologyFunctions_ class (cosmologyParametersClass ), pointer :: cosmologyParameters_ - class (darkMatterProfileDMOClass ), pointer :: darkMatterProfileDMO_ class (virialDensityContrastClass ), pointer :: virialDensityContrast_, virialDensityContrastDefinition_ logical :: useLastIsolatedTime @@ -85,23 +82,21 @@ function massHaloConstructorParameters(parameters) result(self) - !!] - self=nodePropertyExtractorMassHalo(useLastIsolatedTime,cosmologyFunctions_,cosmologyParameters_,darkMatterProfileDMO_,virialDensityContrast_,virialDensityContrastDefinition_) + self=nodePropertyExtractorMassHalo(useLastIsolatedTime,cosmologyFunctions_,cosmologyParameters_,virialDensityContrast_,virialDensityContrastDefinition_) !![ - !!] return end function massHaloConstructorParameters - function massHaloConstructorInternal(useLastIsolatedTime,cosmologyFunctions_,cosmologyParameters_,darkMatterProfileDMO_,virialDensityContrast_,virialDensityContrastDefinition_) result(self) + function massHaloConstructorInternal(useLastIsolatedTime,cosmologyFunctions_,cosmologyParameters_,virialDensityContrast_,virialDensityContrastDefinition_) result(self) !!{ Internal constructor for the ``massHalo'' output analysis property extractor class. !!} @@ -110,10 +105,9 @@ function massHaloConstructorInternal(useLastIsolatedTime,cosmologyFunctions_,cos class (cosmologyParametersClass ), intent(in ), target :: cosmologyParameters_ class (cosmologyFunctionsClass ), intent(in ), target :: cosmologyFunctions_ class (virialDensityContrastClass ), intent(in ), target :: virialDensityContrast_, virialDensityContrastDefinition_ - class (darkMatterProfileDMOClass ), intent(in ), target :: darkMatterProfileDMO_ logical , intent(in ) :: useLastIsolatedTime !![ - + !!] return @@ -130,7 +124,6 @@ subroutine massHaloDestructor(self) - !!] return @@ -161,7 +154,6 @@ double precision function massHaloExtract(self,node,instance) & self%virialDensityContrastDefinition_%densityContrast(basic%mass(),time), & & cosmologyParameters_ =self%cosmologyParameters_ , & & cosmologyFunctions_ =self%cosmologyFunctions_ , & - & darkMatterProfileDMO_ =self%darkMatterProfileDMO_ , & & virialDensityContrast_=self%virialDensityContrast_ , & & useLastIsolatedTime =self%useLastIsolatedTime & & ) diff --git a/source/nodes.property_extractor.mass_profile.F90 b/source/nodes.property_extractor.mass_profile.F90 index 696e936095..b5e52c0cf1 100644 --- a/source/nodes.property_extractor.mass_profile.F90 +++ b/source/nodes.property_extractor.mass_profile.F90 @@ -22,7 +22,6 @@ !!} use :: Dark_Matter_Halo_Scales , only : darkMatterHaloScaleClass use :: Galactic_Structure_Radii_Definitions, only : radiusSpecifier - use :: Galactic_Structure , only : galacticStructureClass use :: Cosmology_Parameters , only : cosmologyParametersClass !![ @@ -36,7 +35,6 @@ !!} private class (darkMatterHaloScaleClass), pointer :: darkMatterHaloScale_ => null() - class (galacticStructureClass ), pointer :: galacticStructure_ => null() class (cosmologyParametersClass), pointer :: cosmologyParameters_ => null() integer :: radiiCount , elementCount_ logical :: includeRadii @@ -77,7 +75,6 @@ function massProfileConstructorParameters(parameters) result(self) type (inputParameters ), intent(inout) :: parameters type (varying_string ), allocatable , dimension(:) :: radiusSpecifiers class (darkMatterHaloScaleClass ), pointer :: darkMatterHaloScale_ - class (galacticStructureClass ), pointer :: galacticStructure_ class (cosmologyParametersClass ), pointer :: cosmologyParameters_ logical :: includeRadii @@ -95,19 +92,17 @@ function massProfileConstructorParameters(parameters) result(self) parameters - !!] - self=nodePropertyExtractorMassProfile(radiusSpecifiers,includeRadii,darkMatterHaloScale_,galacticStructure_,cosmologyParameters_) + self=nodePropertyExtractorMassProfile(radiusSpecifiers,includeRadii,darkMatterHaloScale_,cosmologyParameters_) !![ - !!] return end function massProfileConstructorParameters - function massProfileConstructorInternal(radiusSpecifiers,includeRadii,darkMatterHaloScale_,galacticStructure_,cosmologyParameters_) result(self) + function massProfileConstructorInternal(radiusSpecifiers,includeRadii,darkMatterHaloScale_,cosmologyParameters_) result(self) !!{ Internal constructor for the {\normalfont \ttfamily massProfile} property extractor class. !!} @@ -116,11 +111,10 @@ function massProfileConstructorInternal(radiusSpecifiers,includeRadii,darkMatter type (nodePropertyExtractorMassProfile) :: self type (varying_string ), intent(in ), dimension(:) :: radiusSpecifiers class (darkMatterHaloScaleClass ), intent(in ), target :: darkMatterHaloScale_ - class (galacticStructureClass ), intent(in ), target :: galacticStructure_ class (cosmologyParametersClass ), intent(in ), target :: cosmologyParameters_ logical , intent(in ) :: includeRadii !![ - + !!] if (includeRadii) then @@ -155,7 +149,6 @@ subroutine massProfileDestructor(self) !![ - !!] return @@ -198,6 +191,7 @@ function massProfileExtract(self,node,time,instance) & radiusTypeStellarMassFraction , radiusTypeVirialRadius , radiusTypeSatelliteBoundMassFraction use :: Galacticus_Nodes , only : nodeComponentDarkMatterProfile , nodeComponentDisk , nodeComponentSpheroid , nodeComponentSatellite , & & treeNode + use :: Mass_Distributions , only : massDistributionClass use :: Error , only : Error_Report implicit none double precision , dimension(:,:), allocatable :: massProfileExtract @@ -209,6 +203,7 @@ function massProfileExtract(self,node,time,instance) class (nodeComponentSpheroid ), pointer :: spheroid class (nodeComponentDarkMatterProfile ), pointer :: darkMatterProfile class (nodeComponentSatellite ), pointer :: satellite + class (massDistributionClass ), pointer :: massDistribution_ integer :: i double precision :: radius , radiusVirial, & & mass @@ -239,52 +234,66 @@ function massProfileExtract(self,node,time,instance) case (radiusTypeSpheroidHalfMassRadius%ID) radius=+radius*spheroid %halfMassRadius() case (radiusTypeSatelliteBoundMassFraction%ID) - mass =+satellite%boundMass () & - & *self %fractionDarkMatter - radius=+radius & - & *self%galacticStructure_%radiusEnclosingMass & - & ( & - & node , & - & mass = mass , & - & massType = massTypeDark , & - & componentType= componentTypeAll , & - & weightBy =self%radii(i)%weightBy , & - & weightIndex =self%radii(i)%weightByIndex & - & ) + mass = +satellite %boundMass ( & + & ) & + & *self %fractionDarkMatter + massDistribution_ => node %massDistribution ( & + & massType = massTypeDark , & + & componentType = componentTypeAll, & + & weightBy =self%radii(i)%weightBy , & + & weightIndex =self%radii(i)%weightByIndex & + & ) + radius = +radius & + & *massDistribution_%radiusEnclosingMass( & + & mass = mass & + & ) + !![ + + !!] case (radiusTypeGalacticMassFraction %ID, & & radiusTypeGalacticLightFraction %ID) - radius=+radius & - & *self%galacticStructure_%radiusEnclosingMass & - & ( & - & node , & - & massFractional=self%radii(i)%fraction , & - & massType = massTypeGalactic, & - & componentType = componentTypeAll, & - & weightBy =self%radii(i)%weightBy , & - & weightIndex =self%radii(i)%weightByIndex & - & ) - case (radiusTypeStellarMassFraction %ID) - radius=+radius & - & *self%galacticStructure_%radiusEnclosingMass & - & ( & - & node , & - & massFractional=self%radii(i)%fraction , & - & massType = massTypeStellar , & - & componentType = componentTypeAll, & - & weightBy =self%radii(i)%weightBy , & - & weightIndex =self%radii(i)%weightByIndex & - & ) + massDistribution_ => node %massDistribution ( & + & massType = massTypeStellar , & + & componentType = componentTypeAll, & + & weightBy =self%radii(i)%weightBy , & + & weightIndex =self%radii(i)%weightByIndex & + & ) + radius = +radius & + & *massDistribution_%radiusEnclosingMass( & + & massFractional=self%radii(i)%fraction & + & ) + !![ + + !!] + case (radiusTypeStellarMassFraction %ID) + massDistribution_ => node %massDistribution ( & + & massType = massTypeStellar , & + & componentType = componentTypeAll, & + & weightBy =self%radii(i)%weightBy , & + & weightIndex =self%radii(i)%weightByIndex & + & ) + radius = +radius & + & *massDistribution_%radiusEnclosingMass( & + & massFractional=self%radii(i)%fraction & + & ) + !![ + + !!] case default call Error_Report('unrecognized radius type'//{introspection:location}) end select - massProfileExtract (i,1)=self%galacticStructure_%massEnclosed( & - & node , & - & radius , & - & componentType=self%radii(i)%component, & - & massType =self%radii(i)%mass & - & ) - if (self%includeRadii) & - & massProfileExtract(i,2)= radius + massDistribution_ => node %massDistribution (& + & componentType=self%radii(i)%component, & + & massType =self%radii(i)%mass & + & ) + massProfileExtract (i,1) = massDistribution_%massEnclosedBySphere( & + & radius = radius & + & ) + if (self%includeRadii) & + & massProfileExtract(i,2) = radius + !![ + + !!] end do return end function massProfileExtract diff --git a/source/nodes.property_extractor.mass_stellar.F90 b/source/nodes.property_extractor.mass_stellar.F90 index 0adf11c463..d62d83b721 100644 --- a/source/nodes.property_extractor.mass_stellar.F90 +++ b/source/nodes.property_extractor.mass_stellar.F90 @@ -21,8 +21,6 @@ Contains a module which implements a stellar mass property extractor class. !!} - use :: Galactic_Structure, only : galacticStructureClass - !![ A stellar mass output analysis property extractor class. @@ -33,9 +31,7 @@ A stellar mass property extractor class. !!} private - class(galacticStructureClass), pointer :: galacticStructure_ => null() contains - final :: massStellarDestructor procedure :: extract => massStellarExtract procedure :: name => massStellarName procedure :: description => massStellarDescription @@ -48,7 +44,6 @@ Constructors for the ``massStellar'' property extractor class. !!} module procedure massStellarConstructorParameters - module procedure massStellarConstructorInternal end interface nodePropertyExtractorMassStellar contains @@ -61,58 +56,32 @@ function massStellarConstructorParameters(parameters) result(self) implicit none type (nodePropertyExtractorMassStellar) :: self type (inputParameters ), intent(inout) :: parameters - class(galacticStructureClass ), pointer :: galacticStructure_ - !![ - - !!] - self=nodePropertyExtractorMassStellar(galacticStructure_) + self=nodePropertyExtractorMassStellar() !![ - !!] return end function massStellarConstructorParameters - function massStellarConstructorInternal(galacticStructure_) result(self) - !!{ - Internal constructor for the ``massStellar'' output analysis property extractor class. - !!} - implicit none - type (nodePropertyExtractorMassStellar) :: self - class(galacticStructureClass ), intent(in ), target :: galacticStructure_ - !![ - - !!] - - return - end function massStellarConstructorInternal - - subroutine massStellarDestructor(self) - !!{ - Destructor for the ``massStellar'' output analysis property extractor class. - !!} - implicit none - type(nodePropertyExtractorMassStellar), intent(inout) :: self - - !![ - - !!] - return - end subroutine massStellarDestructor - double precision function massStellarExtract(self,node,instance) !!{ Implement a massStellar output analysis. !!} - use :: Galactic_Structure_Options, only : massTypeStellar, radiusLarge + use :: Galactic_Structure_Options, only : massTypeStellar + use :: Mass_Distributions , only : massDistributionClass implicit none class(nodePropertyExtractorMassStellar), intent(inout), target :: self type (treeNode ), intent(inout), target :: node type (multiCounter ), intent(inout), optional :: instance + class(massDistributionClass ) , pointer :: massDistribution_ !$GLC attributes unused :: self, instance - massStellarExtract=self%galacticStructure_%massEnclosed(node,radiusLarge,massType=massTypeStellar) + massDistribution_ => node %massDistribution(massType=massTypeStellar) + massStellarExtract = massDistribution_%massTotal ( ) + !![ + + !!] return end function massStellarExtract diff --git a/source/nodes.property_extractor.mass_stellar_morphology.F90 b/source/nodes.property_extractor.mass_stellar_morphology.F90 index 32ac7d3cc2..27fc116115 100644 --- a/source/nodes.property_extractor.mass_stellar_morphology.F90 +++ b/source/nodes.property_extractor.mass_stellar_morphology.F90 @@ -21,8 +21,6 @@ Contains a module which implements a stellar mass-weighted morphology output analysis property extractor class. !!} - use :: Galactic_Structure, only : galacticStructureClass - !![ A stellar mass-weighted morphology output analysis property extractor class. @@ -33,9 +31,7 @@ A stellar mass output analysis class. !!} private - class(galacticStructureClass), pointer :: galacticStructure_ => null() contains - final :: massStellarMorphologyDestructor procedure :: extract => massStellarMorphologyExtract procedure :: name => massStellarMorphologyName procedure :: description => massStellarMorphologyDescription @@ -47,7 +43,6 @@ Constructors for the ``massStellarMorphology'' output analysis class. !!} module procedure massStellarMorphologyConstructorParameters - module procedure massStellarMorphologyConstructorInternal end interface nodePropertyExtractorMassStellarMorphology contains @@ -60,60 +55,33 @@ function massStellarMorphologyConstructorParameters(parameters) result(self) implicit none type (nodePropertyExtractorMassStellarMorphology) :: self type (inputParameters ), intent(inout) :: parameters - class(galacticStructureClass ), pointer :: galacticStructure_ - !![ - - !!] - self=nodePropertyExtractorMassStellarMorphology(galacticStructure_) + + self=nodePropertyExtractorMassStellarMorphology() !![ - !!] return end function massStellarMorphologyConstructorParameters - function massStellarMorphologyConstructorInternal(galacticStructure_) result(self) - !!{ - Internal constructor for the ``massStellarMorphology'' output analysis property extractor class. - !!} - implicit none - type (nodePropertyExtractorMassStellarMorphology) :: self - class(galacticStructureClass ), intent(in ), target :: galacticStructure_ - !![ - - !!] - - return - end function massStellarMorphologyConstructorInternal - - subroutine massStellarMorphologyDestructor(self) - !!{ - Destructor for the ``massStellarMorphology'' output analysis property extractor class. - !!} - implicit none - type(nodePropertyExtractorMassStellarMorphology), intent(inout) :: self - - !![ - - !!] - return - end subroutine massStellarMorphologyDestructor - double precision function massStellarMorphologyExtract(self,node,instance) !!{ Implement a stellar mass-weighted morphology output analysis. !!} - use :: Galactic_Structure_Options, only : componentTypeDisk, componentTypeSpheroid, massTypeStellar, radiusLarge + use :: Mass_Distributions , only : massDistributionClass + use :: Galactic_Structure_Options, only : componentTypeDisk , componentTypeSpheroid, massTypeStellar implicit none class (nodePropertyExtractorMassStellarMorphology), intent(inout), target :: self type (treeNode ), intent(inout), target :: node type (multiCounter ), intent(inout), optional :: instance - double precision :: massStellarDisk, massStellarSpheroid + class (massDistributionClass ) , pointer :: massDistributionDisk, massDistributionSpheroid + double precision :: massStellarDisk , massStellarSpheroid !$GLC attributes unused :: self, instance - massStellarDisk =self%galacticStructure_%massEnclosed(node,radiusLarge,massType=massTypeStellar,componentType=componentTypeDisk ) - massStellarSpheroid=self%galacticStructure_%massEnclosed(node,radiusLarge,massType=massTypeStellar,componentType=componentTypeSpheroid) + massDistributionDisk => node %massDistribution(massType=massTypeStellar,componentType=componentTypeDisk ) + massDistributionSpheroid => node %massDistribution(massType=massTypeStellar,componentType=componentTypeSpheroid) + massStellarDisk = massDistributionDisk%massTotal ( ) + massStellarSpheroid = massDistributionDisk%massTotal ( ) if (massStellarDisk+massStellarSpheroid > 0.0d0) then massStellarMorphologyExtract=+ massStellarSpheroid & & /( & @@ -123,6 +91,10 @@ double precision function massStellarMorphologyExtract(self,node,instance) else massStellarMorphologyExtract=+0.0d0 end if + !![ + + + !!] return end function massStellarMorphologyExtract diff --git a/source/nodes.property_extractor.mass_stellar_spheroid.F90 b/source/nodes.property_extractor.mass_stellar_spheroid.F90 index 4a24bf544d..7758f33625 100644 --- a/source/nodes.property_extractor.mass_stellar_spheroid.F90 +++ b/source/nodes.property_extractor.mass_stellar_spheroid.F90 @@ -21,8 +21,6 @@ Contains a module which implements a spheroid stellar mass output analysis property extractor class. !!} - use :: Galactic_Structure, only : galacticStructureClass - !![ A spheroid stellar mass output analysis property extractor class. @@ -33,9 +31,7 @@ A stellar mass output analysis class. !!} private - class(galacticStructureClass), pointer :: galacticStructure_ => null() contains - final :: massStellarSpheroidDestructor procedure :: extract => massStellarSpheroidExtract procedure :: quantity => massStellarSpheroidQuantity procedure :: name => massStellarSpheroidName @@ -48,7 +44,6 @@ Constructors for the ``massStellarSpheroid'' output analysis class. !!} module procedure massStellarSpheroidConstructorParameters - module procedure massStellarSpheroidConstructorInternal end interface nodePropertyExtractorMassStellarSpheroid contains @@ -59,60 +54,34 @@ function massStellarSpheroidConstructorParameters(parameters) result(self) !!} use :: Input_Parameters, only : inputParameters implicit none - type (nodePropertyExtractorMassStellarSpheroid) :: self - type (inputParameters ), intent(inout) :: parameters - class(galacticStructureClass ), pointer :: galacticStructure_ + type(nodePropertyExtractorMassStellarSpheroid) :: self + type(inputParameters ), intent(inout) :: parameters - !![ - - !!] - self=nodePropertyExtractorMassStellarSpheroid(galacticStructure_) + self=nodePropertyExtractorMassStellarSpheroid() !![ - !!] return end function massStellarSpheroidConstructorParameters - function massStellarSpheroidConstructorInternal(galacticStructure_) result(self) - !!{ - Internal constructor for the ``massStellarSpheroid'' output analysis property extractor class. - !!} - implicit none - type (nodePropertyExtractorMassStellarSpheroid) :: self - class(galacticStructureClass ), intent(in ), target :: galacticStructure_ - !![ - - !!] - - return - end function massStellarSpheroidConstructorInternal - - subroutine massStellarSpheroidDestructor(self) - !!{ - Destructor for the ``massStellarSpheroid'' output analysis property extractor class. - !!} - implicit none - type(nodePropertyExtractorMassStellarSpheroid), intent(inout) :: self - - !![ - - !!] - return - end subroutine massStellarSpheroidDestructor - double precision function massStellarSpheroidExtract(self,node,instance) !!{ Implement a stellar mass-weighted morphology output analysis. !!} - use :: Galactic_Structure_Options, only : componentTypeSpheroid, massTypeStellar, radiusLarge + use :: Mass_Distributions , only : massDistributionClass + use :: Galactic_Structure_Options, only : componentTypeSpheroid, massTypeStellar implicit none - class (nodePropertyExtractorMassStellarSpheroid), intent(inout), target :: self - type (treeNode ), intent(inout), target :: node - type (multiCounter ), intent(inout), optional :: instance + class(nodePropertyExtractorMassStellarSpheroid), intent(inout), target :: self + type (treeNode ), intent(inout), target :: node + type (multiCounter ), intent(inout), optional :: instance + class(massDistributionClass ) , pointer :: massDistribution_ !$GLC attributes unused :: self, instance - massStellarSpheroidExtract=self%galacticStructure_%massEnclosed(node,radiusLarge,massType=massTypeStellar,componentType=componentTypeSpheroid) + massDistribution_ => node %massDistribution(massType=massTypeStellar,componentType=componentTypeSpheroid) + massStellarSpheroidExtract = massDistribution_%massTotal ( ) + !![ + + !!] return end function massStellarSpheroidExtract diff --git a/source/nodes.property_extractor.orbital_adiabatic_ratio.disk.F90 b/source/nodes.property_extractor.orbital_adiabatic_ratio.disk.F90 index b022c18fa1..87b63dd042 100644 --- a/source/nodes.property_extractor.orbital_adiabatic_ratio.disk.F90 +++ b/source/nodes.property_extractor.orbital_adiabatic_ratio.disk.F90 @@ -21,7 +21,7 @@ Contains a module which implements a property extractor class for the orbital adiabatic ratio of disks. !!} - use :: Galactic_Structure, only : galacticStructureClass + use :: Dark_Matter_Halo_Scales, only : darkMatterHaloScaleClass !![ @@ -41,7 +41,7 @@ A property extractor class for the orbital adiabatic ratio of disks. !!} private - class(galacticStructureClass), pointer :: galacticStructure_ => null() + class(darkMatterHaloScaleClass), pointer :: darkMatterHaloScale_ => null() contains final :: adiabaticRatioOrbitalDiskDestructor procedure :: extract => adiabaticRatioOrbitalDiskExtract @@ -68,28 +68,28 @@ function adiabaticRatioOrbitalDiskConstructorParameters(parameters) result(self) implicit none type (nodePropertyExtractorAdiabaticRatioOrbitalDisk) :: self type (inputParameters ), intent(inout) :: parameters - class(galacticStructureClass ), pointer :: galacticStructure_ + class(darkMatterHaloScaleClass ), pointer :: darkMatterHaloScale_ !![ - + !!] - self=nodePropertyExtractorAdiabaticRatioOrbitalDisk(galacticStructure_) + self=nodePropertyExtractorAdiabaticRatioOrbitalDisk(darkMatterHaloScale_) !![ - + !!] return end function adiabaticRatioOrbitalDiskConstructorParameters - function adiabaticRatioOrbitalDiskConstructorInternal(galacticStructure_) result(self) + function adiabaticRatioOrbitalDiskConstructorInternal(darkMatterHaloScale_) result(self) !!{ Internal constructor for the {\normalfont \ttfamily adiabaticRatioOrbital} node operator class. !!} implicit none type (nodePropertyExtractorAdiabaticRatioOrbitalDisk) :: self - class(galacticStructureClass ), intent(in ), target :: galacticStructure_ + class(darkMatterHaloScaleClass ), intent(in ), target :: darkMatterHaloScale_ !![ - + !!] return @@ -103,7 +103,7 @@ subroutine adiabaticRatioOrbitalDiskDestructor(self) type(nodePropertyExtractorAdiabaticRatioOrbitalDisk), intent(inout) :: self !![ - + !!] return end subroutine adiabaticRatioOrbitalDiskDestructor @@ -133,7 +133,7 @@ double precision function adiabaticRatioOrbitalDiskExtract(self,node,instance) disk => node %disk () satellite => node %satellite () orbit = satellite%virialOrbit() - call Satellite_Orbit_Extremum_Phase_Space_Coordinates(nodeHost,orbit,extremumPericenter,radiusPericenter,velocityPericenter,self%galacticStructure_) + call Satellite_Orbit_Extremum_Phase_Space_Coordinates(nodeHost,orbit,extremumPericenter,radiusPericenter,velocityPericenter,self%darkMatterHaloScale_) if (disk%radius() > 0.0d0) & & adiabaticRatioOrbitalDiskExtract=+( & & + radiusPericenter & diff --git a/source/nodes.property_extractor.projected_density.F90 b/source/nodes.property_extractor.projected_density.F90 index 206c34bc76..30b32b0898 100644 --- a/source/nodes.property_extractor.projected_density.F90 +++ b/source/nodes.property_extractor.projected_density.F90 @@ -22,7 +22,6 @@ !!} use :: Dark_Matter_Halo_Scales , only : darkMatterHaloScale , darkMatterHaloScaleClass use :: Galactic_Structure_Radii_Definitions, only : radiusSpecifier - use :: Galactic_Structure , only : galacticStructureClass !![ @@ -41,7 +40,6 @@ !!} private class (darkMatterHaloScaleClass), pointer :: darkMatterHaloScale_ => null() - class (galacticStructureClass ), pointer :: galacticStructure_ => null() integer :: radiiCount , elementCount_ logical :: includeRadii type (varying_string ), allocatable, dimension(:) :: radiusSpecifiers @@ -84,7 +82,6 @@ function projectedDensityConstructorParameters(parameters) result(self) type (inputParameters ), intent(inout) :: parameters type (varying_string ), allocatable , dimension(:) :: radiusSpecifiers class (darkMatterHaloScaleClass ), pointer :: darkMatterHaloScale_ - class (galacticStructureClass ), pointer :: galacticStructure_ logical :: includeRadii allocate(radiusSpecifiers(parameters%count('radiusSpecifiers'))) @@ -101,18 +98,16 @@ function projectedDensityConstructorParameters(parameters) result(self) parameters - !!] - self=nodePropertyExtractorProjectedDensity(radiusSpecifiers,includeRadii,darkMatterHaloScale_,galacticStructure_) + self=nodePropertyExtractorProjectedDensity(radiusSpecifiers,includeRadii,darkMatterHaloScale_) !![ - !!] return end function projectedDensityConstructorParameters - function projectedDensityConstructorInternal(radiusSpecifiers,includeRadii,darkMatterHaloScale_,galacticStructure_) result(self) + function projectedDensityConstructorInternal(radiusSpecifiers,includeRadii,darkMatterHaloScale_) result(self) !!{ Internal constructor for the {\normalfont \ttfamily projectedDensity} property extractor class. !!} @@ -121,10 +116,9 @@ function projectedDensityConstructorInternal(radiusSpecifiers,includeRadii,darkM type (nodePropertyExtractorProjectedDensity) :: self type (varying_string ), intent(in ), dimension(:) :: radiusSpecifiers class (darkMatterHaloScaleClass ), intent(in ), target :: darkMatterHaloScale_ - class (galacticStructureClass ), intent(in ), target :: galacticStructure_ logical , intent(in ) :: includeRadii !![ - + !!] if (includeRadii) then @@ -154,7 +148,6 @@ subroutine projectedDensityDestructor(self) !![ - !!] return end subroutine projectedDensityDestructor @@ -197,6 +190,8 @@ function projectedDensityExtract(self,node,time,instance) result(densityProjecte use :: Galacticus_Nodes , only : nodeComponentDarkMatterProfile , nodeComponentDisk , nodeComponentSpheroid , treeNode use :: Numerical_Integration , only : integrator, GSL_Integ_Gauss15 use :: Numerical_Comparison , only : Values_Agree + use :: Mass_Distributions , only : massDistributionClass + use :: Coordinates , only : coordinateSpherical , assignment(=) use :: Error , only : Error_Report implicit none double precision , dimension(:,:), allocatable :: densityProjected @@ -207,6 +202,7 @@ function projectedDensityExtract(self,node,time,instance) result(densityProjecte class (nodeComponentDisk ), pointer :: disk class (nodeComponentSpheroid ), pointer :: spheroid class (nodeComponentDarkMatterProfile ), pointer :: darkMatterProfile + class (massDistributionClass ), pointer :: massDistribution_ double precision , parameter :: toleranceRelative =1.0d-2, epsilonSingularity =1.0d-3 type (integrator ) :: integrator_ integer :: i @@ -214,6 +210,7 @@ function projectedDensityExtract(self,node,time,instance) result(densityProjecte & radiusSingularity , densityProjectedPrevious , & & densityProjectedCurrent , toleranceAbsolute logical :: converged + type (coordinateSpherical ) :: coordinates !$GLC attributes unused :: time, instance allocate(densityProjected(self%radiiCount,self%elementCount_)) @@ -241,51 +238,50 @@ function projectedDensityExtract(self,node,time,instance) result(densityProjecte radius_=+radius_*spheroid %halfMassRadius() case (radiusTypeGalacticMassFraction %ID, & & radiusTypeGalacticLightFraction %ID) - radius_=+radius_ & - & *self%galacticStructure_%radiusEnclosingMass & - & ( & - & node , & - & massFractional=self%radii(i)%fraction , & - & massType = massTypeGalactic, & - & componentType = componentTypeAll, & - & weightBy =self%radii(i)%weightBy , & - & weightIndex =self%radii(i)%weightByIndex & - & ) + massDistribution_ => node %massDistribution ( & + & massType = massTypeStellar , & + & componentType = componentTypeAll, & + & weightBy =self%radii(i)%weightBy , & + & weightIndex =self%radii(i)%weightByIndex & + & ) + radius_ = +radius_ & + & *massDistribution_%radiusEnclosingMass( & + & massFractional=self%radii(i)%fraction & + & ) + !![ + + !!] case (radiusTypeStellarMassFraction %ID) - radius_=+radius_ & - & *self%galacticStructure_%radiusEnclosingMass & - & ( & - & node , & - & massFractional=self%radii(i)%fraction , & - & massType = massTypeStellar , & - & componentType = componentTypeAll, & - & weightBy =self%radii(i)%weightBy , & - & weightIndex =self%radii(i)%weightByIndex & - & ) + massDistribution_ => node %massDistribution ( & + & massType = massTypeStellar , & + & componentType = componentTypeAll, & + & weightBy =self%radii(i)%weightBy , & + & weightIndex =self%radii(i)%weightByIndex & + & ) + radius_ = +radius_ & + & *massDistribution_%radiusEnclosingMass( & + & massFractional=self%radii(i)%fraction & + & ) + !![ + + !!] case default call Error_Report('unrecognized radius type'//{introspection:location}) end select - densityProjectedPrevious=0.0d0 - radiusOuter =max(radius_* 2.0d0 ,radiusVirial) + massDistribution_ => node%massDistribution(self%radii(i)%component,self%radii(i)%mass) + densityProjectedPrevious = 0.0d0 + radiusOuter = max(radius_* 2.0d0 ,radiusVirial) ! Cut out a small region round the coordinate singularity at the inner radius. This region will be integrated analytically ! assuming a constant density over this region. The region outside of this cut-out will be integrated numerically. radiusSingularity =min(radius_*(1.0d0+epsilonSingularity),radiusOuter ) !! Analytic integral within the cut-out. - densityProjected(i,1)=+2.0d0 & - & *sqrt( & - & +radiusSingularity**2 & - & -radius_ **2 & - & ) & - & *self%galacticStructure_%density( & - & node , & - & [ & - & radius_ , & - & 0.0d0 , & - & 0.0d0 & - & ] , & - & componentType=self%radii(i)%component, & - & massType =self%radii(i)%mass & - & ) + coordinates=[radius_,0.0d0,0.0d0] + densityProjected(i,1)=+2.0d0 & + & *sqrt( & + & +radiusSingularity**2 & + & -radius_ **2 & + & ) & + & *massDistribution_%density(coordinates) !! Numerical integral outside of the cut-out. if (radiusSingularity < radiusOuter) then ! Set an absolute tolerance scale for projected density convergence that is a small fraction of the mean halo density, @@ -305,6 +301,9 @@ function projectedDensityExtract(self,node,time,instance) result(densityProjecte densityProjected(i,1)=+densityProjected (i,1) & & +densityProjectedCurrent if (self%includeRadii) densityProjected(i,2)=radius_ + !![ + + !!] end do return @@ -322,22 +321,14 @@ Integrand function used for computing projected densities. if (radius <= radius_) then projectedDensityIntegrand=+0.0d0 else - projectedDensityIntegrand=+2.0d0 & - & *radius **2 & - & /sqrt( & - & +radius **2 & - & -radius_**2 & - & ) & - & *self%galacticStructure_%density( & - & node , & - & [ & - & radius , & - & 0.0d0 , & - & 0.0d0 & - & ] , & - & componentType=self%radii(i)%component, & - & massType =self%radii(i)%mass & - & ) + coordinates=[radius,0.0d0,0.0d0] + projectedDensityIntegrand=+2.0d0 & + & *radius **2 & + & /sqrt( & + & +radius **2 & + & -radius_**2 & + & ) & + & *massDistribution_%density(coordinates) end if return end function projectedDensityIntegrand diff --git a/source/nodes.property_extractor.projected_mass.F90 b/source/nodes.property_extractor.projected_mass.F90 index a49280bddb..a5f1f19aea 100644 --- a/source/nodes.property_extractor.projected_mass.F90 +++ b/source/nodes.property_extractor.projected_mass.F90 @@ -20,9 +20,8 @@ !!{ Contains a module which implements a property extractor class for the projected density at a set of radii. !!} - use :: Dark_Matter_Halo_Scales , only : darkMatterHaloScale , darkMatterHaloScaleClass + use :: Dark_Matter_Halo_Scales , only : darkMatterHaloScale, darkMatterHaloScaleClass use :: Galactic_Structure_Radii_Definitions, only : radiusSpecifier - use :: Galactic_Structure , only : galacticStructureClass !![ @@ -41,7 +40,6 @@ !!} private class (darkMatterHaloScaleClass), pointer :: darkMatterHaloScale_ => null() - class (galacticStructureClass ), pointer :: galacticStructure_ => null() integer :: radiiCount , elementCount_ logical :: includeRadii type (varying_string ), allocatable, dimension(:) :: radiusSpecifiers @@ -84,7 +82,6 @@ function projectedMassConstructorParameters(parameters) result(self) type (inputParameters ), intent(inout) :: parameters type (varying_string ), allocatable , dimension(:) :: radiusSpecifiers class (darkMatterHaloScaleClass ), pointer :: darkMatterHaloScale_ - class (galacticStructureClass ), pointer :: galacticStructure_ logical :: includeRadii allocate(radiusSpecifiers(parameters%count('radiusSpecifiers'))) @@ -101,18 +98,16 @@ function projectedMassConstructorParameters(parameters) result(self) parameters - !!] - self=nodePropertyExtractorProjectedMass(radiusSpecifiers,includeRadii,darkMatterHaloScale_,galacticStructure_) + self=nodePropertyExtractorProjectedMass(radiusSpecifiers,includeRadii,darkMatterHaloScale_) !![ - !!] return end function projectedMassConstructorParameters - function projectedMassConstructorInternal(radiusSpecifiers,includeRadii,darkMatterHaloScale_,galacticStructure_) result(self) + function projectedMassConstructorInternal(radiusSpecifiers,includeRadii,darkMatterHaloScale_) result(self) !!{ Internal constructor for the {\normalfont \ttfamily projectedMass} property extractor class. !!} @@ -121,10 +116,9 @@ function projectedMassConstructorInternal(radiusSpecifiers,includeRadii,darkMatt type (nodePropertyExtractorProjectedMass) :: self type (varying_string ), intent(in ), dimension(:) :: radiusSpecifiers class (darkMatterHaloScaleClass ), intent(in ), target :: darkMatterHaloScale_ - class (galacticStructureClass ), intent(in ), target :: galacticStructure_ logical , intent(in ) :: includeRadii !![ - + !!] if (includeRadii) then @@ -154,7 +148,6 @@ subroutine projectedMassDestructor(self) !![ - !!] return end subroutine projectedMassDestructor @@ -197,6 +190,7 @@ function projectedMassExtract(self,node,time,instance) result(massProjected) use :: Galacticus_Nodes , only : nodeComponentDarkMatterProfile , nodeComponentDisk , nodeComponentSpheroid , treeNode use :: Numerical_Integration , only : integrator, GSL_Integ_Gauss15 use :: Numerical_Comparison , only : Values_Agree + use :: Mass_Distributions , only : massDistributionClass use :: Error , only : Error_Report implicit none double precision , dimension(:,:), allocatable :: massProjected @@ -207,6 +201,7 @@ function projectedMassExtract(self,node,time,instance) result(massProjected) class (nodeComponentDisk ), pointer :: disk class (nodeComponentSpheroid ), pointer :: spheroid class (nodeComponentDarkMatterProfile ), pointer :: darkMatterProfile + class (massDistributionClass ), pointer :: massDistribution_ double precision , parameter :: toleranceRelative =1.0d-2 type (integrator ) :: integrator_ integer :: i @@ -240,50 +235,55 @@ function projectedMassExtract(self,node,time,instance) result(massProjected) radius_=+radius_*spheroid %halfMassRadius() case (radiusTypeGalacticMassFraction %ID, & & radiusTypeGalacticLightFraction %ID) - radius_=+radius_ & - & *self%galacticStructure_%radiusEnclosingMass & - & ( & - & node , & - & massFractional=self%radii(i)%fraction , & - & massType = massTypeGalactic, & - & componentType = componentTypeAll, & - & weightBy =self%radii(i)%weightBy , & - & weightIndex =self%radii(i)%weightByIndex & - & ) + massDistribution_ => node %massDistribution ( & + & massType = massTypeStellar , & + & componentType = componentTypeAll, & + & weightBy =self%radii(i)%weightBy , & + & weightIndex =self%radii(i)%weightByIndex & + & ) + radius_ = +radius_ & + & *massDistribution_%radiusEnclosingMass( & + & massFractional=self%radii(i)%fraction & + & ) + !![ + + !!] case (radiusTypeStellarMassFraction %ID) - radius_=+radius_ & - & *self%galacticStructure_%radiusEnclosingMass & - & ( & - & node , & - & massFractional=self%radii(i)%fraction , & - & massType = massTypeStellar , & - & componentType = componentTypeAll, & - & weightBy =self%radii(i)%weightBy , & - & weightIndex =self%radii(i)%weightByIndex & - & ) + massDistribution_ => node %massDistribution ( & + & massType = massTypeStellar , & + & componentType = componentTypeAll, & + & weightBy =self%radii(i)%weightBy , & + & weightIndex =self%radii(i)%weightByIndex & + & ) + radius_ = +radius_ & + & *massDistribution_%radiusEnclosingMass( & + & massFractional=self%radii(i)%fraction & + & ) + !![ + + !!] case default call Error_Report('unrecognized radius type'//{introspection:location}) end select massProjectedPrevious=0.0d0 radiusOuter =max(radius_*2.0d0,radiusVirial) ! Evaluate the integral, then add on the mass of the sphere entirely enclosed inside the cylinder. - converged=.false. + massDistribution_ => node%massDistribution(componentType=self%radii(i)%component,massType=self%radii(i)%mass) + converged = .false. do while (.not.converged) massProjectedCurrent=integrator_%integrate(log(radius_),log(radiusOuter)) - converged =Values_Agree(massProjectedCurrent,massProjectedPrevious,relTol=toleranceRelative) + converged =Values_Agree(massProjectedCurrent,massProjectedPrevious,relTol=toleranceRelative) if (.not.converged) then radiusOuter =2.0d0*radiusOuter massProjectedPrevious= massProjectedCurrent end if end do - massProjected(i,1)=+ massProjectedCurrent & - & +self%galacticStructure_%massEnclosed ( & - & node , & - & radius_ , & - & componentType=self%radii(i)%component, & - & massType =self%radii(i)%mass & - & ) + massProjected(i,1)=+ massProjectedCurrent & + & +massDistribution_%massEnclosedBySphere(radius_) if (self%includeRadii) massProjected(i,2)=radius_ + !![ + + !!] end do return @@ -293,39 +293,33 @@ double precision function projectedMassIntegrand(radiusLogarithmic) !!{ Integrand function used for computing projected masses. !!} + use :: Coordinates , only : coordinateSpherical, assignment(=) use :: Numerical_Constants_Math, only : Pi implicit none - double precision, intent(in ) :: radiusLogarithmic - double precision :: radius + double precision , intent(in ) :: radiusLogarithmic + double precision :: radius + type (coordinateSpherical) :: coordinates radius=exp(radiusLogarithmic) if (radius <= radius_) then projectedMassIntegrand=+0.0d0 else - projectedMassIntegrand=+4.0d0 & ! ⎫ - & *Pi & ! ⎬ Surface area of the spherical shell. - & *radius**2 & ! ⎭ - & *( & ! ⎫ - & +1.0d0 & ! ⎪ - & -sqrt( & ! ⎪ - & +1.0d0 & ! ⎪ - & -( & ! ⎪ - & +radius_ & ! ⎬ Fraction of shell solid angle lying - & /radius & ! ⎪ inside the cylinder - & )**2 & ! ⎪ - & ) & ! ⎪ - & ) & ! ⎭ - & *self%galacticStructure_%density( & ! ⎫ - & node , & ! ⎪ - & [ & ! ⎪ - & radius , & ! ⎪ - & 0.0d0 , & ! ⎬ Density of the spherical shell. - & 0.0d0 & ! ⎪ - & ] , & ! ⎪ - & componentType=self%radii(i)%component, & ! ⎪ - & massType =self%radii(i)%mass & ! ⎪ - & ) & ! ⎭ - & *radius ! } Account for logarithmic integration variable. + coordinates =[radius,0.0d0,0.0d0] + projectedMassIntegrand=+4.0d0 & ! ⎫ + & *Pi & ! ⎬ Surface area of the spherical shell. + & *radius**2 & ! ⎭ + & *( & ! ⎫ + & +1.0d0 & ! ⎪ + & -sqrt( & ! ⎪ + & +1.0d0 & ! ⎪ + & -( & ! ⎪ + & +radius_ & ! ⎬ Fraction of shell solid angle lying + & /radius & ! ⎪ inside the cylinder + & )**2 & ! ⎪ + & ) & ! ⎪ + & ) & ! ⎭ + & *massDistribution_%density(coordinates) & ! } Density of the spherical shell. + & *radius ! } Account for logarithmic integration variable. end if return end function projectedMassIntegrand diff --git a/source/nodes.property_extractor.radius.half_mass.galactic.F90 b/source/nodes.property_extractor.radius.half_mass.galactic.F90 index 053428847b..f5c956b067 100644 --- a/source/nodes.property_extractor.radius.half_mass.galactic.F90 +++ b/source/nodes.property_extractor.radius.half_mass.galactic.F90 @@ -21,8 +21,6 @@ Contains a module which implements a half-galactic mass radius output analysis property extractor class. !!} - use :: Galactic_Structure, only : galacticStructureClass - !![ A half-galactic mass output analysis property extractor class. @@ -33,9 +31,7 @@ A half-galactic mass property extractor output analysis class. !!} private - class(galacticStructureClass), pointer :: galacticStructure_ => null() contains - final :: radiusHalfMassGalacticDestructor procedure :: extract => radiusHalfMassGalacticExtract procedure :: name => radiusHalfMassGalacticName procedure :: description => radiusHalfMassGalacticDescription @@ -47,7 +43,6 @@ Constructors for the ``radiusHalfMassGalactic'' output analysis class. !!} module procedure radiusHalfMassGalacticConstructorParameters - module procedure radiusHalfMassGalacticConstructorInternal end interface nodePropertyExtractorRadiusHalfMassGalactic contains @@ -60,62 +55,35 @@ function radiusHalfMassGalacticConstructorParameters(parameters) result(self) implicit none type (nodePropertyExtractorRadiusHalfMassGalactic) :: self type (inputParameters ), intent(inout) :: parameters - class(galacticStructureClass ), pointer :: galacticStructure_ - !![ - - !!] - self=nodePropertyExtractorRadiusHalfMassGalactic(galacticStructure_) + self=nodePropertyExtractorRadiusHalfMassGalactic() !![ - !!] return end function radiusHalfMassGalacticConstructorParameters - function radiusHalfMassGalacticConstructorInternal(galacticStructure_) result(self) - !!{ - Internal constructor for the ``radiusHalfMassGalactic'' output analysis property extractor class. - !!} - implicit none - type (nodePropertyExtractorRadiusHalfMassGalactic) :: self - class(galacticStructureClass ), intent(in ), target :: galacticStructure_ - !![ - - !!] - - return - end function radiusHalfMassGalacticConstructorInternal - - subroutine radiusHalfMassGalacticDestructor(self) - !!{ - Destructor for the ``radiusHalfMassGalactic'' output analysis property extractor class. - !!} - implicit none - type(nodePropertyExtractorRadiusHalfMassGalactic), intent(inout) :: self - - !![ - - !!] - return - end subroutine radiusHalfMassGalacticDestructor - double precision function radiusHalfMassGalacticExtract(self,node,instance) !!{ Implement a half-mass output analysis. !!} use :: Galactic_Structure_Options, only : massTypeGalactic + use :: Mass_Distributions , only : massDistributionClass implicit none class(nodePropertyExtractorRadiusHalfMassGalactic), intent(inout), target :: self type (treeNode ), intent(inout), target :: node type (multiCounter ), intent(inout), optional :: instance + class(massDistributionClass ) , pointer :: massDistribution_ !$GLC attributes unused :: self, instance - radiusHalfMassGalacticExtract=self%galacticStructure_%radiusEnclosingMass(node,massFractional=0.5d0,massType=massTypeGalactic) + massDistribution_ => node %massDistribution (massType =massTypeGalactic) + radiusHalfMassGalacticExtract = massDistribution_%radiusEnclosingMass(massFractional=0.5d0 ) + !![ + + !!] return end function radiusHalfMassGalacticExtract - function radiusHalfMassGalacticName(self) !!{ Return the name of the radiusHalfMassGalactic property. diff --git a/source/nodes.property_extractor.radius.half_mass.stellar.F90 b/source/nodes.property_extractor.radius.half_mass.stellar.F90 index 35720f9ae6..dfcbf164af 100644 --- a/source/nodes.property_extractor.radius.half_mass.stellar.F90 +++ b/source/nodes.property_extractor.radius.half_mass.stellar.F90 @@ -21,8 +21,6 @@ Contains a module which implements a half-stellar mass radius output analysis property extractor class. !!} - use :: Galactic_Structure, only : galacticStructureClass - !![ A half-(stellar) mass output analysis property extractor class. @@ -33,9 +31,7 @@ A half-(stellar) mass property extractor output analysis class. !!} private - class(galacticStructureClass), pointer :: galacticStructure_ => null() contains - final :: radiusHalfMassStellarDestructor procedure :: extract => radiusHalfMassStellarExtract procedure :: name => radiusHalfMassStellarName procedure :: description => radiusHalfMassStellarDescription @@ -47,7 +43,6 @@ Constructors for the ``radiusHalfMassStellar'' output analysis class. !!} module procedure radiusHalfMassStellarConstructorParameters - module procedure radiusHalfMassStellarConstructorInternal end interface nodePropertyExtractorRadiusHalfMassStellar contains @@ -60,58 +55,32 @@ function radiusHalfMassStellarConstructorParameters(parameters) result(self) implicit none type (nodePropertyExtractorRadiusHalfMassStellar) :: self type (inputParameters ), intent(inout) :: parameters - class(galacticStructureClass ), pointer :: galacticStructure_ - !![ - - !!] - self=nodePropertyExtractorRadiusHalfMassStellar(galacticStructure_) + self=nodePropertyExtractorRadiusHalfMassStellar() !![ - !!] return end function radiusHalfMassStellarConstructorParameters - function radiusHalfMassStellarConstructorInternal(galacticStructure_) result(self) - !!{ - Internal constructor for the ``radiusHalfMassStellar'' output analysis property extractor class. - !!} - implicit none - type (nodePropertyExtractorRadiusHalfMassStellar) :: self - class(galacticStructureClass ), intent(in ), target :: galacticStructure_ - !![ - - !!] - - return - end function radiusHalfMassStellarConstructorInternal - - subroutine radiusHalfMassStellarDestructor(self) - !!{ - Destructor for the ``radiusHalfMassStellar'' output analysis property extractor class. - !!} - implicit none - type(nodePropertyExtractorRadiusHalfMassStellar), intent(inout) :: self - - !![ - - !!] - return - end subroutine radiusHalfMassStellarDestructor - double precision function radiusHalfMassStellarExtract(self,node,instance) !!{ Implement a half-mass output analysis. !!} use :: Galactic_Structure_Options, only : massTypeStellar + use :: Mass_Distributions , only : massDistributionClass implicit none class(nodePropertyExtractorRadiusHalfMassStellar), intent(inout), target :: self type (treeNode ), intent(inout), target :: node type (multiCounter ), intent(inout), optional :: instance + class(massDistributionClass ) , pointer :: massDistribution_ !$GLC attributes unused :: self, instance - radiusHalfMassStellarExtract=self%galacticStructure_%radiusEnclosingMass(node,massFractional=0.5d0,massType=massTypeStellar) + massDistribution_ => node %massDistribution (massType =massTypeStellar) + radiusHalfMassStellarExtract = massDistribution_%radiusEnclosingMass(massFractional=0.5d0 ) + !![ + + !!] return end function radiusHalfMassStellarExtract diff --git a/source/nodes.property_extractor.radius_Einstein.F90 b/source/nodes.property_extractor.radius_Einstein.F90 index 88150f03bf..b735549263 100644 --- a/source/nodes.property_extractor.radius_Einstein.F90 +++ b/source/nodes.property_extractor.radius_Einstein.F90 @@ -23,8 +23,8 @@ use :: Cosmology_Functions , only : cosmologyFunctionsClass use :: Dark_Matter_Halo_Scales, only : darkMatterHaloScaleClass - use :: Galactic_Structure , only : galacticStructureClass use :: Root_Finder , only : rootFinder + use :: Mass_Distributions , only : massDistributionClass use :: Numerical_Integration , only : integrator !![ @@ -39,7 +39,6 @@ private class (cosmologyFunctionsClass ), pointer :: cosmologyFunctions_ => null() class (darkMatterHaloScaleClass), pointer :: darkMatterHaloScale_ => null() - class (galacticStructureClass ), pointer :: galacticStructure_ => null() double precision :: redshiftSource , timeSource type (rootFinder ) :: finder type (integrator ) :: integratorImpactParameter , integratorLineOfSight @@ -61,10 +60,10 @@ ! Submodule-scope variables used in root-finding. class (nodePropertyExtractorRadiusEinstein), pointer :: self_ - type (treeNode ), pointer :: node_ + class (massDistributionClass ), pointer :: massDistribution_ double precision :: densitySurfaceCritical , radiusImpact_, & & distanceLineOfSightMaximum_ - !$omp threadprivate(self_,node_,densitySurfaceCritical,radiusImpact_,distanceLineOfSightMaximum_) + !$omp threadprivate(self_,massDistribution_,densitySurfaceCritical,radiusImpact_,distanceLineOfSightMaximum_) contains @@ -78,7 +77,6 @@ function radiusEinsteinConstructorParameters(parameters) result(self) type (inputParameters ), intent(inout) :: parameters class (cosmologyFunctionsClass ), pointer :: cosmologyFunctions_ class (darkMatterHaloScaleClass ), pointer :: darkMatterHaloScale_ - class (galacticStructureClass ), pointer :: galacticStructure_ double precision :: redshiftSource !![ @@ -89,24 +87,21 @@ function radiusEinsteinConstructorParameters(parameters) result(self) - !!] self=nodePropertyExtractorRadiusEinstein( & & cosmologyFunctions_%cosmicTime(cosmologyFunctions_%expansionFactorFromRedshift(redshiftSource)), & & cosmologyFunctions_ , & - & darkMatterHaloScale_ , & - & galacticStructure_ & + & darkMatterHaloScale_ & & ) !![ - !!] return end function radiusEinsteinConstructorParameters - function radiusEinsteinConstructorInternal(timeSource,cosmologyFunctions_,darkMatterHaloScale_,galacticStructure_) result(self) + function radiusEinsteinConstructorInternal(timeSource,cosmologyFunctions_,darkMatterHaloScale_) result(self) !!{ Internal constructor for the ``radiusEinstein'' node property extractor. !!} @@ -115,10 +110,9 @@ function radiusEinsteinConstructorInternal(timeSource,cosmologyFunctions_,darkMa type (nodePropertyExtractorRadiusEinstein) :: self class (cosmologyFunctionsClass ), intent(in ), target :: cosmologyFunctions_ class (darkMatterHaloScaleClass ), intent(in ), target :: darkMatterHaloScale_ - class (galacticStructureClass ), intent(in ), target :: galacticStructure_ double precision , intent(in ) :: timeSource !![ - + !!] ! Compute corresponding redshift - these are needed for the descriptor. @@ -154,7 +148,6 @@ subroutine radiusEinsteinDestructor(self) !![ - !!] return end subroutine radiusEinsteinDestructor @@ -178,7 +171,7 @@ double precision function radiusEinsteinExtract(self,node,instance) & *arcsecondstoDegrees & & *degreesToRadians double precision :: distanceAngularLensSource , distanceAngularLens, & - & distanceAngularSource , massTotal + & distanceAngularSource !$GLC attributes unused :: instance radiusEinsteinExtract=-1.0d0 @@ -201,12 +194,11 @@ double precision function radiusEinsteinExtract(self,node,instance) & /distanceAngularLens & & /distanceAngularLensSource ! Find the outer radius of the halo. - satellite => node %satellite ( ) - massTotal = self%galacticStructure_%massEnclosed (node ) - distanceLineOfSightMaximum_ = self%galacticStructure_%radiusEnclosingMass(node,mass=min(satellite%boundMass(),massTotal)) + massDistribution_ => node%massDistribution() + satellite => node %satellite ( ) + distanceLineOfSightMaximum_ = massDistribution_%radiusEnclosingMass(min(satellite%boundMass(),basic%mass())) ! Find the radius within which the mean projected surface density equals the critical density. self_ => self - node_ => node if (radiusEinsteinProjectedDensityRoot(radiusEinsteinTiny*distanceAngularLens) < 0.0d0) then ! For extremely tiny Einstein radii we simply return zero, to avoid unnecessary computation. radiusEinsteinExtract=+0.0d0 @@ -263,25 +255,19 @@ double precision function radiusEinsteinProjectedDensityIntegrandLineOfSight(dis !!{ Integrand function used in finding the mean enclosed projected density. !!} - use :: Galactic_Structure_Options, only : componentTypeAll, massTypeAll + use :: Galactic_Structure_Options, only : componentTypeAll , massTypeAll + use :: Coordinates , only : coordinateSpherical, assignment(=) implicit none - double precision, intent(in ) :: distance - double precision :: radius + double precision , intent(in ) :: distance + double precision :: radius + type (coordinateSpherical) :: coordinates radius =+sqrt( & & +distance **2 & & +radiusImpact_**2 & & ) - radiusEinsteinProjectedDensityIntegrandLineOfSight=+self_%galacticStructure_%density( & - & node_ , & - & [ & - & radius , & - & 0.0d0 , & - & 0.0d0 & - & ] , & - & componentType=componentTypeAll, & - & massType =massTypeAll & - & ) + coordinates = [radius,0.0d0,0.0d0] + radiusEinsteinProjectedDensityIntegrandLineOfSight=+massDistribution_%density(coordinates) return end function radiusEinsteinProjectedDensityIntegrandLineOfSight diff --git a/source/nodes.property_extractor.radius_velocity_maximum.F90 b/source/nodes.property_extractor.radius_velocity_maximum.F90 index 0279ef816e..620c75f180 100644 --- a/source/nodes.property_extractor.radius_velocity_maximum.F90 +++ b/source/nodes.property_extractor.radius_velocity_maximum.F90 @@ -116,14 +116,20 @@ double precision function radiusVelocityMaximumExtract(self,node,instance) !!{ Implement a radius of maximum velocity output analysis. !!} - use :: Galacticus_Nodes, only : nodeComponentBasic, treeNode + use :: Galacticus_Nodes , only : nodeComponentBasic , treeNode + use :: Mass_Distributions, only : massDistributionClass implicit none class(nodePropertyExtractorRadiusVelocityMaximum), intent(inout), target :: self type (treeNode ), intent(inout), target :: node type (multiCounter ), intent(inout), optional :: instance + class(massDistributionClass ) , pointer :: massDistribution_ !$GLC attributes unused :: instance - radiusVelocityMaximumExtract=self%darkMatterProfileDMO_%radiusCircularVelocityMaximum(node) + massDistribution_ => self %darkMatterProfileDMO_%get (node) + radiusVelocityMaximumExtract = massDistribution_ %radiusRotationCurveMaximum( ) + !![ + + !!] return end function radiusVelocityMaximumExtract diff --git a/source/nodes.property_extractor.radius_virial.F90 b/source/nodes.property_extractor.radius_virial.F90 index eafd1fd2cc..31c5324f62 100644 --- a/source/nodes.property_extractor.radius_virial.F90 +++ b/source/nodes.property_extractor.radius_virial.F90 @@ -21,10 +21,9 @@ Contains a module which implements a virial radius output analysis property extractor class. !!} - use :: Cosmology_Parameters , only : cosmologyParametersClass - use :: Cosmology_Functions , only : cosmologyFunctionsClass - use :: Dark_Matter_Profiles_DMO, only : darkMatterProfileDMOClass - use :: Virial_Density_Contrast , only : virialDensityContrastClass + use :: Cosmology_Parameters , only : cosmologyParametersClass + use :: Cosmology_Functions , only : cosmologyFunctionsClass + use :: Virial_Density_Contrast, only : virialDensityContrastClass !![ @@ -40,7 +39,6 @@ radius). !!} private - class (darkMatterProfileDMOClass ), pointer :: darkMatterProfileDMO_ => null() class (virialDensityContrastClass), pointer :: virialDensityContrast_ => null(), virialDensityContrastDefinition_ => null() class (cosmologyParametersClass ), pointer :: cosmologyParameters_ => null() class (cosmologyFunctionsClass ), pointer :: cosmologyFunctions_ => null() @@ -73,7 +71,6 @@ function radiusVirialConstructorParameters(parameters) result(self) type (inputParameters ), intent(inout) :: parameters class (cosmologyFunctionsClass ), pointer :: cosmologyFunctions_ class (cosmologyParametersClass ), pointer :: cosmologyParameters_ - class (darkMatterProfileDMOClass ), pointer :: darkMatterProfileDMO_ class (virialDensityContrastClass ), pointer :: virialDensityContrast_, virialDensityContrastDefinition_ logical :: useLastIsolatedTime @@ -86,23 +83,21 @@ function radiusVirialConstructorParameters(parameters) result(self) - !!] - self=nodePropertyExtractorRadiusVirial(useLastIsolatedTime,cosmologyFunctions_,cosmologyParameters_,darkMatterProfileDMO_,virialDensityContrast_,virialDensityContrastDefinition_) + self=nodePropertyExtractorRadiusVirial(useLastIsolatedTime,cosmologyFunctions_,cosmologyParameters_,virialDensityContrast_,virialDensityContrastDefinition_) !![ - !!] return end function radiusVirialConstructorParameters - function radiusVirialConstructorInternal(useLastIsolatedTime,cosmologyFunctions_,cosmologyParameters_,darkMatterProfileDMO_,virialDensityContrast_,virialDensityContrastDefinition_) result(self) + function radiusVirialConstructorInternal(useLastIsolatedTime,cosmologyFunctions_,cosmologyParameters_,virialDensityContrast_,virialDensityContrastDefinition_) result(self) !!{ Internal constructor for the ``radiusVirial'' output analysis property extractor class. !!} @@ -111,10 +106,9 @@ function radiusVirialConstructorInternal(useLastIsolatedTime,cosmologyFunctions_ class (cosmologyParametersClass ), intent(in ), target :: cosmologyParameters_ class (cosmologyFunctionsClass ), intent(in ), target :: cosmologyFunctions_ class (virialDensityContrastClass ), intent(in ), target :: virialDensityContrast_, virialDensityContrastDefinition_ - class (darkMatterProfileDMOClass ), intent(in ), target :: darkMatterProfileDMO_ logical , intent(in ) :: useLastIsolatedTime !![ - + !!] return @@ -131,7 +125,6 @@ subroutine radiusVirialDestructor(self) - !!] return @@ -163,7 +156,6 @@ double precision function radiusVirialExtract(self,node,instance) & radius = radiusVirialExtract , & & cosmologyParameters_ =self%cosmologyParameters_ , & & cosmologyFunctions_ =self%cosmologyFunctions_ , & - & darkMatterProfileDMO_ =self%darkMatterProfileDMO_ , & & virialDensityContrast_=self%virialDensityContrast_ , & & useLastIsolatedTime =self%useLastIsolatedTime & & ) diff --git a/source/nodes.property_extractor.rotation_curve.F90 b/source/nodes.property_extractor.rotation_curve.F90 index 35a7303f90..c45c0325a3 100644 --- a/source/nodes.property_extractor.rotation_curve.F90 +++ b/source/nodes.property_extractor.rotation_curve.F90 @@ -22,7 +22,6 @@ !!} use :: Dark_Matter_Halo_Scales , only : darkMatterHaloScale , darkMatterHaloScaleClass use :: Galactic_Structure_Radii_Definitions, only : radiusSpecifier - use :: Galactic_Structure , only : galacticStructureClass !![ @@ -41,7 +40,6 @@ !!} private class (darkMatterHaloScaleClass), pointer :: darkMatterHaloScale_ => null() - class (galacticStructureClass ), pointer :: galacticStructure_ => null() integer :: radiiCount , elementCount_ logical :: includeRadii type (varying_string ), allocatable, dimension(:) :: radiusSpecifiers @@ -80,7 +78,6 @@ function rotationCurveConstructorParameters(parameters) result(self) type (inputParameters ), intent(inout) :: parameters type (varying_string ), allocatable , dimension(:) :: radiusSpecifiers class (darkMatterHaloScaleClass ), pointer :: darkMatterHaloScale_ - class (galacticStructureClass ), pointer :: galacticStructure_ logical :: includeRadii allocate(radiusSpecifiers(parameters%count('radiusSpecifiers'))) @@ -97,18 +94,16 @@ function rotationCurveConstructorParameters(parameters) result(self) parameters - !!] - self=nodePropertyExtractorRotationCurve(radiusSpecifiers,includeRadii,darkMatterHaloScale_,galacticStructure_) + self=nodePropertyExtractorRotationCurve(radiusSpecifiers,includeRadii,darkMatterHaloScale_) !![ - !!] return end function rotationCurveConstructorParameters - function rotationCurveConstructorInternal(radiusSpecifiers,includeRadii,darkMatterHaloScale_,galacticStructure_) result(self) + function rotationCurveConstructorInternal(radiusSpecifiers,includeRadii,darkMatterHaloScale_) result(self) !!{ Internal constructor for the {\normalfont \ttfamily rotationCurve} property extractor class. !!} @@ -117,10 +112,9 @@ function rotationCurveConstructorInternal(radiusSpecifiers,includeRadii,darkMatt type (nodePropertyExtractorRotationCurve) :: self type (varying_string ), intent(in ), dimension(:) :: radiusSpecifiers class (darkMatterHaloScaleClass ), intent(in ), target :: darkMatterHaloScale_ - class (galacticStructureClass ), intent(in ), target :: galacticStructure_ logical , intent(in ) :: includeRadii !![ - + !!] if (includeRadii) then @@ -150,7 +144,6 @@ subroutine rotationCurveDestructor(self) !![ - !!] return end subroutine rotationCurveDestructor @@ -192,6 +185,7 @@ function rotationCurveExtract(self,node,time,instance) & radiusTypeStellarMassFraction , radiusTypeVirialRadius use :: Galacticus_Nodes , only : nodeComponentDarkMatterProfile , nodeComponentDisk , nodeComponentSpheroid , treeNode use :: Error , only : Error_Report + use :: Mass_Distributions , only : massDistributionClass implicit none double precision , dimension(:,:), allocatable :: rotationCurveExtract class (nodePropertyExtractorRotationCurve), intent(inout) , target :: self @@ -201,6 +195,7 @@ function rotationCurveExtract(self,node,time,instance) class (nodeComponentDisk ), pointer :: disk class (nodeComponentSpheroid ), pointer :: spheroid class (nodeComponentDarkMatterProfile ), pointer :: darkMatterProfile + class (massDistributionClass ), pointer :: massDistribution_ integer :: i double precision :: radius , radiusVirial !$GLC attributes unused :: time, instance @@ -230,38 +225,48 @@ function rotationCurveExtract(self,node,time,instance) radius=+radius*spheroid %halfMassRadius() case (radiusTypeGalacticMassFraction %ID, & & radiusTypeGalacticLightFraction %ID) - radius=+radius & - & *self%galacticStructure_%radiusEnclosingMass & - & ( & - & node , & - & massFractional=self%radii(i)%fraction , & - & massType = massTypeGalactic, & - & componentType = componentTypeAll, & - & weightBy =self%radii(i)%weightBy , & - & weightIndex =self%radii(i)%weightByIndex & - & ) + massDistribution_ => node %massDistribution ( & + & massType = massTypeStellar , & + & componentType = componentTypeAll, & + & weightBy =self%radii(i)%weightBy , & + & weightIndex =self%radii(i)%weightByIndex & + & ) + radius = +radius & + & *massDistribution_%radiusEnclosingMass( & + & massFractional=self%radii(i)%fraction & + & ) + !![ + + !!] case (radiusTypeStellarMassFraction %ID) - radius=+radius & - & *self%galacticStructure_%radiusEnclosingMass & - & ( & - & node , & - & massFractional=self%radii(i)%fraction , & - & massType = massTypeStellar , & - & componentType = componentTypeAll, & - & weightBy =self%radii(i)%weightBy , & - & weightIndex =self%radii(i)%weightByIndex & - & ) + massDistribution_ => node %massDistribution ( & + & massType = massTypeStellar , & + & componentType = componentTypeAll, & + & weightBy =self%radii(i)%weightBy , & + & weightIndex =self%radii(i)%weightByIndex & + & ) + radius = +radius & + & *massDistribution_%radiusEnclosingMass( & + & massFractional=self%radii(i)%fraction & + & ) + !![ + + !!] case default call Error_Report('unrecognized radius type'//{introspection:location}) end select - rotationCurveExtract (i,1)=self%galacticStructure_%velocityRotation( & - & node , & - & radius , & - & componentType=self%radii(i)%component, & - & massType =self%radii(i)%mass & - & ) - if (self%includeRadii) & - & rotationCurveExtract(i,2)= radius + massDistribution_ => node %massDistribution( & + & componentType=self%radii(i)%component, & + & massType =self%radii(i)%mass & + & ) + rotationCurveExtract (i,1) = massDistribution_%rotationCurve ( & + & radius & + & ) + if (self%includeRadii) & + & rotationCurveExtract(i,2) = radius + !![ + + !!] end do return end function rotationCurveExtract diff --git a/source/nodes.property_extractor.satellite.dynamical_time.F90 b/source/nodes.property_extractor.satellite.dynamical_time.F90 index fc144bf583..8a1571bc85 100644 --- a/source/nodes.property_extractor.satellite.dynamical_time.F90 +++ b/source/nodes.property_extractor.satellite.dynamical_time.F90 @@ -25,7 +25,6 @@ !!} use :: Satellite_Tidal_Stripping_Radii, only : satelliteTidalStrippingRadiusClass - use :: Galactic_Structure , only : galacticStructureClass !![ @@ -46,7 +45,6 @@ !!} private class(satelliteTidalStrippingRadiusClass), pointer :: satelliteTidalStrippingRadius_ => null() - class(galacticStructureClass ), pointer :: galacticStructure_ => null() contains final :: dynamicalTimeDestructor procedure :: extract => dynamicalTimeExtract @@ -74,31 +72,27 @@ function dynamicalTimeConstructorParameters(parameters) result(self) type (nodePropertyExtractorSatelliteDynamicalTime) :: self type (inputParameters ), intent(inout) :: parameters class(satelliteTidalStrippingRadiusClass ), pointer :: satelliteTidalStrippingRadius_ - class(galacticStructureClass ), pointer :: galacticStructure_ !![ - !!] - self=nodePropertyExtractorSatelliteDynamicalTime(satelliteTidalStrippingRadius_, galacticStructure_) + self=nodePropertyExtractorSatelliteDynamicalTime(satelliteTidalStrippingRadius_) !![ - !!] return end function dynamicalTimeConstructorParameters - function dynamicalTimeConstructorInternal(satelliteTidalStrippingRadius_, galacticStructure_) result(self) + function dynamicalTimeConstructorInternal(satelliteTidalStrippingRadius_) result(self) !!{ Internal constructor for the {\normalfont \ttfamily satelliteDynamicalTime} property extractor class. !!} implicit none type (nodePropertyExtractorSatelliteDynamicalTime) :: self class(satelliteTidalStrippingRadiusClass ), intent(in ), target :: satelliteTidalStrippingRadius_ - class(galacticStructureClass ), intent(in ), target :: galacticStructure_ !![ - + !!] return @@ -113,7 +107,6 @@ subroutine dynamicalTimeDestructor(self) !![ - !!] return end subroutine dynamicalTimeDestructor @@ -122,18 +115,25 @@ double precision function dynamicalTimeExtract(self,node,instance) !!{ Implement a dynamical time property extractor. !!} - use :: Numerical_Constants_Math , only : Pi - use :: Numerical_Constants_Astronomical , only : gravitationalConstantGalacticus, Mpc_per_km_per_s_To_Gyr + use :: Mass_Distributions , only : massDistributionClass + use :: Numerical_Constants_Math , only : Pi + use :: Numerical_Constants_Astronomical, only : gravitationalConstantGalacticus, Mpc_per_km_per_s_To_Gyr implicit none class (nodePropertyExtractorSatelliteDynamicalTime), intent(inout), target :: self type (treeNode ), intent(inout), target :: node type (multiCounter ), intent(inout), optional :: instance - double precision :: radiusTidal, massTidal + class (massDistributionClass ) , pointer :: massDistribution_ + double precision :: radiusTidal , massTidal !$GLC attributes unused :: instance - dynamicalTimeExtract =-1.0d0 - radiusTidal =self%satelliteTidalStrippingRadius_%radius (node ) + + dynamicalTimeExtract=-1.0d0 + radiusTidal =self%satelliteTidalStrippingRadius_%radius(node) if (radiusTidal <= 0.0d0) return - massTidal =self%galacticStructure_ %massEnclosed(node,radiusTidal) + massDistribution_ => node %massDistribution ( ) + massTidal = massDistribution_%massEnclosedBySphere(radiusTidal) + !![ + + !!] if (massTidal <= 0.0d0) return dynamicalTimeExtract =+sqrt( & & +Pi **2 & diff --git a/source/nodes.property_extractor.satellite_orbital_extrema.F90 b/source/nodes.property_extractor.satellite_orbital_extrema.F90 index 56abe97a35..9ad2d283ca 100644 --- a/source/nodes.property_extractor.satellite_orbital_extrema.F90 +++ b/source/nodes.property_extractor.satellite_orbital_extrema.F90 @@ -21,7 +21,7 @@ Contains a module which implements satellite orbital extrema property extractor class. !!} - use :: Galactic_Structure, only : galacticStructureClass + use :: Dark_Matter_Halo_Scales, only : darkMatterHaloScaleClass !![ @@ -36,10 +36,10 @@ A satellite orbital extrema property extractor class. !!} private - class (galacticStructureClass), pointer :: galacticStructure_ => null() - integer :: offsetPericenter , offsetApocenter , & - & elementCount_ - logical :: extractPericenter , extractApocenter + class (darkMatterHaloScaleClass), pointer :: darkMatterHaloScale_ => null() + integer :: offsetPericenter , offsetApocenter , & + & elementCount_ + logical :: extractPericenter , extractApocenter contains final :: satelliteOrbitalExtremaDestructor procedure :: elementCount => satelliteOrbitalExtremaElementCount @@ -67,8 +67,8 @@ function satelliteOrbitalExtremaConstructorParameters(parameters) result(self) implicit none type (nodePropertyExtractorSatelliteOrbitalExtrema) :: self type (inputParameters ), intent(inout) :: parameters - class (galacticStructureClass ), pointer :: galacticStructure_ - logical :: extractPericenter , extractApocenter + class (darkMatterHaloScaleClass ), pointer :: darkMatterHaloScale_ + logical :: extractPericenter , extractApocenter !![ @@ -83,17 +83,17 @@ function satelliteOrbitalExtremaConstructorParameters(parameters) result(self) Specifies whether or not satellite orbital apocenter data (radius, velocity) should be extracted. parameters - + !!] - self=nodePropertyExtractorSatelliteOrbitalExtrema(extractPericenter,extractApocenter,galacticStructure_) + self=nodePropertyExtractorSatelliteOrbitalExtrema(extractPericenter,extractApocenter,darkMatterHaloScale_) !![ - + !!] return end function satelliteOrbitalExtremaConstructorParameters - function satelliteOrbitalExtremaConstructorInternal(extractPericenter,extractApocenter,galacticStructure_) result(self) + function satelliteOrbitalExtremaConstructorInternal(extractPericenter,extractApocenter,darkMatterHaloScale_) result(self) !!{ Internal constructor for the {\normalfont \ttfamily satelliteOrbitalExtrema} property extractor class. !!} @@ -101,9 +101,9 @@ function satelliteOrbitalExtremaConstructorInternal(extractPericenter,extractApo implicit none type (nodePropertyExtractorSatelliteOrbitalExtrema) :: self logical , intent(in ) :: extractPericenter , extractApocenter - class (galacticStructureClass ), intent(in ), target :: galacticStructure_ + class (darkMatterHaloScaleClass ), intent(in ), target :: darkMatterHaloScale_ !![ - + !!] self%elementCount_=0 @@ -127,7 +127,7 @@ subroutine satelliteOrbitalExtremaDestructor(self) type(nodePropertyExtractorSatelliteOrbitalExtrema), intent(inout) :: self !![ - + !!] return end subroutine satelliteOrbitalExtremaDestructor @@ -170,7 +170,7 @@ function satelliteOrbitalExtremaExtract(self,node,time,instance) nodeHost => node %parent satellite => node %satellite () orbit = satellite%virialOrbit() - call Satellite_Orbit_Extremum_Phase_Space_Coordinates(nodeHost,orbit,extremumPericenter,radiusOrbital,velocityOrbital,self%galacticStructure_) + call Satellite_Orbit_Extremum_Phase_Space_Coordinates(nodeHost,orbit,extremumPericenter,radiusOrbital,velocityOrbital,self%darkMatterHaloScale_) else radiusOrbital =0.0d0 velocityOrbital=0.0d0 @@ -182,7 +182,7 @@ function satelliteOrbitalExtremaExtract(self,node,time,instance) nodeHost => node %parent satellite => node %satellite () orbit = satellite%virialOrbit() - call Satellite_Orbit_Extremum_Phase_Space_Coordinates(nodeHost,orbit,extremumApocenter ,radiusOrbital,velocityOrbital,self%galacticStructure_) + call Satellite_Orbit_Extremum_Phase_Space_Coordinates(nodeHost,orbit,extremumApocenter ,radiusOrbital,velocityOrbital,self%darkMatterHaloScale_) else radiusOrbital =0.0d0 velocityOrbital=0.0d0 diff --git a/source/nodes.property_extractor.spin_parameter.F90 b/source/nodes.property_extractor.spin_parameter.F90 index 6cfd882de5..8e6fe81e0b 100644 --- a/source/nodes.property_extractor.spin_parameter.F90 +++ b/source/nodes.property_extractor.spin_parameter.F90 @@ -21,8 +21,8 @@ Contains a module which implements a spin parameter output analysis property extractor class. !!} - use :: Dark_Matter_Profiles_DMO, only : darkMatterProfileDMO, darkMatterProfileDMOClass - + use :: Dark_Matter_Halo_Scales, only : darkMatterHaloScaleClass + !![ A spin parameter output analysis property extractor class. @@ -33,7 +33,7 @@ A spin parameter property extractor output analysis class. !!} private - class(darkMatterProfileDMOClass), pointer :: darkMatterProfileDMO_ => null() + class(darkMatterHaloScaleClass), pointer :: darkMatterHaloScale_ => null() contains final :: spinDestructor procedure :: extract => spinExtract @@ -60,29 +60,29 @@ function spinConstructorParameters(parameters) result(self) implicit none type (nodePropertyExtractorSpin) :: self type (inputParameters ), intent(inout) :: parameters - class(darkMatterProfileDMOClass), pointer :: darkMatterProfileDMO_ + class(darkMatterHaloScaleClass ), pointer :: darkMatterHaloScale_ !$GLC attributes unused :: parameters !![ - + !!] - self=nodePropertyExtractorSpin(darkMatterProfileDMO_) + self=nodePropertyExtractorSpin(darkMatterHaloScale_) !![ - + !!] return end function spinConstructorParameters - function spinConstructorInternal(darkMatterProfileDMO_) result(self) + function spinConstructorInternal(darkMatterHaloScale_) result(self) !!{ Internal constructor for the {\normalfont \ttfamily spin} output analysis property extractor class. !!} implicit none type (nodePropertyExtractorSpin) :: self - class(darkMatterProfileDMOClass), intent(in ), target :: darkMatterProfileDMO_ + class(darkMatterHaloScaleClass ), intent(in ), target :: darkMatterHaloScale_ !![ - + !!] return @@ -96,7 +96,7 @@ subroutine spinDestructor(self) type(nodePropertyExtractorSpin), intent(inout) :: self !![ - + !!] return end subroutine spinDestructor @@ -115,8 +115,8 @@ double precision function spinExtract(self,node,instance) !$GLC attributes unused :: self, instance spin => node %spin() - spinExtract = +spin%angularMomentum () & - & /Dark_Matter_Halo_Angular_Momentum_Scale(node,self%darkMatterProfileDMO_) + spinExtract = +spin%angularMomentum () & + & /Dark_Matter_Halo_Angular_Momentum_Scale(node,self%darkmatterHaloScale_) return end function spinExtract diff --git a/source/nodes.property_extractor.tidally_truncated_NFW_fit.F90 b/source/nodes.property_extractor.tidally_truncated_NFW_fit.F90 index 2721456fa9..8baed838f1 100644 --- a/source/nodes.property_extractor.tidally_truncated_NFW_fit.F90 +++ b/source/nodes.property_extractor.tidally_truncated_NFW_fit.F90 @@ -25,7 +25,6 @@ use :: Dark_Matter_Profiles_DMO, only : darkMatterProfileDMOClass, darkMatterProfileDMONFW use :: Dark_Matter_Halo_Scales , only : darkMatterHaloScaleClass - use :: Galactic_Structure , only : galacticStructureClass !![ @@ -46,7 +45,6 @@ type (darkMatterProfileDMONFW ), pointer :: darkMatterProfileDMONFW_ => null() class(darkMatterProfileDMOClass), pointer :: darkMatterProfileDMO_ => null() class(darkMatterHaloScaleClass ), pointer :: darkMatterHaloScale_ => null() - class(galacticStructureClass ), pointer :: galacticStructure_ => null() contains final :: tidallyTruncatedNFWFitDestructor procedure :: elementCount => tidallyTruncatedNFWFitElementCount @@ -76,24 +74,21 @@ function tidallyTruncatedNFWFitConstructorParameters(parameters) result(self) type (inputParameters ), intent(inout) :: parameters class(darkMatterProfileDMOClass ), pointer :: darkMatterProfileDMO_ class(darkMatterHaloScaleClass ), pointer :: darkMatterHaloScale_ - class(galacticStructureClass ), pointer :: galacticStructure_ !![ - !!] - self=nodePropertyExtractorTidallyTruncatedNFWFit(darkMatterHaloScale_,galacticStructure_,darkMatterProfileDMO_) + self=nodePropertyExtractorTidallyTruncatedNFWFit(darkMatterHaloScale_,darkMatterProfileDMO_) !![ - !!] return end function tidallyTruncatedNFWFitConstructorParameters - function tidallyTruncatedNFWFitConstructorInternal(darkMatterHaloScale_,galacticStructure_,darkMatterProfileDMO_) result(self) + function tidallyTruncatedNFWFitConstructorInternal(darkMatterHaloScale_,darkMatterProfileDMO_) result(self) !!{ Internal constructor for the ``tidallyTruncatedNFWFit'' output analysis property extractor class. !!} @@ -101,9 +96,8 @@ function tidallyTruncatedNFWFitConstructorInternal(darkMatterHaloScale_,galactic type (nodePropertyExtractorTidallyTruncatedNFWFit) :: self class(darkMatterProfileDMOClass ), intent(in ), target :: darkMatterProfileDMO_ class(darkMatterHaloScaleClass ), intent(in ), target :: darkMatterHaloScale_ - class(galacticStructureClass ), intent(in ), target :: galacticStructure_ !![ - + !!] allocate(self%darkMatterProfileDMONFW_) @@ -131,7 +125,6 @@ subroutine tidallyTruncatedNFWFitDestructor(self) - !!] return end subroutine tidallyTruncatedNFWFitDestructor @@ -154,9 +147,11 @@ function tidallyTruncatedNFWFitExtract(self,node,time,instance) Implement a tidallyTruncatedNFWFit output analysis. !!} use, intrinsic :: ISO_C_Binding , only : c_size_t + use :: Coordinates , only : coordinateSpherical , assignment(=) use :: Galacticus_Nodes , only : nodeComponentDarkMatterProfile, nodeComponentSatellite use :: Numerical_Ranges , only : Make_Range , rangeTypeLogarithmic use :: Multidimensional_Minimizer, only : multiDMinimizer + use :: Mass_Distributions , only : massDistributionClass implicit none double precision , allocatable , dimension(:) :: tidallyTruncatedNFWFitExtract class (nodePropertyExtractorTidallyTruncatedNFWFit), intent(inout), target :: self @@ -165,41 +160,49 @@ function tidallyTruncatedNFWFitExtract(self,node,time,instance) type (multiCounter ), intent(inout), optional :: instance class (nodeComponentDarkMatterProfile ), pointer :: darkMatterProfile class (nodeComponentSatellite ), pointer :: satellite + class (massDistributionClass ), pointer :: massDistribution_ , massDistributionNFW double precision , allocatable , dimension(:) :: radii , fractionDensity type (multiDMinimizer ), allocatable :: minimizer_ double precision , dimension(1) :: locationMinimum - double precision , parameter :: fractionRadiusScale =0.1d0, fractionMaximum =0.1d0, & - & radiusMaximumFractionDensityVirialMinimum=0.1d0, fractionStep =0.1d0, & + double precision , parameter :: fractionRadiusScale =0.1d0, fractionMaximum =0.1d0, & + & radiusMaximumFractionDensityVirialMinimum=0.1d0, fractionStep =0.1d0, & & radiusMaximumScaleVirialMaximum =1.0d1 - integer , parameter :: radiusMaximumCountRadiiPerDecade =10 , countRadiiPerDecade=10 - integer :: countRadii , i , & + integer , parameter :: radiusMaximumCountRadiiPerDecade =10 , countRadiiPerDecade =10 + integer :: countRadii , i , & & iteration , radiusMaximumCountRadii logical :: converged - double precision :: radiusOuter , massTotal , & - & radiusMinimum , radiusMaximum , & - & radiusScale , radiusVirial , & + double precision :: radiusOuter , massTotal , & + & radiusMinimum , radiusMaximum , & + & radiusScale , radiusVirial , & & radiusMaximumFractionDensityVirial , factorStepRadius + type (coordinateSpherical ) :: coordinates , coordinatesMaximum , & + & coordinatesVirial !$GLC attributes unused :: instance allocate(tidallyTruncatedNFWFitExtract(3)) - darkMatterProfile => node %darkMatterProfile( ) - tidallyTruncatedNFWFitExtract(3) = self%darkMatterProfileDMONFW_%density (node,darkMatterProfile%scale()) + darkMatterProfile => node %darkMatterProfile( ) + massDistribution_ => self %darkMatterProfileDMO_ %get (node ) + massDistributionNFW => self %darkMatterProfileDMONFW_%get (node ) + coordinates = [darkMatterProfile%scale(),0.0d0,0.0d0] + tidallyTruncatedNFWFitExtract(3) = massDistributionNFW %density (coordinates) if (node%isSatellite()) then ! Extract required properties. - satellite => node %satellite ( ) - massTotal = self %galacticStructure_ %massEnclosed (node ) - radiusOuter = self %galacticStructure_ %radiusEnclosingMass(node,mass=min(satellite%boundMass(),massTotal)) - radiusScale = darkMatterProfile %scale ( ) - radiusVirial = self %darkMatterHaloScale_%radiusVirial (node ) + radiusVirial = self %darkMatterHaloScale_%radiusVirial ( node ) + satellite => node %satellite ( ) + massTotal = massDistribution_ %massEnclosedBySphere(radius=radiusVirial ) + radiusOuter = massDistribution_ %radiusEnclosingMass (mass =min(satellite%boundMass(),massTotal)) + radiusScale = darkMatterProfile %scale ( ) ! Choose radii for fitting. radiusMaximum=radiusVirial if (radiusOuter > radiusVirial) then radiusMaximumCountRadii=int(log10(radiusMaximumScaleVirialMaximum)*dble(radiusMaximumCountRadiiPerDecade)+1.0d0) - factorStepRadius = log10(radiusMaximumScaleVirialMaximum)/dble(radiusMaximumCountRadii ) + factorStepRadius = log10(radiusMaximumScaleVirialMaximum)/dble(radiusMaximumCountRadii ) + coordinatesVirial =[radiusVirial,0.0d0,0.0d0] do i=1,radiusMaximumCountRadii radiusMaximum =10.0d0**(log10(radiusVirial)+factorStepRadius*dble(i)) - radiusMaximumFractionDensityVirial=+self%darkMatterProfileDMO_%density(node,radiusMaximum) & - & /self%darkMatterProfileDMO_%density(node,radiusVirial ) + coordinatesMaximum =[radiusMaximum,0.0d0,0.0d0] + radiusMaximumFractionDensityVirial=+massDistribution_%density(coordinatesMaximum) & + & /massDistribution_%density(coordinatesVirial ) if (radiusMaximumFractionDensityVirial < radiusMaximumFractionDensityVirialMinimum) exit end do end if @@ -209,8 +212,9 @@ function tidallyTruncatedNFWFitExtract(self,node,time,instance) ! Tabulate the density ratio relative to an NFW profile. allocate(fractionDensity(countRadii)) do i=1,countRadii - fractionDensity(i)=+self%darkMatterProfileDMO_ %density(node,radii(i)) & - & /self%darkMatterProfileDMONFW_%density(node,radii(i)) + coordinates=[radii(i),0.0d0,0.0d0] + fractionDensity(i)=+massDistribution_ %density(coordinates) & + & /massDistributionNFW%density(coordinates) end do ! Optimize the fit. allocate(minimizer_) @@ -230,6 +234,10 @@ function tidallyTruncatedNFWFitExtract(self,node,time,instance) tidallyTruncatedNFWFitExtract(1)=huge(0.0d0) tidallyTruncatedNFWFitExtract(2)= 0.0d0 end if + !![ + + + !!] return contains diff --git a/source/nodes.property_extractor.velocity_dispersion.F90 b/source/nodes.property_extractor.velocity_dispersion.F90 index de715a8c61..f65bed6440 100644 --- a/source/nodes.property_extractor.velocity_dispersion.F90 +++ b/source/nodes.property_extractor.velocity_dispersion.F90 @@ -22,9 +22,9 @@ !!} use :: Dark_Matter_Halo_Scales , only : darkMatterHaloScale , darkMatterHaloScaleClass use :: Galactic_Structure_Radii_Definitions, only : radiusSpecifier - use :: Galactic_Structure , only : galacticStructureClass use :: Galactic_Structure_Options , only : enumerationMassTypeType, enumerationComponentTypeType, enumerationWeightByType - + use :: Mass_Distributions , only : massDistributionClass , kinematicsDistributionClass + !![ @@ -51,7 +51,6 @@ !!} private class (darkMatterHaloScaleClass), pointer :: darkMatterHaloScale_ => null() - class (galacticStructureClass ), pointer :: galacticStructure_ => null() integer :: radiiCount , elementCount_ logical :: includeRadii , integrationFailureIsFatal double precision :: toleranceRelative @@ -80,14 +79,18 @@ end interface nodePropertyExtractorVelocityDispersion ! Module-scope variables used in integrands. + class (massDistributionClass ), pointer :: massDistribution_ , massDistributionStellarDisk_ , & + & massDistributionWeighted_ , massDistributionStellarSpheroid_ , & + & massDistributionTotal_ + class (kinematicsDistributionClass ), pointer :: kinematicsDistribution_ , kinematicsDistributionStellarDisk_, & + & kinematicsDistributionStellarSpheroid_ class (nodePropertyExtractorVelocityDispersion), pointer :: self_ - type (treeNode ), pointer :: node_ type (enumerationMassTypeType ) :: massType_ type (enumerationComponentTypeType ) :: componentType_ type (enumerationWeightByType ) :: weightBy_ integer :: weightIndex_ double precision :: radiusImpact_ , radiusOuter_ - !$omp threadprivate(self_,node_,weightBy_,componentType_,massType_,weightIndex_,radiusImpact_,radiusOuter_) + !$omp threadprivate(massDistribution_,massDistributionWeighted_,massDistributionStellarDisk_,massDistributionStellarSpheroid_,massDistributionTotal_,kinematicsDistribution_,kinematicsDistributionStellarDisk_,kinematicsDistributionStellarSpheroid_,self_,weightBy_,componentType_,massType_,weightIndex_,radiusImpact_,radiusOuter_) contains @@ -97,13 +100,12 @@ function velocityDispersionConstructorParameters(parameters) result(self) !!} use :: Input_Parameters, only : inputParameter, inputParameters implicit none - type (nodePropertyExtractorVelocityDispersion) :: self - type (inputParameters ), intent(inout) :: parameters - type (varying_string ), allocatable , dimension(:) :: radiusSpecifiers - class (darkMatterHaloScaleClass ), pointer :: darkMatterHaloScale_ - class (galacticStructureClass ), pointer :: galacticStructure_ - double precision :: toleranceRelative - logical :: includeRadii , integrationFailureIsFatal + type (nodePropertyExtractorVelocityDispersion) :: self + type (inputParameters ), intent(inout) :: parameters + type (varying_string ), allocatable , dimension(:) :: radiusSpecifiers + class (darkMatterHaloScaleClass ), pointer :: darkMatterHaloScale_ + double precision :: toleranceRelative + logical :: includeRadii , integrationFailureIsFatal allocate(radiusSpecifiers(parameters%count('radiusSpecifiers'))) !![ @@ -131,18 +133,16 @@ function velocityDispersionConstructorParameters(parameters) result(self) parameters - !!] - self=nodePropertyExtractorVelocityDispersion(radiusSpecifiers,includeRadii,integrationFailureIsFatal,toleranceRelative,darkMatterHaloScale_,galacticStructure_) + self=nodePropertyExtractorVelocityDispersion(radiusSpecifiers,includeRadii,integrationFailureIsFatal,toleranceRelative,darkMatterHaloScale_) !![ - !!] return end function velocityDispersionConstructorParameters - function velocityDispersionConstructorInternal(radiusSpecifiers,includeRadii,integrationFailureIsFatal,toleranceRelative,darkMatterHaloScale_,galacticStructure_) result(self) + function velocityDispersionConstructorInternal(radiusSpecifiers,includeRadii,integrationFailureIsFatal,toleranceRelative,darkMatterHaloScale_) result(self) !!{ Internal constructor for the {\normalfont \ttfamily velocityDispersion} property extractor class. !!} @@ -151,11 +151,10 @@ function velocityDispersionConstructorInternal(radiusSpecifiers,includeRadii,int type (nodePropertyExtractorVelocityDispersion) :: self type (varying_string ), intent(in ), dimension(:) :: radiusSpecifiers class (darkMatterHaloScaleClass ), intent(in ), target :: darkMatterHaloScale_ - class (galacticStructureClass ), intent(in ), target :: galacticStructure_ logical , intent(in ) :: includeRadii , integrationFailureIsFatal double precision , intent(in ) :: toleranceRelative !![ - + !!] if (includeRadii) then @@ -185,7 +184,6 @@ subroutine velocityDispersionDestructor(self) !![ - !!] return end subroutine velocityDispersionDestructor @@ -222,12 +220,13 @@ function velocityDispersionExtract(self,node,time,instance) Implement a {\normalfont \ttfamily velocityDispersion} property extractor. !!} use :: Galactic_Structure_Options , only : componentTypeAll , componentTypeDisk , componentTypeSpheroid , massTypeGalactic , & - & massTypeStellar , radiusLarge + & massTypeStellar , massTypeAll use :: Galactic_Structure_Radii_Definitions, only : directionLambdaR , directionLineOfSight , directionLineOfSightInteriorAverage, directionRadial , & & radiusTypeDarkMatterScaleRadius, radiusTypeDiskHalfMassRadius, radiusTypeDiskRadius , radiusTypeGalacticLightFraction, & & radiusTypeGalacticMassFraction , radiusTypeRadius , radiusTypeSpheroidHalfMassRadius , radiusTypeSpheroidRadius , & & radiusTypeStellarMassFraction , radiusTypeVirialRadius use :: Galacticus_Nodes , only : nodeComponentDarkMatterProfile , nodeComponentDisk , nodeComponentSpheroid , treeNode + use :: Coordinates , only : coordinateSpherical , assignment(=) use :: Numerical_Integration , only : integrator use :: Error , only : Error_Report implicit none @@ -249,6 +248,7 @@ function velocityDispersionExtract(self,node,time,instance) logical :: scaleIsZero type (integrator ) :: integratorVelocitySurfaceDensity , integratorSurfaceDensity, & & integratorLambdaR2 , integratorLambdaR1 + type (coordinateSpherical ) :: coordinates !$GLC attributes unused :: time, instance integratorVelocitySurfaceDensity=integrator(velocityDispersionVelocitySurfaceDensityIntegrand,toleranceRelative=1.0d-3) @@ -291,27 +291,35 @@ function velocityDispersionExtract(self,node,time,instance) scaleIsZero =(spheroid %halfMassRadius() <= 0.0d0) case (radiusTypeGalacticMassFraction %ID, & & radiusTypeGalacticLightFraction %ID ) - radiusFromFraction=+self%galacticStructure_%radiusEnclosingMass( & - & node , & - & massFractional=self%radii(i)%fraction , & - & massType = massTypeGalactic, & - & componentType = componentTypeAll, & - & weightBy =self%radii(i)%weightBy , & - & weightIndex =self%radii(i)%weightByIndex & - & ) - radius = radius*radiusFromFraction - radiusOuter_=max(radius,radiusFromFraction)*outerRadiusMultiplier + massDistribution_ => node %massDistribution ( & + & massType = massTypeGalactic, & + & componentType = componentTypeAll, & + & weightBy =self%radii(i)%weightBy , & + & weightIndex =self%radii(i)%weightByIndex & + & ) + radiusFromFraction = +massDistribution_%radiusEnclosingMass( & + & massFractional=self%radii(i)%fraction & + & ) + radius = +radius*radiusFromFraction + radiusOuter_ = max(radius,radiusFromFraction)*outerRadiusMultiplier + !![ + + !!] case (radiusTypeStellarMassFraction %ID) - radiusFromFraction=+self%galacticStructure_%radiusEnclosingMass( & - & node , & - & massFractional=self%radii(i)%fraction , & - & massType = massTypeStellar , & - & componentType = componentTypeAll, & - & weightBy =self%radii(i)%weightBy , & - & weightIndex =self%radii(i)%weightByIndex & - & ) - radius = radius*radiusFromFraction - radiusOuter_=max(radius,radiusFromFraction)*outerRadiusMultiplier + massDistribution_ => node %massDistribution ( & + & massType = massTypeStellar , & + & componentType = componentTypeAll, & + & weightBy =self%radii(i)%weightBy , & + & weightIndex =self%radii(i)%weightByIndex & + & ) + radiusFromFraction = +massDistribution_%radiusEnclosingMass( & + & massFractional=self%radii(i)%fraction & + & ) + radius = +radius*radiusFromFraction + radiusOuter_ = max(radius,radiusFromFraction)*outerRadiusMultiplier + !![ + + !!] case default call Error_Report('unrecognized radius type'//{introspection:location}) end select @@ -319,20 +327,18 @@ function velocityDispersionExtract(self,node,time,instance) ! Do not compute dispersions if the component scale is zero. velocityDispersionExtract(i,1)=0.0d0 else + massDistribution_ => node % massDistribution(componentType=self%radii(i)%component ,massType=self%radii(i)%mass ) + massDistributionWeighted_ => node % massDistribution(componentType=self%radii(i)%component ,massType=self%radii(i)%mass ,weightBy=self%radii(i)%weightBy,weightIndex=self%radii(i)%weightByIndex) + massDistributionTotal_ => node % massDistribution(componentType= componentTypeAll,massType= massTypeAll ) + kinematicsDistribution_ => massDistribution_%kinematicsDistribution( ) select case (self%radii(i)%direction%ID) case (directionRadial %ID) ! Radial velocity dispersion. - velocityDispersionExtract(i,1)=self%galacticStructure_%velocityDispersion( & - & node , & - & radius , & - & radiusOuter_ , & - & massType =self%radii(i)%mass , & - & componentType=self%radii(i)%component & - & ) + coordinates =[radius,0.0d0,0.0d0] + velocityDispersionExtract(i,1)=kinematicsDistribution_%velocityDispersion1D(coordinates,massDistributionTotal_) case (directionLineOfSight %ID) ! Line-of-sight velocity dispersion. self_ => self - node_ => node massType_ = self%radii(i)%mass componentType_ = self%radii(i)%component weightBy_ = self%radii(i)%integralWeightBy @@ -342,7 +348,6 @@ function velocityDispersionExtract(self,node,time,instance) case (directionLineOfSightInteriorAverage%ID) ! Average over the line-of-sight velocity dispersion within the radius. self_ => self - node_ => node massType_ = self%radii(i)%mass componentType_ = self%radii(i)%component weightBy_ = self%radii(i)%integralWeightBy @@ -359,45 +364,42 @@ function velocityDispersionExtract(self,node,time,instance) case (directionLambdaR %ID) ! The "lambdaR" parameter of Cappellari et al. (2007; MNRAS; 379; 418) self_ => self - node_ => node massType_ = self%radii(i)%mass componentType_ = self%radii(i)%component weightBy_ = self%radii(i)%integralWeightBy weightIndex_ = self%radii(i)%integralWeightByIndex ! Check the total masses of the disk and spheroid components. If either is zero we can use the solutions for the ! appropriate limiting case. - massSpheroid=self%galacticStructure_%massEnclosed( & - & node , & - & radiusLarge , & - & massType =massTypeStellar , & - & componentType=componentTypeSpheroid , & - & weightBy =weightBy_ , & - & weightIndex =weightIndex_ & - & ) - massDisk =self%galacticStructure_%massEnclosed( & - & node , & - & radiusLarge , & - & massType =massTypeStellar , & - & componentType=componentTypeDisk , & - & weightBy =weightBy_ , & - & weightIndex =weightIndex_ & - & ) + massDistributionStellarDisk_ => node%massDistribution(componentType=componentTypeDisk ,massType=massTypeStellar,weightBy=weightBy_,weightIndex=weightIndex_) + massDistributionStellarSpheroid_ => node%massDistribution(componentType=componentTypeSpheroid,massType=massTypeStellar,weightBy=weightBy_,weightIndex=weightIndex_) + massSpheroid=massDistributionStellarSpheroid_%massTotal() + massDisk =massDistributionStellarDisk_ %massTotal() if (massDisk <= 0.0d0) then velocityDispersionExtract(i,1)=0.0d0 else if (massSpheroid <= 0.0d0) then velocityDispersionExtract(i,1)=1.0d0 else ! Full calculation is required. - radiusZero=0.0d0 - numerator =integratorLambdaR2%integrate(radiusZero,radius) - denominator=integratorLambdaR1%integrate(radiusZero,radius) + radiusZero = 0.0d0 + numerator = integratorLambdaR2%integrate(radiusZero,radius) + denominator = integratorLambdaR1%integrate(radiusZero,radius) if (denominator <= 0.0d0) then velocityDispersionExtract(i,1)=0.0d0 else velocityDispersionExtract(i,1)=numerator/denominator end if end if + !![ + + + !!] end select + !![ + + + + + !!] end if if (self%includeRadii) & & velocityDispersionExtract(i,2)=radius @@ -479,7 +481,6 @@ function velocityDispersionUnitsInSI(self,time) return end function velocityDispersionUnitsInSI - double precision function velocityDispersionLambdaRIntegrand1(radius) !!{ Integrand function used for integrating the $\lambda_\mathrm{R}$ statistic of \cite{cappellari_sauron_2007}. In this case we @@ -501,37 +502,27 @@ width $\sigma_\mathrm{s}(r)$ and normalized area $\Sigma_\mathrm{s}(r)$, and a d \sigma^2(r) = \left. \int_{-\infty}^{+\infty} P(V) [V-V(r)]^2 \mathrm{d}V \right/ \int_{-\infty}^{+\infty} P(V) \mathrm{d}V = { \Sigma_\mathrm{s}(r) [\sigma_\mathrm{s}^2(r)] + \Sigma_\mathrm{d}(r) [V_\mathrm{d}(r)-V(r)]^2 \over [\Sigma_\mathrm{d}(r)+\Sigma_\mathrm{s}(r)]}. \end{equation} !!} - use :: Galactic_Structure_Options, only : componentTypeAll, componentTypeDisk, massTypeAll, massTypeStellar - use :: Numerical_Constants_Math , only : Pi - use :: Numerical_Integration , only : integrator + use :: Numerical_Constants_Math, only : Pi + use :: Numerical_Integration , only : integrator + use :: Coordinates , only : coordinateCylindrical, assignment(=) implicit none - double precision , intent(in ) :: radius - double precision , parameter :: fractionSmall =1.0d-3 - type (integrator) :: integratorDensity , integratorVelocityDensity - double precision :: sigmaLineOfSightSquaredSpheroidDensity , densitySpheroid , & - & densityDisk , velocityDisk , & - & velocityMean , sigmaLineOfSightSquared + double precision , intent(in ) :: radius + double precision , parameter :: fractionSmall =1.0d-3 + type (integrator ) :: integratorDensity , integratorVelocityDensity + double precision :: sigmaLineOfSightSquaredSpheroidDensity , densitySpheroid , & + & densityDisk , velocityDisk , & + & velocityMean , sigmaLineOfSightSquared + type (coordinateCylindrical) :: coordinates if (radius <= 0.0d0) then velocityDispersionLambdaRIntegrand1=0.0d0 else - radiusImpact_=radius - integratorDensity =integrator (velocityDispersionDensityIntegrand,toleranceRelative=1.0d-2) - densitySpheroid =integratorDensity %integrate (radius ,radiusOuter_ ) - densityDisk =self_%galacticStructure_%surfaceDensity ( & - & node_ , & - & [radius,0.0d0,0.0d0], & - & massType =massTypeStellar , & - & componentType=componentTypeDisk , & - & weightBy =weightBy_ , & - & weightIndex =weightIndex_ & - & ) - velocityDisk =self_%galacticStructure_%velocityRotation( & - & node_ , & - & radius , & - & massType =massTypeAll , & - & componentType=componentTypeAll & - & ) + radiusImpact_ = radius + coordinates =[radius,0.0d0,0.0d0] + integratorDensity =integrator (velocityDispersionDensityIntegrand,toleranceRelative=1.0d-2) + densitySpheroid =integratorDensity %integrate (radius ,radiusOuter_ ) + densityDisk =massDistributionStellarDisk_%surfaceDensity(coordinates ) + velocityDisk =massDistributionTotal_ %rotationCurve (radius ) ! Test if the spheroid density is significant.... if (densitySpheroid < fractionSmall*densityDisk) then ! ...it is not, so we can avoid computing the spheroid velocity dispersion. @@ -585,29 +576,19 @@ $\Sigma_\mathrm{s}(r)$, and a delta function at $V_\mathrm{d}(r)$ with normalize V(r) = \left. \int_{-\infty}^{+\infty} P(V) V \mathrm{d}V \right/ \int_{-\infty}^{+\infty} P(V) \mathrm{d}V = {\Sigma_\mathrm{d}(r) V_\mathrm{d}(r) \over [\Sigma_\mathrm{d}(r)+\Sigma_\mathrm{s}(r)]}. \end{equation} !!} - use :: Galactic_Structure_Options, only : componentTypeAll, componentTypeDisk, massTypeAll, massTypeStellar - use :: Numerical_Constants_Math , only : Pi + use :: Numerical_Constants_Math, only : Pi + use :: Coordinates , only : coordinateCylindrical, assignment(=) implicit none - double precision, intent(in ) :: radius - double precision :: densityDisk, velocityDisk + double precision , intent(in ) :: radius + double precision :: densityDisk, velocityDisk + type (coordinateCylindrical) :: coordinates if (radius <= 0.0d0) then velocityDispersionLambdaRIntegrand2=0.0d0 else - densityDisk=self_%galacticStructure_%surfaceDensity ( & - & node_ , & - & [radius,0.0d0,0.0d0], & - & massType =massTypeStellar , & - & componentType=componentTypeDisk , & - & weightBy =weightBy_ , & - & weightIndex =weightIndex_ & - & ) - velocityDisk=self_%galacticStructure_%velocityRotation( & - & node_ , & - & radius , & - & massType =massTypeAll , & - & componentType=componentTypeAll & - & ) + coordinates =[radius,0.0d0,0.0d0] + densityDisk =massDistributionStellarDisk_%surfaceDensity(coordinates) + velocityDisk =massDistributionTotal_ %rotationCurve (radius ) velocityDispersionLambdaRIntegrand2=+2.0d0 & & *Pi & & *radius & @@ -621,31 +602,19 @@ double precision function velocityDispersionVelocitySurfaceDensityIntegrand(radi !!{ Integrand function used for integrating line-of-sight velocity dispersion over surface density. !!} + use :: Coordinates, only : coordinateSpherical, assignment(=) implicit none - double precision, intent(in ) :: radius - + double precision , intent(in ) :: radius + type (coordinateSpherical) :: coordinates + if (radius <= 0.0d0) then velocityDispersionVelocitySurfaceDensityIntegrand=0.0d0 else - velocityDispersionVelocitySurfaceDensityIntegrand=+velocityDispersionSolidAngleInCylinder ( & - & radius & - & ) & - & * radius**2 & - & *self_%galacticStructure_%density ( & - & node_ , & - & [radius,0.0d0,0.0d0], & - & massType =massType_ , & - & componentType=componentType_ , & - & weightBy =weightBy_ , & - & weightIndex =weightIndex_ & - & ) & - & *self_%galacticStructure_%velocityDispersion( & - & node_ , & - & radius , & - & radiusOuter_ , & - & massType =massType_ , & - & componentType=componentType_ & - & )**2 + coordinates =[radius,0.0d0,0.0d0] + velocityDispersionVelocitySurfaceDensityIntegrand=+ velocityDispersionSolidAngleInCylinder(radius ) & + & * radius **2 & + & *massDistributionWeighted_%density (coordinates ) & + & *kinematicsDistribution_ %velocityDispersion1D (coordinates,massDistributionTotal_)**2 end if return end function velocityDispersionVelocitySurfaceDensityIntegrand @@ -654,24 +623,18 @@ double precision function velocityDispersionSurfaceDensityIntegrand(radius) !!{ Integrand function used for integrating line-of-sight surface density dispersion over area. !!} + use :: Coordinates, only : coordinateSpherical, assignment(=) implicit none - double precision, intent(in ) :: radius + double precision , intent(in ) :: radius + type (coordinateSpherical) :: coordinates if (radius <= 0.0d0) then velocityDispersionSurfaceDensityIntegrand=+0.0d0 else - velocityDispersionSurfaceDensityIntegrand=+velocityDispersionSolidAngleInCylinder( & - & radius & - & ) & - & * radius **2 & - & *self_%galacticStructure_%density ( & - & node_ , & - & [radius,0.0d0,0.0d0], & - & massType =massType_ , & - & componentType=componentType_ , & - & weightBy =weightBy_ , & - & weightIndex =weightIndex_ & - & ) + coordinates =[radius,0.0d0,0.0d0] + velocityDispersionSurfaceDensityIntegrand=+ velocityDispersionSolidAngleInCylinder(radius ) & + & * radius **2 & + & *massDistributionWeighted_%density (coordinates) end if return end function velocityDispersionSurfaceDensityIntegrand @@ -739,15 +702,8 @@ Integrand function used for computing line-of-sight velocity dispersions. if (radius <= radiusImpact_) then velocityDispersionDensityIntegrand=+0.0d0 else - velocityDispersionDensityIntegrand=+self_%galacticStructure_%densitySphericalAverage( & - & node_ , & - & radius , & - & massType =massType_ , & - & componentType=componentType_, & - & weightBy =weightBy_ , & - & weightIndex =weightIndex_ & - & ) & - & * radius & + velocityDispersionDensityIntegrand=+massDistributionWeighted_%densitySphericalAverage(radius) & + & * radius & & /sqrt(radius**2-radiusImpact_**2) end if return @@ -780,7 +736,6 @@ Integrand function used for computing line-of-sight velocity dispersions. Specif \int_{r_\mathrm{i}}^{r_\mathrm{o}} {\mathrm{G} M( self %darkMatterProfileDMO_%get (node) + velocityMaximumExtract = massDistribution_ %velocityRotationCurveMaximum( ) + !![ + + !!] + return end function velocityMaximumExtract function velocityMaximumName(self) diff --git a/source/numerical.ODE_solver.F90 b/source/numerical.ODE_solver.F90 index b2a4e87657..b86ae49b49 100644 --- a/source/numerical.ODE_solver.F90 +++ b/source/numerical.ODE_solver.F90 @@ -446,7 +446,7 @@ subroutine odeSolverSolve(self,x0,x1,y,z,xStep,status) evolveForward=x1 > x0 ! Reset the driver. status_ =GSL_ODEIV2_Driver_Reset (self%gsl_odeiv2_driver ) - if (status_ /= GSL_Success) call Error_Report('failed to reset ODE driver' //{introspection:location}) + if (status_ /= GSL_Success) call Error_Report('failed to reset ODE driver' //{introspection:location}) if (xStep_ /= 0.0d0) then status_=GSL_ODEIV2_Driver_Reset_hStart(self%gsl_odeiv2_driver,xStep_) if (status_ /= GSL_Success) call Error_Report('failed to reset ODE step size'//{introspection:location}) @@ -456,7 +456,7 @@ subroutine odeSolverSolve(self,x0,x1,y,z,xStep,status) zCount=size(z) allocate(z0(zCount)) z0=z - call self%integrator%integrandSet (zCount,integrandsWrapper) + call self%integrator%integrandSet(zCount,integrandsWrapper) latentIntegrator_=C_FunLoc(latentIntegrator) else allocate(z0(0)) diff --git a/source/numerical.constants.math.F90 b/source/numerical.constants.math.F90 index b41bda54e4..0d67783265 100644 --- a/source/numerical.constants.math.F90 +++ b/source/numerical.constants.math.F90 @@ -58,4 +58,8 @@ module Numerical_Constants_Math !! ζ(3) - https://oeis.org/A002117 double precision, public, parameter :: riemannZeta3=1.20205690315959428539973816151144999076d0 + ! Catalan's constant + !! G - https://oeis.org/A006752 + double precision, public, parameter :: catalan =0.91596559417721901505460351493238411077d0 + end module Numerical_Constants_Math diff --git a/source/objects.coordinates.F90 b/source/objects.coordinates.F90 index 01e77824b9..90cbddc050 100644 --- a/source/objects.coordinates.F90 +++ b/source/objects.coordinates.F90 @@ -53,6 +53,7 @@ module Coordinates + !!] procedure :: toCartesian => Coordinates_Null_To @@ -64,6 +65,7 @@ module Coordinates procedure(scalarDivideTemplate ), deferred :: scalarDivide generic :: operator(*) => scalarMultiply generic :: operator(/) => scalarDivide + procedure(scaleTemplate ), deferred :: scale end type coordinate type, public, extends(coordinate) :: coordinateCartesian @@ -92,6 +94,7 @@ module Coordinates procedure :: rSphericalSquared => Coordinates_Cartesian_R_Spherical_Squared procedure :: scalarMultiply => Coordinates_Cartesian_Scalar_Multiply procedure :: scalarDivide => Coordinates_Cartesian_Scalar_Divide + procedure :: scale => Coordinates_Cartesian_Scale end type coordinateCartesian type, public, extends(coordinate) :: coordinateSpherical @@ -121,6 +124,7 @@ module Coordinates procedure :: rSphericalSquared => Coordinates_Spherical_R_Spherical_Squared procedure :: scalarMultiply => Coordinates_Spherical_Scalar_Multiply procedure :: scalarDivide => Coordinates_Spherical_Scalar_Divide + procedure :: scale => Coordinates_Spherical_Scale end type coordinateSpherical type, public, extends(coordinate) :: coordinateCylindrical @@ -149,6 +153,7 @@ module Coordinates procedure :: rSphericalSquared => Coordinates_Cylindrical_R_Spherical_Squared procedure :: scalarMultiply => Coordinates_Cylindrical_Scalar_Multiply procedure :: scalarDivide => Coordinates_Cylindrical_Scalar_Divide + procedure :: scale => Coordinates_Cylindrical_Scale end type coordinateCylindrical abstract interface @@ -176,6 +181,15 @@ function scalarDivideTemplate(self,divisor) end function scalarDivideTemplate end interface + abstract interface + subroutine scaleTemplate(self,scalar,selfScaled) + import coordinate + class (coordinate), intent(in ) :: self + double precision , intent(in ) :: scalar + class (coordinate), intent(inout), allocatable :: selfScaled + end subroutine scaleTemplate + end interface + ! Interface to multiplication operators with coordinate objects as their second argument. interface operator(*) module procedure Coordinates_Scalar_Multiply_Switched @@ -359,7 +373,7 @@ function Coordinates_Cartesian_Scalar_Multiply(self,multiplier) result(scaled) class (coordinateCartesian), intent(in ) :: self double precision , intent(in ) :: multiplier - allocate(scaled,mold=self) + allocate(scaled,source=self) scaled%position=+self%position & & * multiplier return @@ -374,11 +388,35 @@ function Coordinates_Cartesian_Scalar_Divide(self,divisor) result(scaled) class (coordinateCartesian), intent(in ) :: self double precision , intent(in ) :: divisor - allocate(scaled,mold=self) + allocate(scaled,source=self) scaled%position=+self%position & & / divisor return end function Coordinates_Cartesian_Scalar_Divide + + !![ + + + This function is needed to allow scaling of coordinate objects. It should not be needed as we overload the * and / operators + for coordinate objects. But, until finalization is completed, function results are not finalized causing the overloaded * + and / operators to leak memory. This is a workaround to avoid that. + + + !!] + subroutine Coordinates_Cartesian_Scale(self,scalar,selfScaled) + !!{ + Scale a Cartesian {\normalfont \ttfamily coordinate} object by a scalar. + !!} + implicit none + class (coordinateCartesian), intent(in ) :: self + double precision , intent(in ) :: scalar + class (coordinate ), intent(inout), allocatable :: selfScaled + + allocate(selfScaled,source=self) + selfScaled%position=+selfScaled%position & + & * scalar + return + end subroutine Coordinates_Cartesian_Scale double precision function Coordinates_Cartesian_R_Spherical_Squared(self) !!{ @@ -537,7 +575,7 @@ function Coordinates_Spherical_Scalar_Multiply(self,multiplier) result(scaled) class (coordinateSpherical), intent(in ) :: self double precision , intent(in ) :: multiplier - allocate(scaled,mold=self) + allocate(scaled,source=self) scaled%position =+self %position scaled%position(1)=+scaled%position(1) & & *multiplier @@ -553,13 +591,36 @@ function Coordinates_Spherical_Scalar_Divide(self,divisor) result(scaled) class (coordinateSpherical), intent(in ) :: self double precision , intent(in ) :: divisor - allocate(scaled,mold=self) - scaled%position =+self %position + allocate(scaled,source=self) scaled%position(1)=+scaled%position(1) & & /divisor return end function Coordinates_Spherical_Scalar_Divide + !![ + + + This function is needed to allow scaling of coordinate objects. It should not be needed as we overload the * and / operators + for coordinate objects. But, until finalization is completed, function results are not finalized causing the overloaded * + and / operators to leak memory. This is a workaround to avoid that. + + + !!] + subroutine Coordinates_Spherical_Scale(self,scalar,selfScaled) + !!{ + Scale a spherical {\normalfont \ttfamily coordinate} object by a scalar. + !!} + implicit none + class (coordinateSpherical), intent(in ) :: self + double precision , intent(in ) :: scalar + class (coordinate ), intent(inout), allocatable :: selfScaled + + allocate(selfScaled,source=self) + selfScaled%position(1)=+selfScaled%position(1) & + & * scalar + return + end subroutine Coordinates_Spherical_Scale + ! Cylindrical coordinate object. subroutine Coordinates_Cylindrical_From_Cartesian(self,x) !!{ @@ -688,8 +749,7 @@ function Coordinates_Cylindrical_Scalar_Multiply(self,multiplier) result(scaled) class (coordinateCylindrical), intent(in ) :: self double precision , intent(in ) :: multiplier - allocate(scaled,mold=self) - scaled%position =+self %position + allocate(scaled,source=self) scaled%position(1)=+scaled%position(1) & & *multiplier scaled%position(3)=+scaled%position(3) & @@ -706,8 +766,7 @@ function Coordinates_Cylindrical_Scalar_Divide(self,divisor) result(scaled) class (coordinateCylindrical), intent(in ) :: self double precision , intent(in ) :: divisor - allocate(scaled,mold=self) - scaled%position =+self %position + allocate(scaled,source=self) scaled%position(1)=+scaled%position(1) & & /divisor scaled%position(3)=+scaled%position(3) & @@ -715,6 +774,32 @@ function Coordinates_Cylindrical_Scalar_Divide(self,divisor) result(scaled) return end function Coordinates_Cylindrical_Scalar_Divide + !![ + + + This function is needed to allow scaling of coordinate objects. It should not be needed as we overload the * and / operators + for coordinate objects. But, until finalization is completed, function results are not finalized causing the overloaded * + and / operators to leak memory. This is a workaround to avoid that. + + + !!] + subroutine Coordinates_Cylindrical_Scale(self,scalar,selfScaled) + !!{ + Scale a cylindrical {\normalfont \ttfamily coordinate} object by a scalar. + !!} + implicit none + class (coordinateCylindrical), intent(in ) :: self + double precision , intent(in ) :: scalar + class (coordinate ), intent(inout), allocatable :: selfScaled + + allocate(selfScaled,source=self) + selfScaled%position(1)=+selfScaled%position(1) & + & * scalar + selfScaled%position(3)=+selfScaled%position(3) & + & * scalar + return + end subroutine Coordinates_Cylindrical_Scale + ! General functions. double precision function Coordinates_Radius_Cylindrical(self) implicit none @@ -746,11 +831,10 @@ function Coordinates_Scalar_Multiply_Switched(multiplier,self) result(scaled) Multiply a Cartesian {\normalfont \ttfamily coordinate} object by a scalar. !!} implicit none - class (coordinate), allocatable :: scaled + class (coordinate), allocatable :: scaled class (coordinate), intent(in ) :: self - double precision , intent(in ) :: multiplier + double precision , intent(in ) :: multiplier - allocate(scaled,mold=self) scaled=self*multiplier return end function Coordinates_Scalar_Multiply_Switched diff --git a/source/objects.kepler_orbits.F90 b/source/objects.kepler_orbits.F90 index 1bf437fe11..50c13729f3 100644 --- a/source/objects.kepler_orbits.F90 +++ b/source/objects.kepler_orbits.F90 @@ -40,24 +40,24 @@ module Kepler_Orbits yes yes yes - - - - - - - + + + + + + + - - - - - - - - - - + + + + + + + + + + !!] diff --git a/source/objects.nodes.F90 b/source/objects.nodes.F90 index 0ede0ca2a7..5e90c48645 100644 --- a/source/objects.nodes.F90 +++ b/source/objects.nodes.F90 @@ -28,7 +28,7 @@ module Galacticus_Nodes !!} use :: Abundances_Structure , only : abundances use :: Chemical_Abundances_Structure , only : chemicalAbundances - use :: Galactic_Structure_Options , only : enumerationComponentTypeType , enumerationMassTypeType + use :: Galactic_Structure_Options , only : enumerationComponentTypeType , enumerationMassTypeType , enumerationWeightByType use :: Hashes , only : doubleHash , genericHash use :: Histories , only : history , longIntegerHistory use :: IO_HDF5 , only : hdf5Object @@ -36,15 +36,17 @@ module Galacticus_Nodes use :: ISO_Varying_String , only : varying_string use :: Kepler_Orbits , only : keplerOrbit use :: Kind_Numbers , only : kind_int8 + use :: Mass_Distributions , only : massDistributionClass use :: Merger_Trees_Evolve_Deadlock_Status, only : enumerationDeadlockStatusType - use :: Numerical_Constants_Astronomical , only : gigaYear , luminosityZeroPointAB , massSolar, megaParsec + use :: Numerical_Constants_Astronomical , only : gigaYear , luminosityZeroPointAB , massSolar , megaParsec use :: Numerical_Constants_Prefixes , only : kilo use :: Numerical_Random_Numbers , only : randomNumberGeneratorClass use :: Stellar_Luminosities_Structure , only : stellarLuminosities use :: Tensors , only : tensorNullR2D3Sym , tensorRank2Dimension3Symmetric private - public :: nodeClassHierarchyInitialize, nodeClassHierarchyFinalize, Galacticus_Nodes_Unique_ID_Set, interruptTask , & - & nodeEventBuildFromRaw , propertyEvaluate , propertyActive , propertyInactive + public :: nodeClassHierarchyInitialize , nodeClassHierarchyFinalize, Galacticus_Nodes_Unique_ID_Set, interruptTask , & + & nodeEventBuildFromRaw , propertyEvaluate , propertyActive , propertyInactive, & + & massDistributionCalculationReset, massDistributionsLast , massDistributionsDestroy type, public :: treeNodeList !!{ @@ -241,6 +243,21 @@ end function universeEventTask integer , public :: rateComputeState =propertyTypeActive !$omp threadprivate(rateComputeState) + ! Memoized massDistributions + type :: massDistributionArray + private + integer(kind_int8 ) :: uniqueID = -huge(kind_int8) + type (enumerationComponentTypeType) :: componentType + type (enumerationMassTypeType ) :: massType + type (enumerationWeightByType ) :: weightBy + integer :: weightIndex + class (massDistributionClass ), pointer :: massDistribution_ => null( ) + end type massDistributionArray + integer , parameter :: massDistributionsCount=20 + integer :: massDistributionsLast = 0 + type (massDistributionArray), dimension(massDistributionsCount) :: massDistributions__ + !$omp threadprivate(massDistributions__,massDistributionsLast) + ! Define a constructor for treeNodes. interface treeNode module procedure Tree_Node_Constructor @@ -1318,75 +1335,35 @@ A null {\normalfont \ttfamily tensorRank2Dimension3Symmetric} function for {\nor return end function Node_Component_Null_TensorR2D3_InOut - double precision function Node_Component_Enclosed_Mass_Null(self,radius,componentType,massType,weightBy,weightIndex) - !!{ - A null implementation of the enclosed mass in a component. Always returns zero. - !!} - use :: Galactic_Structure_Options, only : enumerationComponentTypeType, enumerationMassTypeType, enumerationWeightByType - implicit none - class (nodeComponent ), intent(inout) :: self - type (enumerationComponentTypeType), intent(in ) :: componentType - type (enumerationMassTypeType ), intent(in ) :: massType - type (enumerationWeightByType ), intent(in ) :: weightBy - integer , intent(in ) :: weightIndex - double precision , intent(in ) :: radius - !$GLC attributes unused :: self, radius, componentType, massType, weightBy, weightIndex - - Node_Component_Enclosed_Mass_Null=0.0d0 - return - end function Node_Component_Enclosed_Mass_Null - - function Node_Component_Acceleration_Null(self,positionCartesian,componentType,massType) - !!{ - A null implementation of the acceleration due to a component. Always returns zero. - !!} - use :: Galactic_Structure_Options, only : enumerationComponentTypeType, enumerationMassTypeType - implicit none - double precision , dimension(3) :: Node_Component_Acceleration_Null - class (nodeComponent ) , intent(inout) :: self - type (enumerationComponentTypeType) , intent(in ) :: componentType - type (enumerationMassTypeType ) , intent(in ) :: massType - double precision , dimension(3), intent(in ) :: positionCartesian - !$GLC attributes unused :: self, positionCartesian, componentType, massType - - Node_Component_Acceleration_Null=0.0d0 - return - end function Node_Component_Acceleration_Null - - function Node_Component_Chandrasekhar_Integral_Null(self,nodeSatellite,positionCartesian,velocityCartesian,componentType,massType) + function Node_Component_Mass_Distribution_Null(self,componentType,massType,weightBy,weightIndex) result(massDistribution_) !!{ - A null implementation of the acceleration due to a component. Always returns zero. + A null implementation of the mass distribution factory for a component. Always returns null. !!} - use :: Galactic_Structure_Options, only : enumerationComponentTypeType, enumerationMassTypeType + use :: Galactic_Structure_Options, only : enumerationWeightByType, enumerationComponentTypeType, enumerationMassTypeType implicit none - double precision , dimension(3) :: Node_Component_Chandrasekhar_Integral_Null - class (nodeComponent ) , intent(inout) :: self - type (treeNode ) , intent(inout) :: nodeSatellite - type (enumerationComponentTypeType) , intent(in ) :: componentType - type (enumerationMassTypeType ) , intent(in ) :: massType - double precision , dimension(3), intent(in ) :: positionCartesian , velocityCartesian - !$GLC attributes unused :: self, nodeSatellite, positionCartesian, velocityCartesian, componentType, massType + class (massDistributionClass ), pointer :: massDistribution_ + class (nodeComponent ), intent(inout) :: self + type (enumerationComponentTypeType), intent(in ), optional :: componentType + type (enumerationMassTypeType ), intent(in ), optional :: massType + type (enumerationWeightByType ), intent(in ), optional :: weightBy + integer , intent(in ), optional :: weightIndex + !$GLC attributes unused :: self, componentType, massType, weightBy, weightIndex - Node_Component_Chandrasekhar_Integral_Null=0.0d0 + massDistribution_ => null() return - end function Node_Component_Chandrasekhar_Integral_Null + end function Node_Component_Mass_Distribution_Null - function Node_Component_Tidal_Tensor_Null(self,positionCartesian,componentType,massType) + double precision function Node_Component_Mass_Baryonic_Null(self) !!{ - A null implementation of the tidal tensor due to a component. Always returns zero. + A null implementation of the total baryonic mass distribution. Always returns zero. !!} - use :: Galactic_Structure_Options, only : enumerationComponentTypeType, enumerationMassTypeType implicit none - type (tensorRank2Dimension3Symmetric) :: Node_Component_Tidal_Tensor_Null - class (nodeComponent ) , intent(inout) :: self - type (enumerationComponentTypeType ) , intent(in ) :: componentType - type (enumerationMassTypeType ) , intent(in ) :: massType - double precision , dimension(3), intent(in ) :: positionCartesian - !$GLC attributes unused :: self, positionCartesian, componentType, massType + class(nodeComponent), intent(inout):: self + !$GLC attributes unused :: self - Node_Component_Tidal_Tensor_Null=tensorNullR2D3Sym + Node_Component_Mass_Baryonic_Null=0.0d0 return - end function Node_Component_Tidal_Tensor_Null + end function Node_Component_Mass_Baryonic_Null double precision function Node_Component_Density_Null(self,positionSpherical,componentType,massType,weightBy,weightIndex) !!{ @@ -1442,55 +1419,6 @@ double precision function Node_Component_Surface_Density_Null(self,positionCylin return end function Node_Component_Surface_Density_Null - double precision function Node_Component_Potential_Null(self,radius,componentType,massType,status) - !!{ - A null implementation of the gravitational potential in a component. Always returns zero. - !!} - use :: Galactic_Structure_Options, only : enumerationComponentTypeType, enumerationMassTypeType, enumerationStructureErrorCodeType - implicit none - class (nodeComponent ), intent(inout) :: self - type (enumerationComponentTypeType ), intent(in ) :: componentType - type (enumerationMassTypeType ), intent(in ) :: massType - double precision , intent(in ) :: radius - type (enumerationStructureErrorCodeType), intent(inout), optional :: status - !$GLC attributes unused :: self, radius, componentType, massType, status - - Node_Component_Potential_Null=0.0d0 - return - end function Node_Component_Potential_Null - - double precision function Node_Component_Rotation_Curve_Null(self,radius,componentType,massType) - !!{ - A null implementation of the rotation curve due to a component. Always returns zero. - !!} - use :: Galactic_Structure_Options, only : enumerationComponentTypeType, enumerationMassTypeType - implicit none - class (nodeComponent ), intent(inout) :: self - type (enumerationComponentTypeType), intent(in ) :: componentType - type (enumerationMassTypeType ), intent(in ) :: massType - double precision , intent(in ) :: radius - !$GLC attributes unused :: self, radius, componentType, massType - - Node_Component_Rotation_Curve_Null=0.0d0 - return - end function Node_Component_Rotation_Curve_Null - - double precision function Node_Component_Rotation_Curve_Gradient_Null(self,radius,componentType,massType) - !!{ - A null implementation of the gradient of the rotation curve due to a component. Always returns zero. - !!} - use :: Galactic_Structure_Options, only : enumerationComponentTypeType, enumerationMassTypeType - implicit none - class (nodeComponent ), intent(inout) :: self - type (enumerationComponentTypeType), intent(in ) :: componentType - type (enumerationMassTypeType ), intent(in ) :: massType - double precision , intent(in ) :: radius - !$GLC attributes unused :: self, radius, componentType, massType - - Node_Component_Rotation_Curve_Gradient_Null=0.0d0 - return - end function Node_Component_Rotation_Curve_Gradient_Null - ! Simple Boolean functions. logical function Boolean_False() !!{ @@ -1929,5 +1857,40 @@ logical function propertyEvaluate(propertyType,propertyIsInactive) & (propertyType == propertyTypeInactive .and. propertyIsInactive) return end function propertyEvaluate + + subroutine massDistributionCalculationReset(massDistributionsLast,node,uniqueID) + !!{ + Reset the memoized {\normalfont \ttfamily massDistribution} due to a {\normalfont \ttfamily calculationReset} event. + !!} + implicit none + integer , intent(inout) :: massDistributionsLast + type (treeNode ), intent(inout) :: node + integer(kind_int8), intent(in ) :: uniqueID + integer :: i + !$GLC attributes unused :: massDistributionsLast, node, uniqueID + + do i=1,massDistributionsCount + !![ + + !!] + massDistributions__(i)%uniqueID=-huge(kind_int8) + end do + return + end subroutine massDistributionCalculationReset + + subroutine massDistributionsDestroy() + !!{ + Destroy memoized {\normalfont \ttfamily massDistributions}. + !!} + implicit none + integer :: i + + do i=1,massDistributionsCount + !![ + + !!] + end do + return + end subroutine massDistributionsDestroy end module Galacticus_Nodes diff --git a/source/objects.nodes.components.F90 b/source/objects.nodes.components.F90 index c72945eaff..2cb6305c14 100644 --- a/source/objects.nodes.components.F90 +++ b/source/objects.nodes.components.F90 @@ -68,7 +68,8 @@ subroutine Node_Components_Thread_Initialize(parameters) Perform per-thread initialization tasks for node components. !!} use :: Input_Parameters, only : inputParameters - use :: Events_Hooks , only : eventsHooksAtLevelToAllLevels + use :: Events_Hooks , only : eventsHooksAtLevelToAllLevels , calculationResetEvent , openMPThreadBindingAllLevels + use :: Galacticus_Nodes, only : massDistributionCalculationReset, massDistributionsDestroy, massDistributionsLast !![ !!] @@ -91,6 +92,10 @@ subroutine Node_Components_Thread_Initialize(parameters) !![ !!] + ! Attach to an event that will be used to reset massDistributions of treeNodes during evolution. + call calculationResetEvent%attach(massDistributionsLast,massDistributionCalculationReset,openMPThreadBindingAllLevels,label='massDistribution') + call massDistributionsDestroy() + ! Restore event hooking to standard behavior. call eventsHooksAtLevelToAllLevels(.false.) end if initializationThreadCount=initializationThreadCount+1 @@ -118,6 +123,8 @@ subroutine Node_Components_Thread_Uninitialize() !![ !!] + use :: Events_Hooks , only : calculationResetEvent + use :: Galacticus_Nodes, only : massDistributionCalculationReset, massDistributionsLast implicit none initializationThreadCount=initializationThreadCount-1 @@ -129,6 +136,7 @@ subroutine Node_Components_Thread_Uninitialize() !![ !!] + if (calculationResetEvent%isAttached(massDistributionsLast,massDistributionCalculationReset)) call calculationResetEvent%detach(massDistributionsLast,massDistributionCalculationReset) end if return end subroutine Node_Components_Thread_Uninitialize diff --git a/source/objects.nodes.components.black_hole.non_central.F90 b/source/objects.nodes.components.black_hole.non_central.F90 index f2a3cc0be3..86a2006d33 100644 --- a/source/objects.nodes.components.black_hole.non_central.F90 +++ b/source/objects.nodes.components.black_hole.non_central.F90 @@ -29,7 +29,6 @@ module Node_Component_Black_Hole_Noncentral use :: Black_Hole_Binary_Recoil_Velocities, only : blackHoleBinaryRecoilClass use :: Black_Hole_Binary_Separations , only : blackHoleBinarySeparationGrowthRateClass use :: Dark_Matter_Halo_Scales , only : darkMatterHaloScaleClass - use :: Galactic_Structure , only : galacticStructureClass implicit none private public :: Node_Component_Black_Hole_Noncentral_Rate_Compute , Node_Component_Black_Hole_Noncentral_Scale_Set , & @@ -63,8 +62,7 @@ module Node_Component_Black_Hole_Noncentral class(blackHoleBinaryRecoilClass ), pointer :: blackHoleBinaryRecoil_ class(blackHoleBinaryMergerClass ), pointer :: blackHoleBinaryMerger_ class(blackHoleBinarySeparationGrowthRateClass), pointer :: blackHoleBinarySeparationGrowthRate_ - class(galacticStructureClass ), pointer :: galacticStructure_ - !$omp threadprivate(darkMatterHaloScale_,blackHoleBinaryRecoil_,blackHoleBinaryMerger_,blackHoleBinarySeparationGrowthRate_,galacticStructure_) + !$omp threadprivate(darkMatterHaloScale_,blackHoleBinaryRecoil_,blackHoleBinaryMerger_,blackHoleBinarySeparationGrowthRate_) ! Option specifying whether the triple black hole interaction should be used. logical :: tripleInteraction @@ -129,7 +127,6 @@ subroutine Node_Component_Black_Hole_Noncentral_Thread_Initialize(parameters) - !!] end if return @@ -153,7 +150,6 @@ subroutine Node_Component_Black_Hole_Noncentral_Thread_Uninitialize() - !!] end if return @@ -494,35 +490,43 @@ logical function Node_Component_Black_Hole_Noncentral_Recoil_Escapes(node,veloci !!{ Return true if the given recoil velocity is sufficient to eject a black hole from the halo. !!} + use :: Coordinates , only : coordinateSpherical , assignment(=) + use :: Mass_Distributions , only : massDistributionClass use :: Galactic_Structure_Options, only : componentTypeBlackHole use :: Galacticus_Nodes , only : treeNode implicit none - type (treeNode), intent(inout) :: node - double precision , intent(in ) :: velocityRecoil , radius - logical , intent(in ) :: ignoreCentralBlackHole - double precision :: potentialCentral , potentialCentralSelf, & - & potentialHalo , potentialHaloSelf - + type (treeNode ), intent(inout) :: node + double precision , intent(in ) :: velocityRecoil , radius + logical , intent(in ) :: ignoreCentralBlackHole + class (massDistributionClass), pointer :: massDistribution_ + double precision :: potential , potentialSelf + type (coordinateSpherical ) :: coordinates , coordinatesVirial + ! Compute relevant potentials. - potentialCentral =galacticStructure_%potential(node,radius ) - potentialHalo =galacticStructure_%potential(node,darkMatterHaloScale_%radiusVirial(node) ) + coordinates = [ radius ,0.0d0,0.0d0] + coordinatesVirial = [darkMatterHaloScale_%radiusVirial(node),0.0d0,0.0d0] + massDistribution_ => node %massDistribution ( ) + potential = massDistribution_%potentialDifference(coordinates,coordinatesVirial) + !![ + + !!] if (ignoreCentralBlackHole) then ! Compute potential of central black hole to be subtracted off of total value. - potentialCentralSelf=galacticStructure_%potential(node,radius ,componentType=componentTypeBlackHole) - potentialHaloSelf =galacticStructure_%potential(node,darkMatterHaloScale_%radiusVirial(node),componentType=componentTypeBlackHole) + massDistribution_ => node %massDistribution (componentType=componentTypeBlackHole ) + potentialSelf = massDistribution_%potentialDifference( coordinates ,coordinatesVirial) + !![ + + !!] else ! No correction for central black hole as it is to be included. - potentialCentralSelf=0.0d0 - potentialHaloSelf =0.0d0 + potentialSelf=0.0d0 end if ! Evaluate the escape condition. Node_Component_Black_Hole_Noncentral_Recoil_Escapes= & - & +0.5d0*velocityRecoil **2 & - & + potentialCentral & - & - potentialCentralSelf & + & +0.5d0*velocityRecoil**2 & + & + potential & & > & - & + potentialHalo & - & - potentialHaloSelf + & + potentialSelf return end function Node_Component_Black_Hole_Noncentral_Recoil_Escapes @@ -544,7 +548,7 @@ subroutine Node_Component_Black_Hole_NonCentral_State_Store(stateFile,gslStateFi call displayMessage('Storing state for: componentBlackHole -> nonCentral',verbosity=verbosityLevelInfo) !![ - + !!] return end subroutine Node_Component_Black_Hole_NonCentral_State_Store @@ -567,7 +571,7 @@ subroutine Node_Component_Black_Hole_NonCentral_State_Restore(stateFile,gslState call displayMessage('Retrieving state for: componentBlackHole -> nonCentral',verbosity=verbosityLevelInfo) !![ - + !!] return end subroutine Node_Component_Black_Hole_NonCentral_State_Restore diff --git a/source/objects.nodes.components.black_hole.simple.F90 b/source/objects.nodes.components.black_hole.simple.F90 index ddc6933937..8f535cb0ba 100644 --- a/source/objects.nodes.components.black_hole.simple.F90 +++ b/source/objects.nodes.components.black_hole.simple.F90 @@ -58,9 +58,8 @@ module Node_Component_Black_Hole_Simple - - - + + objects.nodes.components.black_hole.simple.bound_functions.inc @@ -226,7 +225,7 @@ subroutine Node_Component_Black_Hole_Simple_Scale_Set(node) if (.not.defaultBlackHoleComponent%simpleIsActive()) return ! Get the black hole component. blackHole => node%blackHole() - ! Ensure that it is of the standard class. + ! Ensure that it is of the simple class. select type (blackHole) class is (nodeComponentBlackHoleSimple) ! Get the spheroid component. diff --git a/source/objects.nodes.components.black_hole.simple.bound_functions.Inc b/source/objects.nodes.components.black_hole.simple.bound_functions.Inc index d048b94f64..6004480194 100644 --- a/source/objects.nodes.components.black_hole.simple.bound_functions.Inc +++ b/source/objects.nodes.components.black_hole.simple.bound_functions.Inc @@ -21,103 +21,63 @@ Contains custom functions for the simple black hole component. !!} -double precision function Node_Component_Black_Hole_Simple_Enclosed_Mass(self,radius,componentType,massType,weightBy,weightIndex) +double precision function Node_Component_Black_Hole_Simple_Mass_Baryonic(self) result(massBaryonic) !!{ - Computes the mass within a given radius for a central black hole. Black hole is treated as a point mass. + Return the baryonic mass for the simple black hole component. !!} - use :: Galactic_Structure_Options, only : componentTypeAll , componentTypeBlackHole, massTypeAll , massTypeBlackHole , & - & weightByMass , massTypeGalactic , enumerationComponentTypeType, enumerationMassTypeType, & - & enumerationWeightByType implicit none - class (nodeComponentBlackHoleSimple), intent(inout) :: self - type (enumerationComponentTypeType), intent(in ) :: componentType - type (enumerationMassTypeType ), intent(in ) :: massType - type (enumerationWeightByType ), intent(in ) :: weightBy - integer , intent(in ) :: weightIndex - double precision , intent(in ) :: radius - !$GLC attributes unused :: weightIndex - - ! Set zero enclosed mass by default. - Node_Component_Black_Hole_Simple_Enclosed_Mass=0.0d0 - ! Return the black hole mass only if massType and componentType are of black hole type. - if (.not.(componentType == componentTypeAll .or. componentType == componentTypeBlackHole)) return - if (.not.(massType == massTypeAll .or. massType == massTypeGalactic .or. massType == massTypeBlackHole )) return - if (.not.(weightBy == weightByMass )) return - if ( radius < 0.0d0 ) return - ! Return the mass of the black hole. - Node_Component_Black_Hole_Simple_Enclosed_Mass=self%mass() - return -end function Node_Component_Black_Hole_Simple_Enclosed_Mass + class(nodeComponentBlackHoleSimple), intent(inout) :: self - function Node_Component_Black_Hole_Simple_Acceleration(self,positionCartesian,componentType,massType) - !!{ - Computes the acceleration due to a central black hole. Black hole is treated as a point mass. - !!} - use :: Galactic_Structure_Options , only : componentTypeAll , componentTypeBlackHole , massTypeAll , massTypeBlackHole, & - & massTypeGalactic , enumerationComponentTypeType, enumerationMassTypeType - use :: Numerical_Constants_Astronomical, only : gravitationalConstantGalacticus, gigaYear , megaParsec - use :: Numerical_Constants_Prefixes , only : kilo - implicit none - double precision , dimension(3) :: Node_Component_Black_Hole_Simple_Acceleration - class (nodeComponentBlackHoleSimple), intent(inout) :: self - type (enumerationComponentTypeType), intent(in ) :: componentType - type (enumerationMassTypeType ), intent(in ) :: massType - double precision , intent(in ), dimension(3) :: positionCartesian - double precision :: radius - - ! Set zero enclosed acceleration by default. - Node_Component_Black_Hole_Simple_Acceleration=0.0d0 - ! Return the black hole mass only if massType and componentType are of black hole type. - if (.not.(componentType == componentTypeAll .or. componentType == componentTypeBlackHole)) return - if (.not.(massType == massTypeAll .or. massType == massTypeGalactic .or. massType == massTypeBlackHole )) return - radius=sqrt(sum(positionCartesian**2)) - if ( radius < 0.0d0 ) return - ! Compute the acceleration. - Node_Component_Black_Hole_Simple_Acceleration=-kilo & - & *gigaYear & - & /megaParsec & - & *gravitationalConstantGalacticus & - & *self%mass() & - & *positionCartesian & - & /radius**3 + massBaryonic=max(0.0d0,self%mass()) return -end function Node_Component_Black_Hole_Simple_Acceleration +end function Node_Component_Black_Hole_Simple_Mass_Baryonic -function Node_Component_Black_Hole_Simple_Tidal_Tensor(self,positionCartesian,componentType,massType) +function Node_Component_Black_Hole_Simple_Mass_Distribution(self,componentType,massType,weightBy,weightIndex) result(massDistribution_) !!{ - Computes the acceleration due to a central black hole. Black hole is treated as a point mass. + Return the mass distribution for the simple black hole component. !!} - use :: Galactic_Structure_Options , only : componentTypeAll , componentTypeBlackHole , massTypeAll , massTypeBlackHole, & - & massTypeGalactic , enumerationComponentTypeType, enumerationMassTypeType - use :: Numerical_Constants_Math , only : Pi - use :: Numerical_Constants_Astronomical, only : gravitationalConstantGalacticus - use :: Tensors , only : tensorRank2Dimension3Symmetric , tensorNullR2D3Sym , tensorIdentityR2D3Sym , assignment(=) , & - & operator(*) - use :: Vectors , only : Vector_Outer_Product + use :: Galactic_Structure_Options, only : componentTypeBlackHole , massTypeBlackHole , weightByMass , weightByLuminosity , & + & enumerationWeightByType , enumerationComponentTypeType, enumerationMassTypeType + use :: Mass_Distributions , only : massDistributionBlackHole, massDistributionClass , massDistributionMatches_, kinematicsDistributionLocal implicit none - type (tensorRank2Dimension3Symmetric) :: Node_Component_Black_Hole_Simple_Tidal_Tensor - class (nodeComponentBlackHoleSimple ), intent(inout) :: self - type (enumerationComponentTypeType ), intent(in ) :: componentType - type (enumerationMassTypeType ), intent(in ) :: massType - double precision , intent(in ), dimension(3) :: positionCartesian - double precision :: radius - type (tensorRank2Dimension3Symmetric) :: positionTensor - - ! Set zero enclosed acceleration by default. - Node_Component_Black_Hole_Simple_Tidal_Tensor=tensorNullR2D3Sym - ! Return the black hole mass only if massType and componentType are of black hole type. - if (.not.(componentType == componentTypeAll .or. componentType == componentTypeBlackHole)) return - if (.not.(massType == massTypeAll .or. massType == massTypeGalactic .or. massType == massTypeBlackHole )) return - radius=sqrt(sum(positionCartesian**2)) - if ( radius < 0.0d0 ) return - ! Compute the tidal tensor. - if (self%radialPosition() <= 0.0d0) then - positionTensor = Vector_Outer_Product(positionCartesian,symmetrize=.true.) - Node_Component_Black_Hole_Simple_Tidal_Tensor=+gravitationalConstantGalacticus & - & *( & - & -(self%mass() /radius**3)*tensorIdentityR2D3Sym & - & +(self%mass()*3.0d0/radius**5)*positionTensor & - & ) + class (massDistributionClass ), pointer :: massDistribution_ + type (kinematicsDistributionLocal ), pointer :: kinematicsDistribution_ + class (nodeComponentBlackHoleSimple), intent(inout) :: self + type (enumerationComponentTypeType), intent(in ), optional :: componentType + type (enumerationMassTypeType ), intent(in ), optional :: massType + type (enumerationWeightByType ), intent(in ), optional :: weightBy + integer , intent(in ), optional :: weightIndex + !$GLC attributes unused :: weightIndex + !![ + + !!] + + if ( & + & massDistributionMatches_(componentTypeBlackHole,massTypeBlackHole,componentType,massType) & + & .and. & + & weightBy_ == weightByMass & + & .and. & + & self%mass() > 0.0d0 & + & ) then + ! Create a black hole mass distribution. + allocate(massDistributionBlackHole :: massDistribution_) + select type (massDistribution_) + type is (massDistributionBlackHole) + !![ + + !!] + end select + ! Construct the kinematic distribution. + allocate(kinematicsDistribution_) + !![ + + !!] + call massDistribution_%setKinematicsDistribution(kinematicsDistribution_) + !![ + + !!] + else + massDistribution_ => null() end if return -end function Node_Component_Black_Hole_Simple_Tidal_Tensor +end function Node_Component_Black_Hole_Simple_Mass_Distribution diff --git a/source/objects.nodes.components.black_hole.simple.structure.F90 b/source/objects.nodes.components.black_hole.simple.structure.F90 deleted file mode 100644 index 849e93dfbc..0000000000 --- a/source/objects.nodes.components.black_hole.simple.structure.F90 +++ /dev/null @@ -1,168 +0,0 @@ -!! Copyright 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, -!! 2019, 2020, 2021, 2022, 2023, 2024 -!! Andrew Benson -!! -!! This file is part of Galacticus. -!! -!! Galacticus is free software: you can redistribute it and/or modify -!! it under the terms of the GNU General Public License as published by -!! the Free Software Foundation, either version 3 of the License, or -!! (at your option) any later version. -!! -!! Galacticus is distributed in the hope that it will be useful, -!! but WITHOUT ANY WARRANTY; without even the implied warranty of -!! MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -!! GNU General Public License for more details. -!! -!! You should have received a copy of the GNU General Public License -!! along with Galacticus. If not, see . - -!!{ -Contains a module which implements the structure tasks for the simple black hole node component. -!!} - -module Node_Component_Black_Hole_Simple_Structure - !!{ - Implements the structure tasks for the simple black hole node component. - !!} - implicit none - private - public :: Node_Component_Black_Hole_Simple_Rotation_Curve, Node_Component_Black_Hole_Simple_Rotation_Curve_Gradient, & - & Node_Component_Black_Hole_Simple_Potential - -contains - - !![ - - Node_Component_Black_Hole_Simple_Rotation_Curve - - !!] - double precision function Node_Component_Black_Hole_Simple_Rotation_Curve(node,radius,componentType,massType) - !!{ - Computes the rotation curve for the central black hole. Assumes a point mass black hole with a Keplerian rotation curve, - \emph{except} that the rotation speed is limited to never exceed the speed of light. - !!} - use :: Black_Hole_Fundamentals , only : Black_Hole_Gravitational_Radius - use :: Galactic_Structure_Options , only : weightByMass , weightIndexNull , enumerationComponentTypeType, enumerationMassTypeType - use :: Galacticus_Nodes , only : nodeComponentBlackHole , nodeComponentBlackHoleSimple, treeNode - use :: Numerical_Constants_Astronomical, only : gravitationalConstantGalacticus - use :: Numerical_Constants_Physical , only : speedLight - use :: Numerical_Constants_Prefixes , only : milli - implicit none - type (treeNode ), intent(inout) :: node - type (enumerationComponentTypeType ), intent(in ) :: componentType - type (enumerationMassTypeType ), intent(in ) :: massType - double precision , intent(in ) :: radius - class (nodeComponentBlackHole ) , pointer :: blackHole - double precision :: componentMass - - ! Set to zero by default. - Node_Component_Black_Hole_Simple_Rotation_Curve=0.0d0 - ! Get the black hole component and check that it is of the simple class. - blackHole => node%blackHole() - select type (blackHole) - class is (nodeComponentBlackHoleSimple) - ! Check if the radius exceeds the gravitational radius. - if (radius > Black_Hole_Gravitational_Radius(blackHole)/(milli*speedLight)**2) then - ! Radius is larger than the gravitational radius - compute the rotation speed. - componentMass=blackHole%enclosedMass(radius,componentType,massType,weightByMass& - &,weightIndexNull) - if (componentMass > 0.0d0) Node_Component_Black_Hole_Simple_Rotation_Curve=sqrt(gravitationalConstantGalacticus& - &*componentMass/radius) - else - ! Radius is less than the gravitational radius - return the speed of light. - Node_Component_Black_Hole_Simple_Rotation_Curve=speedLight*milli - end if - end select - return - end function Node_Component_Black_Hole_Simple_Rotation_Curve - - !![ - - Node_Component_Black_Hole_Simple_Rotation_Curve_Gradient - - !!] - double precision function Node_Component_Black_Hole_Simple_Rotation_Curve_Gradient(node,radius,componentType,massType) - !!{ - Computes the rotation curve gradient for the central black hole. Assumes a point mass black hole with a Keplerian - rotation curve, \emph{except} that the rotation speed is limited to never exceed the speed of light. - !!} - use :: Black_Hole_Fundamentals , only : Black_Hole_Gravitational_Radius - use :: Galactic_Structure_Options , only : componentTypeAll , componentTypeBlackHole , massTypeAll , massTypeBlackHole , & - & weightByMass , weightIndexNull , enumerationComponentTypeType, enumerationMassTypeType - use :: Galacticus_Nodes , only : nodeComponentBlackHole , nodeComponentBlackHoleSimple, treeNode - use :: Numerical_Constants_Astronomical, only : gravitationalConstantGalacticus - implicit none - type (treeNode ), intent(inout) :: node - type (enumerationComponentTypeType ), intent(in ) :: componentType - type (enumerationMassTypeType ), intent(in ) :: massType - double precision , intent(in ) :: radius - class (nodeComponentBlackHole ) , pointer :: blackHole - double precision :: componentMass - - ! Set to zero by default. - Node_Component_Black_Hole_Simple_Rotation_Curve_Gradient=0.0d0 - if (.not.(componentType == componentTypeAll .or. componentType == componentTypeBlackHole)) return - if (.not.(massType == massTypeAll .or. massType == massTypeBlackHole )) return - if ( radius <= 0.0d0 ) return - ! Get the black hole component and check that it is of the simple class. - blackHole => node%blackHole() - select type (blackHole) - class is (nodeComponentBlackHoleSimple) - componentMass=blackHole%enclosedMass(radius,componentType,massType,weightByMass,weightIndexNull) - if (componentMass ==0.0d0 ) return - if (radius > Black_Hole_Gravitational_Radius(blackHole)) then - Node_Component_Black_Hole_Simple_Rotation_Curve_Gradient= & - & -gravitationalConstantGalacticus & - & *componentMass & - & /radius**2 - else - Node_Component_Black_Hole_Simple_Rotation_Curve_Gradient=0.0d0 - end if - end select - return - end function Node_Component_Black_Hole_Simple_Rotation_Curve_Gradient - - !![ - - Node_Component_Black_Hole_Simple_Potential - - !!] - double precision function Node_Component_Black_Hole_Simple_Potential(node,radius,componentType,massType,status) - !!{ - Compute the gravitational potential due to a black hole. - !!} - use :: Black_Hole_Fundamentals , only : Black_Hole_Gravitational_Radius - use :: Galactic_Structure_Options , only : componentTypeAll , componentTypeBlackHole , massTypeAll , massTypeBlackHole , & - & weightByMass , weightIndexNull , enumerationComponentTypeType, enumerationMassTypeType, & - & enumerationStructureErrorCodeType - use :: Galacticus_Nodes , only : nodeComponentBlackHole , nodeComponentBlackHoleSimple, treeNode - use :: Numerical_Constants_Astronomical, only : gravitationalConstantGalacticus - implicit none - type (treeNode ), intent(inout) :: node - type (enumerationComponentTypeType ), intent(in ) :: componentType - type (enumerationMassTypeType ), intent(in ) :: massType - double precision , intent(in ) :: radius - type (enumerationStructureErrorCodeType), intent(inout), optional :: status - class (nodeComponentBlackHole ) , pointer :: blackHole - double precision :: componentMass - !$GLC attributes unused :: status - - ! Set to zero by default. - Node_Component_Black_Hole_Simple_Potential=0.0d0 - if (.not.(componentType == componentTypeAll .or. componentType == componentTypeBlackHole)) return - if (.not.(massType == massTypeAll .or. massType == massTypeBlackHole )) return - ! Get the black hole component and check that it is of the simple class. - blackHole => node%blackHole() - select type (blackHole) - class is (nodeComponentBlackHoleSimple) - if (Black_Hole_Gravitational_Radius(blackHole) <= 0.0d0) return - ! Compute the potential - limit the radius to the gravitational radius to avoid divergent potentials. - componentMass=blackHole%enclosedMass(radius,componentType,massType,weightByMass,weightIndexNull) - Node_Component_Black_Hole_Simple_Potential=-gravitationalConstantGalacticus*componentMass/max(radius & - &,Black_Hole_Gravitational_Radius(blackHole)) - end select - return - end function Node_Component_Black_Hole_Simple_Potential - -end module Node_Component_Black_Hole_Simple_Structure diff --git a/source/objects.nodes.components.black_hole.standard.F90 b/source/objects.nodes.components.black_hole.standard.F90 index 7cc9192a96..98875e824a 100644 --- a/source/objects.nodes.components.black_hole.standard.F90 +++ b/source/objects.nodes.components.black_hole.standard.F90 @@ -33,7 +33,6 @@ module Node_Component_Black_Hole_Standard use :: Black_Hole_Accretion_Rates , only : blackHoleAccretionRateClass use :: Cosmology_Parameters , only : cosmologyParametersClass use :: Dark_Matter_Halo_Scales , only : darkMatterHaloScaleClass - use :: Galactic_Structure , only : galacticStructureClass implicit none private public :: Node_Component_Black_Hole_Standard_Rate_Compute , Node_Component_Black_Hole_Standard_Scale_Set , & @@ -104,9 +103,8 @@ module Node_Component_Black_Hole_Standard - - - + + objects.nodes.components.black_hole.standard.bound_functions.inc @@ -120,8 +118,7 @@ module Node_Component_Black_Hole_Standard class(blackHoleBinaryMergerClass ), pointer :: blackHoleBinaryMerger_ class(blackHoleBinarySeparationGrowthRateClass), pointer :: blackHoleBinarySeparationGrowthRate_ class(darkMatterHaloScaleClass ), pointer :: darkMatterHaloScale_ - class(galacticStructureClass ), pointer :: galacticStructure_ - !$omp threadprivate(accretionDisks_,blackHoleAccretionRate_,blackHoleBinaryRecoil_,blackHoleBinaryInitialSeparation_,blackHoleBinaryMerger_,blackHoleBinarySeparationGrowthRate_,darkMatterHaloScale_,galacticStructure_) + !$omp threadprivate(accretionDisks_,blackHoleAccretionRate_,blackHoleBinaryRecoil_,blackHoleBinaryInitialSeparation_,blackHoleBinaryMerger_,blackHoleBinarySeparationGrowthRate_,darkMatterHaloScale_) ! Accretion model parameters. ! Enhancement factors for the accretion rate. @@ -270,9 +267,9 @@ subroutine Node_Component_Black_Hole_Standard_Thread_Initialize(parameters) !!{ Initializes the tree node standard black hole module. !!} - use :: Events_Hooks , only : satelliteMergerEvent , openMPThreadBindingAtLevel, dependencyRegEx, dependencyDirectionBefore - use :: Galacticus_Nodes, only : defaultBlackHoleComponent - use :: Input_Parameters, only : inputParameter , inputParameters + use :: Events_Hooks , only : satelliteMergerEvent , openMPThreadBindingAtLevel, dependencyRegEx, dependencyDirectionBefore + use :: Galacticus_Nodes , only : defaultBlackHoleComponent + use :: Input_Parameters , only : inputParameter , inputParameters implicit none type(inputParameters), intent(inout) :: parameters type(dependencyRegEx), dimension(1) :: dependencies @@ -291,8 +288,7 @@ subroutine Node_Component_Black_Hole_Standard_Thread_Initialize(parameters) - - !!] + !!] end if return end subroutine Node_Component_Black_Hole_Standard_Thread_Initialize @@ -320,7 +316,6 @@ subroutine Node_Component_Black_Hole_Standard_Thread_Uninitialize() - !!] end if return @@ -599,14 +594,17 @@ logical function Node_Component_Black_Hole_Standard_Recoil_Escapes(node,velocity !!{ Return true if the given recoil velocity is sufficient to eject a black hole from the halo. !!} + use :: Coordinates , only : coordinateSpherical , assignment(=) + use :: Mass_Distributions , only : massDistributionClass use :: Galactic_Structure_Options, only : componentTypeBlackHole use :: Galacticus_Nodes , only : treeNode implicit none - type (treeNode), intent(inout) :: node - double precision , intent(in ) :: velocityRecoil , radius - logical , intent(in ) :: ignoreCentralBlackHole - double precision :: potentialCentral , potentialCentralSelf, & - & potentialHalo , potentialHaloSelf + type (treeNode ), intent(inout) :: node + double precision , intent(in ) :: velocityRecoil , radius + logical , intent(in ) :: ignoreCentralBlackHole + class (massDistributionClass), pointer :: massDistribution_ + double precision :: potential , potentialSelf + type (coordinateSpherical ) :: coordinates , coordinatesVirial ! Return false immediately if the recoil velocity is zero. if (velocityRecoil <= 0.0d0) then @@ -614,25 +612,30 @@ logical function Node_Component_Black_Hole_Standard_Recoil_Escapes(node,velocity return end if ! Compute relevant potentials. - potentialCentral =galacticStructure_%potential(node,radius ) - potentialHalo =galacticStructure_%potential(node,darkMatterHaloScale_%radiusVirial(node) ) + coordinates = [ radius ,0.0d0,0.0d0] + coordinatesVirial = [darkMatterHaloScale_%radiusVirial(node),0.0d0,0.0d0] + massDistribution_ => node %massDistribution ( ) + potential = massDistribution_%potentialDifference(coordinates,coordinatesVirial) + !![ + + !!] if (ignoreCentralBlackHole) then ! Compute potential of central black hole to be subtracted off of total value. - potentialCentralSelf=galacticStructure_%potential(node,radius ,componentType=componentTypeBlackHole) - potentialHaloSelf =galacticStructure_%potential(node,darkMatterHaloScale_%radiusVirial(node),componentType=componentTypeBlackHole) + massDistribution_ => node %massDistribution (componentType=componentTypeBlackHole ) + potentialSelf = massDistribution_%potentialDifference( coordinates ,coordinatesVirial) + !![ + + !!] else ! No correction for central black hole as it is to be included. - potentialCentralSelf=0.0d0 - potentialHaloSelf =0.0d0 + potentialSelf=0.0d0 end if ! Evaluate the escape condition. Node_Component_Black_Hole_Standard_Recoil_Escapes= & - & +0.5d0*velocityRecoil **2 & - & + potentialCentral & - & - potentialCentralSelf & + & +0.5d0*velocityRecoil**2 & + & + potential & & > & - & + potentialHalo & - & - potentialHaloSelf + & + potentialSelf return end function Node_Component_Black_Hole_Standard_Recoil_Escapes @@ -798,7 +801,7 @@ subroutine Node_Component_Black_Hole_Standard_State_Store(stateFile,gslStateFile call displayMessage('Storing state for: componentBlackHole -> standard',verbosity=verbosityLevelInfo) !![ - + !!] return end subroutine Node_Component_Black_Hole_Standard_State_Store @@ -821,7 +824,7 @@ subroutine Node_Component_Black_Hole_Standard_State_Restore(stateFile,gslStateFi call displayMessage('Retrieving state for: componentBlackHole -> standard',verbosity=verbosityLevelInfo) !![ - + !!] return end subroutine Node_Component_Black_Hole_Standard_State_Restore diff --git a/source/objects.nodes.components.black_hole.standard.bound_functions.Inc b/source/objects.nodes.components.black_hole.standard.bound_functions.Inc index e345f0ba11..8a7c164ac9 100644 --- a/source/objects.nodes.components.black_hole.standard.bound_functions.Inc +++ b/source/objects.nodes.components.black_hole.standard.bound_functions.Inc @@ -46,106 +46,63 @@ double precision function Node_Component_Black_Hole_Standard_Spin(self) return end function Node_Component_Black_Hole_Standard_Spin -double precision function Node_Component_Black_Hole_Standard_Enclosed_Mass(self,radius,componentType,massType,weightBy& - &,weightIndex) +double precision function Node_Component_Black_Hole_Standard_Mass_Baryonic(self) result(massBaryonic) !!{ - Computes the mass within a given radius for a central black hole. Black hole is treated as a point mass. + Return the baryonic mass for the standard black hole component. !!} - use :: Galactic_Structure_Options, only : componentTypeAll , componentTypeBlackHole, massTypeAll , massTypeBlackHole , & - & weightByMass , massTypeGalactic , enumerationComponentTypeType, enumerationMassTypeType, & - & enumerationWeightByType implicit none - class (nodeComponentBlackHoleStandard), intent(inout) :: self - type (enumerationComponentTypeType ), intent(in ) :: componentType - type (enumerationMassTypeType ), intent(in ) :: massType - type (enumerationWeightByType ), intent(in ) :: weightBy - integer , intent(in ) :: weightIndex - double precision , intent(in ) :: radius - !$GLC attributes unused :: weightIndex + class(nodeComponentBlackHoleStandard), intent(inout) :: self - ! Set zero enclosed mass by default. - Node_Component_Black_Hole_Standard_Enclosed_Mass=0.0d0 - ! Return if mass is not to be counted, or if radius is negative. - if (.not.(componentType == componentTypeAll .or. componentType == componentTypeBlackHole)) return - if (.not.(massType == massTypeAll .or. massType == massTypeGalactic .or. massType == massTypeBlackHole )) return - if (.not.(weightBy == weightByMass )) return - if ( radius < 0.0d0 ) return - ! Set the mass if the black hole is at the galactic center. - if (self%radialPosition() <= 0.0d0) Node_Component_Black_Hole_Standard_Enclosed_Mass=self%mass() + massBaryonic=max(0.0d0,self%mass()) return -end function Node_Component_Black_Hole_Standard_Enclosed_Mass +end function Node_Component_Black_Hole_Standard_Mass_Baryonic - function Node_Component_Black_Hole_Standard_Acceleration(self,positionCartesian,componentType,massType) +function Node_Component_Black_Hole_Standard_Mass_Distribution(self,componentType,massType,weightBy,weightIndex) result(massDistribution_) !!{ - Computes the acceleration due to a central black hole. Black hole is treated as a point mass. + Return the mass distribution for the standard black hole component. !!} - use :: Galactic_Structure_Options , only : componentTypeAll , componentTypeBlackHole , massTypeAll , massTypeBlackHole, & - & massTypeGalactic , enumerationComponentTypeType, enumerationMassTypeType - use :: Numerical_Constants_Astronomical, only : gravitationalConstantGalacticus, gigaYear , megaParsec - use :: Numerical_Constants_Prefixes , only : kilo + use :: Galactic_Structure_Options, only : componentTypeBlackHole , massTypeBlackHole , weightByMass , weightByLuminosity , & + & enumerationWeightByType , enumerationComponentTypeType, enumerationMassTypeType + use :: Mass_Distributions , only : massDistributionBlackHole, massDistributionClass , massDistributionMatches_, kinematicsDistributionLocal implicit none - double precision , dimension(3) :: Node_Component_Black_Hole_Standard_Acceleration - class (nodeComponentBlackHoleStandard), intent(inout) :: self - type (enumerationComponentTypeType ), intent(in ) :: componentType - type (enumerationMassTypeType ), intent(in ) :: massType - double precision , intent(in ), dimension(3) :: positionCartesian - double precision :: radius - - ! Set zero enclosed acceleration by default. - Node_Component_Black_Hole_Standard_Acceleration=0.0d0 - ! Return the black hole mass only if massType and componentType are of black hole type. - if (.not.(componentType == componentTypeAll .or. componentType == componentTypeBlackHole)) return - if (.not.(massType == massTypeAll .or. massType == massTypeGalactic .or. massType == massTypeBlackHole )) return - radius=sqrt(sum(positionCartesian**2)) - if ( radius < 0.0d0 ) return - ! Compute the acceleration. - if (self%radialPosition() <= 0.0d0) & - & Node_Component_Black_Hole_Standard_Acceleration=-kilo & - & *gigaYear & - & /megaParsec & - & *gravitationalConstantGalacticus & - & *self%mass() & - & *positionCartesian & - & /radius**3 - return -end function Node_Component_Black_Hole_Standard_Acceleration + class (massDistributionClass ), pointer :: massDistribution_ + type (kinematicsDistributionLocal ), pointer :: kinematicsDistribution_ + class (nodeComponentBlackHoleStandard), intent(inout) :: self + type (enumerationComponentTypeType ), intent(in ), optional :: componentType + type (enumerationMassTypeType ), intent(in ), optional :: massType + type (enumerationWeightByType ), intent(in ), optional :: weightBy + integer , intent(in ), optional :: weightIndex + !![ + + !!] + !$GLC attributes unused :: weightIndex, componentType, massType -function Node_Component_Black_Hole_Standard_Tidal_Tensor(self,positionCartesian,componentType,massType) - !!{ - Computes the acceleration due to a central black hole. Black hole is treated as a point mass. - !!} - use :: Galactic_Structure_Options , only : componentTypeAll , componentTypeBlackHole , massTypeAll , massTypeBlackHole, & - & massTypeGalactic , enumerationComponentTypeType, enumerationMassTypeType - use :: Numerical_Constants_Math , only : Pi - use :: Numerical_Constants_Astronomical, only : gravitationalConstantGalacticus - use :: Tensors , only : tensorRank2Dimension3Symmetric , tensorNullR2D3Sym , tensorIdentityR2D3Sym , assignment(=) , & - & operator(*) - use :: Vectors , only : Vector_Outer_Product - implicit none - type (tensorRank2Dimension3Symmetric) :: Node_Component_Black_Hole_Standard_Tidal_Tensor - class (nodeComponentBlackHoleStandard), intent(inout) :: self - type (enumerationComponentTypeType ), intent(in ) :: componentType - type (enumerationMassTypeType ), intent(in ) :: massType - double precision , intent(in ), dimension(3) :: positionCartesian - double precision :: radius - type (tensorRank2Dimension3Symmetric), save :: positionTensor - !$omp threadprivate(positionTensor) - - ! Set zero enclosed acceleration by default. - Node_Component_Black_Hole_Standard_Tidal_Tensor=tensorNullR2D3Sym - ! Return the black hole mass only if massType and componentType are of black hole type. - if (.not.(componentType == componentTypeAll .or. componentType == componentTypeBlackHole)) return - if (.not.(massType == massTypeAll .or. massType == massTypeGalactic .or. massType == massTypeBlackHole )) return - radius=sqrt(sum(positionCartesian**2)) - if ( radius < 0.0d0 ) return - ! Compute the tidal tensor. - if (self%radialPosition() <= 0.0d0) then - positionTensor = Vector_Outer_Product(positionCartesian,symmetrize=.true.) - Node_Component_Black_Hole_Standard_Tidal_Tensor=+gravitationalConstantGalacticus & - & *( & - & -(self%mass() /radius**3)*tensorIdentityR2D3Sym & - & +(self%mass()*3.0d0/radius**5)*positionTensor & - & ) + if ( & + & massDistributionMatches_(componentTypeBlackHole,massTypeBlackHole,componentType,massType) & + & .and. & + & weightBy_ == weightByMass & + & .and. & + & self%mass() > 0.0d0 & + & ) then + ! Create a black hole mass distribution. + allocate(massDistributionBlackHole :: massDistribution_) + select type (massDistribution_) + type is (massDistributionBlackHole) + !![ + + !!] + end select + ! Construct the kinematic distribution. + allocate(kinematicsDistribution_) + !![ + + !!] + call massDistribution_%setKinematicsDistribution(kinematicsDistribution_) + !![ + + !!] + else + massDistribution_ => null() end if return -end function Node_Component_Black_Hole_Standard_Tidal_Tensor +end function Node_Component_Black_Hole_Standard_Mass_Distribution diff --git a/source/objects.nodes.components.black_hole.standard.structure_tasks.F90 b/source/objects.nodes.components.black_hole.standard.structure_tasks.F90 deleted file mode 100644 index 39a029be5e..0000000000 --- a/source/objects.nodes.components.black_hole.standard.structure_tasks.F90 +++ /dev/null @@ -1,170 +0,0 @@ -!! Copyright 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, -!! 2019, 2020, 2021, 2022, 2023, 2024 -!! Andrew Benson -!! -!! This file is part of Galacticus. -!! -!! Galacticus is free software: you can redistribute it and/or modify -!! it under the terms of the GNU General Public License as published by -!! the Free Software Foundation, either version 3 of the License, or -!! (at your option) any later version. -!! -!! Galacticus is distributed in the hope that it will be useful, -!! but WITHOUT ANY WARRANTY; without even the implied warranty of -!! MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -!! GNU General Public License for more details. -!! -!! You should have received a copy of the GNU General Public License -!! along with Galacticus. If not, see . - -!+ Contributions to this file made by: Stéphane Mangeon, Andrew Benson. - -!!{ -Contains a module which implements galactic structure tasks for the standard black hole node component. -!!} - -module Node_Component_Black_Hole_Standard_Structure_Tasks - !!{ - Implements galactic structure tasks for the standard black hole tree node component. - !!} - implicit none - private - public :: Node_Component_Black_Hole_Standard_Rotation_Curve , Node_Component_Black_Hole_Standard_Potential, & - & Node_Component_Black_Hole_Standard_Rotation_Curve_Gradient - -contains - - !![ - - Node_Component_Black_Hole_Standard_Rotation_Curve - - !!] - double precision function Node_Component_Black_Hole_Standard_Rotation_Curve(node,radius,componentType,massType) - !!{ - Computes the rotation curve for the central black hole. Assumes a point mass black hole with a Keplerian rotation curve, - \emph{except} that the rotation speed is limited to never exceed the speed of light. - !!} - use :: Black_Hole_Fundamentals , only : Black_Hole_Gravitational_Radius - use :: Galactic_Structure_Options , only : weightByMass , weightIndexNull , enumerationComponentTypeType, enumerationMassTypeType - use :: Galacticus_Nodes , only : nodeComponentBlackHole , nodeComponentBlackHoleStandard, treeNode - use :: Numerical_Constants_Astronomical, only : gravitationalConstantGalacticus - use :: Numerical_Constants_Physical , only : speedLight - use :: Numerical_Constants_Prefixes , only : milli - implicit none - type (treeNode ), intent(inout) :: node - type (enumerationComponentTypeType), intent(in ) :: componentType - type (enumerationMassTypeType ), intent(in ) :: massType - double precision , intent(in ) :: radius - class (nodeComponentBlackHole ) , pointer :: blackHole - double precision :: componentMass - - ! Set to zero by default. - Node_Component_Black_Hole_Standard_Rotation_Curve=0.0d0 - ! Get the black hole component and check that it is of the standard class. - blackHole => node%blackHole(instance=1) - select type (blackHole) - class is (nodeComponentBlackHoleStandard) - ! Check if the radius exceeds the gravitational radius. - if (radius > Black_Hole_Gravitational_Radius(blackHole)) then - ! Radius is larger than the gravitational radius - compute the rotation speed. - componentMass=blackHole%enclosedMass(radius,componentType,massType,weightByMass & - &,weightIndexNull) - if (componentMass > 0.0d0) Node_Component_Black_Hole_Standard_Rotation_Curve=sqrt(gravitationalConstantGalacticus& - &*componentMass/radius) - else - ! Radius is less than the gravitational radius - return the speed of light. - Node_Component_Black_Hole_Standard_Rotation_Curve=speedLight*milli - end if - end select - return - end function Node_Component_Black_Hole_Standard_Rotation_Curve - - !![ - - Node_Component_Black_Hole_Standard_Potential - - !!] - double precision function Node_Component_Black_Hole_Standard_Potential(node,radius,componentType,massType,status) - !!{ - Compute the gravitational potential due to a black hole. - !!} - use :: Black_Hole_Fundamentals , only : Black_Hole_Gravitational_Radius - use :: Galactic_Structure_Options , only : componentTypeAll , componentTypeBlackHole , massTypeAll , massTypeBlackHole , & - & weightByMass , weightIndexNull , enumerationComponentTypeType, enumerationMassTypeType, & - & enumerationStructureErrorCodeType - use :: Galacticus_Nodes , only : nodeComponentBlackHole , nodeComponentBlackHoleStandard, treeNode - use :: Numerical_Constants_Astronomical, only : gravitationalConstantGalacticus - implicit none - type (treeNode ), intent(inout) :: node - type (enumerationComponentTypeType ), intent(in ) :: componentType - type (enumerationMassTypeType ), intent(in ) :: massType - double precision , intent(in ) :: radius - type (enumerationStructureErrorCodeType), intent(inout), optional :: status - class (nodeComponentBlackHole ) , pointer :: blackHole - double precision :: componentMass - !$GLC attributes unused :: status - - Node_Component_Black_Hole_Standard_Potential=0.0d0 - if (.not.(componentType == componentTypeAll .or. componentType == componentTypeBlackHole)) return - if (.not.(massType == massTypeAll .or. massType == massTypeBlackHole )) return - ! Get the black hole component and check that it is of the standard class. - blackHole => node%blackHole(instance=1) - select type (blackHole) - class is (nodeComponentBlackHoleStandard) - if (Black_Hole_Gravitational_Radius(blackHole) <=0.0d0) return - ! Computes the potential - limit the radius to the gravitational radius to avoid divergent potentials. - componentMass=blackHole%enclosedMass(radius,componentType,massType,weightByMass& - &,weightIndexNull) - Node_Component_Black_Hole_Standard_Potential=-gravitationalConstantGalacticus*componentMass/max(radius & - &,Black_Hole_Gravitational_Radius(blackHole)) - end select - return - end function Node_Component_Black_Hole_Standard_Potential - - !![ - - Node_Component_Black_Hole_Standard_Rotation_Curve_Gradient - - !!] - double precision function Node_Component_Black_Hole_Standard_Rotation_Curve_Gradient(node,radius,componentType,massType) - !!{ - Computes the rotation curve gradient for the central black hole. Assumes a point mass black hole with a Keplerian - rotation curve, \emph{except} that the rotation speed is limited to never exceed the speed of light. - !!} - use :: Black_Hole_Fundamentals , only : Black_Hole_Gravitational_Radius - use :: Galactic_Structure_Options , only : componentTypeAll , componentTypeBlackHole , massTypeAll , massTypeBlackHole , & - & weightByMass , weightIndexNull , enumerationComponentTypeType, enumerationMassTypeType - use :: Galacticus_Nodes , only : nodeComponentBlackHole , nodeComponentBlackHoleStandard, treeNode - use :: Numerical_Constants_Astronomical, only : gravitationalConstantGalacticus - implicit none - type (treeNode ), intent(inout) :: node - type (enumerationComponentTypeType), intent(in ) :: componentType - type (enumerationMassTypeType ), intent(in ) :: massType - double precision , intent(in ) :: radius - class (nodeComponentBlackHole ) , pointer :: blackHole - double precision :: componentMass - - ! Set to zero by default. - Node_Component_Black_Hole_Standard_Rotation_Curve_Gradient=0.0d0 - if (.not.(componentType == componentTypeAll .or. componentType == componentTypeBlackHole)) return - if (.not.(massType == massTypeAll .or. massType == massTypeBlackHole )) return - if ( radius <= 0.0d0 ) return - ! Get the black hole component and check that it is of the standard class. - blackHole => node%blackHole(instance=1) - select type (blackHole) - class is (nodeComponentBlackHoleStandard) - componentMass=blackHole%enclosedMass(radius,componentType,massType,weightByMass,weightIndexNull) - if (componentMass == 0.0d0) return - if (radius > Black_Hole_Gravitational_Radius(blackHole)) then - Node_Component_Black_Hole_Standard_Rotation_Curve_Gradient= & - & -gravitationalConstantGalacticus & - & *componentMass & - & /radius**2 - else - Node_Component_Black_Hole_Standard_Rotation_Curve_Gradient=0.0d0 - end if - end select - return - end function Node_Component_Black_Hole_Standard_Rotation_Curve_Gradient - -end module Node_Component_Black_Hole_Standard_Structure_Tasks diff --git a/source/objects.nodes.components.dark_matter_halo.spin.scalar.F90 b/source/objects.nodes.components.dark_matter_halo.spin.scalar.F90 index 01215b638a..12f72d263c 100644 --- a/source/objects.nodes.components.dark_matter_halo.spin.scalar.F90 +++ b/source/objects.nodes.components.dark_matter_halo.spin.scalar.F90 @@ -128,7 +128,7 @@ subroutine Node_Component_Halo_Angular_Momentum_Scalar_Scale_Set(node) select type (spin) class is (nodeComponentSpinScalar) ! Set scale for spin. - call spin%angularMomentumScale(max(spin%angularMomentum(),spinMinimum*Dark_Matter_Halo_Angular_Momentum_Scale(node,darkMatterHaloScale_=darkMatterHaloScale_,useBullockDefinition=.true.))) + call spin%angularMomentumScale(max(spin%angularMomentum(),spinMinimum*Dark_Matter_Halo_Angular_Momentum_Scale(node,darkMatterHaloScale_,useBullockDefinition=.true.))) end select return end subroutine Node_Component_Halo_Angular_Momentum_Scalar_Scale_Set diff --git a/source/objects.nodes.components.dark_matter_halo.spin.vector.F90 b/source/objects.nodes.components.dark_matter_halo.spin.vector.F90 index 21aba41e7c..eb246ae1ca 100644 --- a/source/objects.nodes.components.dark_matter_halo.spin.vector.F90 +++ b/source/objects.nodes.components.dark_matter_halo.spin.vector.F90 @@ -160,7 +160,7 @@ subroutine Node_Component_Halo_Angular_Momentum_Vector_Scale_Set(node) select type (spin) class is (nodeComponentSpinVector) ! Set scale for spin. - call spin%angularMomentumVectorScale([1.0d0,1.0d0,1.0d0]*max(spin%angularMomentum(),spinMinimum*Dark_Matter_Halo_Angular_Momentum_Scale(node,darkMatterHaloScale_=darkMatterHaloScale_,useBullockDefinition=.true.))) + call spin%angularMomentumVectorScale([1.0d0,1.0d0,1.0d0]*max(spin%angularMomentum(),spinMinimum*Dark_Matter_Halo_Angular_Momentum_Scale(node,darkMatterHaloScale_,useBullockDefinition=.true.))) end select return end subroutine Node_Component_Halo_Angular_Momentum_Vector_Scale_Set diff --git a/source/objects.nodes.components.dark_matter_profile.scale.F90 b/source/objects.nodes.components.dark_matter_profile.scale.F90 index c250d1184d..4074337e5f 100644 --- a/source/objects.nodes.components.dark_matter_profile.scale.F90 +++ b/source/objects.nodes.components.dark_matter_profile.scale.F90 @@ -25,12 +25,16 @@ module Node_Component_Dark_Matter_Profile_Scale !!{ Implements a dark matter profile method that provides a scale radius. !!} - use :: Dark_Matter_Halo_Scales, only : darkMatterHaloScaleClass + use :: Dark_Matter_Halo_Scales , only : darkMatterHaloScaleClass + use :: Dark_Matter_Profiles , only : darkMatterProfileClass + use :: Dark_Matter_Profiles_DMO, only : darkMatterProfileDMOClass + use :: Mass_Distributions , only : massDistributionClass implicit none private public :: Node_Component_Dark_Matter_Profile_Scale_Scale_Set , Node_Component_Dark_Matter_Profile_Scale_Plausibility , & & Node_Component_Dark_Matter_Profile_Scale_Thread_Initialize, Node_Component_Dark_Matter_Profile_Scale_Thread_Uninitialize, & - & Node_Component_Dark_Matter_Profile_Scale_State_Store , Node_Component_Dark_Matter_Profile_Scale_State_Restore + & Node_Component_Dark_Matter_Profile_Scale_State_Store , Node_Component_Dark_Matter_Profile_Scale_State_Restore , & + & Node_Component_Dark_Matter_Profile_Scale_Initialize !![ @@ -47,15 +51,63 @@ module Node_Component_Dark_Matter_Profile_Scale -1.0d0 + + + + class(massDistributionClass), pointer + 0 + Galactic_Structure_Options, only : enumerationWeightByType, enumerationComponentTypeType, enumerationMassTypeType + Mass_Distributions , only : massDistributionClass + + type (enumerationComponentTypeType), intent(in ), optional :: componentType + type (enumerationMassTypeType ), intent(in ), optional :: massType + type (enumerationWeightByType ), intent(in ), optional :: weightBy + integer , intent(in ), optional :: weightIndex + + + !!] ! Objects used by this component. - class(darkMatterHaloScaleClass), pointer :: darkMatterHaloScale_ - !$omp threadprivate(darkMatterHaloScale_) + class(darkMatterHaloScaleClass ), pointer :: darkMatterHaloScale_ + class(darkMatterProfileClass ), pointer :: darkMatterProfile_ + class(darkMatterProfileDMOClass), pointer :: darkMatterProfileDMO_ + !$omp threadprivate(darkMatterHaloScale_,darkMatterProfile_,darkMatterProfileDMO_) + + ! Procedure pointers to mass distribution functions. + procedure(Node_Component_Dark_Matter_Profile_Scale_Mass_Distribution), pointer :: Node_Component_Dark_Matter_Profile_Scale_Mass_Distribution_ + + ! Mass distribution pointer used for post-construction initialization. + class(massDistributionClass), pointer :: massDistribution__ + !$omp threadprivate(massDistribution__) contains + !![ + + Node_Component_Dark_Matter_Profile_Scale_Initialize + + !!] + subroutine Node_Component_Dark_Matter_Profile_Scale_Initialize(parameters) + !!{ + Initializes the scale dark matter profile component. + !!} + use :: Input_Parameters, only : inputParameters + use :: Galacticus_Nodes, only : defaultDarkMatterProfileComponent, nodeComponentDarkMatterProfileScale + type(inputParameters ), intent(inout) :: parameters + type(nodeComponentDarkMatterProfileScale) :: darkMatterProfile + !$GLC attributes unused :: parameters + + !$omp critical (Node_Component_Dark_Matter_Profile_Initialize) + if (defaultDarkMatterProfileComponent%scaleIsActive()) then + Node_Component_Dark_Matter_Profile_Scale_Mass_Distribution_ => Node_Component_Dark_Matter_Profile_Scale_Mass_Distribution + call darkMatterProfile%massDistributionFunction (Node_Component_Dark_Matter_Profile_Scale_Mass_Distribution_) + end if + !$omp end critical (Node_Component_Dark_Matter_Profile_Initialize) + return + end subroutine Node_Component_Dark_Matter_Profile_Scale_Initialize + !![ Node_Component_Dark_Matter_Profile_Scale_Thread_Initialize @@ -73,7 +125,9 @@ subroutine Node_Component_Dark_Matter_Profile_Scale_Thread_Initialize(parameters if (defaultDarkMatterProfileComponent%scaleIsActive()) then !![ - + + + !!] end if return @@ -93,7 +147,9 @@ subroutine Node_Component_Dark_Matter_Profile_Scale_Thread_Uninitialize() if (defaultDarkMatterProfileComponent%scaleIsActive()) then !![ - + + + !!] end if return @@ -177,7 +233,7 @@ subroutine Node_Component_Dark_Matter_Profile_Scale_State_Store(stateFile,gslSta call displayMessage('Storing state for: componentDarkMatterProfile -> scale',verbosity=verbosityLevelInfo) !![ - + !!] return end subroutine Node_Component_Dark_Matter_Profile_Scale_State_Store @@ -198,11 +254,55 @@ subroutine Node_Component_Dark_Matter_Profile_Scale_State_Restore(stateFile,gslS integer(c_size_t), intent(in ) :: stateOperationID type (c_ptr ), intent(in ) :: gslStateFile - call displayMessage('Retrieving state for: componentDarkMatterProfile -> scale',verbosity=verbosityLevelInfo) + call displayMessage('Retrieving state for: componentDar -> scale',verbosity=verbosityLevelInfo) !![ - + !!] return end subroutine Node_Component_Dark_Matter_Profile_Scale_State_Restore + function Node_Component_Dark_Matter_Profile_Scale_Mass_Distribution(self,componentType,massType,weightBy,weightIndex) result(massDistribution_) + !!{ + Return the mass distribution associated with the dark matter profile. + !!} + use :: Galacticus_Nodes , only : nodeComponentDarkMatterProfileScale + use :: Galactic_Structure_Options, only : enumerationWeightByType , enumerationComponentTypeType, enumerationMassTypeType, componentTypeAll, & + & componentTypeDarkHalo , componentTypeDarkMatterOnly , massTypeAll , massTypeDark + use :: Mass_Distributions , only : massDistributionClass + implicit none + class (massDistributionClass ), pointer :: massDistribution_ + class (nodeComponentDarkMatterProfileScale), intent(inout) :: self + type (enumerationComponentTypeType ), intent(in ), optional :: componentType + type (enumerationMassTypeType ), intent(in ), optional :: massType + type (enumerationWeightByType ), intent(in ), optional :: weightBy + integer , intent(in ), optional :: weightIndex + !![ + + + !!] + + massDistribution_ => null() + massDistribution__ => null() + if ( & + & massType_ == massTypeAll & + & .or. & + & massType_ == massTypeDark & + & ) then + if ( & + & componentType_ == componentTypeAll & + & .or. & + & componentType_ == componentTypeDarkHalo & + & ) then + massDistribution_ => darkMatterProfile_ %get(self%hostNode,weightBy,weightIndex) + massDistribution__ => massDistribution_ + else if ( & + & componentType_ == componentTypeDarkMatterOnly & + & ) then + massDistribution_ => darkMatterProfileDMO_%get(self%hostNode,weightBy,weightIndex) + call massDistribution_%setTypes(componentType=componentTypeDarkMatterOnly) + end if + end if + return + end function Node_Component_Dark_Matter_Profile_Scale_Mass_Distribution + end module Node_Component_Dark_Matter_Profile_Scale diff --git a/source/objects.nodes.components.dark_matter_profile.scale_free.F90 b/source/objects.nodes.components.dark_matter_profile.scale_free.F90 new file mode 100644 index 0000000000..732fd52a9b --- /dev/null +++ b/source/objects.nodes.components.dark_matter_profile.scale_free.F90 @@ -0,0 +1,239 @@ +!! Copyright 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, +!! 2019, 2020, 2021, 2022, 2023, 2024 +!! Andrew Benson +!! +!! This file is part of Galacticus. +!! +!! Galacticus is free software: you can redistribute it and/or modify +!! it under the terms of the GNU General Public License as published by +!! the Free Software Foundation, either version 3 of the License, or +!! (at your option) any later version. +!! +!! Galacticus is distributed in the hope that it will be useful, +!! but WITHOUT ANY WARRANTY; without even the implied warranty of +!! MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +!! GNU General Public License for more details. +!! +!! You should have received a copy of the GNU General Public License +!! along with Galacticus. If not, see . + +!!{ +Contains a module which implements a dark matter profile method that provides no properties (but does provide a mass distribution factory). +!!} + +module Node_Component_Dark_Matter_Profile_Scale_Free + !!{ + Implements a dark matter profile method that provides no properties (but does provide a mass distribution factory). + !!} + use :: Dark_Matter_Profiles , only : darkMatterProfileClass + use :: Dark_Matter_Profiles_DMO, only : darkMatterProfileDMOClass + use :: Mass_Distributions , only : massDistributionClass + implicit none + private + public :: Node_Component_Dark_Matter_Profile_Scale_Free_Thread_Init, Node_Component_Dark_Matter_Profile_Scale_Free_Thread_Uninit, & + & Node_Component_Dark_Matter_Profile_Scale_Free_State_Store, Node_Component_Dark_Matter_Profile_Scale_Free_State_Restore, & + & Node_Component_Dark_Matter_Profile_Scale_Free_Init + + !![ + + darkMatterProfile + scaleFree + false + + + + class(massDistributionClass), pointer + 0 + Galactic_Structure_Options, only : enumerationWeightByType, enumerationComponentTypeType, enumerationMassTypeType + Mass_Distributions , only : massDistributionClass + + type (enumerationComponentTypeType), intent(in ), optional :: componentType + type (enumerationMassTypeType ), intent(in ), optional :: massType + type (enumerationWeightByType ), intent(in ), optional :: weightBy + integer , intent(in ), optional :: weightIndex + + + + + void + + + + + + !!] + + ! Objects used by this component. + class(darkMatterProfileClass ), pointer :: darkMatterProfile_ + class(darkMatterProfileDMOClass), pointer :: darkMatterProfileDMO_ + !$omp threadprivate(darkMatterProfile_,darkMatterProfileDMO_) + + ! Procedure pointers to mass distribution functions. + procedure(Node_Component_Dark_Matter_Profile_Scale_Free_Mass_Dist), pointer :: Node_Component_Dark_Matter_Profile_Scale_Free_Mass_Dist_ + + ! Mass distribution pointer used for post-construction initialization. + class(massDistributionClass), pointer :: massDistribution__ + !$omp threadprivate(massDistribution__) + +contains + + !![ + + Node_Component_Dark_Matter_Profile_Scale_Free_Init + + !!] + subroutine Node_Component_Dark_Matter_Profile_Scale_Free_Init(parameters) + !!{ + Initializes the scale dark matter profile component. + !!} + use :: Input_Parameters, only : inputParameters + use :: Galacticus_Nodes, only : defaultDarkMatterProfileComponent, nodeComponentDarkMatterProfileScaleFree + type(inputParameters ), intent(inout) :: parameters + type(nodeComponentDarkMatterProfileScaleFree) :: darkMatterProfile + !$GLC attributes unused :: parameters + + !$omp critical (Node_Component_Dark_Matter_Profile_Init) + if (defaultDarkMatterProfileComponent%scaleFreeIsActive()) then + Node_Component_Dark_Matter_Profile_Scale_Free_Mass_Dist_ => Node_Component_Dark_Matter_Profile_Scale_Free_Mass_Dist + call darkMatterProfile%massDistributionFunction(Node_Component_Dark_Matter_Profile_Scale_Free_Mass_Dist_) + end if + !$omp end critical (Node_Component_Dark_Matter_Profile_Init) + return + end subroutine Node_Component_Dark_Matter_Profile_Scale_Free_Init + + !![ + + Node_Component_Dark_Matter_Profile_Scale_Free_Thread_Init + + !!] + subroutine Node_Component_Dark_Matter_Profile_Scale_Free_Thread_Init(parameters_) + !!{ + Initializes the tree node scale dark matter profile module. + !!} + use :: Galacticus_Nodes, only : defaultDarkMatterProfileComponent + use :: Input_Parameters, only : inputParameter , inputParameters + implicit none + type(inputParameters), intent(inout) :: parameters_ + !$GLC attributes unused :: parameters_ + + if (defaultDarkMatterProfileComponent%scaleFreeIsActive()) then + !![ + + + !!] + end if + return + end subroutine Node_Component_Dark_Matter_Profile_Scale_Free_Thread_Init + + !![ + + Node_Component_Dark_Matter_Profile_Scale_Free_Thread_Uninit + + !!] + subroutine Node_Component_Dark_Matter_Profile_Scale_Free_Thread_Uninit() + !!{ + Uninitializes the tree node scale dark matter profile module. + !!} + use :: Galacticus_Nodes, only : defaultDarkMatterProfileComponent + implicit none + + if (defaultDarkMatterProfileComponent%scaleFreeIsActive()) then + !![ + + + !!] + end if + return + end subroutine Node_Component_Dark_Matter_Profile_Scale_Free_Thread_Uninit + + !![ + + Node_Component_Dark_Matter_Profile_Scale_Free_State_Store + + !!] + subroutine Node_Component_Dark_Matter_Profile_Scale_Free_State_Store(stateFile,gslStateFile,stateOperationID) + !!{ + Store object state, + !!} + use :: Display , only : displayMessage, verbosityLevelInfo + use, intrinsic :: ISO_C_Binding, only : c_ptr , c_size_t + implicit none + integer , intent(in ) :: stateFile + integer(c_size_t), intent(in ) :: stateOperationID + type (c_ptr ), intent(in ) :: gslStateFile + + call displayMessage('Storing state for: componentDarkMatterProfile -> scaleFree',verbosity=verbosityLevelInfo) + !![ + + !!] + return + end subroutine Node_Component_Dark_Matter_Profile_Scale_Free_State_Store + + !![ + + Node_Component_Dark_Matter_Profile_Scale_Free_State_Restore + + !!] + subroutine Node_Component_Dark_Matter_Profile_Scale_Free_State_Restore(stateFile,gslStateFile,stateOperationID) + !!{ + Retrieve object state. + !!} + use :: Display , only : displayMessage, verbosityLevelInfo + use, intrinsic :: ISO_C_Binding, only : c_ptr , c_size_t + implicit none + integer , intent(in ) :: stateFile + integer(c_size_t), intent(in ) :: stateOperationID + type (c_ptr ), intent(in ) :: gslStateFile + + call displayMessage('Retrieving state for: componentDarkProfile -> scaleFree',verbosity=verbosityLevelInfo) + !![ + + !!] + return + end subroutine Node_Component_Dark_Matter_Profile_Scale_Free_State_Restore + + function Node_Component_Dark_Matter_Profile_Scale_Free_Mass_Dist(self,componentType,massType,weightBy,weightIndex) result(massDistribution_) + !!{ + Return the mass distribution associated with the dark matter profile. + !!} + use :: Galacticus_Nodes , only : nodeComponentDarkMatterProfileScaleFree + use :: Galactic_Structure_Options, only : enumerationWeightByType , enumerationComponentTypeType, enumerationMassTypeType, componentTypeAll, & + & componentTypeDarkHalo , componentTypeDarkMatterOnly , massTypeAll , massTypeDark + use :: Mass_Distributions , only : massDistributionClass + implicit none + class (massDistributionClass ), pointer :: massDistribution_ + class (nodeComponentDarkMatterProfileScaleFree), intent(inout) :: self + type (enumerationComponentTypeType ), intent(in ), optional :: componentType + type (enumerationMassTypeType ), intent(in ), optional :: massType + type (enumerationWeightByType ), intent(in ), optional :: weightBy + integer , intent(in ), optional :: weightIndex + !![ + + + !!] + + massDistribution_ => null() + massDistribution__ => null() + if ( & + & massType_ == massTypeAll & + & .or. & + & massType_ == massTypeDark & + & ) then + if ( & + & componentType_ == componentTypeAll & + & .or. & + & componentType_ == componentTypeDarkHalo & + & ) then + massDistribution_ => darkMatterProfile_ %get(self%hostNode,weightBy,weightIndex) + massDistribution__ => massDistribution_ + else if ( & + & componentType_ == componentTypeDarkMatterOnly & + & ) then + massDistribution_ => darkMatterProfileDMO_%get(self%hostNode,weightBy,weightIndex) + call massDistribution_%setTypes(componentType=componentTypeDarkMatterOnly) + end if + end if + return + end function Node_Component_Dark_Matter_Profile_Scale_Free_Mass_Dist + +end module Node_Component_Dark_Matter_Profile_Scale_Free diff --git a/source/objects.nodes.components.disk.standard.F90 b/source/objects.nodes.components.disk.standard.F90 index d18aca6fdf..1ee978e9f8 100644 --- a/source/objects.nodes.components.disk.standard.F90 +++ b/source/objects.nodes.components.disk.standard.F90 @@ -29,7 +29,6 @@ module Node_Component_Disk_Standard use :: Satellite_Merging_Mass_Movements, only : mergerMassMovementsClass use :: Star_Formation_Histories , only : starFormationHistory , starFormationHistoryClass use :: Stellar_Population_Properties , only : stellarPopulationPropertiesClass - use :: Galactic_Structure , only : galacticStructureClass implicit none private public :: Node_Component_Disk_Standard_Scale_Set , Node_Component_Disk_Standard_Pre_Evolve , & @@ -140,27 +139,9 @@ module Node_Component_Disk_Standard - - - - - - - - - - - - - double - 3 - - type (treeNode ), intent(inout) :: nodeSatellite - double precision , intent(in ), dimension(3) :: positionCartesian, velocityCartesian - type (enumerationComponentTypeType), intent(in ) :: componentType - type (enumerationMassTypeType ), intent(in ) :: massType - - + + + objects.nodes.components.disk.standard.bound_functions.inc @@ -171,8 +152,7 @@ module Node_Component_Disk_Standard class(stellarPopulationPropertiesClass), pointer :: stellarPopulationProperties_ class(starFormationHistoryClass ), pointer :: starFormationHistory_ class(mergerMassMovementsClass ), pointer :: mergerMassMovements_ - class(galacticStructureClass ), pointer :: galacticStructure_ - !$omp threadprivate(darkMatterHaloScale_,stellarPopulationProperties_,starFormationHistory_,mergerMassMovements_,galacticStructure_) + !$omp threadprivate(darkMatterHaloScale_,stellarPopulationProperties_,starFormationHistory_,mergerMassMovements_) ! Internal count of abundances. integer :: abundancesCount @@ -230,8 +210,6 @@ subroutine Node_Component_Disk_Standard_Initialize(parameters) call diskStandardComponent%attachPipes() pipesAttached=.true. end if - ! Bind the Chandrasekhar integral function. - call diskStandardComponent%chandrasekharIntegralFunction(Node_Component_Disk_Standard_Chandrasekhar_Integral) ! Find our parameters. subParameters=parameters%subParameters('componentDisk') ! Read parameters controlling the physical implementation. @@ -286,13 +264,14 @@ subroutine Node_Component_Disk_Standard_Thread_Initialize(parameters) !!{ Initializes the standard disk component module for each thread. !!} - use :: Events_Hooks , only : dependencyDirectionAfter , dependencyRegEx , openMPThreadBindingAtLevel, postEvolveEvent, & + use :: Events_Hooks , only : dependencyDirectionAfter , dependencyRegEx , openMPThreadBindingAtLevel, postEvolveEvent, & & satelliteMergerEvent , mergerTreeExtraOutputEvent use :: Error , only : Error_Report use :: Galacticus_Nodes , only : defaultDiskComponent use :: Input_Parameters , only : inputParameter , inputParameters - use :: Mass_Distributions , only : massDistributionCylindrical - use :: Node_Component_Disk_Standard_Data, only : massDistributionDisk , massDistributionDisk_ + use :: Node_Component_Disk_Standard_Data, only : massDistributionStellar_ , massDistributionGas_ , kinematicDistribution_ + use :: Mass_Distributions , only : massDistributionCylindrical, kinematicsDistributionLocal + use :: Galactic_Structure_Options , only : componentTypeDisk , massTypeStellar , massTypeGaseous implicit none type (inputParameters), intent(inout) :: parameters type (dependencyRegEx), dimension(2) :: dependencies @@ -310,12 +289,11 @@ subroutine Node_Component_Disk_Standard_Thread_Initialize(parameters) ! Find our parameters. subParameters=parameters%subParameters('componentDisk') !![ - - - - - - + + + + + @@ -324,29 +302,36 @@ subroutine Node_Component_Disk_Standard_Thread_Initialize(parameters) !!] ! Validate the disk mass distribution. - select type (massDistributionDisk_) + select type (massDistributionStellar_) class is (massDistributionCylindrical) - ! Since the disk must be cylindrical, deep-copy it to an object of that class. Then we do not need to perform - ! type-guards elsewhere in the code. - allocate(massDistributionDisk,mold=massDistributionDisk_) - !$omp critical(diskStandardDeepCopy) - !![ - - - - !!] - !$omp end critical(diskStandardDeepCopy) + ! The disk mass distribution must have cylindrical symmetry. So, this is acceptable. class default call Error_Report('only cylindrically symmetric mass distributions are allowed'//{introspection:location}) end select - if (.not.massDistributionDisk%isDimensionless()) call Error_Report('disk mass distribution must be dimensionless'//{introspection:location}) + if (.not.massDistributionStellar_%isDimensionless()) call Error_Report('disk mass distribution must be dimensionless'//{introspection:location}) + ! Duplicate the dimensionless mass distribution to use for the gas component, and set component and mass types in both. + !$omp critical(diskStandardDeepCopy) + allocate(massDistributionGas_,mold=massDistributionStellar_) + !![ + + + + !!] + !$omp end critical(diskStandardDeepCopy) + call massDistributionStellar_%setTypes(componentTypeDisk,massTypeStellar) + call massDistributionGas_ %setTypes(componentTypeDisk,massTypeGaseous) + ! Construct the kinematic distribution. + allocate(kinematicDistribution_) + !![ + + !!] ! Compute the specific angular momentum of the disk at this structure solver radius in units of the mean specific angular ! momentum of the disk assuming a flat rotation curve. !! Determine the specific angular momentum at the size solver radius in units of the mean specific angular !! momentum of the disk. This is equal to the ratio of the 1st to 2nd radial moments of the surface density !! distribution (assuming a flat rotation curve). - massDistributionDiskDensityMoment1=massDistributionDisk%surfaceDensityRadialMoment(1.0d0,isInfinite=surfaceDensityMoment1IsInfinite) - massDistributionDiskDensityMoment2=massDistributionDisk%surfaceDensityRadialMoment(2.0d0,isInfinite=surfaceDensityMoment2IsInfinite) + massDistributionDiskDensityMoment1=massDistributionStellar_%surfaceDensityRadialMoment(1.0d0,isInfinite=surfaceDensityMoment1IsInfinite) + massDistributionDiskDensityMoment2=massDistributionStellar_%surfaceDensityRadialMoment(2.0d0,isInfinite=surfaceDensityMoment2IsInfinite) if (surfaceDensityMoment1IsInfinite.or.surfaceDensityMoment2IsInfinite) then ! One or both of the moments are infinite. Simply assume a value of 0.5 as a default. diskStructureSolverSpecificAngularMomentum=0.5d0 @@ -362,9 +347,9 @@ subroutine Node_Component_Disk_Standard_Thread_Initialize(parameters) ! curves for thin disk and a spherical mass distribution. if (structureSolverUseCole2000Method) then diskRadiusSolverFlatVsSphericalFactor= & - & +massDistributionDisk%rotationCurve (radiusStructureSolver)**2 & - & * radiusStructureSolver & - & -massDistributionDisk%massEnclosedBySphere(radiusStructureSolver) + & +massDistributionStellar_%rotationCurve (radiusStructureSolver)**2 & + & * radiusStructureSolver & + & -massDistributionStellar_%massEnclosedBySphere(radiusStructureSolver) end if end if return @@ -379,9 +364,9 @@ subroutine Node_Component_Disk_Standard_Thread_Uninitialize() !!{ Uninitializes the standard disk component module for each thread. !!} - use :: Events_Hooks , only : postEvolveEvent , satelliteMergerEvent , mergerTreeExtraOutputEvent + use :: Events_Hooks , only : postEvolveEvent , satelliteMergerEvent, mergerTreeExtraOutputEvent use :: Galacticus_Nodes , only : defaultDiskComponent - use :: Node_Component_Disk_Standard_Data, only : massDistributionDisk, massDistributionDisk_ + use :: Node_Component_Disk_Standard_Data, only : massDistributionStellar_, massDistributionGas_, kinematicDistribution_ implicit none if (defaultDiskComponent%standardIsActive()) then @@ -393,9 +378,9 @@ subroutine Node_Component_Disk_Standard_Thread_Uninitialize() - - - + + + !!] end if return @@ -1247,9 +1232,9 @@ subroutine Node_Component_Disk_Standard_State_Store(stateFile,gslStateFile,state !!{ Write the tabulation state to file. !!} - use :: Display , only : displayMessage , verbosityLevelInfo - use, intrinsic :: ISO_C_Binding , only : c_ptr , c_size_t - use :: Node_Component_Disk_Standard_Data, only : massDistributionDisk + use :: Display , only : displayMessage , verbosityLevelInfo + use, intrinsic :: ISO_C_Binding , only : c_ptr , c_size_t + use :: Node_Component_Disk_Standard_Data, only : massDistributionStellar_, massDistributionGas_, kinematicDistribution_ implicit none integer , intent(in ) :: stateFile integer(c_size_t), intent(in ) :: stateOperationID @@ -1257,7 +1242,7 @@ subroutine Node_Component_Disk_Standard_State_Store(stateFile,gslStateFile,state call displayMessage('Storing state for: componentDisk -> standard',verbosity=verbosityLevelInfo) !![ - + !!] write (stateFile) diskStructureSolverSpecificAngularMomentum,diskRadiusSolverFlatVsSphericalFactor return @@ -1272,9 +1257,9 @@ subroutine Node_Component_Disk_Standard_State_Retrieve(stateFile,gslStateFile,st !!{ Retrieve the tabulation state from the file. !!} - use :: Display , only : displayMessage , verbosityLevelInfo - use, intrinsic :: ISO_C_Binding , only : c_ptr , c_size_t - use :: Node_Component_Disk_Standard_Data, only : massDistributionDisk + use :: Display , only : displayMessage , verbosityLevelInfo + use, intrinsic :: ISO_C_Binding , only : c_ptr , c_size_t + use :: Node_Component_Disk_Standard_Data, only : massDistributionStellar_, massDistributionGas_, kinematicDistribution_ implicit none integer , intent(in ) :: stateFile integer(c_size_t), intent(in ) :: stateOperationID @@ -1282,152 +1267,10 @@ subroutine Node_Component_Disk_Standard_State_Retrieve(stateFile,gslStateFile,st call displayMessage('Retrieving state for: componentDisk -> standard',verbosity=verbosityLevelInfo) !![ - + !!] read (stateFile) diskStructureSolverSpecificAngularMomentum,diskRadiusSolverFlatVsSphericalFactor return end subroutine Node_Component_Disk_Standard_State_Retrieve - function Node_Component_Disk_Standard_Chandrasekhar_Integral(self,nodeSatellite,positionCartesian,velocityCartesian,componentType,massType) - !!{ - Computes the gravitational acceleration at a given position for a standard disk. - !!} - use :: Galacticus_Nodes , only : nodeComponentDiskStandard , treeNode - use :: Galactic_Structure_Options , only : componentTypeAll , componentTypeDisk , massTypeAll , weightByMass , & - & weightIndexNull , enumerationComponentTypeType, enumerationMassTypeType - use :: Numerical_Constants_Math , only : Pi - use :: Coordinates , only : assignment(=) , coordinateSpherical , coordinateCartesian , coordinateCylindrical - use :: Numerical_Constants_Astronomical, only : gravitationalConstantGalacticus - use :: Mass_Distributions , only : massDistributionGaussianEllipsoid - use :: Linear_Algebra , only : vector , matrix , assignment(=) - implicit none - double precision , dimension(3) :: Node_Component_Disk_Standard_Chandrasekhar_Integral - class (nodeComponentDiskStandard ), intent(inout) :: self - type (treeNode ), intent(inout) :: nodeSatellite - double precision , intent(in ), dimension(3) :: positionCartesian , velocityCartesian - type (enumerationComponentTypeType ), intent(in ) :: componentType - type (enumerationMassTypeType ), intent(in ) :: massType - double precision , parameter :: toomreQRadiusHalfMass =1.50d0 ! The Toomre Q-parameter at the disk half-mass radius (Benson et al., - ! 2004 , https://ui.adsabs.harvard.edu/abs/2004MNRAS.351.1215B, Appendix A). - double precision , parameter :: toomreQFactor =3.36d0 ! The factor appearing in the definition of the Toomre Q-parameter for - ! a stellar disk (Binney & Tremaine, eqn. 6.71). - double precision , dimension(3) :: velocityDisk , velocityRelative , & - & positionSpherical , positionSphericalMidplane , & - & positionCartesianMidplane , positionCylindricalMidplane , & - & positionCylindricalHalfMass - type (massDistributionGaussianEllipsoid), save :: velocityDistribution - logical , save :: velocityDistributionInitialized =.false. - !$omp threadprivate(velocityDistribution,velocityDistributionInitialized) - type (coordinateSpherical ) :: coordinatesSpherical - type (coordinateCartesian ) :: coordinatesCartesian - type (coordinateCylindrical ) :: coordinatesCylindrical - double precision :: velocityDispersionRadial , velocityDispersionAzimuthal , & - & velocityDispersionVertical , velocityCircular , & - & velocityCircularHalfMassRadius , velocityCircularSquaredGradient , & - & velocityCircularSquaredGradientHalfMassRadius , density , & - & densityMidPlane , densitySurface , & - & heightScale , radiusMidplane , & - & frequencyCircular , frequencyEpicyclic , & - & frequencyCircularHalfMassRadius , frequencyEpicyclicHalfMassRadius, & - & densitySurfaceRadiusHalfMass , velocityDispersionRadialHalfMass, & - & velocityDispersionMaximum , velocityRelativeMagnitude , & - & factorSuppressionExtendedMass , radiusHalfMass - type (matrix ) :: rotation - - ! Return if the disk component is not selected. - Node_Component_Disk_Standard_Chandrasekhar_Integral=0.0d0 - if (.not.(componentType == componentTypeAll .or. componentType == componentTypeDisk) .or. self%radius() <= 0.0d0) return - ! Construct the velocity vector of the disk rotation. - positionCartesianMidplane =[positionCartesian(1),positionCartesian(2),0.0d0] - coordinatesCartesian = positionCartesian - coordinatesSpherical = coordinatesCartesian - positionSpherical = coordinatesSpherical - coordinatesCartesian = positionCartesianMidplane - coordinatesSpherical = coordinatesCartesian - coordinatesCylindrical = coordinatesCartesian - positionSphericalMidplane = coordinatesSpherical - positionCylindricalMidplane = coordinatesCylindrical - positionCylindricalHalfMass =[self%halfMassRadius(),0.0d0,0.0d0] - radiusMidplane = coordinatesCylindrical%r() - velocityCircular =self%rotationCurve ( radiusMidplane ,componentType,massType) - velocityCircularSquaredGradient =self%rotationCurveGradient( radiusMidplane ,componentType,massType) - velocityCircularHalfMassRadius =self%rotationCurve (self%halfMassRadius(),componentType,massType) - velocityCircularSquaredGradientHalfMassRadius=self%rotationCurveGradient(self%halfMassRadius(),componentType,massType) - velocityDisk =+[positionCartesianMidplane(2),-positionCartesianMidplane(1),0.0d0] & - & /radiusMidplane & - & *velocityCircular - ! Compute epicyclic frequency. - frequencyCircular =velocityCircular / radiusMidplane - frequencyCircularHalfMassRadius =velocityCircularHalfMassRadius/self%halfMassRadius() - frequencyEpicyclic =sqrt(velocityCircularSquaredGradient / radiusMidplane +2.0d0*frequencyCircular **2) - frequencyEpicyclicHalfMassRadius=sqrt(velocityCircularSquaredGradientHalfMassRadius/self%halfMassRadius()+2.0d0*frequencyCircularHalfMassRadius**2) - ! Get disk structural properties. - density =+self%density (positionSpherical ,componentTypeDisk,massTypeAll,weightByMass,weightIndexNull) - densityMidPlane =+self%density (positionSphericalMidplane ,componentTypeDisk,massTypeAll,weightByMass,weightIndexNull) - densitySurface =+self%surfaceDensity(positionCylindricalMidplane,componentTypeDisk,massTypeAll,weightByMass,weightIndexNull) - densitySurfaceRadiusHalfMass=+self%surfaceDensity(positionCylindricalHalfMass,componentTypeDisk,massTypeAll,weightByMass,weightIndexNull) - if (density <= 0.0d0) return - heightScale =+0.5d0 & - & *densitySurface & - & /densityMidPlane - ! Compute normalization of the radial velocity dispersion. - velocityDispersionRadialHalfMass=+toomreQFactor & - & *gravitationalConstantGalacticus & - & *densitySurfaceRadiusHalfMass & - & *toomreQRadiusHalfMass & - & /frequencyEpicyclicHalfMassRadius - ! Find the velocity dispersion components of the disk. - velocityDispersionRadial =+velocityDispersionRadialHalfMass & - & *exp(- radiusMidPlane /self%radius()/2.0d0) & - & /exp(-self%halfMassRadius()/self%radius()/2.0d0) - velocityDispersionAzimuthal=+velocityDispersionRadial*frequencyEpicyclic/2.0d0/frequencyCircular - velocityDispersionVertical =+sqrt(Pi*gravitationalConstantGalacticus*densitySurface*heightScale) - velocityDispersionMaximum =+maxval([velocityDispersionRadial,velocityDispersionAzimuthal,velocityDispersionVertical]) - velocityDispersionRadial =+velocityDispersionRadial /velocityDispersionMaximum - velocityDispersionAzimuthal=+velocityDispersionAzimuthal/velocityDispersionMaximum - velocityDispersionVertical =+velocityDispersionVertical /velocityDispersionMaximum - if (any([velocityDispersionRadial,velocityDispersionAzimuthal,velocityDispersionVertical] <= 0.0d0)) return - ! Find the relative velocity of the perturber and the disk. - velocityRelative=(velocityCartesian-velocityDisk)/velocityDispersionMaximum - ! Handle limiting case of large relative velocity. - velocityRelativeMagnitude=sqrt(sum(velocityRelative**2)) - ! Initialize the velocity distribution. - rotation=reshape( & - & [ & - & +positionCartesianMidplane(1),-positionCartesianMidplane(2),+0.0d0 , & - & +positionCartesianMidplane(2),+positionCartesianMidplane(1),+0.0d0 , & - & +0.0d0 ,+0.0d0 ,+radiusMidplane & - & ] & - & /radiusMidplane , & - & [3,3] & - & ) - coordinatesCartesian=velocityRelative - if (.not.velocityDistributionInitialized) then - velocityDistribution =massDistributionGaussianEllipsoid(scaleLength=[1.0d0,1.0d0,1.0d0],rotation=rotation,mass=1.0d0,dimensionless=.true.) - velocityDistributionInitialized=.true. - end if - call velocityDistribution%initialize(scaleLength=[velocityDispersionRadial,velocityDispersionAzimuthal,velocityDispersionVertical],rotation=rotation) - ! Compute suppression factor due to satellite being an extended mass distribution. This is largely untested - it is meant to - ! simply avoid extremely large accelerations for subhalo close to the disk plane when that subhalo is much more extended than - ! the disk. - radiusHalfMass=galacticStructure_%radiusEnclosingMass( & - & nodeSatellite , & - & massFractional=0.5d0 , & - & componentType =componentTypeAll, & - & massType =massTypeAll & - & ) - if (radiusHalfMass > heightScale) then - factorSuppressionExtendedMass=+heightScale & - & /radiusHalfMass - else - factorSuppressionExtendedMass=+1.0d0 - end if - ! Evaluate the integral. - Node_Component_Disk_Standard_Chandrasekhar_Integral=+density & - & *velocityDistribution %acceleration(coordinatesCartesian) & - & /velocityDispersionMaximum **2 & - & *factorSuppressionExtendedMass - return - end function Node_Component_Disk_Standard_Chandrasekhar_Integral - end module Node_Component_Disk_Standard diff --git a/source/objects.nodes.components.disk.standard.bound_functions.Inc b/source/objects.nodes.components.disk.standard.bound_functions.Inc index ee3f7cb983..eaf87c8382 100644 --- a/source/objects.nodes.components.disk.standard.bound_functions.Inc +++ b/source/objects.nodes.components.disk.standard.bound_functions.Inc @@ -32,476 +32,145 @@ subroutine Node_Component_Disk_Standard_Attach_Pipes(self) !$GLC attributes unused :: self if (hotHalo%hotHaloCoolingMassRateIsAttached ()) & - call Error_Report('expected to find unclaimed hot halo mass cooling pipe'//{introspection:location}) + call Error_Report('expected to find unclaimed hot halo mass cooling pipe' //{introspection:location}) if (hotHalo%hotHaloCoolingAngularMomentumRateIsAttached()) & call Error_Report('expected to find unclaimed hot halo angular momentum cooling pipe'//{introspection:location}) if (hotHalo%hotHaloCoolingAbundancesRateIsAttached ()) & - call Error_Report('expected to find unclaimed hot halo abundances cooling pipe'//{introspection:location}) + call Error_Report('expected to find unclaimed hot halo abundances cooling pipe' //{introspection:location}) call hotHalo%hotHaloCoolingMassRateFunction (DiskStandardMassGasRateGeneric ) call hotHalo%hotHaloCoolingAngularMomentumRateFunction(DiskStandardAngularMomentumRateGeneric) call hotHalo%hotHaloCoolingAbundancesRateFunction (DiskStandardAbundancesGasRateGeneric ) return end subroutine Node_Component_Disk_Standard_Attach_Pipes -double precision function Node_Component_Disk_Standard_Half_Mass_Radius(self) +function Node_Component_Disk_Standard_Mass_Distribution(self,componentType,massType,weightBy,weightIndex) result(massDistribution_) !!{ - Return the half-mass radius of the standard disk. - !!} - use :: Error , only : Error_Report - use :: Node_Component_Disk_Standard_Data, only : massDistributionDisk - implicit none - class(nodeComponentDiskStandard), intent(inout) :: self - - Node_Component_Disk_Standard_Half_Mass_Radius=self%radius()*massDistributionDisk%radiusHalfMass() - return -end function Node_Component_Disk_Standard_Half_Mass_Radius - -double precision function Node_Component_Disk_Standard_Enclosed_Mass(self,radius,componentType,massType,weightBy,weightIndex) - !!{ - Computes the mass within a given radius for an standard disk. - !!} - use :: Galactic_Structure_Options , only : componentTypeAll , componentTypeDisk, massTypeAll , massTypeBaryonic , & - & massTypeGalactic , massTypeGaseous , massTypeStellar , radiusLarge , & - & weightByLuminosity , weightByMass , enumerationComponentTypeType, enumerationMassTypeType, & - & enumerationWeightByType - use :: Node_Component_Disk_Standard_Data, only : massDistributionDisk - use :: Math_Arithmetic , only : divideSafe - implicit none - class (nodeComponentDiskStandard ), intent(inout) :: self - type (enumerationComponentTypeType), intent(in ) :: componentType - type (enumerationMassTypeType ), intent(in ) :: massType - type (enumerationWeightByType ), intent(in ) :: weightBy - integer , intent(in ) :: weightIndex - double precision , intent(in ) :: radius - double precision :: radiusDisk , fractionalRadius - type (stellarLuminosities ), save :: luminositiesDisk - !$omp threadprivate(luminositiesDisk) - - ! Return immediately if disk component is not requested. - Node_Component_Disk_Standard_Enclosed_Mass=0.0d0 - if (.not.(componentType == componentTypeAll .or. componentType == componentTypeDisk)) return - ! Get the total mass. - select case (weightBy%ID) - case (weightByMass %ID) - select case (massType%ID) - case (massTypeAll%ID,massTypeBaryonic%ID,massTypeGalactic%ID) - Node_Component_Disk_Standard_Enclosed_Mass=self%massGas()+self%massStellar() - case (massTypeGaseous%ID) - Node_Component_Disk_Standard_Enclosed_Mass=self%massGas() - case (massTypeStellar%ID) - Node_Component_Disk_Standard_Enclosed_Mass= self%massStellar() - end select - case (weightByLuminosity%ID) - select case (massType%ID) - case (massTypeAll%ID,massTypeBaryonic%ID,massTypeGalactic%ID,massTypeStellar%ID) - luminositiesDisk=self%luminositiesStellar() - Node_Component_Disk_Standard_Enclosed_Mass =luminositiesDisk%luminosity(weightIndex) - end select - end select - ! Return if no mass. - if (Node_Component_Disk_Standard_Enclosed_Mass <= 0.0d0) return - ! Return if the total mass was requested. - if (radius >= radiusLarge) return - ! Compute the actual mass. - radiusDisk=self%radius() - if (radiusDisk > 0.0d0) then - fractionalRadius=divideSafe(radius,radiusDisk) - Node_Component_Disk_Standard_Enclosed_Mass= & - & +Node_Component_Disk_Standard_Enclosed_Mass & - & *massDistributionDisk%massEnclosedBySphere(fractionalRadius) - end if - return -end function Node_Component_Disk_Standard_Enclosed_Mass - -function Node_Component_Disk_Standard_Acceleration(self,positionCartesian,componentType,massType) - !!{ - Computes the gravitational acceleration at a given position for a standard disk. - !!} - use :: Coordinates , only : assignment(=) , coordinateCartesian - use :: Galactic_Structure_Options , only : componentTypeAll , componentTypeDisk , massTypeAll , massTypeBaryonic , & - & massTypeGalactic , massTypeGaseous , massTypeStellar , enumerationComponentTypeType, & - & enumerationMassTypeType - use :: Node_Component_Disk_Standard_Data, only : massDistributionDisk - use :: Numerical_Constants_Astronomical , only : gigaYear , megaParsec , gravitationalConstantGalacticus - use :: Numerical_Constants_Prefixes , only : kilo - implicit none - double precision , dimension(3) :: Node_Component_Disk_Standard_Acceleration - class (nodeComponentDiskStandard ), intent(inout) :: self - double precision , intent(in ), dimension(3) :: positionCartesian - type (enumerationComponentTypeType), intent(in ) :: componentType - type (enumerationMassTypeType ), intent(in ) :: massType - double precision :: radius , massTotal - type (coordinateCartesian ) :: positionScaleFree - - ! Return if the disk component is not selected. - Node_Component_Disk_Standard_Acceleration=0.0d0 - if (.not.(componentType == componentTypeAll .or. componentType == componentTypeDisk)) return - ! Determine total mass. - select case (massType%ID) - case (massTypeAll%ID,massTypeBaryonic%ID,massTypeGalactic%ID) - massTotal=self%massGas()+self%massStellar() - case (massTypeGaseous%ID) - massTotal=self%massGas() - case (massTypeStellar%ID) - massTotal= self%massStellar() - case default - massTotal=0.0d0 - end select - ! Return if no mass. - if (massTotal <= 0.0d0 .or. self%radius() <= 0.0d0) return - ! Compute the acceleration. - radius =sqrt(sum(positionCartesian**2)) - positionScaleFree=positionCartesian/self%radius() - Node_Component_Disk_Standard_Acceleration=+kilo & - & *gigaYear & - & /megaParsec & - & *gravitationalConstantGalacticus & - & *massTotal & - & /self%radius() **2 & - & *massDistributionDisk%acceleration(positionScaleFree) - return -end function Node_Component_Disk_Standard_Acceleration - -function Node_Component_Disk_Standard_Tidal_Tensor(self,positionCartesian,componentType,massType) - !!{ - Computes the gravitational acceleration at a given position for a standard disk. - !!} - use :: Coordinates , only : assignment(=) , coordinateCartesian - use :: Galactic_Structure_Options , only : componentTypeAll , componentTypeDisk , massTypeAll , massTypeBaryonic , & - & massTypeGalactic , massTypeGaseous , massTypeStellar, enumerationComponentTypeType, & - & enumerationMassTypeType - use :: Node_Component_Disk_Standard_Data, only : massDistributionDisk - use :: Numerical_Constants_Astronomical , only : gravitationalConstantGalacticus - use :: Tensors , only : tensorRank2Dimension3Symmetric , tensorNullR2D3Sym , operator(*) - implicit none - type (tensorRank2Dimension3Symmetric) :: Node_Component_Disk_Standard_Tidal_Tensor - class (nodeComponentDiskStandard ), intent(inout) :: self - double precision , intent(in ), dimension(3) :: positionCartesian - type (enumerationComponentTypeType ), intent(in ) :: componentType - type (enumerationMassTypeType ), intent(in ) :: massType - double precision :: radius , massTotal - type (coordinateCartesian ) :: positionScaleFree - - ! Return if the disk component is not selected.q - Node_Component_Disk_Standard_Tidal_Tensor=tensorNullR2D3Sym - if (.not.(componentType == componentTypeAll .or. componentType == componentTypeDisk)) return - ! Determine total mass. - select case (massType%ID) - case (massTypeAll%ID,massTypeBaryonic%ID,massTypeGalactic%ID) - massTotal=self%massGas()+self%massStellar() - case (massTypeGaseous%ID) - massTotal=self%massGas() - case (massTypeStellar%ID) - massTotal= self%massStellar() - case default - massTotal=0.0d0 - end select - ! Return if no mass. - if (massTotal <= 0.0d0 .or. self%radius() <= 0.0d0) return - ! Compute the acceleration. - radius =sqrt(sum(positionCartesian**2)) - positionScaleFree=positionCartesian/self%radius() - Node_Component_Disk_Standard_Tidal_Tensor=+gravitationalConstantGalacticus & - & *massTotal & - & /self%radius() **3 & - & *massDistributionDisk%tidalTensor(positionScaleFree) - return -end function Node_Component_Disk_Standard_Tidal_Tensor - -double precision function Node_Component_Disk_Standard_Density(self,positionSpherical,componentType,massType,weightBy,weightIndex) - !!{ - Computes the density at a given position for an standard disk. - !!} - use :: Coordinates , only : assignment(=) , coordinateSpherical - use :: Galactic_Structure_Options , only : componentTypeAll , componentTypeDisk , massTypeAll , massTypeBaryonic , & - & massTypeGalactic , massTypeGaseous , massTypeStellar , weightByLuminosity , & - & weightByMass , enumerationComponentTypeType, enumerationMassTypeType, enumerationWeightByType - use :: Node_Component_Disk_Standard_Data, only : massDistributionDisk - implicit none - class (nodeComponentDiskStandard ), intent(inout) :: self - type (enumerationComponentTypeType), intent(in ) :: componentType - type (enumerationMassTypeType ), intent(in ) :: massType - type (enumerationWeightByType ), intent(in ) :: weightBy - integer , intent(in ) :: weightIndex - double precision , intent(in ) :: positionSpherical(3) - type (stellarLuminosities ), save :: luminositiesDisk - !$omp threadprivate(luminositiesDisk) - type (coordinateSpherical ) :: position - - ! Return immediately if disk component is not requested. - Node_Component_Disk_Standard_Density=0.0d0 - if (.not.(componentType == componentTypeAll .or. componentType == componentTypeDisk)) return - ! Determine mass/luminosity type. - select case (weightBy%ID) - case (weightByMass %ID) - select case (massType%ID) - case (massTypeAll%ID,massTypeBaryonic%ID,massTypeGalactic%ID) - Node_Component_Disk_Standard_Density=self%massGas()+self%massStellar() - case (massTypeGaseous%ID) - Node_Component_Disk_Standard_Density=self%massGas() - case (massTypeStellar%ID) - Node_Component_Disk_Standard_Density= self%massStellar() - end select - case (weightByLuminosity%ID) - select case (massType%ID) - case (massTypeAll%ID,massTypeBaryonic%ID,massTypeGalactic%ID,massTypeStellar%ID) - luminositiesDisk=self%luminositiesStellar() - Node_Component_Disk_Standard_Density=luminositiesDisk%luminosity(weightIndex) - end select - end select - ! Skip further calculation if mass or radius is zero. - if (Node_Component_Disk_Standard_Density > 0.0d0 .and. self%radius() > 0.0d0) then - ! Compute the actual density. - position=[positionSpherical(1)/self%radius(),positionSpherical(2),positionSpherical(3)] - Node_Component_Disk_Standard_Density=+Node_Component_Disk_Standard_Density & - & /self %radius ( )**3 & - & *massDistributionDisk%density(position) - end if - return -end function Node_Component_Disk_Standard_Density - -double precision function Node_Component_Disk_Standard_Density_Spherical_Average(self,radius,componentType,massType,weightBy,weightIndex) - !!{ - Computes the density at a given position for an standard disk. - !!} - use :: Galactic_Structure_Options , only : componentTypeAll , componentTypeDisk , massTypeAll , massTypeBaryonic , & - & massTypeGalactic , massTypeGaseous , massTypeStellar , weightByLuminosity , & - & weightByMass , enumerationComponentTypeType, enumerationMassTypeType, enumerationWeightByType - use :: Node_Component_Disk_Standard_Data, only : massDistributionDisk - use :: Math_Arithmetic , only : divideSafe - implicit none - class (nodeComponentDiskStandard ), intent(inout) :: self - type (enumerationComponentTypeType), intent(in ) :: componentType - type (enumerationMassTypeType ), intent(in ) :: massType - type (enumerationWeightByType ), intent(in ) :: weightBy - integer , intent(in ) :: weightIndex - double precision , intent(in ) :: radius - type (stellarLuminosities ), save :: luminositiesDisk - !$omp threadprivate(luminositiesDisk) - - ! Return immediately if disk component is not requested. - Node_Component_Disk_Standard_Density_Spherical_Average=0.0d0 - if (.not.(componentType == componentTypeAll .or. componentType == componentTypeDisk)) return - ! Determine mass/luminosity type. - select case (weightBy%ID) - case (weightByMass %ID) - select case (massType%ID) - case (massTypeAll%ID,massTypeBaryonic%ID,massTypeGalactic%ID) - Node_Component_Disk_Standard_Density_Spherical_Average=self%massGas()+self%massStellar() - case (massTypeGaseous%ID) - Node_Component_Disk_Standard_Density_Spherical_Average=self%massGas() - case (massTypeStellar%ID) - Node_Component_Disk_Standard_Density_Spherical_Average= self%massStellar() - end select - case (weightByLuminosity%ID) - select case (massType%ID) - case (massTypeAll%ID,massTypeBaryonic%ID,massTypeGalactic%ID,massTypeStellar%ID) - luminositiesDisk=self%luminositiesStellar() - Node_Component_Disk_Standard_Density_Spherical_Average=luminositiesDisk%luminosity(weightIndex) - end select - end select - ! Skip further calculation if mass or radius is zero. - if (Node_Component_Disk_Standard_Density_Spherical_Average > 0.0d0 .and. self%radius() > 0.0d0) then - ! Compute the actual density. - Node_Component_Disk_Standard_Density_Spherical_Average=+Node_Component_Disk_Standard_Density_Spherical_Average & - & /self %radius ( )**3 & - & *massDistributionDisk%densitySphericalAverage(divideSafe(radius,self%radius())) - end if - return - end function Node_Component_Disk_Standard_Density_Spherical_Average - -double precision function Node_Component_Disk_Standard_Potential(self,radius,componentType,massType,status) - !!{ - Compute the gravitational potential due to an standard disk. + Return the mass distribution for the standard disk component. !!} - use :: Coordinates , only : assignment(=) , coordinateCylindrical - use :: Galactic_Structure_Options , only : componentTypeAll , componentTypeDisk , radiusLarge , weightByMass , & - & weightIndexNull , enumerationComponentTypeType, enumerationMassTypeType, enumerationStructureErrorCodeType - use :: Node_Component_Disk_Standard_Data, only : massDistributionDisk - use :: Numerical_Constants_Astronomical , only : gravitationalConstantGalacticus - use :: Math_Arithmetic , only : divideSafe + use :: Mass_Distributions , only : massDistributionClass , massDistributionCylindricalScaler, massDistributionComposite , massDistributionList , & + & massDistributionCylindrical, massDistributionMatches_ + use :: Node_Component_Disk_Standard_Data, only : massDistributionStellar_ , massDistributionGas_ , kinematicDistribution_ + use :: Galactic_Structure_Options , only : componentTypeDisk , massTypeStellar , massTypeGaseous , enumerationWeightByType, & + & weightByMass , weightByLuminosity , enumerationComponentTypeType, enumerationMassTypeType implicit none - class (nodeComponentDiskStandard ), intent(inout) :: self - type (enumerationComponentTypeType ), intent(in ) :: componentType - type (enumerationMassTypeType ), intent(in ) :: massType - double precision , intent(in ) :: radius - type (enumerationStructureErrorCodeType), intent(inout), optional :: status - double precision :: componentMass - type (coordinateCylindrical ) :: position - !$GLC attributes unused :: status - - ! Return immediately if disk component is not requested. - Node_Component_Disk_Standard_Potential=0.0d0 - if (.not.(componentType == componentTypeAll .or. componentType == componentTypeDisk)) return - ! Avoid an arithmetic exception at radius zero. - if (radius <= 0.0d0) return - ! Get the relevant mass of the disk. - componentMass=self%enclosedMass(radiusLarge,componentType,massType,weightByMass,weightIndexNull) - if (componentMass <= 0.0d0) return - ! Check for zero-sized disk. - if (self%radius() <= 0.0d0) then - ! Treat as a point mass. - Node_Component_Disk_Standard_Potential=-gravitationalConstantGalacticus & - & *componentMass & - & /radius + class (massDistributionClass ), pointer :: massDistribution_ + class (nodeComponentDiskStandard ), intent(inout) :: self + type (enumerationComponentTypeType ), intent(in ), optional :: componentType + type (enumerationMassTypeType ), intent(in ), optional :: massType + type (enumerationWeightByType ), intent(in ), optional :: weightBy + integer , intent(in ), optional :: weightIndex + type (massDistributionCylindricalScaler), pointer :: massDistributionStellar , massDistributionGas + type (massDistributionComposite ), pointer :: massDistributionTotal + type (massDistributionList ), pointer :: massDistributionComponents + type (stellarLuminosities ), save :: luminosities + !$omp threadprivate(luminosities) + double precision :: massStellar , massGas , & + & radiusScale + logical :: includeGas , includeStars + !![ + + !!] + + ! Determine which components of the disk to include. + includeGas =massDistributionMatches_(componentTypeDisk,massTypeGaseous,componentType,massType) .and. weightBy_ == weightByMass + includeStars=massDistributionMatches_(componentTypeDisk,massTypeStellar,componentType,massType) .and. (weightBy_ == weightByMass .or. weightBy_ == weightByLuminosity) + ! Get properties of the mass distribution and ensure they are physical. + if (weightBy_ == weightByMass ) then + massStellar = max (0.0d0,self %massStellar ( )) + massGas = max (0.0d0,self %massGas ( )) + else if (weightBy_ == weightByLuminosity) then + luminosities = self %luminositiesStellar( ) + massStellar = max (0.0d0,luminosities%luminosity (weightIndex)) + massGas = 0.0d0 else - ! Compute the potential. - position=[divideSafe(radius,self%radius()),0.0d0,0.0d0] - Node_Component_Disk_Standard_Potential=+gravitationalConstantGalacticus & - & *componentMass & - & /self%radius() & - & *massDistributionDisk%potential(position) + massDistribution_ => null() + return end if - return -end function Node_Component_Disk_Standard_Potential - -double precision function Node_Component_Disk_Standard_Rotation_Curve(self,radius,componentType,massType) - !!{ - Computes the rotation curve at a given radius for an standard disk. - !!} - use :: Galactic_Structure_Options , only : radiusLarge , weightByMass, weightIndexNull, enumerationComponentTypeType, & - & enumerationMassTypeType - use :: Node_Component_Disk_Standard_Data, only : massDistributionDisk - use :: Numerical_Constants_Astronomical , only : gravitationalConstantGalacticus - use :: Math_Arithmetic , only : divideSafe - implicit none - class (nodeComponentDiskStandard ), intent(inout) :: self - type (enumerationComponentTypeType), intent(in ) :: componentType - type (enumerationMassTypeType ), intent(in ) :: massType - double precision , intent(in ) :: radius - double precision :: componentMass, radiusDisk, fractionalRadius - - ! Set to zero by default. - Node_Component_Disk_Standard_Rotation_Curve=0.0d0 - ! Get the mass of the disk. - componentMass=self%enclosedMass(radiusLarge,componentType,massType,weightByMass,weightIndexNull) - if (componentMass <= 0.0d0) return - ! Compute the actual velocity. - radiusDisk=self%radius() - if (radiusDisk > 0.0d0) then - fractionalRadius=divideSafe(radius,radiusDisk) - Node_Component_Disk_Standard_Rotation_Curve=+sqrt( & - & +gravitationalConstantGalacticus & - & *componentMass & - & /radiusDisk & - & ) & - & *massDistributionDisk%rotationCurve(fractionalRadius) + ! Determine which components to build. + radiusScale=self%radius() + if (radiusScale <= 0.0d0 .or. .not.(includeGas .or. includeStars)) then + ! Disk has non-positive size, or no components matched. Return a null distribution. + massDistribution_ => null() + else + ! Build the individual distributions. + massDistributionStellar => null() + massDistributionGas => null() + if (includeStars) then + allocate(massDistributionStellar) + select type (massDistributionStellar_) + class is (massDistributionCylindrical) + !![ + + !!] + end select + call massDistributionStellar%setKinematicsDistribution(kinematicDistribution_) + end if + if (includeGas ) then + allocate(massDistributionGas ) + select type (massDistributionGas_ ) + class is (massDistributionCylindrical) + !![ + + !!] + end select + call massDistributionGas %setKinematicsDistribution(kinematicDistribution_) + end if + ! Combine the distributions as necessary. + if (includeStars .and. includeGas) then + ! Wrap the dimensionless mass distribution inside scaler classes to allow us to re-scale it to any disk system, and then composite those. + allocate(massDistributionTotal ) + allocate(massDistributionComponents ) + allocate(massDistributionComponents%next) + massDistributionComponents %massDistribution_ => massDistributionStellar + massDistributionComponents%next%massDistribution_ => massDistributionGas + !![ + + + + !!] + nullify(massDistributionComponents) + ! Return a pointer to the disk mass distribution. + massDistribution_ => massDistributionTotal + else if (includeStars ) then + ! Return just the stellar component. + massDistribution_ => massDistributionStellar + else if ( includeGas) then + ! Return just the gas component. + massDistribution_ => massDistributionGas + end if end if return -end function Node_Component_Disk_Standard_Rotation_Curve +end function Node_Component_Disk_Standard_Mass_Distribution -double precision function Node_Component_Disk_Standard_Rotation_Curve_Gradient(self,radius,componentType,massType) +double precision function Node_Component_Disk_Standard_Mass_Baryonic(self) result(massBaryonic) !!{ - Computes the rotation curve gradient for an standard disk. + Return the baryonic mass for the standard disk component. !!} - use :: Galactic_Structure_Options , only : radiusLarge , weightByMass, weightIndexNull, enumerationComponentTypeType, & - & enumerationMassTypeType - use :: Node_Component_Disk_Standard_Data, only : massDistributionDisk - use :: Numerical_Constants_Astronomical , only : gravitationalConstantGalacticus - use :: Math_Arithmetic , only : divideSafe implicit none - class (nodeComponentDiskStandard ), intent(inout) :: self - type (enumerationComponentTypeType), intent(in ) :: componentType - type (enumerationMassTypeType ), intent(in ) :: massType - double precision , intent(in ) :: radius - double precision :: radiusDisk , fractionalRadius, & - & componentMass + class(nodeComponentDiskStandard), intent(inout) :: self - ! Set to zero by default. - Node_Component_Disk_Standard_Rotation_Curve_Gradient=0.0d0 - ! Return if radius is zero. - if (radius <= 0.0d0) return - ! Get the mass of the disk. - componentMass=self%enclosedMass(radiusLarge,componentType,massType,weightByMass,weightIndexNull) - if (componentMass <= 0.0d0) return - ! Compute the rotation curve gradient. - radiusDisk=self%radius() - if (radiusDisk > 0.0d0) then - fractionalRadius=divideSafe(radius,radiusDisk) - Node_Component_Disk_Standard_Rotation_Curve_Gradient=+gravitationalConstantGalacticus & - & *componentMass & - & /radiusDisk**2 & - & *massDistributionDisk%rotationCurveGradient(fractionalRadius) - end if + massBaryonic=+max(0.0d0,self%massStellar()) & + & +max(0.0d0,self%massGas ()) return -end function Node_Component_Disk_Standard_Rotation_Curve_Gradient +end function Node_Component_Disk_Standard_Mass_Baryonic -double precision function Node_Component_Disk_Standard_Surface_Density(self,positionCylindrical,componentType,massType,weightBy,weightIndex) +double precision function Node_Component_Disk_Standard_Half_Mass_Radius(self) result(radiusHalfMass) !!{ - Computes the surface density at a given position for an standard disk. + Return the half-mass radius of the standard disk. !!} - use :: Coordinates , only : coordinateCylindrical - use :: Galactic_Structure_Options , only : componentTypeAll , componentTypeDisk , massTypeAll , massTypeBaryonic , & - & massTypeGalactic , massTypeGaseous , massTypeStellar , weightByLuminosity , & - & weightByMass , enumerationComponentTypeType, enumerationMassTypeType , enumerationWeightByType - use :: Node_Component_Disk_Standard_Data, only : Node_Component_Disk_Standard_Reset , massDistributionDisk , lastUniqueID , radiusScaleDisk , & - & radiusScaleDiskComputed , surfaceDensityCentralGas , surfaceDensityCentralGasComputed , surfaceDensityCentralStellar, & - & surfaceDensityCentralStellarComputed, surfaceDensityCentralTotal , surfaceDensityCentralTotalComputed - use :: Numerical_Constants_Math , only : Pi + use :: Error , only : Error_Report + use :: Mass_Distributions , only : massDistributionCylindrical + use :: Node_Component_Disk_Standard_Data, only : massDistributionStellar_ implicit none - class (nodeComponentDiskStandard ), intent(inout) :: self - type (enumerationComponentTypeType), intent(in ) :: componentType - type (enumerationMassTypeType ), intent(in ) :: massType - type (enumerationWeightByType ), intent(in ) :: weightBy - integer , intent(in ) :: weightIndex - double precision , intent(in ) :: positionCylindrical(3) - type (treeNode ), pointer :: selfNode - type (stellarLuminosities ), save :: luminositiesDisk - !$omp threadprivate(luminositiesDisk) - type (coordinateCylindrical ) :: position + class(nodeComponentDiskStandard), intent(inout) :: self - ! Return immediately if disk component is not requested. - Node_Component_Disk_Standard_Surface_Density=0.0d0 - if (.not.(componentType == componentTypeAll .or. componentType == componentTypeDisk)) return - ! Check whether this is a new node. - selfNode => self%host() - if (selfNode%uniqueID() /= lastUniqueID) call Node_Component_Disk_Standard_Reset(selfNode%uniqueID()) - ! Determine disk radius. - if (.not.radiusScaleDiskComputed) then - radiusScaleDisk =self%radius() - radiusScaleDiskComputed=.true. - end if - ! Return zero if the disk has unphysical size. - if (radiusScaleDisk <= 0.0d0) then - Node_Component_Disk_Standard_Surface_Density=0.0d0 - return - end if - ! Determine mass type. - select case (weightBy%ID) - case (weightByMass %ID) - select case (massType%ID) - case (massTypeAll%ID,massTypeBaryonic%ID,massTypeGalactic%ID) - if (.not.surfaceDensityCentralTotalComputed ) then - surfaceDensityCentralTotal =(self%massGas()+self%massStellar())/radiusScaleDisk**2 - surfaceDensityCentralTotalComputed =.true. - end if - Node_Component_Disk_Standard_Surface_Density=surfaceDensityCentralTotal - case (massTypeGaseous%ID) - if (.not.surfaceDensityCentralGasComputed ) then - surfaceDensityCentralGas = self%massGas() /radiusScaleDisk**2 - surfaceDensityCentralGasComputed =.true. - end if - Node_Component_Disk_Standard_Surface_Density=surfaceDensityCentralGas - case (massTypeStellar%ID) - if (.not.surfaceDensityCentralStellarComputed) then - surfaceDensityCentralStellar = self%massStellar() /radiusScaleDisk**2 - surfaceDensityCentralStellarComputed=.true. - end if - Node_Component_Disk_Standard_Surface_Density=surfaceDensityCentralStellar - end select - case (weightByLuminosity%ID) - select case (massType%ID) - case (massTypeAll%ID,massTypeBaryonic%ID,massTypeGalactic%ID,massTypeStellar%ID) - luminositiesDisk=self%luminositiesStellar() - Node_Component_Disk_Standard_Surface_Density=luminositiesDisk%luminosity(weightIndex)/2.0d0/Pi/radiusScaleDisk**2 - end select + select type (massDistributionStellar_) + class is (massDistributionCylindrical) + radiusHalfMass=+massDistributionStellar_%radiusHalfMass() & + & *self %radius () + class default + radiusHalfMass=0.0d0 + call Error_Report('disk mass distribution is not cylindrically-symmetric'//{introspection:location}) end select - ! Return if no surface density. - if (Node_Component_Disk_Standard_Surface_Density <= 0.0d0) return - ! Compute the surface density. - call position%rSet(positionCylindrical(1)/radiusScaleDisk) - Node_Component_Disk_Standard_Surface_Density= & - & +Node_Component_Disk_Standard_Surface_Density & - & *massDistributionDisk%surfaceDensity(position) return -end function Node_Component_Disk_Standard_Surface_Density +end function Node_Component_Disk_Standard_Half_Mass_Radius diff --git a/source/objects.nodes.components.disk.standard.data.F90 b/source/objects.nodes.components.disk.standard.data.F90 index 6b4fde528b..a7133ab98c 100644 --- a/source/objects.nodes.components.disk.standard.data.F90 +++ b/source/objects.nodes.components.disk.standard.data.F90 @@ -26,29 +26,29 @@ module Node_Component_Disk_Standard_Data Stores data for the standard disk node component. !!} use :: Kind_Numbers , only : kind_int8 - use :: Mass_Distributions, only : massDistributionClass, massDistributionCylindrical + use :: Mass_Distributions, only : massDistributionClass, kinematicsDistributionLocal implicit none public ! Record of unique ID of node which we last computed results for. - integer (kind=kind_int8 ) :: lastUniqueID =-1 + integer (kind=kind_int8 ) :: lastUniqueID =-1 !$omp threadprivate(lastUniqueID) ! Records of previously computed and stored quantities. - logical :: surfaceDensityCentralGasComputed , surfaceDensityCentralStellarComputed, & - & surfaceDensityCentralTotalComputed + logical :: surfaceDensityCentralGasComputed , surfaceDensityCentralStellarComputed, & + & surfaceDensityCentralTotalComputed !$omp threadprivate(surfaceDensityCentralGasComputed,surfaceDensityCentralStellarComputed,surfaceDensityCentralTotalComputed) - double precision :: surfaceDensityCentralGas , surfaceDensityCentralStellar , & - & surfaceDensityCentralTotal + double precision :: surfaceDensityCentralGas , surfaceDensityCentralStellar , & + & surfaceDensityCentralTotal !$omp threadprivate(surfaceDensityCentralGas,surfaceDensityCentralStellar,surfaceDensityCentralTotal) - logical :: radiusScaleDiskComputed + logical :: radiusScaleDiskComputed !$omp threadprivate(radiusScaleDiskComputed) - double precision :: radiusScaleDisk + double precision :: radiusScaleDisk !$omp threadprivate(radiusScaleDisk) - ! The mass distribution object. - class (massDistributionClass ), pointer :: massDistributionDisk_ - class (massDistributionCylindrical), pointer :: massDistributionDisk - !$omp threadprivate(massDistributionDisk_,massDistributionDisk) + ! The mass distribution objects. + class (massDistributionClass ), pointer :: massDistributionStellar_ , massDistributionGas_ + type (kinematicsDistributionLocal), pointer :: kinematicDistribution_ + !$omp threadprivate(massDistributionStellar_,massDistributionGas_,kinematicDistribution_) contains diff --git a/source/objects.nodes.components.disk.very_simple.F90 b/source/objects.nodes.components.disk.very_simple.F90 index 58fad8547b..0d2e92dde6 100644 --- a/source/objects.nodes.components.disk.very_simple.F90 +++ b/source/objects.nodes.components.disk.very_simple.F90 @@ -27,7 +27,6 @@ module Node_Component_Disk_Very_Simple !!} use :: Cosmology_Functions , only : cosmologyFunctionsClass use :: Dark_Matter_Halo_Scales , only : darkMatterHaloScaleClass - use :: Dark_Matter_Profiles_DMO , only : darkMatterProfileDMOClass use :: Galacticus_Nodes , only : treeNode use :: Math_Exponentiation , only : fastExponentiator use :: Satellite_Merging_Mass_Movements, only : mergerMassMovementsClass @@ -97,8 +96,8 @@ module Node_Component_Disk_Very_Simple - - + + objects.nodes.components.disk.very_simple.bound_functions.inc @@ -110,9 +109,8 @@ module Node_Component_Disk_Very_Simple class(darkMatterHaloScaleClass ), pointer :: darkMatterHaloScale_ class(stellarFeedbackOutflowsClass ), pointer :: stellarFeedbackOutflows_ class(starFormationRateDisksClass ), pointer :: starFormationRateDisks_ - class(darkMatterProfileDMOClass ), pointer :: darkMatterProfileDMO_ class(mergerMassMovementsClass ), pointer :: mergerMassMovements_ - !$omp threadprivate(cosmologyFunctions_,stellarPopulationProperties_,darkMatterHaloScale_,stellarFeedbackOutflows_,starFormationRateDisks_,darkMatterProfileDMO_,mergerMassMovements_) + !$omp threadprivate(cosmologyFunctions_,stellarPopulationProperties_,darkMatterHaloScale_,stellarFeedbackOutflows_,starFormationRateDisks_,mergerMassMovements_) ! Record of whether to use the simple disk analytic solver. logical :: useAnalyticSolver @@ -222,7 +220,6 @@ subroutine Node_Component_Disk_Very_Simple_Thread_Initialize(parameters) - @@ -253,7 +250,6 @@ subroutine Node_Component_Disk_Very_Simple_Thread_Uninitialize() - @@ -904,7 +900,7 @@ subroutine Node_Component_Disk_Very_Simple_State_Store(stateFile,gslStateFile,st call displayMessage('Storing state for: componentDisk -> verySimple',verbosity=verbosityLevelInfo) !![ - + !!] return end subroutine Node_Component_Disk_Very_Simple_State_Store @@ -927,7 +923,7 @@ subroutine Node_Component_Disk_Very_Simple_State_Restore(stateFile,gslStateFile, call displayMessage('Retrieving state for: componentDisk -> verySimple',verbosity=verbosityLevelInfo) !![ - + !!] return end subroutine Node_Component_Disk_Very_Simple_State_Restore diff --git a/source/objects.nodes.components.disk.very_simple.bound_functions.Inc b/source/objects.nodes.components.disk.very_simple.bound_functions.Inc index 126be1ab96..fd9e91b525 100644 --- a/source/objects.nodes.components.disk.very_simple.bound_functions.Inc +++ b/source/objects.nodes.components.disk.very_simple.bound_functions.Inc @@ -45,6 +45,18 @@ subroutine Node_Component_Disk_Very_Simple_Attach_Pipe(self) return end subroutine Node_Component_Disk_Very_Simple_Attach_Pipe +double precision function Node_Component_Disk_Very_Simple_Mass_Baryonic(self) result(massBaryonic) + !!{ + Return the baryonic mass for the very simple disk component. + !!} + implicit none + class(nodeComponentDiskVerySimple), intent(inout) :: self + + massBaryonic=+max(0.0d0,self%massStellar()) & + & +max(0.0d0,self%massGas ()) + return +end function Node_Component_Disk_Very_Simple_Mass_Baryonic + double precision function Node_Component_Disk_Very_Simple_Enclosed_Mass(self,radius,componentType,massType,weightBy,weightIndex) !!{ Computes the mass within a given radius for an very simple disk. diff --git a/source/objects.nodes.components.disk.very_simple.size.F90 b/source/objects.nodes.components.disk.very_simple.size.F90 index 831d0afa7c..4c07996ea4 100644 --- a/source/objects.nodes.components.disk.very_simple.size.F90 +++ b/source/objects.nodes.components.disk.very_simple.size.F90 @@ -65,8 +65,7 @@ module Node_Component_Disk_Very_Simple_Size - - + objects.nodes.components.disk.very_simple.size.bound_functions.inc @@ -121,7 +120,8 @@ subroutine Node_Component_Disk_Very_Simple_Size_Thread_Initialize(parameters) use :: Galacticus_Nodes , only : defaultDiskComponent use :: Input_Parameters , only : inputParameter , inputParameters use :: Mass_Distributions , only : massDistributionCylindrical - use :: Node_Component_Disk_Very_Simple_Size_Data, only : diskMassDistribution + use :: Node_Component_Disk_Very_Simple_Size_Data, only : massDistributionStellar_ , massDistributionGas_ + use :: Galactic_Structure_Options , only : componentTypeDisk , massTypeStellar , massTypeGaseous implicit none type(inputParameters), intent(inout) :: parameters type(inputParameters) :: subParameters @@ -130,15 +130,33 @@ subroutine Node_Component_Disk_Very_Simple_Size_Thread_Initialize(parameters) ! Find our parameters. subParameters=parameters%subParameters('componentDisk') !![ - + - + - + !!] - if (.not.diskMassDistribution%isDimensionless()) call Error_Report('disk mass distribution must be dimensionless'//{introspection:location}) + ! Validate the disk mass distribution. + select type (massDistributionStellar_) + class is (massDistributionCylindrical) + ! The disk mass distribution must have cylindrical symmetry. So, this is acceptable. + class default + call Error_Report('only cylindrically symmetric mass distributions are allowed'//{introspection:location}) + end select + if (.not.massDistributionStellar_%isDimensionless()) call Error_Report('disk mass distribution must be dimensionless'//{introspection:location}) + ! Duplicate the dimensionless mass distribution to use for the gas component, and set component and mass types in both. + !$omp critical(diskVerySimpleSizeDeepCopy) + allocate(massDistributionGas_,mold=massDistributionStellar_) + !![ + + + + !!] + !$omp end critical(diskVerySimpleSizeDeepCopy) + call massDistributionStellar_%setTypes(componentTypeDisk,massTypeStellar) + call massDistributionGas_ %setTypes(componentTypeDisk,massTypeGaseous) end if return end subroutine Node_Component_Disk_Very_Simple_Size_Thread_Initialize @@ -153,12 +171,13 @@ subroutine Node_Component_Disk_Very_Simple_Size_Thread_Uninitialize() Uninitializes the tree node standard merging statistics module. !!} use :: Galacticus_Nodes , only : defaultDiskComponent - use :: Node_Component_Disk_Very_Simple_Size_Data, only : diskMassDistribution + use :: Node_Component_Disk_Very_Simple_Size_Data, only : massDistributionStellar_, massDistributionGas_ implicit none if (defaultDiskComponent%verySimpleSizeIsActive()) then !![ - + + !!] end if return @@ -322,9 +341,9 @@ subroutine Node_Component_Disk_Very_Simple_Size_State_Store(stateFile,gslStateFi !!{ Write the tabulation state to file. !!} - use :: Display , only : displayMessage , verbosityLevelInfo - use, intrinsic :: ISO_C_Binding , only : c_ptr , c_size_t - use :: Node_Component_Disk_Very_Simple_Size_Data, only : diskMassDistribution + use :: Display , only : displayMessage , verbosityLevelInfo + use, intrinsic :: ISO_C_Binding , only : c_ptr , c_size_t + use :: Node_Component_Disk_Very_Simple_Size_Data, only : massDistributionStellar_, massDistributionGas_ implicit none integer , intent(in ) :: stateFile integer(c_size_t), intent(in ) :: stateOperationID @@ -332,7 +351,7 @@ subroutine Node_Component_Disk_Very_Simple_Size_State_Store(stateFile,gslStateFi call displayMessage('Storing state for: componentDisk -> standard',verbosity=verbosityLevelInfo) !![ - + !!] return end subroutine Node_Component_Disk_Very_Simple_Size_State_Store @@ -346,9 +365,9 @@ subroutine Node_Component_Disk_Very_Simple_Size_State_Retrieve(stateFile,gslStat !!{ Retrieve the tabulation state from the file. !!} - use :: Display , only : displayMessage , verbosityLevelInfo - use, intrinsic :: ISO_C_Binding , only : c_ptr , c_size_t - use :: Node_Component_Disk_Very_Simple_Size_Data, only : diskMassDistribution + use :: Display , only : displayMessage , verbosityLevelInfo + use, intrinsic :: ISO_C_Binding , only : c_ptr , c_size_t + use :: Node_Component_Disk_Very_Simple_Size_Data, only : massDistributionStellar_, massDistributionGas_ implicit none integer , intent(in ) :: stateFile integer(c_size_t), intent(in ) :: stateOperationID @@ -356,7 +375,7 @@ subroutine Node_Component_Disk_Very_Simple_Size_State_Retrieve(stateFile,gslStat call displayMessage('Retrieving state for: componentDisk -> standard',verbosity=verbosityLevelInfo) !![ - + !!] return end subroutine Node_Component_Disk_Very_Simple_Size_State_Retrieve diff --git a/source/objects.nodes.components.disk.very_simple.size.bound_functions.Inc b/source/objects.nodes.components.disk.very_simple.size.bound_functions.Inc index a3a79cabff..91d642e94d 100644 --- a/source/objects.nodes.components.disk.very_simple.size.bound_functions.Inc +++ b/source/objects.nodes.components.disk.very_simple.size.bound_functions.Inc @@ -21,154 +21,120 @@ Contains custom functions for the very simple size disk component. !!} -double precision function Node_Component_Disk_Very_Simple_Size_Half_Mass_Radius(self) +function Node_Component_Disk_Very_Simple_Size_Mass_Distribution(self,componentType,massType,weightBy,weightIndex) result(massDistribution_) !!{ - Return the half-mass radius of the very simple size disk. + Return the mass distribution for the very simple size disk component. !!} + use :: Mass_Distributions , only : massDistributionClass , massDistributionCylindricalScaler, massDistributionComposite , massDistributionList , & + & massDistributionCylindrical, massDistributionMatches_ + use :: Node_Component_Disk_Very_Simple_Size_Data, only : massDistributionStellar_ , massDistributionGas_ + use :: Galactic_Structure_Options , only : componentTypeDisk , massTypeStellar , massTypeGaseous , enumerationWeightByType, & + & weightByMass , weightByLuminosity , enumerationComponentTypeType, enumerationMassTypeType implicit none - class (nodeComponentDiskVerySimpleSize), intent(inout) :: self + class (massDistributionClass ), pointer :: massDistribution_ + class (nodeComponentDiskVerySimpleSize ), intent(inout) :: self + type (enumerationComponentTypeType ), intent(in ), optional :: componentType + type (enumerationMassTypeType ), intent(in ), optional :: massType + type (enumerationWeightByType ), intent(in ), optional :: weightBy + integer , intent(in ), optional :: weightIndex + type (massDistributionCylindricalScaler), pointer :: massDistributionStellar , massDistributionGas + type (massDistributionComposite ), pointer :: massDistributionTotal + type (massDistributionList ), pointer :: massDistributionComponents + type (stellarLuminosities ), save :: luminosities + !$omp threadprivate(luminosities) + double precision :: massStellar , massGas , & + & radiusScale + logical :: includeGas , includeStars + !![ + + !!] - Node_Component_Disk_Very_Simple_Size_Half_Mass_Radius=self%radius() - return -end function Node_Component_Disk_Very_Simple_Size_Half_Mass_Radius - -double precision function Node_Component_Disk_Very_Simple_Size_Enclosed_Mass(self,radius,componentType,massType,weightBy,weightIndex) - !!{ - Computes the mass within a given radius for an standard disk. - !!} - use :: Galactic_Structure_Options , only : componentTypeAll , componentTypeDisk, massTypeAll , massTypeBaryonic , & - & massTypeGalactic , massTypeGaseous , massTypeStellar , radiusLarge , & - & weightByLuminosity , weightByMass , enumerationComponentTypeType, enumerationMassTypeType, & - & enumerationWeightByType - use :: Node_Component_Disk_Very_Simple_Size_Data, only : diskMassDistribution - implicit none - class (nodeComponentDiskVerySimpleSize), intent(inout) :: self - type (enumerationComponentTypeType ), intent(in ) :: componentType - type (enumerationMassTypeType ), intent(in ) :: massType - type (enumerationWeightByType ), intent(in ) :: weightBy - integer , intent(in ) :: weightIndex - double precision , intent(in ) :: radius - double precision :: radiusDisk , fractionalRadius - type (stellarLuminosities ), save :: luminositiesDisk - !$omp threadprivate(luminositiesDisk) - - ! Return immediately if disk component is not requested. - Node_Component_Disk_Very_Simple_Size_Enclosed_Mass=0.0d0 - if (.not.(componentType == componentTypeAll .or. componentType == componentTypeDisk)) return - ! Get the total mass. - select case (weightBy%ID) - case (weightByMass %ID) - select case (massType%ID) - case (massTypeAll%ID,massTypeBaryonic%ID,massTypeGalactic%ID) - Node_Component_Disk_Very_Simple_Size_Enclosed_Mass=self%massGas()+self%massStellar() - case (massTypeGaseous%ID) - Node_Component_Disk_Very_Simple_Size_Enclosed_Mass=self%massGas() - case (massTypeStellar%ID) - Node_Component_Disk_Very_Simple_Size_Enclosed_Mass= self%massStellar() - end select - case (weightByLuminosity%ID) - select case (massType%ID) - case (massTypeAll%ID,massTypeBaryonic%ID,massTypeGalactic%ID,massTypeStellar%ID) - luminositiesDisk=self%luminositiesStellar() - Node_Component_Disk_Very_Simple_Size_Enclosed_Mass =luminositiesDisk%luminosity(weightIndex) - end select - end select - ! Return if no mass. - if (Node_Component_Disk_Very_Simple_Size_Enclosed_Mass <= 0.0d0) return - ! Return if the total mass was requested. - if (radius >= radiusLarge) return - ! Compute the actual mass. - radiusDisk=self%radius() - if (radiusDisk > 0.0d0) then - fractionalRadius=radius/radiusDisk - Node_Component_Disk_Very_Simple_Size_Enclosed_Mass= & - & +Node_Component_Disk_Very_Simple_Size_Enclosed_Mass & - & *diskMassDistribution%massEnclosedBySphere(fractionalRadius) + ! Determine which components of the disk to include. + includeGas =massDistributionMatches_(componentTypeDisk,massTypeGaseous,componentType,massType) .and. weightBy_ == weightByMass + includeStars=massDistributionMatches_(componentTypeDisk,massTypeStellar,componentType,massType) .and. (weightBy_ == weightByMass .or. weightBy_ == weightByLuminosity) + ! Get properties of the mass distribution and ensure they are physical. + if (weightBy_ == weightByMass ) then + massStellar = max (0.0d0,self %massStellar ( )) + massGas = max (0.0d0,self %massGas ( )) + else if (weightBy_ == weightByLuminosity) then + luminosities = self %luminositiesStellar( ) + massStellar = max (0.0d0,luminosities%luminosity (weightIndex)) + massGas = 0.0d0 + else + massDistribution_ => null() + return + end if + ! Determine which components to build. + radiusScale=self%radius() + if (radiusScale <= 0.0d0 .or. .not.(includeGas .or. includeStars)) then + ! Disk has non-positive size, or no components matched. Return a null distribution. + massDistribution_ => null() + else + ! Build the individual distributions. + massDistributionStellar => null() + massDistributionGas => null() + if (includeStars) then + allocate(massDistributionStellar) + select type (massDistributionStellar_) + class is (massDistributionCylindrical) + !![ + + !!] + end select + end if + if (includeGas ) then + allocate(massDistributionGas ) + select type (massDistributionGas_ ) + class is (massDistributionCylindrical) + !![ + + !!] + end select + end if + ! Combine the distributions as necessary. + if (includeStars .and. includeGas) then + ! Wrap the dimensionless mass distribution inside scaler classes to allow us to re-scale it to any disk system, and then composite those. + allocate(massDistributionTotal ) + allocate(massDistributionComponents ) + allocate(massDistributionComponents%next) + massDistributionComponents %massDistribution_ => massDistributionStellar + massDistributionComponents%next%massDistribution_ => massDistributionGas + !![ + + + + !!] + nullify(massDistributionComponents) + ! Return a pointer to the disk mass distribution. + massDistribution_ => massDistributionTotal + else if (includeStars ) then + ! Return just the stellar component. + massDistribution_ => massDistributionStellar_ + else if ( includeGas) then + ! Return just the gas component. + massDistribution_ => massDistributionGas_ + end if end if return -end function Node_Component_Disk_Very_Simple_Size_Enclosed_Mass +end function Node_Component_Disk_Very_Simple_Size_Mass_Distribution -double precision function Node_Component_Disk_Very_Simple_Size_Surface_Density(self,positionCylindrical,componentType,massType,weightBy,weightIndex) +double precision function Node_Component_Disk_Very_Simple_Size_Half_Mass_Radius(self) result(radiusHalfMass) !!{ - Computes the surface density at a given position for an standard disk. + Return the half-mass radius of the very simple size disk. !!} - use :: Coordinates , only : coordinateCylindrical - use :: Galactic_Structure_Options , only : componentTypeAll , componentTypeDisk , massTypeAll , massTypeBaryonic , & - & massTypeGalactic , massTypeGaseous , massTypeStellar , weightByLuminosity , & - & weightByMass , enumerationComponentTypeType, enumerationMassTypeType , enumerationWeightByType + use :: Error , only : Error_Report use :: Mass_Distributions , only : massDistributionCylindrical - use :: Node_Component_Disk_Very_Simple_Size_Data, only : Node_Component_Disk_Very_Simple_Size_Reset, diskMassDistribution , lastUniqueID , radiusScaleDisk , & - & radiusScaleDiskComputed , surfaceDensityCentralGas , surfaceDensityCentralGasComputed , surfaceDensityCentralStellar, & - & surfaceDensityCentralStellarComputed , surfaceDensityCentralTotal , surfaceDensityCentralTotalComputed - use :: Numerical_Constants_Math , only : Pi + use :: Node_Component_Disk_Very_Simple_Size_Data, only : massDistributionStellar_ implicit none - class (nodeComponentDiskVerySimpleSize), intent(inout) :: self - type (enumerationComponentTypeType ), intent(in ) :: componentType - type (enumerationMassTypeType ), intent(in ) :: massType - type (enumerationWeightByType ), intent(in ) :: weightBy - integer , intent(in ) :: weightIndex - double precision , intent(in ) :: positionCylindrical(3) - type (treeNode ), pointer :: selfNode - type (stellarLuminosities ), save :: luminositiesDisk - !$omp threadprivate(luminositiesDisk) - type (coordinateCylindrical ) :: position + class(nodeComponentDiskVerySimpleSize), intent(inout) :: self - ! Return immediately if disk component is not requested. - Node_Component_Disk_Very_Simple_Size_Surface_Density=0.0d0 - if (.not.(componentType == componentTypeAll .or. componentType == componentTypeDisk)) return - ! Check whether this is a new node. - selfNode => self%host() - if (selfNode%uniqueID() /= lastUniqueID) call Node_Component_Disk_Very_Simple_Size_Reset(selfNode%uniqueID()) - ! Determine disk radius. - if (.not.radiusScaleDiskComputed) then - radiusScaleDisk =self%radius() - radiusScaleDiskComputed=.true. - end if - ! Return zero if the disk has unphysical size. - if (radiusScaleDisk <= 0.0d0) then - Node_Component_Disk_Very_Simple_Size_Surface_Density=0.0d0 - return - end if - ! Determine mass type. - select case (weightBy%ID) - case (weightByMass %ID) - select case (massType%ID) - case (massTypeAll%ID,massTypeBaryonic%ID,massTypeGalactic%ID) - if (.not.surfaceDensityCentralTotalComputed ) then - surfaceDensityCentralTotal =(self%massGas()+self%massStellar())/radiusScaleDisk**2 - surfaceDensityCentralTotalComputed =.true. - end if - Node_Component_Disk_Very_Simple_Size_Surface_Density=surfaceDensityCentralTotal - case (massTypeGaseous%ID) - if (.not.surfaceDensityCentralGasComputed ) then - surfaceDensityCentralGas = self%massGas() /radiusScaleDisk**2 - surfaceDensityCentralGasComputed =.true. - end if - Node_Component_Disk_Very_Simple_Size_Surface_Density=surfaceDensityCentralGas - case (massTypeStellar%ID) - if (.not.surfaceDensityCentralStellarComputed) then - surfaceDensityCentralStellar = self%massStellar() /radiusScaleDisk**2 - surfaceDensityCentralStellarComputed=.true. - end if - Node_Component_Disk_Very_Simple_Size_Surface_Density=surfaceDensityCentralStellar - end select - case (weightByLuminosity%ID) - select case (massType%ID) - case (massTypeAll%ID,massTypeBaryonic%ID,massTypeGalactic%ID,massTypeStellar%ID) - luminositiesDisk=self%luminositiesStellar() - Node_Component_Disk_Very_Simple_Size_Surface_Density=luminositiesDisk%luminosity(weightIndex)/2.0d0/Pi/radiusScaleDisk**2 - end select - end select - ! Return if no density. - if (Node_Component_Disk_Very_Simple_Size_Surface_Density <= 0.0d0) return - ! Check that the mass distribution is cylindrical. - select type (diskMassDistribution) + select type (massDistributionStellar_) class is (massDistributionCylindrical) - ! Compute the surface density. - call position%rSet(positionCylindrical(1)/self%radius()) - Node_Component_Disk_Very_Simple_Size_Surface_Density= & - & +Node_Component_Disk_Very_Simple_Size_Surface_Density & - & *diskMassDistribution%surfaceDensity(position) + radiusHalfMass=+massDistributionStellar_%radiusHalfMass() & + & *self %radius () + class default + radiusHalfMass=0.0d0 + call Error_Report('disk mass distribution is not cylindrically-symmetric'//{introspection:location}) end select return -end function Node_Component_Disk_Very_Simple_Size_Surface_Density +end function Node_Component_Disk_Very_Simple_Size_Half_Mass_Radius diff --git a/source/objects.nodes.components.disk.very_simple.size.data.F90 b/source/objects.nodes.components.disk.very_simple.size.data.F90 index ec1669b9f9..842eef04f4 100644 --- a/source/objects.nodes.components.disk.very_simple.size.data.F90 +++ b/source/objects.nodes.components.disk.very_simple.size.data.F90 @@ -31,7 +31,7 @@ module Node_Component_Disk_Very_Simple_Size_Data public ! Record of unique ID of node which we last computed results for. - integer (kind=kind_int8 ) :: lastUniqueID =-1 + integer (kind=kind_int8 ) :: lastUniqueID =-1 !$omp threadprivate(lastUniqueID) ! Records of previously computed and stored quantities. logical :: surfaceDensityCentralGasComputed =.false., surfaceDensityCentralStellarComputed=.false., & @@ -45,9 +45,9 @@ module Node_Component_Disk_Very_Simple_Size_Data double precision :: radiusScaleDisk !$omp threadprivate(radiusScaleDisk) - ! The mass distribution object. - class (massDistributionClass), pointer :: diskMassDistribution - !$omp threadprivate(diskMassDistribution) + ! The mass distribution objects. + class (massDistributionClass), pointer :: massDistributionStellar_ , massDistributionGas_ + !$omp threadprivate(massDistributionStellar_,massDistributionGas_) contains diff --git a/source/objects.nodes.components.hot_halo.cold_mode.F90 b/source/objects.nodes.components.hot_halo.cold_mode.F90 index 747eb81aee..e0015513f3 100644 --- a/source/objects.nodes.components.hot_halo.cold_mode.F90 +++ b/source/objects.nodes.components.hot_halo.cold_mode.F90 @@ -27,11 +27,12 @@ module Node_Component_Hot_Halo_Cold_Mode Implements an extension to the standard hot halo node component which supports a cold mode reservoir. !!} - use :: Accretion_Halos , only : accretionHaloClass - use :: Cooling_Cold_Mode_Infall_Rates , only : coldModeInfallRateClass - use :: Cosmology_Parameters , only : cosmologyParametersClass - use :: Galactic_Structure , only : galacticStructureClass - use :: Hot_Halo_Outflows_Reincorporations, only : hotHaloOutflowReincorporationClass + use :: Accretion_Halos , only : accretionHaloClass + use :: Cooling_Cold_Mode_Infall_Rates , only : coldModeInfallRateClass + use :: Cosmology_Parameters , only : cosmologyParametersClass + use :: Dark_Matter_Halo_Scales , only : darkMatterHaloScaleClass + use :: Hot_Halo_Outflows_Reincorporations , only : hotHaloOutflowReincorporationClass + use :: Hot_Halo_Cold_Mode_Mass_Distributions, only : hotHaloColdModeMassDistributionClass implicit none private public :: Node_Component_Hot_Halo_Cold_Mode_Initialize , Node_Component_Hot_Halo_Cold_Mode_Rate_Compute , & @@ -79,17 +80,34 @@ module Node_Component_Hot_Halo_Cold_Mode Node_Component_Hot_Halo_Cold_Mode_Mass_Total + + + + class(massDistributionClass), pointer + 0 + Galactic_Structure_Options, only : enumerationWeightByType, enumerationComponentTypeType, enumerationMassTypeType + Mass_Distributions , only : massDistributionClass + + type (enumerationComponentTypeType), intent(in ), optional :: componentType + type (enumerationMassTypeType ), intent(in ), optional :: massType + type (enumerationWeightByType ), intent(in ), optional :: weightBy + integer , intent(in ), optional :: weightIndex + + + + objects.nodes.components.hot_halo.cold_mode.bound_functions.inc !!] ! Objects used by this component. - class(accretionHaloClass ), pointer :: accretionHalo_ - class(coldModeInfallRateClass ), pointer :: coldModeInfallRate_ - class(cosmologyParametersClass ), pointer :: cosmologyParameters_ - class(galacticStructureClass ), pointer :: galacticStructure_ - class(hotHaloOutflowReincorporationClass), pointer :: hotHaloOutflowReincorporation_ - !$omp threadprivate(accretionHalo_,coldModeInfallRate_,cosmologyParameters_,galacticStructure_,hotHaloOutflowReincorporation_) + class(accretionHaloClass ), pointer :: accretionHalo_ + class(coldModeInfallRateClass ), pointer :: coldModeInfallRate_ + class(cosmologyParametersClass ), pointer :: cosmologyParameters_ + class(darkMatterHaloScaleClass ), pointer :: darkMatterHaloScale_ + class(hotHaloOutflowReincorporationClass ), pointer :: hotHaloOutflowReincorporation_ + class(hotHaloColdModeMassDistributionClass), pointer :: hotHaloColdModeMassDistribution_ + !$omp threadprivate(accretionHalo_,coldModeInfallRate_,cosmologyParameters_,darkMatterHaloScale_,hotHaloOutflowReincorporation_,hotHaloColdModeMassDistribution_) ! Options controlling the behavior of the cold mode gas. logical :: outflowToColdMode @@ -101,6 +119,9 @@ module Node_Component_Hot_Halo_Cold_Mode integer :: thread !$omp threadprivate(thread) + ! Procedure pointer to mass distribution function. + procedure(Node_Component_Hot_Halo_Cold_Mode_Mass_Distribution), pointer :: Node_Component_Hot_Halo_Cold_Mode_Mass_Distribution_ + contains !![ @@ -139,6 +160,9 @@ subroutine Node_Component_Hot_Halo_Cold_Mode_Initialize(parameters) ! Bind the outflow return function if outflow returns to the cold mode. (If it does not, do ! not bind any function and let the parent class handle this behavior.) if (outflowToColdMode) call hotHalo%outflowReturnFunction(Node_Component_Hot_Halo_Cold_Mode_Outflow_Return) + ! Bind the mass distribution function. + Node_Component_Hot_Halo_Cold_Mode_Mass_Distribution_ => Node_Component_Hot_Halo_Cold_Mode_Mass_Distribution + call hotHalo%massDistributionFunction(Node_Component_Hot_Halo_Cold_Mode_Mass_Distribution_) end if !$omp end critical (Node_Component_Hot_Halo_Cold_Mode_Initialize) return @@ -153,12 +177,11 @@ subroutine Node_Component_Hot_Halo_Cold_Mode_Thread_Initialize(parameters) !!{ Initializes the tree node hot halo cold mode methods module. !!} - use :: Events_Hooks , only : nodePromotionEvent , satelliteMergerEvent , openMPThreadBindingAtLevel, dependencyRegEx, & - & dependencyDirectionAfter , haloFormationEvent - use :: Galacticus_Nodes , only : defaultHotHaloComponent - use :: Hot_Halo_Cold_Mode_Density_Core_Radii , only : hotHaloColdModeCoreRadii - use :: Input_Parameters , only : inputParameter , inputParameters - use :: Node_Component_Hot_Halo_Cold_Mode_Structure_Tasks, only : darkMatterHaloScale_ , hotHaloColdModeCoreRadii_ + use :: Events_Hooks , only : nodePromotionEvent , satelliteMergerEvent, openMPThreadBindingAtLevel, dependencyRegEx, & + & dependencyDirectionAfter , haloFormationEvent + use :: Galacticus_Nodes , only : defaultHotHaloComponent + use :: Hot_Halo_Cold_Mode_Density_Core_Radii, only : hotHaloColdModeCoreRadii + use :: Input_Parameters , only : inputParameter , inputParameters implicit none type(inputParameters), intent(inout) :: parameters type(dependencyRegEx), dimension(1) :: dependencies @@ -168,13 +191,12 @@ subroutine Node_Component_Hot_Halo_Cold_Mode_Thread_Initialize(parameters) ! Find our parameters. subParameters=parameters%subParameters('componentHotHalo') !![ - - - - - - - + + + + + + !!] dependencies(1)=dependencyRegEx(dependencyDirectionAfter,'^remnantStructure:') call nodePromotionEvent %attach(thread,nodePromotion ,openMPThreadBindingAtLevel,label='nodeComponentHotHaloColdMode' ) @@ -193,20 +215,18 @@ subroutine Node_Component_Hot_Halo_Cold_Mode_Thread_Uninitialize() !!{ Uninitializes the tree node hot halo cold mode methods module. !!} - use :: Events_Hooks , only : nodePromotionEvent , satelliteMergerEvent , haloFormationEvent - use :: Galacticus_Nodes , only : defaultHotHaloComponent - use :: Node_Component_Hot_Halo_Cold_Mode_Structure_Tasks, only : darkMatterHaloScale_ , hotHaloColdModeCoreRadii_ + use :: Events_Hooks , only : nodePromotionEvent , satelliteMergerEvent , haloFormationEvent + use :: Galacticus_Nodes, only : defaultHotHaloComponent implicit none if (defaultHotHaloComponent%coldModeIsActive()) then !![ - - - - - - - + + + + + + !!] if (nodePromotionEvent %isAttached(thread,nodePromotion )) call nodePromotionEvent %detach(thread,nodePromotion ) if (satelliteMergerEvent%isAttached(thread,satelliteMerger)) call satelliteMergerEvent%detach(thread,satelliteMerger) @@ -284,27 +304,30 @@ subroutine Node_Component_Hot_Halo_Cold_Mode_Rate_Compute(node,interrupt,interru !!{ Compute the hot halo node mass rate of change. !!} - use :: Abundances_Structure , only : abs - use :: Accretion_Halos , only : accretionModeCold - use :: Galactic_Structure_Options , only : componentTypeColdHalo , coordinateSystemSpherical , massTypeGaseous - use :: Galacticus_Nodes , only : defaultHotHaloComponent , interruptTask , nodeComponentBasic, nodeComponentHotHalo, & - & nodeComponentHotHaloColdMode , propertyInactive , treeNode , nodeComponentSpin - use :: Node_Component_Hot_Halo_Standard_Data , only : angularMomentumAlwaysGrows, outerRadiusOverVirialRadiusMinimum - use :: Node_Component_Hot_Halo_Cold_Mode_Structure_Tasks, only : darkMatterHaloScale_ - use :: Numerical_Constants_Math , only : Pi + use :: Abundances_Structure , only : abs + use :: Accretion_Halos , only : accretionModeCold + use :: Galactic_Structure_Options , only : componentTypeColdHalo , massTypeGaseous + use :: Galacticus_Nodes , only : defaultHotHaloComponent , interruptTask , nodeComponentBasic, nodeComponentHotHalo, & + & nodeComponentHotHaloColdMode, propertyInactive , treeNode , nodeComponentSpin + use :: Node_Component_Hot_Halo_Standard_Data, only : angularMomentumAlwaysGrows , outerRadiusOverVirialRadiusMinimum + use :: Numerical_Constants_Math , only : Pi + use :: Coordinates , only : coordinateSpherical , assignment(=) + use :: Mass_Distributions , only : massDistributionClass implicit none - type (treeNode ), intent(inout) :: node - logical , intent(inout) :: interrupt - procedure (interruptTask ), intent(inout), pointer :: interruptProcedure - integer , intent(in ) :: propertyType - class (nodeComponentSpin ) , pointer :: spin - class (nodeComponentHotHalo) , pointer :: hotHalo - class (nodeComponentBasic ) , pointer :: basic - double precision :: angularMomentumAccretionRate, densityAtOuterRadius , & - & massAccretionRate , massLossRate , & - & outerRadius , outerRadiusGrowthRate, & - & gasMass , infallRate - + type (treeNode ), intent(inout) :: node + logical , intent(inout) :: interrupt + procedure (interruptTask ), intent(inout), pointer :: interruptProcedure + integer , intent(in ) :: propertyType + class (nodeComponentSpin ) , pointer :: spin + class (nodeComponentHotHalo ) , pointer :: hotHalo + class (nodeComponentBasic ) , pointer :: basic + class (massDistributionClass) , pointer :: massDistribution_ + double precision :: angularMomentumAccretionRate, densityAtOuterRadius , & + & massAccretionRate , massLossRate , & + & outerRadius , outerRadiusGrowthRate, & + & gasMass , infallRate + type (coordinateSpherical ) :: coordinates + ! Return immediately if inactive variables are requested. if (propertyInactive(propertyType)) return ! Return immediately if this class is not in use. @@ -351,7 +374,12 @@ subroutine Node_Component_Hot_Halo_Cold_Mode_Rate_Compute(node,interrupt,interru & outerRadius > outerRadiusOverVirialRadiusMinimum*darkMatterHaloScale_%radiusVirial(node) & & ) then ! The ram pressure stripping radius is within the outer radius. Remove mass from the cold mode halo at the appropriate rate. - densityAtOuterRadius=galacticStructure_%density(node,[outerRadius,0.0d0,0.0d0],coordinateSystemSpherical,componentTypeColdHalo,massTypeGaseous) + coordinates = [outerRadius,0.0d0,0.0d0] + massDistribution_ => node %massDistribution(componentType=componentTypeColdHalo,massType=massTypeGaseous) + densityAtOuterRadius = massDistribution_%density ( coordinates ) + !![ + + !!] ! Compute the mass loss rate. massLossRate=4.0d0*Pi*densityAtOuterRadius*outerRadius**2*outerRadiusGrowthRate ! Adjust the rates. @@ -375,28 +403,31 @@ subroutine Node_Component_Hot_Halo_Cold_Mode_Outflow_Return(self,interrupt,inter !!{ Return outflowed gas to the cold mode reservoir. !!} - use :: Abundances_Structure , only : abundances , max , operator(*) - use :: Galactic_Structure_Options , only : componentTypeColdHalo, coordinateSystemSpherical, massTypeGaseous - use :: Error , only : Error_Report - use :: Galacticus_Nodes , only : interruptTask , nodeComponentBasic , nodeComponentHotHaloColdMode, nodeComponentHotHaloStandard, & - & treeNode - use :: Node_Component_Hot_Halo_Standard_Data , only : starveSatellites - use :: Node_Component_Hot_Halo_Cold_Mode_Structure_Tasks, only : darkMatterHaloScale_ - use :: Numerical_Constants_Astronomical , only : gigaYear , megaParsec - use :: Numerical_Constants_Math , only : Pi - use :: Numerical_Constants_Prefixes , only : kilo + use :: Abundances_Structure , only : abundances , max , operator(*) + use :: Galactic_Structure_Options , only : componentTypeColdHalo, massTypeGaseous + use :: Error , only : Error_Report + use :: Galacticus_Nodes , only : interruptTask , nodeComponentBasic, nodeComponentHotHaloColdMode, nodeComponentHotHaloStandard, & + & treeNode + use :: Node_Component_Hot_Halo_Standard_Data, only : starveSatellites + use :: Numerical_Constants_Astronomical , only : gigaYear , megaParsec + use :: Numerical_Constants_Math , only : Pi + use :: Numerical_Constants_Prefixes , only : kilo + use :: Coordinates , only : coordinateSpherical , assignment(=) + use :: Mass_Distributions , only : massDistributionClass implicit none class (nodeComponentHotHaloStandard), intent(inout) :: self logical , intent(inout) :: interrupt procedure (interruptTask ), intent(inout), pointer :: interruptProcedure type (treeNode ), pointer :: node class (nodeComponentBasic ), pointer :: basic + class (massDistributionClass ), pointer :: massDistribution_ double precision :: outflowedMass , massReturnRate, & & angularMomentumReturnRate, radiusVirial , & & densityAtOuterRadius , densityMinimum, & & outerRadius type (abundances ), save :: abundancesReturnRate !$omp threadprivate(abundancesReturnRate) + type (coordinateSpherical ) :: coordinates select type (self) class is (nodeComponentHotHaloColdMode) @@ -422,7 +453,12 @@ subroutine Node_Component_Hot_Halo_Cold_Mode_Outflow_Return(self,interrupt,inter outerRadius =self%outerRadius() radiusVirial=darkMatterHaloScale_%radiusVirial(node) if (outerRadius < radiusVirial) then - densityAtOuterRadius=galacticStructure_%density(node,[outerRadius,0.0d0,0.0d0],coordinateSystemSpherical,componentTypeColdHalo,massTypeGaseous) + coordinates = [outerRadius,0.0d0,0.0d0] + massDistribution_ => node %massDistribution(componentType=componentTypeColdHalo,massType=massTypeGaseous) + densityAtOuterRadius = massDistribution_%density ( coordinates ) + !![ + + !!] ! If the outer radius and density are non-zero we can expand the outer radius at a rate determined by the current ! density profile. if (outerRadius > 0.0d0 .and. densityAtOuterRadius > 0.0d0) then @@ -462,10 +498,9 @@ subroutine Node_Component_Hot_Halo_Cold_Mode_Scale_Set(node) !!{ Set scales for properties of {\normalfont \ttfamily node}. !!} - use :: Abundances_Structure , only : unitAbundances - use :: Galacticus_Nodes , only : nodeComponentBasic , nodeComponentHotHalo, nodeComponentHotHaloColdMode, treeNode, & - & defaultHotHaloComponent - use :: Node_Component_Hot_Halo_Cold_Mode_Structure_Tasks, only : darkMatterHaloScale_ + use :: Abundances_Structure, only : unitAbundances + use :: Galacticus_Nodes , only : nodeComponentBasic , nodeComponentHotHalo, nodeComponentHotHaloColdMode, treeNode, & + & defaultHotHaloComponent implicit none type (treeNode ), intent(inout), pointer :: node class (nodeComponentHotHalo) , pointer :: hotHalo @@ -564,25 +599,27 @@ subroutine Node_Component_Hot_Halo_Cold_Mode_Node_Merger(node) !!{ Starve {\normalfont \ttfamily node} by transferring its hot halo to its parent. !!} - use :: Abundances_Structure , only : abundances , operator(*) , zeroAbundances - use :: Accretion_Halos , only : accretionModeCold , accretionModeTotal - use :: Galactic_Structure_Options , only : componentTypeAll , massTypeBaryonic , radiusLarge - use :: Galacticus_Nodes , only : nodeComponentBasic , nodeComponentHotHalo , nodeComponentHotHaloColdMode, nodeComponentSpin, & - & treeNode , defaultHotHaloComponent + use :: Abundances_Structure , only : abundances , operator(*) , zeroAbundances + use :: Accretion_Halos , only : accretionModeCold , accretionModeTotal + use :: Galactic_Structure_Options , only : componentTypeAll , massTypeBaryonic + use :: Galacticus_Nodes , only : nodeComponentBasic , nodeComponentHotHalo , nodeComponentHotHaloColdMode, nodeComponentSpin, & + & treeNode , defaultHotHaloComponent use :: Node_Component_Hot_Halo_Standard_Data, only : fractionBaryonLimitInNodeMerger, starveSatellites + use :: Mass_Distributions , only : massDistributionClass implicit none - type (treeNode ), intent(inout) :: node - type (treeNode ), pointer :: nodeParent - class (nodeComponentHotHalo), pointer :: hotHaloParent , hotHalo - class (nodeComponentSpin ), pointer :: spinParent - class (nodeComponentBasic ), pointer :: basicParent , basic - double precision :: baryonicMassCurrent , baryonicMassMaximum , & - & fractionRemove , massAccretedCold , & - & massAccreted , massUnaccreted , & - & angularMomentumAccreted, massReaccreted , & - & fractionAccreted - type (abundances ), save :: massMetalsAccreted , fractionMetalsAccreted, & - & massMetalsReaccreted + type (treeNode ), intent(inout) :: node + type (treeNode ), pointer :: nodeParent + class (nodeComponentHotHalo ), pointer :: hotHaloParent , hotHalo + class (nodeComponentSpin ), pointer :: spinParent + class (nodeComponentBasic ), pointer :: basicParent , basic + class (massDistributionClass), pointer :: massDistribution_ + double precision :: baryonicMassCurrent , baryonicMassMaximum , & + & fractionRemove , massAccretedCold , & + & massAccreted , massUnaccreted , & + & angularMomentumAccreted, massReaccreted , & + & fractionAccreted + type (abundances ), save :: massMetalsAccreted , fractionMetalsAccreted, & + & massMetalsReaccreted !$omp threadprivate(massMetalsAccreted,fractionMetalsAccreted,massMetalsReaccreted) ! Return immediately if this class is not in use. @@ -669,15 +706,14 @@ subroutine Node_Component_Hot_Halo_Cold_Mode_Node_Merger(node) ! Check if the baryon fraction in the parent hot halo exceeds the universal value. If it does, mitigate this by moving ! some of the mass to the failed accretion reservoir. if (fractionBaryonLimitInNodeMerger) then - baryonicMassMaximum=+basicParent %mass () & - & *cosmologyParameters_%omegaBaryon () & - & /cosmologyParameters_%omegaMatter () - baryonicMassCurrent= galacticStructure_ %massEnclosed( & - & nodeParent , & - & radiusLarge , & - & massType =massTypeBaryonic, & - & componentType=componentTypeAll & - & ) + massDistribution_ => nodeParent %massDistribution(massType=massTypeBaryonic) + baryonicMassMaximum = +basicParent %mass ( ) & + & *cosmologyParameters_%omegaBaryon ( ) & + & /cosmologyParameters_%omegaMatter ( ) + baryonicMassCurrent = +massDistribution_ %massTotal ( ) + !![ + + !!] if (baryonicMassCurrent > baryonicMassMaximum .and. hotHaloParent%mass()+hotHaloParent%massCold() > 0.0d0) then fractionRemove=min((baryonicMassCurrent-baryonicMassMaximum)/hotHaloParent%massTotal(),1.0d0) call hotHaloParent% unaccretedMassSet( & @@ -852,7 +888,7 @@ subroutine Node_Component_Hot_Halo_Cold_Mode_State_Store(stateFile,gslStateFile, call displayMessage('Storing state for: componentHotHalo -> coldMode',verbosity=verbosityLevelInfo) !![ - + !!] return end subroutine Node_Component_Hot_Halo_Cold_Mode_State_Store @@ -875,9 +911,77 @@ subroutine Node_Component_Hot_Halo_Cold_Mode_State_Restore(stateFile,gslStateFil call displayMessage('Retrieving state for: componentHotHalo -> coldMode',verbosity=verbosityLevelInfo) !![ - + !!] return end subroutine Node_Component_Hot_Halo_Cold_Mode_State_Restore + function Node_Component_Hot_Halo_Cold_Mode_Mass_Distribution(self,componentType,massType,weightBy,weightIndex) result(massDistribution_) + !!{ + Return the mass distribution associated with the hot halo. + !!} + use :: Galacticus_Nodes , only : nodeComponentHotHaloStandard, nodeComponentHotHaloColdMode + use :: Galactic_Structure_Options, only : enumerationWeightByType , enumerationComponentTypeType, enumerationMassTypeType , componentTypeColdHalo, & + & massTypeGaseous + use :: Mass_Distributions , only : massDistributionClass , kinematicsDistributionLocal , massDistributionComposite, massDistributionList , & + & massDistributionMatches_ + implicit none + class (massDistributionClass ), pointer :: massDistributionHotMode , massDistributionColdMode, & + & massDistribution_ + type (kinematicsDistributionLocal ), pointer :: kinematicsDistribution_ + type (massDistributionComposite ), pointer :: massDistributionTotal + type (massDistributionList ), pointer :: massDistributionComponents + class (nodeComponentHotHaloStandard), intent(inout) :: self + type (enumerationComponentTypeType), intent(in ), optional :: componentType + type (enumerationMassTypeType ), intent(in ), optional :: massType + type (enumerationWeightByType ), intent(in ), optional :: weightBy + integer , intent(in ), optional :: weightIndex + + select type (self) + class is (nodeComponentHotHaloColdMode) + if (massDistributionMatches_(componentTypeColdHalo,massTypeGaseous,componentType,massType)) then + massDistributionColdMode => hotHaloColdModeMassDistribution_ %get (self%hostNode ,weightBy,weightIndex) + else + massDistributionColdMode => null() + end if + massDistributionHotMode => self %nodeComponentHotHaloStandard%massDistribution(componentType,massType,weightBy,weightIndex) + if (associated(massDistributionColdMode)) then + allocate(kinematicsDistribution_) + !![ + + !!] + call massDistributionColdMode%setKinematicsDistribution(kinematicsDistribution_) + !![ + + !!] + end if + if (.not.associated(massDistributionColdMode)) then + if (.not.associated(massDistributionHotMode)) then + massDistribution_ => null() + else + massDistribution_ => massDistributionHotMode + end if + else + if (.not.associated(massDistributionHotMode)) then + massDistribution_ => massDistributionColdMode + else + allocate(massDistributionTotal ) + allocate(massDistributionComponents ) + allocate(massDistributionComponents%next) + massDistributionComponents %massDistribution_ => massDistributionHotMode + massDistributionComponents%next%massDistribution_ => massDistributionColdMode + !![ + + + + !!] + nullify(massDistributionComponents) + end if + end if + class default + call Error_Report('unexpected class'//{introspection:location}) + end select + return + end function Node_Component_Hot_Halo_Cold_Mode_Mass_Distribution + end module Node_Component_Hot_Halo_Cold_Mode diff --git a/source/objects.nodes.components.hot_halo.cold_mode.bound_functions.Inc b/source/objects.nodes.components.hot_halo.cold_mode.bound_functions.Inc index 75242299f7..5fd0c3d596 100644 --- a/source/objects.nodes.components.hot_halo.cold_mode.bound_functions.Inc +++ b/source/objects.nodes.components.hot_halo.cold_mode.bound_functions.Inc @@ -32,3 +32,16 @@ double precision function Node_Component_Hot_Halo_Cold_Mode_Mass_Total(self) return end function Node_Component_Hot_Halo_Cold_Mode_Mass_Total +double precision function Node_Component_Hot_Halo_Cole_Mode_Mass_Baryonic(self) result(massBaryonic) + !!{ + Return the baryonic mass for the very simple disk component. + !!} + implicit none + class(nodeComponentHotHaloColdMode), intent(inout) :: self + + massBaryonic=+max(0.0d0,self%mass ()) & + & +max(0.0d0,self%massCold ()) & + & +max(0.0d0,self%outflowedMass()) + return +end function Node_Component_Hot_Halo_Cole_Mode_Mass_Baryonic + diff --git a/source/objects.nodes.components.hot_halo.cold_mode.structure_tasks.F90 b/source/objects.nodes.components.hot_halo.cold_mode.structure_tasks.F90 deleted file mode 100644 index 4aef8cedd2..0000000000 --- a/source/objects.nodes.components.hot_halo.cold_mode.structure_tasks.F90 +++ /dev/null @@ -1,376 +0,0 @@ -!! Copyright 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, -!! 2019, 2020, 2021, 2022, 2023, 2024 -!! Andrew Benson -!! -!! This file is part of Galacticus. -!! -!! Galacticus is free software: you can redistribute it and/or modify -!! it under the terms of the GNU General Public License as published by -!! the Free Software Foundation, either version 3 of the License, or -!! (at your option) any later version. -!! -!! Galacticus is distributed in the hope that it will be useful, -!! but WITHOUT ANY WARRANTY; without even the implied warranty of -!! MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -!! GNU General Public License for more details. -!! -!! You should have received a copy of the GNU General Public License -!! along with Galacticus. If not, see . - -!!{ -Contains a module which implements structure tasks for the cold mode hot halo component. -!!} - -module Node_Component_Hot_Halo_Cold_Mode_Structure_Tasks - !!{ - Implements structure tasks for the cold mode hot halo component. - !!} - use :: Hot_Halo_Cold_Mode_Density_Core_Radii, only : hotHaloColdModeCoreRadiiClass - use :: Mass_Distributions , only : massDistributionBetaProfile - use :: Dark_Matter_Halo_Scales , only : darkMatterHaloScaleClass - implicit none - private - public :: Node_Component_Hot_Halo_Cold_Mode_Enclosed_Mass_Task , Node_Component_Hot_Halo_Cold_Mode_Rotation_Curve_Task , & - & Node_Component_Hot_Halo_Cold_Mode_Rotation_Curve_Gradient_Task, Node_Component_Hot_Halo_Cold_Mode_Density_Task , & - & Node_Component_Hot_Halo_Cold_Mode_Acceleration_Task , Node_Component_Hot_Halo_Cold_Mode_Tidal_Tensor_Task , & - & Node_Component_Hot_Halo_Cold_Mode_Chandrasekhar_Integral , Node_Component_Hot_Halo_Cold_Mode_Density_Sphrcl_Avrg_Task - - type (massDistributionBetaProfile ), public :: coldModeMassDistribution - class(hotHaloColdModeCoreRadiiClass), public, pointer :: hotHaloColdModeCoreRadii_ - class(darkMatterHaloScaleClass ), public, pointer :: darkMatterHaloScale_ - !$omp threadprivate(coldModeMassDistribution,hotHaloColdModeCoreRadii_,darkMatterHaloScale_) - -contains - - !![ - - Node_Component_Hot_Halo_Cold_Mode_Enclosed_Mass_Task - - !!] - double precision function Node_Component_Hot_Halo_Cold_Mode_Enclosed_Mass_Task(node,radius,componentType,massType,weightBy,weightIndex) - !!{ - Computes the mass within a given radius for the cold mode hot halo component. - !!} - use :: Galactic_Structure_Options, only : componentTypeAll , componentTypeColdHalo , massTypeAll , massTypeBaryonic , & - & massTypeGaseous , radiusLarge , weightByMass, enumerationComponentTypeType, & - & enumerationMassTypeType, enumerationWeightByType - use :: Galacticus_Nodes , only : defaultHotHaloComponent, nodeComponentHotHalo , treeNode - implicit none - type (treeNode ), intent(inout) :: node - type (enumerationComponentTypeType), intent(in ) :: componentType - type (enumerationMassTypeType ), intent(in ) :: massType - type (enumerationWeightByType ), intent(in ) :: weightBy - integer , intent(in ) :: weightIndex - double precision , intent(in ) :: radius - class (nodeComponentHotHalo ) , pointer :: hotHalo - double precision :: radiusOuter , radiusCore - !$GLC attributes unused :: weightIndex - - ! Return zero mass if the requested mass type or component is not matched. - Node_Component_Hot_Halo_Cold_Mode_Enclosed_Mass_Task=0.0d0 - if (.not.defaultHotHaloComponent%coldModeIsActive() ) return - if (.not.(componentType == componentTypeAll .or. componentType == componentTypeColdHalo )) return - if (.not.(massType == massTypeAll .or. massType == massTypeBaryonic .or. massType == massTypeGaseous)) return - if (.not.(weightBy == weightByMass )) return - ! Get the hot halo component. - hotHalo => node %hotHalo () - ! Check for total mass request. - if (radius >= radiusLarge) then - Node_Component_Hot_Halo_Cold_Mode_Enclosed_Mass_Task=hotHalo%massCold() - return - end if - ! Get the outer radius. - radiusOuter = hotHalo%outerRadius() - if (radiusOuter <= 0.0d0) return - ! Compute the enclosed mass. - ! Find the scale length of the cold mode halo. - radiusCore=hotHaloColdModeCoreRadii_%radius(node) - ! Initialize the mass profile - coldModeMassDistribution=massDistributionBetaProfile(beta=2.0d0/3.0d0,coreRadius=radiusCore,mass=hotHalo%massCold(),outerRadius=hotHalo%outerRadius()) - ! Compute the enclosed mass. - Node_Component_Hot_Halo_Cold_Mode_Enclosed_Mass_Task=coldModeMassDistribution%massEnclosedBySphere(radius) - return - end function Node_Component_Hot_Halo_Cold_Mode_Enclosed_Mass_Task - - !![ - - Node_Component_Hot_Halo_Cold_Mode_Acceleration_Task - - !!] - function Node_Component_Hot_Halo_Cold_Mode_Acceleration_Task(node,positionCartesian,componentType,massType) - !!{ - Computes the acceleration due to a cold-mode profile. - !!} - use :: Galactic_Structure_Options , only : weightByMass , weightIndexNull, enumerationComponentTypeType, enumerationMassTypeType - use :: Galacticus_Nodes , only : treeNode - use :: Numerical_Constants_Astronomical, only : gigaYear , megaParsec - use :: Numerical_Constants_Astronomical, only : gravitationalConstantGalacticus - use :: Numerical_Constants_Prefixes , only : kilo - implicit none - double precision , dimension(3) :: Node_Component_Hot_Halo_Cold_Mode_Acceleration_Task - type (treeNode ), intent(inout) :: node - type (enumerationComponentTypeType), intent(in ) :: componentType - type (enumerationMassTypeType ), intent(in ) :: massType - double precision , intent(in ), dimension(3) :: positionCartesian - double precision :: radius - - radius =+sqrt(sum(positionCartesian**2)) - Node_Component_Hot_Halo_Cold_Mode_Acceleration_Task=-kilo & - & *gigaYear & - & /megaParsec & - & *gravitationalConstantGalacticus & - & *Node_Component_Hot_Halo_Cold_Mode_Enclosed_Mass_Task(node,radius,componentType,massType,weightByMass,weightIndexNull) & - & *positionCartesian & - & /radius**3 - return - end function Node_Component_Hot_Halo_Cold_Mode_Acceleration_Task - - !![ - - Node_Component_Hot_Halo_Cold_Mode_Tidal_Tensor_Task - - !!] - function Node_Component_Hot_Halo_Cold_Mode_Tidal_Tensor_Task(node,positionCartesian,componentType,massType) - !!{ - Computes the tidalTensor due to the cold mode halo. - !!} - use :: Galactic_Structure_Options , only : weightByMass , weightIndexNull , enumerationComponentTypeType, enumerationMassTypeType - use :: Galacticus_Nodes , only : treeNode - use :: Numerical_Constants_Math , only : Pi - use :: Numerical_Constants_Astronomical, only : gravitationalConstantGalacticus - use :: Tensors , only : tensorRank2Dimension3Symmetric , tensorIdentityR2D3Sym, assignment(=) , operator(*) - use :: Vectors , only : Vector_Outer_Product - implicit none - type (tensorRank2Dimension3Symmetric) :: Node_Component_Hot_Halo_Cold_Mode_Tidal_Tensor_Task - type (treeNode ), intent(inout) :: node - type (enumerationComponentTypeType ), intent(in ) :: componentType - type (enumerationMassTypeType ), intent(in ) :: massType - double precision , intent(in ), dimension(3) :: positionCartesian - double precision , dimension(3) :: positionSpherical - double precision :: radius , massEnclosed, & - & density - type (tensorRank2Dimension3Symmetric) :: positionTensor - - radius =sqrt(sum(positionCartesian**2)) - positionSpherical =[radius,0.0d0,0.0d0] - massEnclosed =Node_Component_Hot_Halo_Cold_Mode_Enclosed_Mass_Task(node,radius ,componentType,massType,weightByMass,weightIndexNull) - density =Node_Component_Hot_Halo_Cold_Mode_Density_Task (node,positionSpherical,componentType,massType,weightByMass,weightIndexNull) - positionTensor =Vector_Outer_Product ( positionCartesian,symmetrize=.true. ) - Node_Component_Hot_Halo_Cold_Mode_Tidal_Tensor_Task=+gravitationalConstantGalacticus & - & *( & - & -(massEnclosed /radius**3)*tensorIdentityR2D3Sym & - & +(massEnclosed*3.0d0 /radius**5)*positionTensor & - & -(density *4.0d0*Pi/radius**2)*positionTensor & - & ) - return - end function Node_Component_Hot_Halo_Cold_Mode_Tidal_Tensor_Task - - !![ - - Node_Component_Hot_Halo_Cold_Mode_Rotation_Curve_Task - - !!] - double precision function Node_Component_Hot_Halo_Cold_Mode_Rotation_Curve_Task(node,radius,componentType,massType) - !!{ - Computes the rotation curve at a given radius for the hot halo density profile. - !!} - use :: Galactic_Structure_Options , only : weightByMass , weightIndexNull, enumerationComponentTypeType, enumerationMassTypeType - use :: Galacticus_Nodes , only : treeNode - use :: Numerical_Constants_Astronomical, only : gravitationalConstantGalacticus - implicit none - type (treeNode ), intent(inout) :: node - type (enumerationComponentTypeType), intent(in ) :: componentType - type (enumerationMassTypeType ), intent(in ) :: massType - double precision , intent(in ) :: radius - double precision :: componentMass - - ! Set to zero by default. - Node_Component_Hot_Halo_Cold_Mode_Rotation_Curve_Task=0.0d0 - ! Compute if a spheroid is present. - if (radius > 0.0d0) then - componentMass=Node_Component_Hot_Halo_Cold_Mode_Enclosed_Mass_Task(node,radius,componentType,massType,weightByMass,weightIndexNull) - if (componentMass > 0.0d0) Node_Component_Hot_Halo_Cold_Mode_Rotation_Curve_Task=sqrt(gravitationalConstantGalacticus*componentMass)/sqrt(radius) - end if - return - end function Node_Component_Hot_Halo_Cold_Mode_Rotation_Curve_Task - - !![ - - Node_Component_Hot_Halo_Cold_Mode_Rotation_Curve_Gradient_Task - - !!] - double precision function Node_Component_Hot_Halo_Cold_Mode_Rotation_Curve_Gradient_Task(node,radius,componentType,massType) - !!{ - Computes the rotation curve gradient at a given radius for the hot halo density profile. - !!} - use :: Galactic_Structure_Options , only : weightByMass , weightIndexNull, enumerationComponentTypeType, enumerationMassTypeType - use :: Galacticus_Nodes , only : treeNode - use :: Numerical_Constants_Math , only : Pi - use :: Numerical_Constants_Astronomical, only : gravitationalConstantGalacticus - implicit none - type (treeNode ), intent(inout) :: node - type (enumerationComponentTypeType), intent(in ) :: componentType - type (enumerationMassTypeType ), intent(in ) :: massType - double precision , intent(in ) :: radius - double precision :: componentDensity, componentMass - - ! Set to zero by default. - Node_Component_Hot_Halo_Cold_Mode_Rotation_Curve_Gradient_Task=0.0d0 - ! Compute if a spheroid is present. - if (radius > 0.0d0) then - componentMass=Node_Component_Hot_Halo_Cold_Mode_Enclosed_Mass_Task(node,radius,componentType,massType,weightByMass,weightIndexNull) - if (componentMass > 0.0d0) then - componentDensity=Node_Component_Hot_Halo_Cold_Mode_Density_Task(node,[radius,0.0d0,0.0d0],componentType,massType,weightByMass,weightIndexNull) - Node_Component_Hot_Halo_Cold_Mode_Rotation_Curve_Gradient_Task=gravitationalConstantGalacticus*(-componentMass/radius**2+4.0d0*Pi*radius& - &*componentDensity) - end if - end if - return - end function Node_Component_Hot_Halo_Cold_Mode_Rotation_Curve_Gradient_Task - - !![ - - Node_Component_Hot_Halo_Cold_Mode_Density_Task - - !!] - double precision function Node_Component_Hot_Halo_Cold_Mode_Density_Task(node,positionSpherical,componentType,massType,weightBy,weightIndex) - !!{ - Computes the density at a given position for a cold-mode profile. - !!} - use :: Coordinates , only : assignment(=) , coordinateSpherical - use :: Galactic_Structure_Options, only : componentTypeAll , componentTypeColdHalo, massTypeAll , massTypeBaryonic , & - & massTypeGaseous , weightByMass , enumerationComponentTypeType, enumerationMassTypeType, & - & enumerationWeightByType - use :: Galacticus_Nodes , only : defaultHotHaloComponent, nodeComponentHotHalo , treeNode - implicit none - type (treeNode ), intent(inout) :: node - type (enumerationComponentTypeType), intent(in ) :: componentType - type (enumerationMassTypeType ), intent(in ) :: massType - type (enumerationWeightByType ), intent(in ) :: weightBy - integer , intent(in ) :: weightIndex - double precision , intent(in ) :: positionSpherical(3) - class (nodeComponentHotHalo ) , pointer :: hotHalo - type (coordinateSpherical ) :: position - double precision :: radiusOuter , radiusCore - !$GLC attributes unused :: weightIndex - - Node_Component_Hot_Halo_Cold_Mode_Density_Task=0.0d0 - if (.not.defaultHotHaloComponent%coldModeIsActive() ) return - if (.not.(componentType == componentTypeAll .or. componentType == componentTypeColdHalo )) return - if (.not.(massType == massTypeAll .or. massType == massTypeBaryonic .or. massType == massTypeGaseous)) return - if (.not.(weightBy == weightByMass )) return - ! Get the hot halo component. - hotHalo => node%hotHalo() - ! Get the outer radius. - radiusOuter = hotHalo%outerRadius() - if (radiusOuter <= 0.0d0) return - ! Compute the enclosed mass. - ! Find the scale length of the cold mode halo. - radiusCore=hotHaloColdModeCoreRadii_%radius(node) - ! Initialize the mass profile - coldModeMassDistribution=massDistributionBetaProfile(beta=2.0d0/3.0d0,coreRadius=radiusCore,mass=hotHalo%massCold(),outerRadius=hotHalo%outerRadius()) - ! Compute the density. - position=[positionSpherical(1)/radiusCore,0.0d0,0.0d0] - Node_Component_Hot_Halo_Cold_Mode_Density_Task=coldModeMassDistribution%density(position) - return - end function Node_Component_Hot_Halo_Cold_Mode_Density_Task - - !![ - - Node_Component_Hot_Halo_Cold_Mode_Density_Sphrcl_Avrg_Task - - !!] - double precision function Node_Component_Hot_Halo_Cold_Mode_Density_Sphrcl_Avrg_Task(node,radius,componentType,massType,weightBy,weightIndex) - !!{ - Computes the spherically-averaged density at a given position for a cold-mode profile. - !!} - use :: Coordinates , only : assignment(=) , coordinateSpherical - use :: Galactic_Structure_Options, only : componentTypeAll , componentTypeColdHalo, massTypeAll , massTypeBaryonic , & - & massTypeGaseous , weightByMass , enumerationComponentTypeType, enumerationMassTypeType, & - & enumerationWeightByType - use :: Galacticus_Nodes , only : defaultHotHaloComponent, nodeComponentHotHalo , treeNode - implicit none - type (treeNode ), intent(inout) :: node - type (enumerationComponentTypeType), intent(in ) :: componentType - type (enumerationMassTypeType ), intent(in ) :: massType - type (enumerationWeightByType ), intent(in ) :: weightBy - integer , intent(in ) :: weightIndex - double precision , intent(in ) :: radius - class (nodeComponentHotHalo ) , pointer :: hotHalo - type (coordinateSpherical ) :: position - double precision :: radiusOuter , radiusCore - !$GLC attributes unused :: weightIndex - - Node_Component_Hot_Halo_Cold_Mode_Density_Sphrcl_Avrg_Task=0.0d0 - if (.not.defaultHotHaloComponent%coldModeIsActive() ) return - if (.not.(componentType == componentTypeAll .or. componentType == componentTypeColdHalo )) return - if (.not.(massType == massTypeAll .or. massType == massTypeBaryonic .or. massType == massTypeGaseous)) return - if (.not.(weightBy == weightByMass )) return - ! Get the hot halo component. - hotHalo => node%hotHalo() - ! Get the outer radius. - radiusOuter = hotHalo%outerRadius() - if (radiusOuter <= 0.0d0) return - ! Compute the enclosed mass. - ! Find the scale length of the cold mode halo. - radiusCore=hotHaloColdModeCoreRadii_%radius(node) - ! Initialize the mass profile - coldModeMassDistribution=massDistributionBetaProfile(beta=2.0d0/3.0d0,coreRadius=radiusCore,mass=hotHalo%massCold(),outerRadius=hotHalo%outerRadius()) - ! Compute the density. - position=[radius/radiusCore,0.0d0,0.0d0] - Node_Component_Hot_Halo_Cold_Mode_Density_Sphrcl_Avrg_Task=coldModeMassDistribution%density(position) - return - end function Node_Component_Hot_Halo_Cold_Mode_Density_Sphrcl_Avrg_Task - - !![ - - Node_Component_Hot_Halo_Cold_Mode_Chandrasekhar_Integral - - !!] - function Node_Component_Hot_Halo_Cold_Mode_Chandrasekhar_Integral(node,nodeSatellite,positionCartesian,velocityCartesian,componentType,massType) - !!{ - Computes the Chandrasekhar integral due to a cold-mode profile. - !!} - use :: Dark_Matter_Halo_Scales , only : darkMatterHaloScale, darkMatterHaloScaleClass - use :: Galactic_Structure_Options, only : weightByMass , weightIndexNull , enumerationComponentTypeType, enumerationMassTypeType - use :: Galacticus_Nodes , only : treeNode , defaultHotHaloComponent - use :: Numerical_Constants_Math , only : Pi - implicit none - double precision , dimension(3) :: Node_Component_Hot_Halo_Cold_Mode_Chandrasekhar_Integral - type (treeNode ), intent(inout) :: node , nodeSatellite - type (enumerationComponentTypeType), intent(in ) :: componentType - type (enumerationMassTypeType ), intent(in ) :: massType - double precision , intent(in ), dimension(3) :: positionCartesian , velocityCartesian - double precision , dimension(3) :: positionSpherical - double precision , parameter :: XvMaximum =10.0d0 - double precision :: radius , velocity , & - & density , xV - !$GLC attributes unused :: nodeSatellite - - Node_Component_Hot_Halo_Cold_Mode_Chandrasekhar_Integral=0.0d0 - if (.not.defaultHotHaloComponent%coldModeIsActive() ) return - radius = sqrt(sum(positionCartesian**2)) - velocity = sqrt(sum(velocityCartesian**2)) - if (velocity <= 0.0d0) return - positionSpherical = [radius,0.0d0,0.0d0] - density = Node_Component_Hot_Halo_Cold_Mode_Density_Task(node,positionSpherical,componentType,massType,weightByMass,weightIndexNull) - if (density <= 0.0d0) return - xV = + velocity & - & /darkMatterHaloScale_%velocityVirial(node) & - & /sqrt(2.0d0) - Node_Component_Hot_Halo_Cold_Mode_Chandrasekhar_Integral = -density & - & *velocityCartesian & - & /velocity **3 - if (Xv <= XvMaximum) & - & Node_Component_Hot_Halo_Cold_Mode_Chandrasekhar_Integral=+Node_Component_Hot_Halo_Cold_Mode_Chandrasekhar_Integral & - & *( & - & +erf ( xV ) & - & -2.0d0 & - & * xV & - & *exp (-xV**2) & - & /sqrt( Pi ) & - & ) - return - end function Node_Component_Hot_Halo_Cold_Mode_Chandrasekhar_Integral - -end module Node_Component_Hot_Halo_Cold_Mode_Structure_Tasks diff --git a/source/objects.nodes.components.hot_halo.standard.F90 b/source/objects.nodes.components.hot_halo.standard.F90 index 605317ec5a..76469c885c 100644 --- a/source/objects.nodes.components.hot_halo.standard.F90 +++ b/source/objects.nodes.components.hot_halo.standard.F90 @@ -34,8 +34,8 @@ module Node_Component_Hot_Halo_Standard use :: Cosmology_Functions , only : cosmologyFunctionsClass use :: Cosmology_Parameters , only : cosmologyParametersClass use :: Dark_Matter_Halo_Scales , only : darkMatterHaloScaleClass - use :: Galactic_Structure , only : galacticStructureClass use :: Hot_Halo_Mass_Distributions , only : hotHaloMassDistributionClass + use :: Hot_Halo_Temperature_Profiles , only : hotHaloTemperatureProfileClass use :: Hot_Halo_Outflows_Reincorporations , only : hotHaloOutflowReincorporationClass use :: Hot_Halo_Ram_Pressure_Stripping , only : hotHaloRamPressureStrippingClass use :: Hot_Halo_Ram_Pressure_Stripping_Timescales, only : hotHaloRamPressureTimescaleClass @@ -220,7 +220,7 @@ module Node_Component_Hot_Halo_Standard void - logical, intent(inout) :: interrupt + logical , intent(inout) :: interrupt procedure(interruptTask), intent(inout), pointer :: interruptProcedure @@ -231,6 +231,20 @@ module Node_Component_Hot_Halo_Standard + + + class(massDistributionClass), pointer + 0 + Galactic_Structure_Options, only : enumerationWeightByType, enumerationComponentTypeType, enumerationMassTypeType + Mass_Distributions , only : massDistributionClass + + type (enumerationComponentTypeType), intent(in ), optional :: componentType + type (enumerationMassTypeType ), intent(in ), optional :: massType + type (enumerationWeightByType ), intent(in ), optional :: weightBy + integer , intent(in ), optional :: weightIndex + + + objects.nodes.components.hot_halo.standard.bound_functions.inc @@ -242,6 +256,7 @@ module Node_Component_Hot_Halo_Standard class(coolingSpecificAngularMomentumClass), pointer :: coolingSpecificAngularMomentum_ class(coolingInfallRadiusClass ), pointer :: coolingInfallRadius_ class(hotHaloMassDistributionClass ), pointer :: hotHaloMassDistribution_ + class(hotHaloTemperatureProfileClass ), pointer :: hotHaloTemperatureProfile_ class(accretionHaloClass ), pointer :: accretionHalo_ class(hotHaloRamPressureStrippingClass ), pointer :: hotHaloRamPressureStripping_ class(hotHaloRamPressureTimescaleClass ), pointer :: hotHaloRamPressureTimescale_ @@ -249,36 +264,38 @@ module Node_Component_Hot_Halo_Standard class(chemicalStateClass ), pointer :: chemicalState_ class(coolingRateClass ), pointer :: coolingRate_ class(cosmologyParametersClass ), pointer :: cosmologyParameters_ - class(galacticStructureClass ), pointer :: galacticStructure_ - !$omp threadprivate(cosmologyFunctions_,darkMatterHaloScale_,coolingSpecificAngularMomentum_,coolingInfallRadius_,hotHaloMassDistribution_,accretionHalo_,chemicalState_,hotHaloRamPressureStripping_,hotHaloRamPressureTimescale_,coolingRate_,cosmologyParameters_,hotHaloOutflowReincorporation_,galacticStructure_) + !$omp threadprivate(cosmologyFunctions_,darkMatterHaloScale_,coolingSpecificAngularMomentum_,coolingInfallRadius_,hotHaloMassDistribution_,hotHaloTemperatureProfile_,accretionHalo_,chemicalState_,hotHaloRamPressureStripping_,hotHaloRamPressureTimescale_,coolingRate_,cosmologyParameters_,hotHaloOutflowReincorporation_) ! Internal count of abundances and chemicals. - integer :: abundancesCount , chemicalsCount + integer :: abundancesCount , chemicalsCount ! Configuration variables. - logical :: hotHaloExcessHeatDrivesOutflow - double precision :: rateMaximumExpulsion , efficiencyStrippingOutflow + logical :: hotHaloExcessHeatDrivesOutflow + double precision :: rateMaximumExpulsion , efficiencyStrippingOutflow ! Quantities stored to avoid repeated computation. - integer (kind=kind_int8 ) :: uniqueIDPrevious - logical :: gotAngularMomentumCoolingRate =.false., gotCoolingRate =.false., & - & gotOuterRadiusGrowthRate =.false. - double precision :: angularMomentumHeatingRateRemaining , rateCooling , & - & massHeatingRateRemaining , outerRadiusGrowthRateStored + integer (kind=kind_int8 ) :: uniqueIDPrevious + logical :: gotAngularMomentumCoolingRate =.false., gotCoolingRate =.false., & + & gotOuterRadiusGrowthRate =.false. + double precision :: angularMomentumHeatingRateRemaining , rateCooling , & + & massHeatingRateRemaining , outerRadiusGrowthRateStored !$omp threadprivate(gotCoolingRate,gotAngularMomentumCoolingRate,gotOuterRadiusGrowthRate,rateCooling,massHeatingRateRemaining,angularMomentumHeatingRateRemaining,outerRadiusGrowthRateStored,uniqueIDPrevious) ! Radiation structure. - class (radiationFieldClass ), pointer :: radiation + class (radiationFieldClass ), pointer :: radiation !$omp threadprivate(radiation) ! Tracked properties control. - logical :: trackStrippedGas + logical :: trackStrippedGas ! Parameters controlling absolute tolerance scales. - double precision , parameter :: scaleMassRelative =1.0d-3 - double precision , parameter :: scaleRadiusRelative =1.0d-1 + double precision , parameter :: scaleMassRelative =1.0d-3 + double precision , parameter :: scaleRadiusRelative =1.0d-1 + ! Procedure pointer to mass distribution function. + procedure (Node_Component_Hot_Halo_Standard_Mass_Distribution), pointer :: Node_Component_Hot_Halo_Standard_Mass_Distribution_ + ! A threadprivate object used to track to which thread events are attached. - integer :: thread + integer :: thread !$omp threadprivate(thread) contains @@ -449,6 +466,9 @@ subroutine Node_Component_Hot_Halo_Standard_Initialize(parameters) call hotHalo% outflowingAbundancesRateFunction(Node_Component_Hot_Halo_Standard_Outflowing_Abundances_Rate) ! Bind a creation function. call hotHalo% createFunctionSet(Node_Component_Hot_Halo_Standard_Initializor ) + ! Bind the mass distribution function. + Node_Component_Hot_Halo_Standard_Mass_Distribution_ => Node_Component_Hot_Halo_Standard_Mass_Distribution + call hotHalo% massDistributionFunction(Node_Component_Hot_Halo_Standard_Mass_Distribution_ ) ! Bind the mass sink function. call hotHalo% massSinkRateFunction(Node_Component_Hot_Halo_Standard_Mass_Sink ) ! Bind the outflow return function. @@ -491,13 +511,13 @@ subroutine Node_Component_Hot_Halo_Standard_Thread_Initialize(parameters) + - !!] dependencies(1)=dependencyRegEx(dependencyDirectionAfter,'^remnantStructure:') call nodePromotionEvent %attach(thread,nodePromotion ,openMPThreadBindingAtLevel,label='nodeComponentHotHaloStandard' ) @@ -542,6 +562,7 @@ subroutine Node_Component_Hot_Halo_Standard_Thread_Uninitialize() + @@ -549,7 +570,6 @@ subroutine Node_Component_Hot_Halo_Standard_Thread_Uninitialize() - !!] if (nodePromotionEvent %isAttached(thread,nodePromotion )) call nodePromotionEvent %detach(thread,nodePromotion ) if (satelliteMergerEvent%isAttached(thread,satelliteMerger)) call satelliteMergerEvent%detach(thread,satelliteMerger) @@ -1025,17 +1045,24 @@ double precision function Node_Component_Hot_Halo_Standard_Outflow_Stripped_Frac Compute the fraction of material outflowing into the hot halo of {\normalfont \ttfamily node} which is susceptible to being stripped away. !!} - use :: Galacticus_Nodes, only : nodeComponentHotHaloStandard, treeNode + use :: Mass_Distributions , only : massDistributionClass + use :: Galactic_Structure_Options, only : componentTypeHotHalo , massTypeGaseous + use :: Galacticus_Nodes , only : nodeComponentHotHaloStandard, treeNode implicit none type (treeNode ), intent(inout), pointer :: node + class (massDistributionClass ) , pointer :: massDistribution_ class (nodeComponentHotHaloStandard) :: hotHalo - double precision :: massOuter , massVirial , & - & radiusOuter, radiusVirial - - radiusOuter =hotHalo %outerRadius ( ) - radiusVirial=darkMatterHaloScale_ %radiusVirial(node ) - massOuter =hotHaloMassDistribution_%enclosedMass(node,radiusOuter ) - massVirial =hotHaloMassDistribution_%enclosedMass(node,radiusVirial) + double precision :: massOuter , massVirial , & + & radiusOuter , radiusVirial + + massDistribution_ => node %massDistribution (componentTypeHotHalo,massTypeGaseous) + radiusOuter = hotHalo %outerRadius ( ) + radiusVirial = darkMatterHaloScale_%radiusVirial (node ) + massOuter = massDistribution_ %massEnclosedBySphere(radiusOuter ) + massVirial = massDistribution_ %massEnclosedBySphere(radiusVirial ) + !![ + + !!] if (massVirial > 0.0d0) then Node_Component_Hot_Halo_Standard_Outflow_Stripped_Fraction=efficiencyStrippingOutflow*(1.0d0-massOuter/massVirial) else @@ -1206,24 +1233,29 @@ subroutine Node_Component_Hot_Halo_Standard_Rate_Compute(node,interrupt,interrup !!{ Compute the hot halo node mass rate of change. !!} - use :: Abundances_Structure , only : abundances , abs - use :: Accretion_Halos , only : accretionModeHot , accretionModeTotal - use :: Galacticus_Nodes , only : defaultHotHaloComponent , interruptTask , nodeComponentBasic, nodeComponentHotHalo, & - & nodeComponentHotHaloStandard , propertyInactive , treeNode , nodeComponentSpin - use :: Node_Component_Hot_Halo_Standard_Data, only : outerRadiusOverVirialRadiusMinimum , angularMomentumAlwaysGrows + use :: Abundances_Structure , only : abundances , abs + use :: Accretion_Halos , only : accretionModeHot , accretionModeTotal + use :: Galacticus_Nodes , only : defaultHotHaloComponent , interruptTask , nodeComponentBasic, nodeComponentHotHalo, & + & nodeComponentHotHaloStandard , propertyInactive , treeNode , nodeComponentSpin + use :: Node_Component_Hot_Halo_Standard_Data, only : outerRadiusOverVirialRadiusMinimum, angularMomentumAlwaysGrows use :: Numerical_Constants_Math , only : Pi + use :: Mass_Distributions , only : massDistributionClass + use :: Coordinates , only : coordinateSpherical , assignment(=) + use :: Galactic_Structure_Options , only : componentTypeHotHalo , massTypeGaseous implicit none - type (treeNode ), intent(inout) :: node - logical , intent(inout) :: interrupt - procedure (interruptTask ), intent(inout), pointer :: interruptProcedure - integer , intent(in ) :: propertyType - class (nodeComponentSpin ) , pointer :: spin - class (nodeComponentHotHalo) , pointer :: hotHalo - class (nodeComponentBasic ) , pointer :: basic - double precision :: angularMomentumAccretionRate, outerRadiusGrowthRate , & - & densityAtOuterRadius , rateAccretionMassFailed, & - & massLossRate , rateAccretionMass , & - & outerRadius + type (treeNode ), intent(inout) :: node + logical , intent(inout) :: interrupt + procedure (interruptTask ), intent(inout), pointer :: interruptProcedure + integer , intent(in ) :: propertyType + class (nodeComponentSpin ) , pointer :: spin + class (nodeComponentHotHalo ) , pointer :: hotHalo + class (nodeComponentBasic ) , pointer :: basic + class (massDistributionClass) , pointer :: massDistribution_ + type (coordinateSpherical ) :: coordinates + double precision :: angularMomentumAccretionRate, outerRadiusGrowthRate , & + & densityAtOuterRadius , rateAccretionMassFailed, & + & massLossRate , rateAccretionMass , & + & outerRadius ! Return immediately if inactive variables are requested. if (propertyInactive(propertyType)) return @@ -1287,7 +1319,12 @@ subroutine Node_Component_Hot_Halo_Standard_Rate_Compute(node,interrupt,interrup & .and. & & outerRadius > outerRadiusOverVirialRadiusMinimum*darkMatterHaloScale_%radiusVirial(node) & & ) then - densityAtOuterRadius=hotHaloMassDistribution_%density(node,outerRadius) + coordinates = [outerRadius,0.0d0,0.0d0] + massDistribution_ => node %massDistribution(componentTypeHotHalo,massTypeGaseous) + densityAtOuterRadius = massDistribution_%density (coordinates ) + !![ + + !!] massLossRate =4.0d0*Pi*densityAtOuterRadius*outerRadius**2*outerRadiusGrowthRate call hotHalo%outerRadiusRate(+outerRadiusGrowthRate,interrupt,interruptProcedure) call hotHalo% massSinkRate(+ massLossRate,interrupt,interruptProcedure) @@ -1342,17 +1379,22 @@ subroutine Node_Component_Hot_Halo_Standard_Outflow_Return(self,interrupt,interr !!{ Return outflowed gas to the hot halo. !!} - use :: Abundances_Structure , only : abundances , max + use :: Abundances_Structure , only : abundances , max use :: Chemical_Abundances_Structure , only : chemicalAbundances - use :: Galacticus_Nodes , only : interruptTask , nodeComponentBasic , nodeComponentHotHaloStandard, treeNode - use :: Node_Component_Hot_Halo_Standard_Data, only : starveSatellites , starveSatellitesOutflowed + use :: Galacticus_Nodes , only : interruptTask , nodeComponentBasic , nodeComponentHotHaloStandard, treeNode + use :: Node_Component_Hot_Halo_Standard_Data, only : starveSatellites , starveSatellitesOutflowed use :: Numerical_Constants_Math , only : Pi + use :: Mass_Distributions , only : massDistributionClass + use :: Galactic_Structure_Options , only : componentTypeHotHalo , massTypeGaseous + use :: Coordinates , only : coordinateSpherical , assignment(=) implicit none class (nodeComponentHotHaloStandard), intent(inout) :: self logical , intent(inout) :: interrupt procedure (interruptTask ), intent(inout), pointer :: interruptProcedure type (treeNode ) , pointer :: node class (nodeComponentBasic ) , pointer :: basic + class (massDistributionClass ) , pointer :: massDistribution_ + type (coordinateSpherical ) :: coordinates double precision :: outflowedMass , massReturnRate , & & angularMomentumReturnRate, densityAtOuterRadius, & & radiusVirial , outerRadius , & @@ -1385,8 +1427,13 @@ subroutine Node_Component_Hot_Halo_Standard_Outflow_Return(self,interrupt,interr outerRadius =self %outerRadius ( ) radiusVirial=darkMatterHaloScale_%radiusVirial(node) if (outerRadius < radiusVirial) then - basic => node %basic ( ) - densityAtOuterRadius = hotHaloMassDistribution_%density(node,outerRadius) + coordinates = [outerRadius,0.0d0,0.0d0] + basic => node %basic ( ) + massDistribution_ => node %massDistribution(componentTypeHotHalo,massTypeGaseous) + densityAtOuterRadius = massDistribution_%density (coordinates ) + !![ + + !!] ! If the outer radius and density are non-zero we can expand the outer radius at a rate determined by the current ! density profile. if (outerRadius > 0.0d0 .and. densityAtOuterRadius > 0.0d0) then @@ -1604,24 +1651,26 @@ subroutine Node_Component_Hot_Halo_Standard_Node_Merger(node) use :: Abundances_Structure , only : abundances , operator(*) , zeroAbundances , operator(>) use :: Accretion_Halos , only : accretionModeHot , accretionModeTotal use :: Chemical_Abundances_Structure , only : chemicalAbundances , operator(*) , zeroChemicalAbundances , operator(>) - use :: Galactic_Structure_Options , only : componentTypeAll , massTypeBaryonic , radiusLarge + use :: Galactic_Structure_Options , only : componentTypeAll , massTypeBaryonic use :: Galacticus_Nodes , only : nodeComponentBasic , nodeComponentHotHalo , nodeComponentHotHaloStandard, nodeComponentSpin, & & treeNode , defaultHotHaloComponent + use :: Mass_Distributions , only : massDistributionClass use :: Error , only : Error_Report use :: Node_Component_Hot_Halo_Standard_Data, only : fractionBaryonLimitInNodeMerger, starveSatellites , starveSatellitesOutflowed implicit none - type (treeNode ), intent(inout) :: node - type (treeNode ), pointer :: nodeParent - class (nodeComponentHotHalo), pointer :: hotHaloParent , hotHalo - class (nodeComponentSpin ), pointer :: spinParent - class (nodeComponentBasic ), pointer :: parentBasic , basic - double precision :: baryonicMassCurrent , baryonicMassMaximum , & - & fractionRemove , massAccreted , & - & massUnaccreted , massReaccreted , & - & fractionAccreted , angularMomentumAccreted , & - & massAccretedHot - logical :: massTotalNonZero - type (abundances ), save :: massMetalsReaccreted + type (treeNode ), intent(inout) :: node + type (treeNode ), pointer :: nodeParent + class (nodeComponentHotHalo ), pointer :: hotHaloParent , hotHalo + class (nodeComponentSpin ), pointer :: spinParent + class (nodeComponentBasic ), pointer :: basicParent , basic + class (massDistributionClass), pointer :: massDistribution_ + double precision :: baryonicMassCurrent , baryonicMassMaximum , & + & fractionRemove , massAccreted , & + & massUnaccreted , massReaccreted , & + & fractionAccreted , angularMomentumAccreted , & + & massAccretedHot + logical :: massTotalNonZero + type (abundances ), save :: massMetalsReaccreted !$omp threadprivate(massMetalsReaccreted) type (chemicalAbundances ), save :: massChemicalsAccreted , fractionChemicalsAccreted, & & massChemicalsReaccreted @@ -1639,7 +1688,7 @@ subroutine Node_Component_Hot_Halo_Standard_Node_Merger(node) call Node_Component_Hot_Halo_Standard_Create(nodeParent) hotHaloParent => nodeParent%hotHalo(autoCreate=.true.) spinParent => nodeParent%spin ( ) - parentBasic => nodeParent%basic ( ) + basicParent => nodeParent%basic ( ) basic => node %basic ( ) ! Any gas that failed to be accreted by this halo is always transferred to the parent. call hotHaloParent% unaccretedMassSet( & @@ -1675,7 +1724,7 @@ subroutine Node_Component_Hot_Halo_Standard_Node_Merger(node) massReaccreted=+hotHaloParent %unaccretedMass() & & *fractionAccreted & & *basic % mass() & - & /parentBasic % mass() + & /basicParent % mass() !! Reaccrete the gas. call hotHaloParent%unaccretedMassSet(hotHaloParent%unaccretedMass()-massReaccreted) call hotHaloParent% massSet(hotHaloParent% mass()+massReaccreted) @@ -1683,13 +1732,13 @@ subroutine Node_Component_Hot_Halo_Standard_Node_Merger(node) massMetalsReaccreted=+hotHaloParent %unaccretedAbundances() & & *fractionAccreted & & *basic % mass() & - & /parentBasic % mass() + & /basicParent % mass() call hotHaloParent%unaccretedAbundancesSet(hotHaloParent%unaccretedAbundances()-massMetalsReaccreted) call hotHaloParent% abundancesSet(hotHaloParent% abundances()+massMetalsReaccreted) ! Compute the reaccreted angular momentum. angularMomentumAccreted=+ massReaccreted & & *spinParent %angularMomentum() & - & /parentBasic%mass () + & /basicParent%mass () call hotHaloParent%angularMomentumSet(hotHaloParent%angularMomentum()+angularMomentumAccreted) end if ! Compute the reaccreted chemicals. @@ -1707,7 +1756,7 @@ subroutine Node_Component_Hot_Halo_Standard_Node_Merger(node) massChemicalsReaccreted=+hotHaloParent %unaccretedMass() & & *fractionChemicalsAccreted & & *basic % mass() & - & /parentBasic % mass() + & /basicParent % mass() !! Reaccrete the chemicals. call hotHaloParent%chemicalsSet(hotHaloParent%chemicals()+massChemicalsReaccreted) end if @@ -1724,7 +1773,7 @@ subroutine Node_Component_Hot_Halo_Standard_Node_Merger(node) & hotHaloParent%angularMomentum () & & +hotHalo %mass () & & *spinParent %angularMomentum () & - & /parentBasic %mass () & + & /basicParent %mass () & & ) end if call hotHaloParent% outflowedMassSet( & @@ -1735,7 +1784,7 @@ subroutine Node_Component_Hot_Halo_Standard_Node_Merger(node) & hotHaloParent%outflowedAngularMomentum() & & +hotHalo %outflowedMass () & & *spinParent %angularMomentum () & - & /parentBasic %mass () & + & /basicParent %mass () & & ) if (starveSatellites) then call hotHalo % massSet( & @@ -1787,8 +1836,14 @@ subroutine Node_Component_Hot_Halo_Standard_Node_Merger(node) ! some of the mass to the failed accretion reservoir. if (fractionBaryonLimitInNodeMerger) then ! Get the default cosmology. - baryonicMassMaximum=parentBasic%mass()*cosmologyParameters_%OmegaBaryon()/cosmologyParameters_%OmegaMatter() - baryonicMassCurrent=galacticStructure_%massEnclosed(nodeParent,radiusLarge,massType=massTypeBaryonic,componentType =componentTypeAll) + massDistribution_ => nodeParent %massDistribution(massType=massTypeBaryonic) + baryonicMassMaximum = +basicParent %mass ( ) & + & *cosmologyParameters_%omegaBaryon ( ) & + & /cosmologyParameters_%omegaMatter ( ) + baryonicMassCurrent = +massDistribution_ %massTotal ( ) + !![ + + !!] if (baryonicMassCurrent > baryonicMassMaximum .and. hotHaloParent%mass() > 0.0d0) then fractionRemove=min((baryonicMassCurrent-baryonicMassMaximum)/hotHaloParent%massTotal(),1.0d0) call hotHaloParent% unaccretedMassSet( & @@ -2179,7 +2234,7 @@ subroutine Node_Component_Hot_Halo_Standard_State_Store(stateFile,gslStateFile,s call displayMessage('Storing state for: componentHotHalo -> standard',verbosity=verbosityLevelInfo) !![ - + !!] return end subroutine Node_Component_Hot_Halo_Standard_State_Store @@ -2202,9 +2257,42 @@ subroutine Node_Component_Hot_Halo_Standard_State_Restore(stateFile,gslStateFile call displayMessage('Retrieving state for: componentHotHalo -> standard',verbosity=verbosityLevelInfo) !![ - + !!] return end subroutine Node_Component_Hot_Halo_Standard_State_Restore + function Node_Component_Hot_Halo_Standard_Mass_Distribution(self,componentType,massType,weightBy,weightIndex) result(massDistribution_) + !!{ + Return the mass distribution associated with the hot halo. + !!} + use :: Galacticus_Nodes , only : nodeComponentHotHaloStandard + use :: Galactic_Structure_Options, only : enumerationWeightByType , enumerationComponentTypeType, enumerationMassTypeType , massTypeGaseous, & + & componentTypeHotHalo + use :: Mass_Distributions , only : massDistributionClass , kinematicsDistributionClass , massDistributionMatches_ + implicit none + class (massDistributionClass ), pointer :: massDistribution_ + class (kinematicsDistributionClass ), pointer :: kinematicsDistribution_ + class (nodeComponentHotHaloStandard), intent(inout) :: self + type (enumerationComponentTypeType), intent(in ), optional :: componentType + type (enumerationMassTypeType ), intent(in ), optional :: massType + type (enumerationWeightByType ), intent(in ), optional :: weightBy + integer , intent(in ), optional :: weightIndex + !$GLC attributes unused :: weightIndex + + if (massDistributionMatches_(componentTypeHotHalo,massTypeGaseous,componentType,massType)) then + massDistribution_ => hotHaloMassDistribution_%get(self%hostNode,weightBy,weightIndex) + if (associated(massDistribution_)) then + kinematicsDistribution_ => hotHaloTemperatureProfile_%get(self%hostNode) + call massDistribution_%setKinematicsDistribution(kinematicsDistribution_) + !![ + + !!] + end if + else + massDistribution_ => null() + end if + return + end function Node_Component_Hot_Halo_Standard_Mass_Distribution + end module Node_Component_Hot_Halo_Standard diff --git a/source/objects.nodes.components.hot_halo.standard.bound_functions.Inc b/source/objects.nodes.components.hot_halo.standard.bound_functions.Inc index 5edc7ca016..a1a8c07a40 100644 --- a/source/objects.nodes.components.hot_halo.standard.bound_functions.Inc +++ b/source/objects.nodes.components.hot_halo.standard.bound_functions.Inc @@ -45,3 +45,14 @@ double precision function Node_Component_Hot_Halo_Standard_Mass_Total(self) return end function Node_Component_Hot_Halo_Standard_Mass_Total +double precision function Node_Component_Hot_Halo_Standard_Mass_Baryonic(self) result(massBaryonic) + !!{ + Return the baryonic mass for the hot halo standard component. + !!} + implicit none + class(nodeComponentHotHaloStandard), intent(inout) :: self + + massBaryonic=+max(0.0d0,self%mass ()) & + & +max(0.0d0,self%outflowedMass()) + return +end function Node_Component_Hot_Halo_Standard_Mass_Baryonic diff --git a/source/objects.nodes.components.hot_halo.very_simple.F90 b/source/objects.nodes.components.hot_halo.very_simple.F90 index 29d09a6992..04c9be7019 100644 --- a/source/objects.nodes.components.hot_halo.very_simple.F90 +++ b/source/objects.nodes.components.hot_halo.very_simple.F90 @@ -94,6 +94,10 @@ module Node_Component_Hot_Halo_Very_Simple + + + + objects.nodes.components.hot_halo.very_simple.bound_functions.inc !!] diff --git a/source/objects.nodes.components.hot_halo.very_simple.bound_functions.Inc b/source/objects.nodes.components.hot_halo.very_simple.bound_functions.Inc new file mode 100644 index 0000000000..462bf0a7d0 --- /dev/null +++ b/source/objects.nodes.components.hot_halo.very_simple.bound_functions.Inc @@ -0,0 +1,33 @@ +!! Copyright 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, +!! 2019, 2020, 2021, 2022, 2023, 2024 +!! Andrew Benson +!! +!! This file is part of Galacticus. +!! +!! Galacticus is free software: you can redistribute it and/or modify +!! it under the terms of the GNU General Public License as published by +!! the Free Software Foundation, either version 3 of the License, or +!! (at your option) any later version. +!! +!! Galacticus is distributed in the hope that it will be useful, +!! but WITHOUT ANY WARRANTY; without even the implied warranty of +!! MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +!! GNU General Public License for more details. +!! +!! You should have received a copy of the GNU General Public License +!! along with Galacticus. If not, see . + +!!{ +Contains custom functions for the very simple hot halo component. +!!} + +double precision function Node_Component_Hot_Halo_Very_Simple_Mass_Baryonic(self) result(massBaryonic) + !!{ + Return the baryonic mass for the very simple hot halo component. + !!} + implicit none + class(nodeComponentHotHaloVerySimple), intent(inout) :: self + + massBaryonic=+max(0.0d0,self%mass()) + return +end function Node_Component_Hot_Halo_Very_Simple_Mass_Baryonic diff --git a/source/objects.nodes.components.hot_halo.very_simple_delayed.F90 b/source/objects.nodes.components.hot_halo.very_simple_delayed.F90 index 20c6ac1eb4..e8e27dc9e4 100644 --- a/source/objects.nodes.components.hot_halo.very_simple_delayed.F90 +++ b/source/objects.nodes.components.hot_halo.very_simple_delayed.F90 @@ -61,6 +61,10 @@ module Node_Component_Hot_Halo_VS_Delayed + + + + objects.nodes.components.hot_halo.very_simple_delayed.bound_functions.inc !!] diff --git a/source/objects.nodes.components.hot_halo.very_simple_delayed.bound_functions.Inc b/source/objects.nodes.components.hot_halo.very_simple_delayed.bound_functions.Inc new file mode 100644 index 0000000000..e71d3765f0 --- /dev/null +++ b/source/objects.nodes.components.hot_halo.very_simple_delayed.bound_functions.Inc @@ -0,0 +1,34 @@ +!! Copyright 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, +!! 2019, 2020, 2021, 2022, 2023, 2024 +!! Andrew Benson +!! +!! This file is part of Galacticus. +!! +!! Galacticus is free software: you can redistribute it and/or modify +!! it under the terms of the GNU General Public License as published by +!! the Free Software Foundation, either version 3 of the License, or +!! (at your option) any later version. +!! +!! Galacticus is distributed in the hope that it will be useful, +!! but WITHOUT ANY WARRANTY; without even the implied warranty of +!! MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +!! GNU General Public License for more details. +!! +!! You should have received a copy of the GNU General Public License +!! along with Galacticus. If not, see . + +!!{ +Contains custom functions for the very simple delayed hot halo component. +!!} + +double precision function Node_Component_Hot_Halo_Very_Simple_Delayed_Mass_Baryonic(self) result(massBaryonic) + !!{ + Return the baryonic mass for the very simple delayed hot halo component. + !!} + implicit none + class(nodeComponentHotHaloVerySimpleDelayed), intent(inout) :: self + + massBaryonic=+max(0.0d0,self%mass ()) & + & +max(0.0d0,self%outflowedMass()) + return +end function Node_Component_Hot_Halo_Very_Simple_Delayed_Mass_Baryonic diff --git a/source/objects.nodes.components.satellite.orbiting.F90 b/source/objects.nodes.components.satellite.orbiting.F90 index ce65604903..c19b33fb31 100644 --- a/source/objects.nodes.components.satellite.orbiting.F90 +++ b/source/objects.nodes.components.satellite.orbiting.F90 @@ -26,15 +26,13 @@ module Node_Component_Satellite_Orbiting !!{ Implements the orbiting satellite component. !!} - use :: Cosmology_Parameters , only : cosmologyParametersClass - use :: Cosmology_Functions , only : cosmologyFunctionsClass - use :: Dark_Matter_Profiles_DMO, only : darkMatterProfileDMOClass - use :: Galactic_Structure , only : galacticStructureClass - use :: Virial_Density_Contrast , only : virialDensityContrastClass - use :: Dark_Matter_Halo_Scales , only : darkMatterHaloScaleClass - use :: Kepler_Orbits , only : keplerOrbit - use :: Tensors , only : tensorRank2Dimension3Symmetric - use :: Virial_Orbits , only : virialOrbit , virialOrbitClass + use :: Cosmology_Parameters , only : cosmologyParametersClass + use :: Cosmology_Functions , only : cosmologyFunctionsClass + use :: Virial_Density_Contrast, only : virialDensityContrastClass + use :: Dark_Matter_Halo_Scales, only : darkMatterHaloScaleClass + use :: Kepler_Orbits , only : keplerOrbit + use :: Tensors , only : tensorRank2Dimension3Symmetric + use :: Virial_Orbits , only : virialOrbit , virialOrbitClass implicit none private public :: Node_Component_Satellite_Orbiting_Scale_Set , Node_Component_Satellite_Orbiting_Create , & @@ -122,14 +120,12 @@ module Node_Component_Satellite_Orbiting !!] ! Objects used by this module. - class(darkMatterProfileDMOClass ), pointer :: darkMatterProfileDMO_ class(virialDensityContrastClass), pointer :: virialDensityContrast_ class(cosmologyParametersClass ), pointer :: cosmologyParameters_ class(cosmologyFunctionsClass ), pointer :: cosmologyFunctions_ class(darkMatterHaloScaleClass ), pointer :: darkMatterHaloScale_ class(virialOrbitClass ), pointer :: virialOrbit_ - class(galacticStructureClass ), pointer :: galacticStructure_ - !$omp threadprivate(darkMatterHaloScale_,virialOrbit_,darkMatterProfileDMO_,virialDensityContrast_,cosmologyParameters_,cosmologyFunctions_,galacticStructure_) + !$omp threadprivate(darkMatterHaloScale_,virialOrbit_,virialDensityContrast_,cosmologyParameters_,cosmologyFunctions_) ! Option controlling whether or not unbound virial orbits are acceptable. logical , parameter :: acceptUnboundOrbits =.false. @@ -239,11 +235,9 @@ subroutine Node_Component_Satellite_Orbiting_Thread_Initialize(parameters) !![ - - !!] dependenciesSubhaloPromotion(1)=dependencyExact(dependencyDirectionBefore,'mergerTreeNodeEvolver') call subhaloPromotionEvent%attach(thread,subhaloPromotion ,openMPThreadBindingAtLevel,label='nodeComponentSatelliteOrbiting',dependencies=dependenciesSubhaloPromotion) @@ -272,11 +266,9 @@ subroutine Node_Component_Satellite_Orbiting_Thread_Uninitialize() !![ - - !!] if ( subhaloPromotionEvent%isAttached(thread,subhaloPromotion )) call subhaloPromotionEvent%detach(thread,subhaloPromotion ) if (satellitePreHostChangeEvent%isAttached(thread,satellitePreHostChange)) call satellitePreHostChangeEvent%detach(thread,satellitePreHostChange) @@ -584,14 +576,15 @@ subroutine Node_Component_Satellite_Orbiting_Bound_Mass_Initialize(satellite,nod !!{ Set the initial bound mass of the satellite. !!} + use :: Mass_Distributions , only : massDistributionClass use :: Dark_Matter_Profile_Mass_Definitions, only : Dark_Matter_Profile_Mass_Definition use :: Error , only : Error_Report use :: Galacticus_Nodes , only : nodeComponentSatellite , nodeComponentSatelliteOrbiting, treeNode implicit none class (nodeComponentSatellite), intent(inout) :: satellite type (treeNode ), intent(inout) :: node - double precision :: virialRadius , maximumRadius, & - & massSatellite + class (massDistributionClass ), pointer :: massDistribution_ + double precision :: massSatellite , maximumRadius select type (satellite) class is (nodeComponentSatelliteOrbiting) @@ -600,9 +593,10 @@ subroutine Node_Component_Satellite_Orbiting_Bound_Mass_Initialize(satellite,nod ! Do nothing. The bound mass of this satellite is set to the node mass by default. case (initializationTypeMassBoundMaximumRadius %ID) ! Set the initial bound mass of this satellite by integrating the density profile up to a maximum radius. - virialRadius =darkMatterHaloScale_%radiusVirial(node ) - maximumRadius=radiusMaximumOverRadiusVirial*virialRadius - massSatellite=galacticStructure_ %massEnclosed(node,maximumRadius) + maximumRadius = + radiusMaximumOverRadiusVirial & + & *darkMatterHaloScale_%radiusVirial (node ) + massDistribution_ => node %massDistribution ( ) + massSatellite = massDistribution_ %massEnclosedBySphere (maximumRadius) call satellite%boundMassSet(massSatellite) case (initializationTypeMassBoundDensityContrast%ID) ! Set the initial bound mass of this satellite by assuming a specified density contrast. @@ -611,7 +605,6 @@ subroutine Node_Component_Satellite_Orbiting_Bound_Mass_Initialize(satellite,nod & densityContrastMassBound, & & cosmologyParameters_ =cosmologyParameters_ , & & cosmologyFunctions_ =cosmologyFunctions_ , & - & darkMatterProfileDMO_ =darkMatterProfileDMO_ , & & virialDensityContrast_=virialDensityContrast_ & & ) call satellite%boundMassSet(massSatellite) @@ -640,7 +633,7 @@ subroutine Node_Component_Satellite_Orbiting_State_Store(stateFile,gslStateFile, call displayMessage('Storing state for: componentSatellite -> orbiting',verbosity=verbosityLevelInfo) !![ - + !!] return end subroutine Node_Component_Satellite_Orbiting_State_Store @@ -663,7 +656,7 @@ subroutine Node_Component_Satellite_Orbiting_State_Restore(stateFile,gslStateFil call displayMessage('Retrieving state for: componentSatellite -> orbiting',verbosity=verbosityLevelInfo) !![ - + !!] return end subroutine Node_Component_Satellite_Orbiting_State_Restore diff --git a/source/objects.nodes.components.spheroid.standard.F90 b/source/objects.nodes.components.spheroid.standard.F90 index 0d11068beb..0291bb4b27 100644 --- a/source/objects.nodes.components.spheroid.standard.F90 +++ b/source/objects.nodes.components.spheroid.standard.F90 @@ -148,15 +148,8 @@ module Node_Component_Spheroid_Standard - - - - - - - - - + + objects.nodes.components.spheroid.standard.bound_functions.inc @@ -267,29 +260,30 @@ subroutine Node_Component_Spheroid_Standard_Thread_Initialize(parameters) !!{ Initializes the standard spheroid module for each thread. !!} - use :: Events_Hooks , only : dependencyDirectionAfter , dependencyRegEx , openMPThreadBindingAtLevel, postEvolveEvent, & - & satelliteMergerEvent , mergerTreeExtraOutputEvent + use :: Events_Hooks , only : dependencyDirectionAfter , dependencyRegEx , openMPThreadBindingAtLevel, postEvolveEvent, & + & satelliteMergerEvent , mergerTreeExtraOutputEvent use :: Error , only : Error_Report use :: Galacticus_Nodes , only : defaultSpheroidComponent - use :: Input_Parameters , only : inputParameter , inputParameters - use :: Mass_Distributions , only : massDistributionSymmetrySpherical - use :: Node_Component_Spheroid_Standard_Data, only : massDistributionSpheroid + use :: Input_Parameters , only : inputParameter , inputParameters + use :: Mass_Distributions , only : massDistributionSpherical, kinematicsDistributionLocal + use :: Node_Component_Spheroid_Standard_Data, only : massDistributionStellar_ , massDistributionGas_ , kinematicDistribution_ + use :: Galactic_Structure_Options , only : componentTypeSpheroid , massTypeStellar , massTypeGaseous implicit none type (inputParameters), intent(inout) :: parameters - logical :: densityMoment2IsInfinite , densityMoment3IsInfinite - double precision :: massDistributionSpheroidDensityMomentum2 , massDistributionSpheroidDensityMomentum3, & - & ratioAngularMomentumScaleRadiusDefault type (dependencyRegEx), dimension(3) :: dependencies + logical :: densityMoment2IsInfinite , densityMoment3IsInfinite + double precision :: massDistributionSpheroidDensityMomentum2, massDistributionSpheroidDensityMomentum3, & + & ratioAngularMomentumScaleRadiusDefault type (inputParameters) :: subParameters ! Check if this implementation is selected. If so, initialize the mass distribution. if (defaultSpheroidComponent%standardIsActive()) then - call postEvolveEvent %attach(thread,postEvolve ,openMPThreadBindingAtLevel,label='nodeComponentSpheroidStandard' ) dependencies(1)=dependencyRegEx(dependencyDirectionAfter,'^remnantStructure:') dependencies(2)=dependencyRegEx(dependencyDirectionAfter,'^preAnalysis:' ) dependencies(3)=dependencyRegEx(dependencyDirectionAfter,'^nodeComponentDisk') call satelliteMergerEvent %attach(thread,satelliteMerger ,openMPThreadBindingAtLevel,label='nodeComponentSpheroidStandard',dependencies=dependencies) call mergerTreeExtraOutputEvent%attach(thread,mergerTreeExtraOutput,openMPThreadBindingAtLevel,label='nodeComponentSpheroidStandard' ) + call postEvolveEvent %attach(thread,postEvolve ,openMPThreadBindingAtLevel,label='nodeComponentSpheroidStandard' ) ! Find our parameters. subParameters=parameters%subParameters('componentSpheroid') !![ @@ -298,7 +292,7 @@ subroutine Node_Component_Spheroid_Standard_Thread_Initialize(parameters) - + @@ -306,15 +300,35 @@ subroutine Node_Component_Spheroid_Standard_Thread_Initialize(parameters) !!] - if (.not.massDistributionSpheroid%isDimensionless() ) & - & call Error_Report('spheroid mass distribution must be dimensionless' //{introspection:location}) - if (.not.massDistributionSpheroid%symmetry () == massDistributionSymmetrySpherical) & - & call Error_Report('spheroid mass distribution must be spherically symmetric'//{introspection:location}) + ! Validate the disk mass distribution. + select type (massDistributionStellar_) + class is (massDistributionSpherical) + ! The spheroid mass distribution must have spherical symmetry. So, this is acceptable. + class default + call Error_Report('only spehrically symmetric mass distributions are allowed'//{introspection:location}) + end select + if (.not.massDistributionStellar_%isDimensionless()) call Error_Report('spheroid mass distribution must be dimensionless'//{introspection:location}) + ! Duplicate the dimensionless mass distribution to use for the gas component, and set component and mass types in both. + !$omp critical(spheroidStandardDeepCopy) + allocate(massDistributionGas_,mold=massDistributionStellar_) + !![ + + + + !!] + !$omp end critical(spheroidStandardDeepCopy) + call massDistributionStellar_%setTypes(componentTypeSpheroid,massTypeStellar) + call massDistributionGas_ %setTypes(componentTypeSpheroid,massTypeGaseous) + ! Construct the kinematic distribution. + allocate(kinematicDistribution_) + !![ + + !!] ! Determine the specific angular momentum at the scale radius in units of the mean specific angular ! momentum of the spheroid. This is equal to the ratio of the 2nd to 3rd radial moments of the density ! distribution (assuming a flat rotation curve). - massDistributionSpheroidDensityMomentum2=massDistributionSpheroid%densityRadialMoment(2.0d0,isInfinite=densityMoment2IsInfinite) - massDistributionSpheroidDensityMomentum3=massDistributionSpheroid%densityRadialMoment(3.0d0,isInfinite=densityMoment3IsInfinite) + massDistributionSpheroidDensityMomentum2=massDistributionStellar_%densityRadialMoment(2.0d0,isInfinite=densityMoment2IsInfinite) + massDistributionSpheroidDensityMomentum3=massDistributionStellar_%densityRadialMoment(3.0d0,isInfinite=densityMoment3IsInfinite) if (densityMoment2IsInfinite.or.densityMoment3IsInfinite) then ! One of the moments is infinite, so we can not compute the appropriate ratio. Simply assume a value ! of 0.5 as a default. @@ -350,7 +364,7 @@ subroutine Node_Component_Spheroid_Standard_Thread_Uninitialize() !!} use :: Events_Hooks , only : postEvolveEvent , satelliteMergerEvent, mergerTreeExtraOutputEvent use :: Galacticus_Nodes , only : defaultSpheroidComponent - use :: Node_Component_Spheroid_Standard_Data, only : massDistributionSpheroid + use :: Node_Component_Spheroid_Standard_Data, only : massDistributionStellar_, massDistributionGas_, kinematicDistribution_ implicit none if (defaultSpheroidComponent%standardIsActive()) then @@ -363,7 +377,9 @@ subroutine Node_Component_Spheroid_Standard_Thread_Uninitialize() - + + + !!] end if return @@ -1536,7 +1552,7 @@ subroutine Node_Component_Spheroid_Standard_State_Store(stateFile,gslStateFile,s !!} use :: Display , only : displayMessage , verbosityLevelInfo use, intrinsic :: ISO_C_Binding , only : c_ptr , c_size_t - use :: Node_Component_Spheroid_Standard_Data, only : massDistributionSpheroid + use :: Node_Component_Spheroid_Standard_Data, only : massDistributionStellar_, massDistributionGas_, kinematicDistribution_ implicit none integer , intent(in ) :: stateFile integer(c_size_t), intent(in ) :: stateOperationID @@ -1544,7 +1560,7 @@ subroutine Node_Component_Spheroid_Standard_State_Store(stateFile,gslStateFile,s call displayMessage('Storing state for: componentSpheroid -> standard',verbosity=verbosityLevelInfo) !![ - + !!] write (stateFile) ratioAngularMomentumScaleRadius return @@ -1561,7 +1577,7 @@ subroutine Node_Component_Spheroid_Standard_State_Retrieve(stateFile,gslStateFil !!} use :: Display , only : displayMessage , verbosityLevelInfo use, intrinsic :: ISO_C_Binding , only : c_ptr , c_size_t - use :: Node_Component_Spheroid_Standard_Data, only : massDistributionSpheroid + use :: Node_Component_Spheroid_Standard_Data, only : massDistributionStellar_, massDistributionGas_, kinematicDistribution_ implicit none integer , intent(in ) :: stateFile integer(c_size_t), intent(in ) :: stateOperationID @@ -1569,7 +1585,7 @@ subroutine Node_Component_Spheroid_Standard_State_Retrieve(stateFile,gslStateFil call displayMessage('Retrieving state for: componentSpheroid -> standard',verbosity=verbosityLevelInfo) !![ - + !!] read (stateFile) ratioAngularMomentumScaleRadius return diff --git a/source/objects.nodes.components.spheroid.standard.bound_functions.Inc b/source/objects.nodes.components.spheroid.standard.bound_functions.Inc index 6e1bf80938..6be7147c13 100644 --- a/source/objects.nodes.components.spheroid.standard.bound_functions.Inc +++ b/source/objects.nodes.components.spheroid.standard.bound_functions.Inc @@ -21,417 +21,135 @@ Contains custom functions for the standard spheroid component. !!} -double precision function Node_Component_Spheroid_Standard_Half_Mass_Radius(self) +function Node_Component_Spheroid_Standard_Mass_Distribution(self,componentType,massType,weightBy,weightIndex) result(massDistribution_) !!{ - Return the half-mass radius of the standard spheroid. - !!} - use :: Error , only : Error_Report - use :: Mass_Distributions , only : massDistributionSpherical - use :: Node_Component_Spheroid_Standard_Data, only : massDistributionSpheroid - implicit none - class(nodeComponentSpheroidStandard), intent(inout) :: self - - select type (massDistributionSpheroid) - class is (massDistributionSpherical) - Node_Component_Spheroid_Standard_Half_Mass_Radius=self%radius()*massDistributionSpheroid%radiusHalfMass() - class default - Node_Component_Spheroid_Standard_Half_Mass_Radius=0.0d0 - call Error_Report('mass distribution should be spherical'//{introspection:location}) - end select - return -end function Node_Component_Spheroid_Standard_Half_Mass_Radius - -double precision function Node_Component_Spheroid_Standard_Enclosed_Mass(self,radius,componentType,massType,weightBy,weightIndex) - !!{ - Computes the mass within a given radius for an standard spheroid. - !!} - use :: Galactic_Structure_Options , only : componentTypeAll , componentTypeSpheroid, massTypeAll , massTypeBaryonic , & - & massTypeGalactic , massTypeGaseous , massTypeStellar , radiusLarge , & - & weightByLuminosity , weightByMass , enumerationComponentTypeType, enumerationMassTypeType, & - & enumerationWeightByType - use :: Node_Component_Spheroid_Standard_Data, only : massDistributionSpheroid - implicit none - class (nodeComponentSpheroidStandard), intent(inout) :: self - type (enumerationComponentTypeType ), intent(in ) :: componentType - type (enumerationMassTypeType ), intent(in ) :: massType - type (enumerationWeightByType ), intent(in ) :: weightBy - integer , intent(in ) :: weightIndex - double precision , intent(in ) :: radius - double precision :: fractionalRadius , radiusSpheroid - type (stellarLuminosities ), save :: luminositiesSpheroid - !$omp threadprivate(luminositiesSpheroid) - - ! Return if the spheroid component is not selected. - Node_Component_Spheroid_Standard_Enclosed_Mass=0.0d0 - if (.not.(componentType == componentTypeAll .or. componentType == componentTypeSpheroid)) return - ! Get the total mass. - select case (weightBy%ID) - case (weightByMass %ID) - select case (massType%ID) - case (massTypeAll%ID,massTypeBaryonic%ID,massTypeGalactic%ID) - Node_Component_Spheroid_Standard_Enclosed_Mass=self%massGas()+self%massStellar() - case (massTypeGaseous%ID) - Node_Component_Spheroid_Standard_Enclosed_Mass=self%massGas() - case (massTypeStellar%ID) - Node_Component_Spheroid_Standard_Enclosed_Mass= self%massStellar() - end select - case (weightByLuminosity%ID) - select case (massType%ID) - case (massTypeAll%ID,massTypeBaryonic%ID,massTypeGalactic%ID,massTypeStellar%ID) - luminositiesSpheroid=self%luminositiesStellar() - Node_Component_Spheroid_Standard_Enclosed_Mass =luminositiesSpheroid%luminosity(weightIndex) - end select - end select - ! Return if total mass was requested. - if (radius >= radiusLarge) return - ! Return if mass is zero. - if (Node_Component_Spheroid_Standard_Enclosed_Mass <= 0.0d0) return - ! Compute actual mass. - radiusSpheroid=self%radius() - if (radiusSpheroid > 0.0d0) then - fractionalRadius=radius/radiusSpheroid - Node_Component_Spheroid_Standard_Enclosed_Mass =Node_Component_Spheroid_Standard_Enclosed_Mass*massDistributionSpheroid%massEnclosedBySphere(fractionalRadius) - end if - return -end function Node_Component_Spheroid_Standard_Enclosed_Mass - -function Node_Component_Spheroid_Standard_Acceleration(self,positionCartesian,componentType,massType) - !!{ - Computes the gravitational acceleration at a given position for a standard spheroid. - !!} - use :: Galactic_Structure_Options , only : componentTypeAll , componentTypeSpheroid , weightByMass , weightIndexNull, & - & enumerationComponentTypeType, enumerationMassTypeType - use :: Numerical_Constants_Astronomical, only : gigaYear , megaParsec , gravitationalConstantGalacticus - use :: Numerical_Constants_Prefixes , only : kilo - implicit none - double precision , dimension(3) :: Node_Component_Spheroid_Standard_Acceleration - class (nodeComponentSpheroidStandard), intent(inout) :: self - double precision , intent(in ), dimension(3) :: positionCartesian - type (enumerationComponentTypeType ), intent(in ) :: componentType - type (enumerationMassTypeType ), intent(in ) :: massType - double precision :: radius , massEnclosed - - ! Return if the spheroid component is not selected. - Node_Component_Spheroid_Standard_Acceleration=0.0d0 - if (.not.(componentType == componentTypeAll .or. componentType == componentTypeSpheroid)) return - ! Compute the acceleration. - radius =sqrt(sum(positionCartesian**2)) - massEnclosed =Node_Component_Spheroid_Standard_Enclosed_Mass(self,radius,componentType,massType,weightByMass,weightIndexNull) - Node_Component_Spheroid_Standard_Acceleration=-kilo & - & *gigaYear & - & /megaParsec & - & *gravitationalConstantGalacticus & - & *massEnclosed & - & *positionCartesian & - & /radius**3 - return -end function Node_Component_Spheroid_Standard_Acceleration - -function Node_Component_Spheroid_Standard_Tidal_Tensor(self,positionCartesian,componentType,massType) - !!{ - Computes the gravitational tidal tensor at a given position for a standard spheroid. - !!} - use :: Coordinates , only : assignment(=) , coordinateCartesian - use :: Galactic_Structure_Options , only : componentTypeAll , componentTypeSpheroid , weightByMass , weightIndexNull, & - & radiusLarge , enumerationComponentTypeType, enumerationMassTypeType - use :: Node_Component_Spheroid_Standard_Data, only : massDistributionSpheroid - use :: Numerical_Constants_Astronomical , only : gravitationalConstantGalacticus - use :: Tensors , only : tensorRank2Dimension3Symmetric , tensorNullR2D3Sym , operator(*) - implicit none - type (tensorRank2Dimension3Symmetric) :: Node_Component_Spheroid_Standard_Tidal_Tensor - class (nodeComponentSpheroidStandard ), intent(inout) :: self - double precision , intent(in ), dimension(3) :: positionCartesian - type (enumerationComponentTypeType ), intent(in ) :: componentType - type (enumerationMassTypeType ), intent(in ) :: massType - double precision :: massTotal - type (coordinateCartesian ) :: positionScaleFree - - ! Return if the spheroid component is not selected. - Node_Component_Spheroid_Standard_Tidal_Tensor=tensorNullR2D3Sym - if (.not.(componentType == componentTypeAll .or. componentType == componentTypeSpheroid)) return - ! Find the total mass. - massTotal=Node_Component_Spheroid_Standard_Enclosed_Mass(self,radiusLarge,componentType,massType,weightByMass,weightIndexNull) - if (massTotal <= 0.0d0 .or. self%radius() <= 0.0d0) return - ! Compute the tidal tensor. - positionScaleFree=positionCartesian/self%radius() - Node_Component_Spheroid_Standard_Tidal_Tensor=+gravitationalConstantGalacticus & - & *massTotal & - & *massDistributionSpheroid%tidalTensor(positionScaleFree) & - & /self%radius()**3 - return -end function Node_Component_Spheroid_Standard_Tidal_Tensor - -function Node_Component_Spheroid_Standard_Chandrasekhar_Integral(self,nodeSatellite,positionCartesian,velocityCartesian,componentType,massType) - !!{ - Computes the gravitational acceleration at a given position for a standard spheroid. + Return the mass distribution for the standard spheroid component. !!} - use :: Galactic_Structure_Options, only : componentTypeAll , componentTypeSpheroid , weightByMass, weightIndexNull, & - & enumerationComponentTypeType, enumerationMassTypeType - use :: Numerical_Constants_Math , only : Pi + use :: Mass_Distributions , only : massDistributionClass , massDistributionSphericalScaler, massDistributionComposite , massDistributionList , & + & massDistributionSpherical, massDistributionMatches_ + use :: Node_Component_Spheroid_Standard_Data, only : massDistributionStellar_ , massDistributionGas_ , kinematicDistribution_ + use :: Galactic_Structure_Options , only : componentTypeSpheroid , massTypeStellar , massTypeGaseous , enumerationWeightByType, & + & weightByMass , weightByLuminosity , enumerationComponentTypeType, enumerationMassTypeType implicit none - double precision , dimension(3) :: Node_Component_Spheroid_Standard_Chandrasekhar_Integral - class (nodeComponentSpheroidStandard), intent(inout) :: self - type (treeNode ), intent(inout) :: nodeSatellite - double precision , intent(in ), dimension(3) :: positionCartesian , velocityCartesian - type (enumerationComponentTypeType ), intent(in ) :: componentType - type (enumerationMassTypeType ), intent(in ) :: massType - double precision , dimension(3) :: positionSpherical - double precision , parameter :: XvMaximum =10.0d0 - double precision :: radius , velocity , & - & density , xV , & - & velocityRotation , velocityDispersion - !$GLC attributes unused :: nodeSatellite + class (massDistributionClass ), pointer :: massDistribution_ + class (nodeComponentSpheroidStandard ), intent(inout) :: self + type (enumerationComponentTypeType ), intent(in ), optional :: componentType + type (enumerationMassTypeType ), intent(in ), optional :: massType + type (enumerationWeightByType ), intent(in ), optional :: weightBy + integer , intent(in ), optional :: weightIndex + type (massDistributionSphericalScaler), pointer :: massDistributionStellar , massDistributionGas + type (massDistributionComposite ), pointer :: massDistributionTotal + type (massDistributionList ), pointer :: massDistributionComponents + type (stellarLuminosities ), save :: luminosities + !$omp threadprivate(luminosities) + double precision :: massStellar , massGas , & + & radiusScale + logical :: includeGas , includeStars + !![ + + !!] - ! Return if the spheroid component is not selected. - Node_Component_Spheroid_Standard_Chandrasekhar_Integral=0.0d0 - if (.not.(componentType == componentTypeAll .or. componentType == componentTypeSpheroid)) return - ! Compute the integral. - radius = sqrt(sum(positionCartesian**2)) - velocity = sqrt(sum(velocityCartesian**2)) - if (velocity <= 0.0d0) return - positionSpherical = [radius,0.0d0,0.0d0] - velocityRotation = Node_Component_Spheroid_Standard_Rotation_Curve(self,radius ,componentType,massType ) - density = Node_Component_Spheroid_Standard_Density (self,positionSpherical,componentType,massType,weightByMass,weightIndexNull) - if (density <= 0.0d0) return - velocityDispersion = +velocityRotation & - & /sqrt(2.0d0) - xV = +velocity & - & /velocityDispersion & - & /sqrt(2.0d0) - Node_Component_Spheroid_Standard_Chandrasekhar_Integral = -density & - & *velocityCartesian & - & /velocity **3 - if (Xv <= XvMaximum) & - & Node_Component_Spheroid_Standard_Chandrasekhar_Integral=+Node_Component_Spheroid_Standard_Chandrasekhar_Integral & - & *( & - & +erf ( xV ) & - & -2.0d0 & - & * xV & - & *exp (-xV**2) & - & /sqrt( Pi ) & - & ) - return -end function Node_Component_Spheroid_Standard_Chandrasekhar_Integral - -double precision function Node_Component_Spheroid_Standard_Rotation_Curve(self,radius,componentType,massType) - !!{ - Computes the rotation curve at a given radius for a standard spheroid. - !!} - use :: Galactic_Structure_Options , only : weightByMass , weightIndexNull, enumerationComponentTypeType, enumerationMassTypeType - use :: Numerical_Constants_Astronomical, only : gravitationalConstantGalacticus - implicit none - class (nodeComponentSpheroidStandard), intent(inout) :: self - type (enumerationComponentTypeType ), intent(in ) :: componentType - type (enumerationMassTypeType ), intent(in ) :: massType - double precision , intent(in ) :: radius - double precision :: componentMass - - ! Set to zero by default. - Node_Component_Spheroid_Standard_Rotation_Curve=0.0d0 - - ! Return immediately for non-positive radius. - if (radius <= 0.0d0) return - - ! Compute if a spheroid is present. - componentMass=self%enclosedMass(radius,componentType,massType,weightByMass,weightIndexNull) - if (componentMass > 0.0d0) Node_Component_Spheroid_Standard_Rotation_Curve=sqrt(gravitationalConstantGalacticus& - &*componentMass)/sqrt(radius) - return -end function Node_Component_Spheroid_Standard_Rotation_Curve - -double precision function Node_Component_Spheroid_Standard_Rotation_Curve_Gradient(self,radius,componentType,massType) - !!{ - Computes the rotation curve gradient for the standard spheroid. - !!} - use :: Galactic_Structure_Options , only : weightByMass , weightIndexNull, enumerationComponentTypeType, enumerationMassTypeType - use :: Numerical_Constants_Math , only : Pi - use :: Numerical_Constants_Astronomical, only : gravitationalConstantGalacticus - implicit none - class (nodeComponentSpheroidStandard), intent(inout) :: self - type (enumerationComponentTypeType ), intent(in ) :: componentType - type (enumerationMassTypeType ), intent(in ) :: massType - double precision , intent(in ) :: radius - double precision :: componentDensity, componentMass, positionSpherical(3) - - ! Set to zero by default. - Node_Component_Spheroid_Standard_Rotation_Curve_Gradient=0.0d0 - - ! Return immediately for non-positive radius. - if (radius <= 0.0d0) return - - ! Compute if a spheroid is present. - positionSpherical= [radius,0.0d0,0.0d0] - componentMass =self%enclosedMass(radius ,componentType,massType,weightByMass,weightIndexNull) - componentDensity =self%density (positionSpherical,componentType,massType,weightByMass,weightIndexNull) - if (componentMass == 0.0d0 .or. componentDensity == 0.0d0) return - Node_Component_Spheroid_Standard_Rotation_Curve_Gradient= & - & gravitationalConstantGalacticus & - & *( & - & -componentMass/radius**2 & - & +4.0d0*Pi*radius*componentDensity & - & ) - return -end function Node_Component_Spheroid_Standard_Rotation_Curve_Gradient - -double precision function Node_Component_Spheroid_Standard_Density(self,positionSpherical,componentType,massType,weightBy,weightIndex) - !!{ - Computes the density at a given position for an standard spheroid. - !!} - use :: Coordinates , only : assignment(=) , coordinateSpherical - use :: Galactic_Structure_Options , only : componentTypeAll , componentTypeSpheroid , massTypeAll , massTypeBaryonic , & - & massTypeGalactic , massTypeGaseous , massTypeStellar , weightByLuminosity , & - & weightByMass , enumerationComponentTypeType, enumerationMassTypeType, enumerationWeightByType - use :: Node_Component_Spheroid_Standard_Data, only : massDistributionSpheroid - implicit none - class (nodeComponentSpheroidStandard), intent(inout) :: self - type (enumerationComponentTypeType ), intent(in ) :: componentType - type (enumerationMassTypeType ), intent(in ) :: massType - type (enumerationWeightByType ), intent(in ) :: weightBy - integer , intent(in ) :: weightIndex - double precision , intent(in ) :: positionSpherical(3) - double precision , parameter :: radiusHuge =1.0d+100 - type (coordinateSpherical ) :: position - type (stellarLuminosities ), save :: luminositiesSpheroid - !$omp threadprivate(luminositiesSpheroid) - - Node_Component_Spheroid_Standard_Density=0.0d0 - if (.not.(componentType == componentTypeAll .or. componentType == componentTypeSpheroid)) return - - ! Get the spheroid component and check that it is of the standard class. - select type (self) - class is (nodeComponentSpheroidStandard) - - if (self%radius() <= 0.0d0 .or. self%radius() > radiusHuge) return - select case (weightBy%ID) - case (weightByMass %ID) - select case (massType%ID) - case (massTypeAll%ID,massTypeBaryonic%ID,massTypeGalactic%ID) - Node_Component_Spheroid_Standard_Density=self%massGas()+self%massStellar() - case (massTypeGaseous%ID) - Node_Component_Spheroid_Standard_Density=self%massGas() - case (massTypeStellar%ID) - Node_Component_Spheroid_Standard_Density= self%massStellar() + ! Determine which components of the spheroid to include. + includeGas =massDistributionMatches_(componentTypeSpheroid,massTypeGaseous,componentType,massType) .and. weightBy_ == weightByMass + includeStars=massDistributionMatches_(componentTypeSpheroid,massTypeStellar,componentType,massType) .and. (weightBy_ == weightByMass .or. weightBy_ == weightByLuminosity) + ! Get properties of the mass distribution and ensure they are physical. + if (weightBy_ == weightByMass ) then + massStellar = max (0.0d0,self %massStellar ( )) + massGas = max (0.0d0,self %massGas ( )) + else if (weightBy_ == weightByLuminosity) then + luminosities = self %luminositiesStellar( ) + massStellar = max (0.0d0,luminosities%luminosity (weightIndex)) + massGas = 0.0d0 + else + massDistribution_ => null() + return + end if + ! Determine which components to build. + radiusScale=self%radius() + if (radiusScale <= 0.0d0 .or. .not.(includeGas .or. includeStars)) then + ! Disk has non-positive size, or no components matched. Return a null distribution. + massDistribution_ => null() + else + ! Build the individual distributions. + massDistributionStellar => null() + massDistributionGas => null() + if (includeStars) then + allocate(massDistributionStellar) + select type (massDistributionStellar_) + class is (massDistributionSpherical) + !![ + + !!] end select - case (weightByLuminosity%ID) - select case (massType%ID) - case (massTypeAll%ID,massTypeBaryonic%ID,massTypeGalactic%ID,massTypeStellar%ID) - luminositiesSpheroid=self%luminositiesStellar() - Node_Component_Spheroid_Standard_Density=luminositiesSpheroid%luminosity(weightIndex) + call massDistributionStellar%setKinematicsDistribution(kinematicDistribution_) + end if + if (includeGas ) then + allocate(massDistributionGas ) + select type (massDistributionGas_ ) + class is (massDistributionSpherical) + !![ + + !!] end select - end select - ! Return if density is zero. - if (Node_Component_Spheroid_Standard_Density <= 0.0d0) then - Node_Component_Spheroid_Standard_Density=0.0d0 - return + call massDistributionGas %setKinematicsDistribution(kinematicDistribution_) end if - ! Compute actual density. - position=[positionSpherical(1)/self%radius(),0.0d0,0.0d0] - Node_Component_Spheroid_Standard_Density =+Node_Component_Spheroid_Standard_Density & - & *massDistributionSpheroid %density(position) - if (Node_Component_Spheroid_Standard_Density > 0.0d0) & - & Node_Component_Spheroid_Standard_Density=+Node_Component_Spheroid_Standard_Density & - & /self %radius ( )**3 - end select + ! Combine the distributions as necessary. + if (includeStars .and. includeGas) then + ! Wrap the dimensionless mass distribution inside scaler classes to allow us to re-scale it to any spheroid system, and then composite those. + allocate(massDistributionTotal ) + allocate(massDistributionComponents ) + allocate(massDistributionComponents%next) + massDistributionComponents %massDistribution_ => massDistributionStellar + massDistributionComponents%next%massDistribution_ => massDistributionGas + !![ + + + + !!] + nullify(massDistributionComponents) + ! Return a pointer to the spheroid mass distribution. + massDistribution_ => massDistributionTotal + else if (includeStars ) then + ! Return just the stellar component. + massDistribution_ => massDistributionStellar + else if ( includeGas) then + ! Return just the gas component. + massDistribution_ => massDistributionGas + end if + end if return -end function Node_Component_Spheroid_Standard_Density +end function Node_Component_Spheroid_Standard_Mass_Distribution -double precision function Node_Component_Spheroid_Standard_Density_Spherical_Average(self,radius,componentType,massType,weightBy,weightIndex) +double precision function Node_Component_Spheroid_Standard_Mass_Baryonic(self) result(massBaryonic) !!{ - Computes the spherically-averaged density at a given radius for an standard spheroid. + Return the baryonic mass for the standard spheroid component. !!} - use :: Coordinates , only : assignment(=) , coordinateSpherical - use :: Galactic_Structure_Options , only : componentTypeAll , componentTypeSpheroid , massTypeAll , massTypeBaryonic , & - & massTypeGalactic , massTypeGaseous , massTypeStellar , weightByLuminosity , & - & weightByMass , enumerationComponentTypeType, enumerationMassTypeType, enumerationWeightByType - use :: Node_Component_Spheroid_Standard_Data, only : massDistributionSpheroid implicit none - class (nodeComponentSpheroidStandard), intent(inout) :: self - type (enumerationComponentTypeType ), intent(in ) :: componentType - type (enumerationMassTypeType ), intent(in ) :: massType - type (enumerationWeightByType ), intent(in ) :: weightBy - integer , intent(in ) :: weightIndex - double precision , intent(in ) :: radius - double precision , parameter :: radiusHuge =1.0d+100 - type (coordinateSpherical ) :: position - type (stellarLuminosities ), save :: luminositiesSpheroid - !$omp threadprivate(luminositiesSpheroid) - - Node_Component_Spheroid_Standard_Density_Spherical_Average=0.0d0 - if (.not.(componentType == componentTypeAll .or. componentType == componentTypeSpheroid)) return - - ! Get the spheroid component and check that it is of the standard class. - select type (self) - class is (nodeComponentSpheroidStandard) + class(nodeComponentSpheroidStandard), intent(inout) :: self - if (self%radius() <= 0.0d0 .or. self%radius() > radiusHuge) return - select case (weightBy%ID) - case (weightByMass %ID) - select case (massType%ID) - case (massTypeAll%ID,massTypeBaryonic%ID,massTypeGalactic%ID) - Node_Component_Spheroid_Standard_Density_Spherical_Average=self%massGas()+self%massStellar() - case (massTypeGaseous%ID) - Node_Component_Spheroid_Standard_Density_Spherical_Average=self%massGas() - case (massTypeStellar%ID) - Node_Component_Spheroid_Standard_Density_Spherical_Average= self%massStellar() - end select - case (weightByLuminosity%ID) - select case (massType%ID) - case (massTypeAll%ID,massTypeBaryonic%ID,massTypeGalactic%ID,massTypeStellar%ID) - luminositiesSpheroid=self%luminositiesStellar() - Node_Component_Spheroid_Standard_Density_Spherical_Average=luminositiesSpheroid%luminosity(weightIndex) - end select - end select - ! Return if density is zero. - if (Node_Component_Spheroid_Standard_Density_Spherical_Average <= 0.0d0) then - Node_Component_Spheroid_Standard_Density_Spherical_Average=0.0d0 - return - end if - ! Compute actual density. - position=[radius/self%radius(),0.0d0,0.0d0] - Node_Component_Spheroid_Standard_Density_Spherical_Average =+Node_Component_Spheroid_Standard_Density_Spherical_Average & - & *massDistributionSpheroid %density(position) - if (Node_Component_Spheroid_Standard_Density_Spherical_Average > 0.0d0) & - & Node_Component_Spheroid_Standard_Density_Spherical_Average=+Node_Component_Spheroid_Standard_Density_Spherical_Average & - & /self %radius ( )**3 - end select + massBaryonic=+max(0.0d0,self%massStellar()) & + & +max(0.0d0,self%massGas ()) return -end function Node_Component_Spheroid_Standard_Density_Spherical_Average +end function Node_Component_Spheroid_Standard_Mass_Baryonic -double precision function Node_Component_Spheroid_Standard_Potential(self,radius,componentType,massType,status) - use :: Coordinates , only : assignment(=) , coordinateSpherical - use :: Galactic_Structure_Options , only : radiusLarge , weightByMass , weightIndexNull, enumerationComponentTypeType, & - & enumerationMassTypeType , enumerationStructureErrorCodeType - use :: Node_Component_Spheroid_Standard_Data, only : massDistributionSpheroid - use :: Numerical_Constants_Astronomical , only : gravitationalConstantGalacticus +double precision function Node_Component_Spheroid_Standard_Half_Mass_Radius(self) result(radiusHalfMass) !!{ - Return the potential due to the standard spheroid. + Return the half-mass radius of the standard spheroid. !!} - class (nodeComponentSpheroidStandard ), intent(inout) :: self - type (enumerationComponentTypeType ), intent(in ) :: componentType - type (enumerationMassTypeType ), intent(in ) :: massType - double precision , intent(in ) :: radius - type (enumerationStructureErrorCodeType), intent(inout), optional :: status - double precision :: componentMass - type (coordinateSpherical ) :: position - !$GLC attributes unused :: status - - ! Set to zero by default. - Node_Component_Spheroid_Standard_Potential=0.0d0 + use :: Error , only : Error_Report + use :: Mass_Distributions , only : massDistributionSpherical + use :: Node_Component_Spheroid_Standard_Data, only : massDistributionStellar_ + implicit none + class(nodeComponentSpheroidStandard), intent(inout) :: self - ! Return immediately for non-positive radius. - if (radius <= 0.0d0) return - ! Get the spheroid component and check that it is of the standard class. - select type (self) - class is (nodeComponentSpheroidStandard) - ! Compute if a spheroid is present. - componentMass=self%enclosedMass(radiusLarge,componentType,massType,weightByMass& - &,weightIndexNull) - if (componentMass > 0.0d0 .and. self%radius() > 0.0d0) then - position=[radius/self%radius(),0.0d0,0.0d0] - Node_Component_Spheroid_Standard_Potential=(gravitationalConstantGalacticus*componentMass& - &/self%radius())*massDistributionSpheroid%potential(position) - end if + select type (massDistributionStellar_) + class is (massDistributionSpherical) + radiusHalfMass=+massDistributionStellar_%radiusHalfMass() & + & *self %radius () + class default + radiusHalfMass=0.0d0 + call Error_Report('spheroid mass distribution is not spherically-symmetric'//{introspection:location}) end select return -end function Node_Component_Spheroid_Standard_Potential +end function Node_Component_Spheroid_Standard_Half_Mass_Radius diff --git a/source/objects.nodes.components.spheroid.standard.data.F90 b/source/objects.nodes.components.spheroid.standard.data.F90 index bff0f11699..b22221ce34 100644 --- a/source/objects.nodes.components.spheroid.standard.data.F90 +++ b/source/objects.nodes.components.spheroid.standard.data.F90 @@ -25,12 +25,13 @@ module Node_Component_Spheroid_Standard_Data !!{ Contains data for standard spheroid components. !!} - use :: Mass_Distributions, only : massDistributionClass + use :: Mass_Distributions, only : massDistributionClass, kinematicsDistributionLocal implicit none public - ! The mass distribution object. - class(massDistributionClass), pointer :: massDistributionSpheroid - !$omp threadprivate(massDistributionSpheroid) + ! The mass distribution objects. + class(massDistributionClass ), pointer :: massDistributionStellar_, massDistributionGas_ + type (kinematicsDistributionLocal), pointer :: kinematicDistribution_ + !$omp threadprivate(massDistributionStellar_,massDistributionGas_,kinematicDistribution_) end module Node_Component_Spheroid_Standard_Data diff --git a/source/objects.nodes.components.spheroid.very_simple.F90 b/source/objects.nodes.components.spheroid.very_simple.F90 index 41b5e62fb6..98c0ccc883 100644 --- a/source/objects.nodes.components.spheroid.very_simple.F90 +++ b/source/objects.nodes.components.spheroid.very_simple.F90 @@ -111,7 +111,7 @@ module Node_Component_Spheroid_Very_Simple - + objects.nodes.components.spheroid.very_simple.bound_functions.inc diff --git a/source/objects.nodes.components.spheroid.very_simple.bound_functions.Inc b/source/objects.nodes.components.spheroid.very_simple.bound_functions.Inc index 6eedc1bc96..45159976e1 100644 --- a/source/objects.nodes.components.spheroid.very_simple.bound_functions.Inc +++ b/source/objects.nodes.components.spheroid.very_simple.bound_functions.Inc @@ -21,56 +21,25 @@ Contains custom functions for the very simple spheroid component. !!} -double precision function Node_Component_Spheroid_Very_Simple_Enclosed_Mass(self,radius,componentType,massType,weightBy,weightIndex) +double precision function Node_Component_Spheroid_Very_Simple_Half_Mass_Radius(self) !!{ - Computes the mass within a given radius for an very simple spheroid. + Return the half-mass radius of the very simple spheroid. !!} - use :: Galactic_Structure_Options, only : componentTypeAll, componentTypeSpheroid , massTypeAll , massTypeBaryonic , & - & massTypeGalactic, massTypeGaseous , massTypeStellar , radiusLarge , & - & weightByMass , enumerationComponentTypeType, enumerationMassTypeType, enumerationWeightByType - use :: Error , only : Error_Report implicit none - class (nodeComponentSpheroidVerySimple), intent(inout) :: self - type (enumerationComponentTypeType ), intent(in ) :: componentType - type (enumerationMassTypeType ), intent(in ) :: massType - type (enumerationWeightByType ), intent(in ) :: weightBy - integer , intent(in ) :: weightIndex - double precision , intent(in ) :: radius - !$GLC attributes unused :: weightIndex + class(nodeComponentSpheroidVerySimple), intent(inout) :: self - ! Return zero mass if the mass and component types do not match. - Node_Component_Spheroid_Very_Simple_Enclosed_Mass=0.0d0 - if (.not.(componentType == componentTypeAll .or. componentType == componentTypeSpheroid)) return - ! Determine mass contributed. - select case (weightBy%ID) - case (weightByMass %ID) - select case (massType%ID) - case (massTypeAll%ID,massTypeBaryonic%ID,massTypeGalactic%ID) - Node_Component_Spheroid_Very_Simple_Enclosed_Mass=self%massGas()+self%massStellar() - case (massTypeGaseous%ID) - Node_Component_Spheroid_Very_Simple_Enclosed_Mass=self%massGas() - case (massTypeStellar%ID) - Node_Component_Spheroid_Very_Simple_Enclosed_Mass= self%massStellar() - end select - case default - call Error_Report('this component does not track luminosity'//{introspection:location}) - end select - ! Return if no mass. - if (Node_Component_Spheroid_Very_Simple_Enclosed_Mass <= 0.0d0) return - ! Return if the total mass was requested. - if (radius >= radiusLarge) return - ! Otherwise we have an error. - call Error_Report('this component does not specify a mass profile'//{introspection:location}) + Node_Component_Spheroid_Very_Simple_Half_Mass_Radius=self%radius() return -end function Node_Component_Spheroid_Very_Simple_Enclosed_Mass +end function Node_Component_Spheroid_Very_Simple_Half_Mass_Radius -double precision function Node_Component_Spheroid_Very_Simple_Half_Mass_Radius(self) +double precision function Node_Component_Spheroid_Very_Simple_Mass_Baryonic(self) result(massBaryonic) !!{ - Return the half-mass radius of the very simple spheroid. + Return the baryonic mass for the very simple spheroid component. !!} implicit none class(nodeComponentSpheroidVerySimple), intent(inout) :: self - Node_Component_Spheroid_Very_Simple_Half_Mass_Radius=self%radius() + massBaryonic=+max(0.0d0,self%massStellar()) & + & +max(0.0d0,self%massGas ()) return -end function Node_Component_Spheroid_Very_Simple_Half_Mass_Radius +end function Node_Component_Spheroid_Very_Simple_Mass_Baryonic diff --git a/source/objects.tensors.F90 b/source/objects.tensors.F90 index 6bd318a3e5..829435c2b0 100644 --- a/source/objects.tensors.F90 +++ b/source/objects.tensors.F90 @@ -64,6 +64,7 @@ module Tensors contains !![ + @@ -101,6 +102,7 @@ module Tensors generic :: operator(/) => Tensor_R2_D3_Sym_Scalar_Divide generic :: operator(==) => Tensor_R2_D3_Sym_Matrix_Equality procedure :: nonStaticSizeOf => Tensor_R2_D3_Sym_Non_Static_Size_Of + procedure :: element => Tensor_R2_D3_Sym_Element procedure :: isZero => Tensor_R2_D3_Sym_Is_Zero procedure :: destroy => Tensor_R2_D3_Sym_Destroy procedure :: setToIdentity => Tensor_R2_D3_Sym_Set_To_Identity @@ -201,6 +203,13 @@ module subroutine Tensor_R2_D3_Sym_Set_To_Identity(self) !!} class(tensorRank2Dimension3Symmetric), intent(inout) :: self end subroutine Tensor_R2_D3_Sym_Set_To_Identity + module double precision function Tensor_R2_D3_Sym_Element(self,i,j) + !!{ + Return the enumeration element of a {\normalfont \ttfamily tensorRank2Dimension3Symmetric} object. + !!} + class (tensorRank2Dimension3Symmetric), intent(in) :: self + integer , intent(in) :: i , j + end function Tensor_R2_D3_Sym_Element module logical function Tensor_R2_D3_Sym_Is_Zero(self) !!{ Test whether a {\normalfont \ttfamily tensorRank2Dimension3Symmetric} object is zero. diff --git a/source/objects.tensors.rank2.dimension3.symmetric.F90 b/source/objects.tensors.rank2.dimension3.symmetric.F90 index aa6973daf8..c116069e83 100644 --- a/source/objects.tensors.rank2.dimension3.symmetric.F90 +++ b/source/objects.tensors.rank2.dimension3.symmetric.F90 @@ -259,6 +259,56 @@ end function tensorRank2Dimension3SymmetricInternal return end procedure Tensor_R2_D3_Sym_Is_Zero + module procedure Tensor_R2_D3_Sym_Element + !!{ + Return the enumeration element of a {\normalfont \ttfamily tensorRank2Dimension3Symmetric} object. + !!} + use :: Error, only : Error_Report + implicit none + + select case (i) + case (0) + select case (j) + case (0) + Tensor_R2_D3_Sym_Element=self%x00 + return + case (1) + Tensor_R2_D3_Sym_Element=self%x01 + return + case (2) + Tensor_R2_D3_Sym_Element=self%x02 + return + end select + case (1) + select case (j) + case (0) + Tensor_R2_D3_Sym_Element=self%x01 + return + case (1) + Tensor_R2_D3_Sym_Element=self%x11 + return + case (2) + Tensor_R2_D3_Sym_Element=self%x12 + return + end select + case (2) + select case (j) + case (0) + Tensor_R2_D3_Sym_Element=self%x02 + return + case (1) + Tensor_R2_D3_Sym_Element=self%x12 + return + case (2) + Tensor_R2_D3_Sym_Element=self%x22 + return + end select + end select + Tensor_R2_D3_Sym_Element=0.0d0 + call Error_Report('invalid indices'//{introspection:location}) + return + end procedure Tensor_R2_D3_Sym_Element + module procedure Tensor_R2_D3_Sym_Add !!{ Add two {\normalfont \ttfamily tensorRank2Dimension3Symmetric} objects. diff --git a/source/output.analyses.HI_vs_halo_mass_relation.ALFALFA_Padmanabhan_2017.F90 b/source/output.analyses.HI_vs_halo_mass_relation.ALFALFA_Padmanabhan_2017.F90 index b7480dfbb4..c3095bf293 100644 --- a/source/output.analyses.HI_vs_halo_mass_relation.ALFALFA_Padmanabhan_2017.F90 +++ b/source/output.analyses.HI_vs_halo_mass_relation.ALFALFA_Padmanabhan_2017.F90 @@ -37,8 +37,6 @@ class (cosmologyFunctionsClass ), pointer :: cosmologyFunctions_ => null() class (outputAnalysisMolecularRatioClass), pointer :: outputAnalysisMolecularRatio_ => null() class (virialDensityContrastClass ), pointer :: virialDensityContrast_ => null() - class (darkMatterProfileDMOClass ), pointer :: darkMatterProfileDMO_ => null() - class (galacticStructureClass ), pointer :: galacticStructure_ => null() double precision , allocatable , dimension(:) :: systematicErrorPolynomialCoefficient integer (c_size_t ) :: likelihoodBin contains @@ -61,7 +59,6 @@ function hiVsHaloMassRelationPadmanabhan2017ConstructorParameters(parameters) re !!} use :: Cosmology_Functions , only : cosmologyFunctions , cosmologyFunctionsClass use :: Cosmology_Parameters , only : cosmologyParameters , cosmologyParametersClass - use :: Galactic_Structure , only : galacticStructureClass use :: Functions_Global , only : Virial_Density_Contrast_Percolation_Objects_Constructor_ use :: Input_Parameters , only : inputParameter , inputParameters use :: Output_Analysis_Molecular_Ratios, only : outputAnalysisMolecularRatio , outputAnalysisMolecularRatioClass @@ -73,8 +70,6 @@ function hiVsHaloMassRelationPadmanabhan2017ConstructorParameters(parameters) re class (outputTimesClass ), pointer :: outputTimes_ class (cosmologyParametersClass ), pointer :: cosmologyParameters_ class (virialDensityContrastClass ), pointer :: virialDensityContrast_ - class (darkMatterProfileDMOClass ), pointer :: darkMatterProfileDMO_ - class (galacticStructureClass ), pointer :: galacticStructure_ class (outputAnalysisMolecularRatioClass ), pointer :: outputAnalysisMolecularRatio_ class (* ), pointer :: percolationObjects_ integer (c_size_t ) :: likelihoodBin @@ -104,25 +99,21 @@ function hiVsHaloMassRelationPadmanabhan2017ConstructorParameters(parameters) re - - !!] percolationObjects_ => Virial_Density_Contrast_Percolation_Objects_Constructor_(parameters) - self = outputAnalysisHIVsHaloMassRelationPadmanabhan2017(likelihoodBin,systematicErrorPolynomialCoefficient,cosmologyParameters_,cosmologyFunctions_,virialDensityContrast_,darkMatterProfileDMO_,outputAnalysisMolecularRatio_,outputTimes_,galacticStructure_,percolationObjects_) + self = outputAnalysisHIVsHaloMassRelationPadmanabhan2017(likelihoodBin,systematicErrorPolynomialCoefficient,cosmologyParameters_,cosmologyFunctions_,virialDensityContrast_,outputAnalysisMolecularRatio_,outputTimes_,percolationObjects_) !![ - - !!] return end function hiVsHaloMassRelationPadmanabhan2017ConstructorParameters - function hiVsHaloMassRelationPadmanabhan2017ConstructorInternal(likelihoodBin,systematicErrorPolynomialCoefficient,cosmologyParameters_,cosmologyFunctions_,virialDensityContrast_,darkMatterProfileDMO_,outputAnalysisMolecularRatio_,outputTimes_,galacticStructure_,percolationObjects_) result (self) + function hiVsHaloMassRelationPadmanabhan2017ConstructorInternal(likelihoodBin,systematicErrorPolynomialCoefficient,cosmologyParameters_,cosmologyFunctions_,virialDensityContrast_,outputAnalysisMolecularRatio_,outputTimes_,percolationObjects_) result (self) !!{ Constructor for the ``hiVsHaloMassRelationPadmanabhan2017'' output analysis class for internal use. !!} @@ -153,8 +144,6 @@ function hiVsHaloMassRelationPadmanabhan2017ConstructorInternal(likelihoodBin,sy class (cosmologyParametersClass ), intent(inout), target :: cosmologyParameters_ class (cosmologyFunctionsClass ), intent(inout), target :: cosmologyFunctions_ class (virialDensityContrastClass ), intent(in ), target :: virialDensityContrast_ - class (darkMatterProfileDMOClass ), intent(in ), target :: darkMatterProfileDMO_ - class (galacticStructureClass ), intent(in ), target :: galacticStructure_ class (outputTimesClass ), intent(inout), target :: outputTimes_ class (outputAnalysisMolecularRatioClass ), intent(in ), target :: outputAnalysisMolecularRatio_ class (* ), intent(in ), target :: percolationObjects_ @@ -204,7 +193,7 @@ function hiVsHaloMassRelationPadmanabhan2017ConstructorInternal(likelihoodBin,sy & jacobianVelocity0 , jacobianVelocity1 integer (c_size_t ) :: iBin !![ - + !!] ! Construct survey geometry. @@ -327,7 +316,7 @@ function hiVsHaloMassRelationPadmanabhan2017ConstructorInternal(likelihoodBin,sy ! Create an HI mass weight property extractor. allocate(outputAnalysisWeightPropertyExtractor_ ) !![ - + !!] ! Create a halo mass weight property extractor. The virial density contrast is chosen to equal that expected for a ! friends-of-friends algorithm with linking length parameter b=0.2 since that is what was used by Sheth, Mo & Tormen (2001) in @@ -338,7 +327,7 @@ function hiVsHaloMassRelationPadmanabhan2017ConstructorInternal(likelihoodBin,sy !!] allocate(nodePropertyExtractor_ ) !![ - + !!] ! Create a halo scale object from which to compute virial velocities. Padmanabhan & Refrigier use the Bryan & Norman (1998) ! virial density contrast definition. However (Padmanabhan, private communication), they assume it gives the density contrast @@ -486,9 +475,7 @@ subroutine hiVsHaloMassRelationPadmanabhan2017Destructor(self) - - !!] return end subroutine hiVsHaloMassRelationPadmanabhan2017Destructor diff --git a/source/output.analyses.ICM_Xray_luminosity_temperature.F90 b/source/output.analyses.ICM_Xray_luminosity_temperature.F90 index 6a68f2de04..93cadcd20d 100644 --- a/source/output.analyses.ICM_Xray_luminosity_temperature.F90 +++ b/source/output.analyses.ICM_Xray_luminosity_temperature.F90 @@ -31,13 +31,11 @@ An ICM X-ray luminosity-temperature relation output analysis class. !!} private - double precision , allocatable , dimension(:) :: systematicErrorPolynomialCoefficient , randomErrorPolynomialCoefficient - class (darkMatterHaloScaleClass ), pointer :: darkMatterHaloScale_ => null() - class (hotHaloMassDistributionClass ), pointer :: hotHaloMassDistribution_ => null() - class (hotHaloTemperatureProfileClass), pointer :: hotHaloTemperatureProfile_ => null() - class (coolingFunctionClass ), pointer :: coolingFunction_ => null() - class (cosmologyFunctionsClass ), pointer :: cosmologyFunctions_ => null() - double precision :: randomErrorMinimum , randomErrorMaximum + double precision , allocatable , dimension(:) :: systematicErrorPolynomialCoefficient , randomErrorPolynomialCoefficient + class (darkMatterHaloScaleClass), pointer :: darkMatterHaloScale_ => null() + class (coolingFunctionClass ), pointer :: coolingFunction_ => null() + class (cosmologyFunctionsClass ), pointer :: cosmologyFunctions_ => null() + double precision :: randomErrorMinimum , randomErrorMaximum contains final :: icmXrayLuminosityTemperatureDestructor end type outputAnalysisICMXrayLuminosityTemperature @@ -56,20 +54,16 @@ function icmXrayLuminosityTemperatureConstructorParameters(parameters) result (s !!{ Constructor for the ``icmXrayLuminosityTemperature'' output analysis class which takes a parameter set as input. !!} - use :: Input_Parameters , only : inputParameter , inputParameters - use :: Cooling_Functions , only : coolingFunctionClass - use :: Cosmology_Functions , only : cosmologyFunctionsClass - use :: Dark_Matter_Halo_Scales , only : darkMatterHaloScaleClass - use :: Hot_Halo_Mass_Distributions , only : hotHaloMassDistributionClass - use :: Hot_Halo_Temperature_Profiles, only : hotHaloTemperatureProfileClass + use :: Input_Parameters , only : inputParameter , inputParameters + use :: Cooling_Functions , only : coolingFunctionClass + use :: Cosmology_Functions , only : cosmologyFunctionsClass + use :: Dark_Matter_Halo_Scales, only : darkMatterHaloScaleClass implicit none type (outputAnalysisICMXrayLuminosityTemperature) :: self type (inputParameters ), intent(inout) :: parameters double precision , allocatable , dimension(:) :: systematicErrorPolynomialCoefficient, randomErrorPolynomialCoefficient class (outputTimesClass ), pointer :: outputTimes_ class (darkMatterHaloScaleClass ), pointer :: darkMatterHaloScale_ - class (hotHaloMassDistributionClass ), pointer :: hotHaloMassDistribution_ - class (hotHaloTemperatureProfileClass ), pointer :: hotHaloTemperatureProfile_ class (coolingFunctionClass ), pointer :: coolingFunction_ class (cosmologyFunctionsClass ), pointer :: cosmologyFunctions_ double precision :: randomErrorMinimum , randomErrorMaximum @@ -106,28 +100,24 @@ function icmXrayLuminosityTemperatureConstructorParameters(parameters) result (s 0.05d0 The maximum random error for X-ray temperature. - - - - - - + + + + !!] ! Build the object. - self=outputAnalysisICMXrayLuminosityTemperature(systematicErrorPolynomialCoefficient,randomErrorPolynomialCoefficient,randomErrorMinimum,randomErrorMaximum,outputTimes_,cosmologyFunctions_,darkMatterHaloScale_,hotHaloMassDistribution_,hotHaloTemperatureProfile_,coolingFunction_) + self=outputAnalysisICMXrayLuminosityTemperature(systematicErrorPolynomialCoefficient,randomErrorPolynomialCoefficient,randomErrorMinimum,randomErrorMaximum,outputTimes_,cosmologyFunctions_,darkMatterHaloScale_,coolingFunction_) !![ - - - - - - + + + + !!] return end function icmXrayLuminosityTemperatureConstructorParameters - function icmXrayLuminosityTemperatureConstructorInternal(systematicErrorPolynomialCoefficient,randomErrorPolynomialCoefficient,randomErrorMinimum,randomErrorMaximum,outputTimes_,cosmologyFunctions_,darkMatterHaloScale_,hotHaloMassDistribution_,hotHaloTemperatureProfile_,coolingFunction_) result (self) + function icmXrayLuminosityTemperatureConstructorInternal(systematicErrorPolynomialCoefficient,randomErrorPolynomialCoefficient,randomErrorMinimum,randomErrorMaximum,outputTimes_,cosmologyFunctions_,darkMatterHaloScale_,coolingFunction_) result (self) !!{ Constructor for the ``icmXrayLuminosityTemperature'' output analysis class for internal use. !!} @@ -151,8 +141,6 @@ function icmXrayLuminosityTemperatureConstructorInternal(systematicErrorPolynomi class (outputTimesClass ), intent(inout), target :: outputTimes_ class (cosmologyFunctionsClass ), intent(in ), target :: cosmologyFunctions_ class (darkMatterHaloScaleClass ), intent(in ), target :: darkMatterHaloScale_ - class (hotHaloMassDistributionClass ), intent(in ), target :: hotHaloMassDistribution_ - class (hotHaloTemperatureProfileClass ), intent(in ), target :: hotHaloTemperatureProfile_ class (coolingFunctionClass ), intent(in ), target :: coolingFunction_ integer , parameter :: covarianceBinomialBinsPerDecade =10 double precision , parameter :: covarianceBinomialMassHaloMinimum = 1.0d08, covarianceBinomialMassHaloMaximum =1.0d16 @@ -178,7 +166,7 @@ function icmXrayLuminosityTemperatureConstructorInternal(systematicErrorPolynomi & countTemperaturesPerDecade =5.0d0 integer (c_size_t ) :: iOutput , countTemperatures !![ - + !!] ! Construct bins in temperature. @@ -258,12 +246,12 @@ function icmXrayLuminosityTemperatureConstructorInternal(systematicErrorPolynomi ! Create an X-ray temperature property extractor. allocate(nodePropertyExtractor_ ) !![ - + !!] ! Create an X-ray luminosity property extractor. allocate(outputAnalysisWeightPropertyExtractor_ ) !![ - + !!] ! Build the object. self%outputAnalysisMeanFunction1D=outputAnalysisMeanFunction1D( & @@ -328,12 +316,10 @@ subroutine icmXrayLuminosityTemperatureDestructor(self) type(outputAnalysisICMXrayLuminosityTemperature), intent(inout) :: self !![ - - - - - - + + + + !!] return end subroutine icmXrayLuminosityTemperatureDestructor diff --git a/source/output.analyses.Local_Group.mass_metallicity_relation.F90 b/source/output.analyses.Local_Group.mass_metallicity_relation.F90 index 0f5bda5116..51471bf224 100644 --- a/source/output.analyses.Local_Group.mass_metallicity_relation.F90 +++ b/source/output.analyses.Local_Group.mass_metallicity_relation.F90 @@ -34,7 +34,6 @@ private class (outputAnalysisClass ), pointer :: outputAnalysis_ => null() class (outputTimesClass ), pointer :: outputTimes_ => null() - class (galacticStructureClass ), pointer :: galacticStructure_ => null() double precision , allocatable, dimension(:) :: randomErrorPolynomialCoefficient , systematicErrorPolynomialCoefficient, & & metallicitySystematicErrorPolynomialCoefficient integer :: covarianceBinomialBinsPerDecade @@ -64,7 +63,6 @@ function localGroupMassMetallicityRelationConstructorParameters(parameters) resu Constructor for the ``localGroupMassMetallicityRelation'' output analysis class which takes a parameter set as input. !!} use :: Input_Parameters , only : inputParameter , inputParameters - use :: Galactic_Structure , only : galacticStructureClass use :: Output_Times , only : outputTimes , outputTimesClass use :: Galactic_Filters , only : enumerationPositionTypeEncode use :: Models_Likelihoods_Constants, only : logImprobable @@ -72,7 +70,6 @@ function localGroupMassMetallicityRelationConstructorParameters(parameters) resu type (outputAnalysisLocalGroupMassMetallicityRelation) :: self type (inputParameters ), intent(inout) :: parameters class (outputTimesClass ), pointer :: outputTimes_ - class (galacticStructureClass ), pointer :: galacticStructure_ double precision , allocatable , dimension(:) :: randomErrorPolynomialCoefficient , systematicErrorPolynomialCoefficient, & & metallicitySystematicErrorPolynomialCoefficient integer :: covarianceBinomialBinsPerDecade @@ -147,10 +144,9 @@ The maximum halo mass to consider when constructing Local Group ste var_str('orbital') The type of position to use in survey geometry filters. - - + !!] - self=outputAnalysisLocalGroupMassMetallicityRelation(outputTimes_,galacticStructure_,enumerationPositionTypeEncode(positionType,includesPrefix=.false.),randomErrorMinimum,randomErrorMaximum,randomErrorPolynomialCoefficient,systematicErrorPolynomialCoefficient,metallicitySystematicErrorPolynomialCoefficient,covarianceBinomialBinsPerDecade,covarianceBinomialMassHaloMinimum,covarianceBinomialMassHaloMaximum) + self=outputAnalysisLocalGroupMassMetallicityRelation(outputTimes_,enumerationPositionTypeEncode(positionType,includesPrefix=.false.),randomErrorMinimum,randomErrorMaximum,randomErrorPolynomialCoefficient,systematicErrorPolynomialCoefficient,metallicitySystematicErrorPolynomialCoefficient,covarianceBinomialBinsPerDecade,covarianceBinomialMassHaloMinimum,covarianceBinomialMassHaloMaximum) !![ @@ -158,7 +154,7 @@ The maximum halo mass to consider when constructing Local Group ste return end function localGroupMassMetallicityRelationConstructorParameters - function localGroupMassMetallicityRelationConstructorInternal(outputTimes_,galacticStructure_,positionType,randomErrorMinimum,randomErrorMaximum,randomErrorPolynomialCoefficient,systematicErrorPolynomialCoefficient,metallicitySystematicErrorPolynomialCoefficient,covarianceBinomialBinsPerDecade,covarianceBinomialMassHaloMinimum,covarianceBinomialMassHaloMaximum) result (self) + function localGroupMassMetallicityRelationConstructorInternal(outputTimes_,positionType,randomErrorMinimum,randomErrorMaximum,randomErrorPolynomialCoefficient,systematicErrorPolynomialCoefficient,metallicitySystematicErrorPolynomialCoefficient,covarianceBinomialBinsPerDecade,covarianceBinomialMassHaloMinimum,covarianceBinomialMassHaloMaximum) result (self) !!{ Constructor for the ``localGroupMassMetallicityRelation'' output analysis class for internal use. !!} @@ -189,7 +185,6 @@ function localGroupMassMetallicityRelationConstructorInternal(outputTimes_,galac & metallicitySystematicErrorPolynomialCoefficient type (enumerationPositionTypeType ), intent(in ) :: positionType class (outputTimesClass ), intent(inout), target :: outputTimes_ - class (galacticStructureClass ), intent(in ), target :: galacticStructure_ type (nodePropertyExtractorMassStellar ) , pointer :: nodePropertyExtractor_ type (nodePropertyExtractorMetallicityStellar ) , pointer :: outputAnalysisWeightPropertyExtractor_ type (outputAnalysisPropertyOperatorMetallicitySolarRelative) , pointer :: outputAnalysisWeightPropertyOperatorMetallicity_ @@ -227,7 +222,7 @@ function localGroupMassMetallicityRelationConstructorInternal(outputTimes_,galac type (localGroupDB ) :: localGroupDB_ double precision :: massesWidthBin !![ - + !!] ! Construct mass bins. @@ -305,7 +300,7 @@ function localGroupMassMetallicityRelationConstructorInternal(outputTimes_,galac ! Create a stellar mass property extractor. allocate(nodePropertyExtractor_ ) !![ - + !!] ! Create a stellar metallicity weight property extractor. allocate(outputAnalysisWeightPropertyExtractor_ ) @@ -506,9 +501,8 @@ subroutine localGroupMassMetallicityRelationDestructor(self) type(outputAnalysisLocalGroupMassMetallicityRelation), intent(inout) :: self !![ - - - + + !!] return end subroutine localGroupMassMetallicityRelationDestructor diff --git a/source/output.analyses.Local_Group.mass_size_relation.F90 b/source/output.analyses.Local_Group.mass_size_relation.F90 index fc13a436f4..0b6f4d1fb4 100644 --- a/source/output.analyses.Local_Group.mass_size_relation.F90 +++ b/source/output.analyses.Local_Group.mass_size_relation.F90 @@ -34,7 +34,6 @@ private class (outputAnalysisClass ), pointer :: outputAnalysis_ => null() class (outputTimesClass ), pointer :: outputTimes_ => null() - class (galacticStructureClass ), pointer :: galacticStructure_ => null() double precision , allocatable , dimension(:) :: randomErrorPolynomialCoefficient , systematicErrorPolynomialCoefficient, & & sizeSystematicErrorPolynomialCoefficient integer :: covarianceBinomialBinsPerDecade @@ -64,7 +63,6 @@ function localGroupMassSizeRelationConstructorParameters(parameters) result(self Constructor for the ``localGroupMassSizeRelation'' output analysis class which takes a parameter set as input. !!} use :: Input_Parameters , only : inputParameter , inputParameters - use :: Galactic_Structure , only : galacticStructureClass use :: Output_Times , only : outputTimes , outputTimesClass use :: Galactic_Filters , only : enumerationPositionTypeEncode use :: Models_Likelihoods_Constants, only : logImprobable @@ -72,7 +70,6 @@ function localGroupMassSizeRelationConstructorParameters(parameters) result(self type (outputAnalysisLocalGroupMassSizeRelation) :: self type (inputParameters ), intent(inout) :: parameters class (outputTimesClass ), pointer :: outputTimes_ - class (galacticStructureClass ), pointer :: galacticStructure_ double precision , allocatable , dimension(:) :: randomErrorPolynomialCoefficient , systematicErrorPolynomialCoefficient, & & sizeSystematicErrorPolynomialCoefficient integer :: covarianceBinomialBinsPerDecade @@ -148,9 +145,8 @@ function localGroupMassSizeRelationConstructorParameters(parameters) result(self The type of position to use in survey geometry filters. - !!] - self=outputAnalysisLocalGroupMassSizeRelation(outputTimes_,galacticStructure_,enumerationPositionTypeEncode(positionType,includesPrefix=.false.),randomErrorMinimum,randomErrorMaximum,randomErrorPolynomialCoefficient,systematicErrorPolynomialCoefficient,sizeSystematicErrorPolynomialCoefficient,covarianceBinomialBinsPerDecade,covarianceBinomialMassHaloMinimum,covarianceBinomialMassHaloMaximum) + self=outputAnalysisLocalGroupMassSizeRelation(outputTimes_,enumerationPositionTypeEncode(positionType,includesPrefix=.false.),randomErrorMinimum,randomErrorMaximum,randomErrorPolynomialCoefficient,systematicErrorPolynomialCoefficient,sizeSystematicErrorPolynomialCoefficient,covarianceBinomialBinsPerDecade,covarianceBinomialMassHaloMinimum,covarianceBinomialMassHaloMaximum) !![ @@ -158,7 +154,7 @@ function localGroupMassSizeRelationConstructorParameters(parameters) result(self return end function localGroupMassSizeRelationConstructorParameters - function localGroupMassSizeRelationConstructorInternal(outputTimes_,galacticStructure_,positionType,randomErrorMinimum,randomErrorMaximum,randomErrorPolynomialCoefficient,systematicErrorPolynomialCoefficient,sizeSystematicErrorPolynomialCoefficient,covarianceBinomialBinsPerDecade,covarianceBinomialMassHaloMinimum,covarianceBinomialMassHaloMaximum) result (self) + function localGroupMassSizeRelationConstructorInternal(outputTimes_,positionType,randomErrorMinimum,randomErrorMaximum,randomErrorPolynomialCoefficient,systematicErrorPolynomialCoefficient,sizeSystematicErrorPolynomialCoefficient,covarianceBinomialBinsPerDecade,covarianceBinomialMassHaloMinimum,covarianceBinomialMassHaloMaximum) result (self) !!{ Constructor for the ``localGroupMassSizeRelation'' output analysis class for internal use. !!} @@ -187,7 +183,6 @@ function localGroupMassSizeRelationConstructorInternal(outputTimes_,galacticStru & sizeSystematicErrorPolynomialCoefficient type (enumerationPositionTypeType ), intent(in ) :: positionType class (outputTimesClass ), intent(inout), target :: outputTimes_ - class (galacticStructureClass ), intent(in ), target :: galacticStructure_ type (nodePropertyExtractorMassStellar ) , pointer :: nodePropertyExtractor_ type (nodePropertyExtractorRadiusHalfMassStellar ) , pointer :: outputAnalysisWeightPropertyExtractor_ type (outputAnalysisPropertyOperatorSystmtcPolynomial ) , pointer :: outputAnalysisPropertyOperatorSystmtcPolynomial_ , outputAnalysisWeightPropertyOperatorSystmtcPolynomial_ @@ -227,7 +222,7 @@ function localGroupMassSizeRelationConstructorInternal(outputTimes_,galacticStru type (localGroupDB ) :: localGroupDB_ double precision :: massesWidthBin !![ - + !!] ! Construct mass bins. @@ -312,12 +307,12 @@ function localGroupMassSizeRelationConstructorInternal(outputTimes_,galacticStru ! Create a stellar mass property extractor. allocate(nodePropertyExtractor_ ) !![ - + !!] ! Create a stellar metallicity weight property extractor. allocate(outputAnalysisWeightPropertyExtractor_ ) !![ - + !!] ! Build a size weight property operator. allocate(outputAnalysisWeightPropertyOperatorSystmtcPolynomial_) @@ -521,9 +516,8 @@ subroutine localGroupMassSizeRelationDestructor(self) type(outputAnalysisLocalGroupMassSizeRelation), intent(inout) :: self !![ - - - + + !!] return end subroutine localGroupMassSizeRelationDestructor diff --git a/source/output.analyses.Local_Group.mass_velocity_dispersion_relation.F90 b/source/output.analyses.Local_Group.mass_velocity_dispersion_relation.F90 index dabca5ac6f..a14c27ffaa 100644 --- a/source/output.analyses.Local_Group.mass_velocity_dispersion_relation.F90 +++ b/source/output.analyses.Local_Group.mass_velocity_dispersion_relation.F90 @@ -34,7 +34,6 @@ private class (outputAnalysisClass ), pointer :: outputAnalysis_ => null() class (outputTimesClass ), pointer :: outputTimes_ => null() - class (galacticStructureClass ), pointer :: galacticStructure_ => null() class (darkMatterHaloScaleClass ), pointer :: darkMatterHaloScale_ => null() double precision , allocatable , dimension(:) :: randomErrorPolynomialCoefficient , systematicErrorPolynomialCoefficient, & & velocityDispersionSystematicErrorPolynomialCoefficient @@ -65,7 +64,6 @@ function localGroupMassVelocityDispersionRelationConstructorParameters(parameter Constructor for the ``localGroupMassVelocityDispersionRelation'' output analysis class which takes a parameter set as input. !!} use :: Input_Parameters , only : inputParameter , inputParameters - use :: Galactic_Structure , only : galacticStructureClass use :: Output_Times , only : outputTimes , outputTimesClass use :: Galactic_Filters , only : enumerationPositionTypeEncode use :: Models_Likelihoods_Constants, only : logImprobable @@ -73,7 +71,6 @@ function localGroupMassVelocityDispersionRelationConstructorParameters(parameter type (outputAnalysisLocalGroupMassVelocityDispersionRelation) :: self type (inputParameters ), intent(inout) :: parameters class (outputTimesClass ), pointer :: outputTimes_ - class (galacticStructureClass ), pointer :: galacticStructure_ class (darkMatterHaloScaleClass ), pointer :: darkMatterHaloScale_ double precision , allocatable , dimension(:) :: randomErrorPolynomialCoefficient , systematicErrorPolynomialCoefficient, & & velocityDispersionSystematicErrorPolynomialCoefficient @@ -150,20 +147,18 @@ function localGroupMassVelocityDispersionRelationConstructorParameters(parameter The type of position to use in survey geometry filters. - !!] - self=outputAnalysisLocalGroupMassVelocityDispersionRelation(outputTimes_,galacticStructure_,darkMatterHaloScale_,enumerationPositionTypeEncode(positionType,includesPrefix=.false.),randomErrorMinimum,randomErrorMaximum,randomErrorPolynomialCoefficient,systematicErrorPolynomialCoefficient,velocityDispersionSystematicErrorPolynomialCoefficient,covarianceBinomialBinsPerDecade,covarianceBinomialMassHaloMinimum,covarianceBinomialMassHaloMaximum) + self=outputAnalysisLocalGroupMassVelocityDispersionRelation(outputTimes_,darkMatterHaloScale_,enumerationPositionTypeEncode(positionType,includesPrefix=.false.),randomErrorMinimum,randomErrorMaximum,randomErrorPolynomialCoefficient,systematicErrorPolynomialCoefficient,velocityDispersionSystematicErrorPolynomialCoefficient,covarianceBinomialBinsPerDecade,covarianceBinomialMassHaloMinimum,covarianceBinomialMassHaloMaximum) !![ - !!] return end function localGroupMassVelocityDispersionRelationConstructorParameters - function localGroupMassVelocityDispersionRelationConstructorInternal(outputTimes_,galacticStructure_,darkMatterHaloScale_,positionType,randomErrorMinimum,randomErrorMaximum,randomErrorPolynomialCoefficient,systematicErrorPolynomialCoefficient,velocityDispersionSystematicErrorPolynomialCoefficient,covarianceBinomialBinsPerDecade,covarianceBinomialMassHaloMinimum,covarianceBinomialMassHaloMaximum) result (self) + function localGroupMassVelocityDispersionRelationConstructorInternal(outputTimes_,darkMatterHaloScale_,positionType,randomErrorMinimum,randomErrorMaximum,randomErrorPolynomialCoefficient,systematicErrorPolynomialCoefficient,velocityDispersionSystematicErrorPolynomialCoefficient,covarianceBinomialBinsPerDecade,covarianceBinomialMassHaloMinimum,covarianceBinomialMassHaloMaximum) result (self) !!{ Constructor for the ``localGroupMassVelocityDispersionRelation'' output analysis class for internal use. !!} @@ -193,7 +188,6 @@ function localGroupMassVelocityDispersionRelationConstructorInternal(outputTimes & velocityDispersionSystematicErrorPolynomialCoefficient type (enumerationPositionTypeType ), intent(in ) :: positionType class (outputTimesClass ), intent(inout), target :: outputTimes_ - class (galacticStructureClass ), intent(in ), target :: galacticStructure_ class (darkMatterHaloScaleClass ), intent(in ), target :: darkMatterHaloScale_ type (nodePropertyExtractorMassStellar ) , pointer :: nodePropertyExtractor_ type (nodePropertyExtractorScalarizer ) , pointer :: outputAnalysisWeightPropertyScalarizer_ @@ -236,7 +230,7 @@ function localGroupMassVelocityDispersionRelationConstructorInternal(outputTimes double precision :: massesWidthBin type (varying_string ) , dimension(1) :: radiusSpecifier !![ - + !!] ! Construct mass bins. @@ -321,13 +315,13 @@ function localGroupMassVelocityDispersionRelationConstructorInternal(outputTimes ! Create a stellar mass property extractor. allocate(nodePropertyExtractor_ ) !![ - + !!] ! Create a velocity dispersion weight property extractor. allocate(outputAnalysisWeightPropertyExtractor_ ) radiusSpecifier(1)=var_str('stellarMassFraction{0.5}:all:galactic:lineOfSight:1.0') !![ - + !!] allocate(outputAnalysisWeightPropertyScalarizer_ ) !![ @@ -538,7 +532,6 @@ subroutine localGroupMassVelocityDispersionRelationDestructor(self) !![ - !!] return diff --git a/source/output.analyses.Local_Group.occupation_fraction.F90 b/source/output.analyses.Local_Group.occupation_fraction.F90 index 39be4d1a09..a3c89deec5 100644 --- a/source/output.analyses.Local_Group.occupation_fraction.F90 +++ b/source/output.analyses.Local_Group.occupation_fraction.F90 @@ -34,7 +34,6 @@ private class (outputAnalysisClass ), pointer :: outputAnalysis_ => null() class (outputTimesClass ), pointer :: outputTimes_ => null() - class (galacticStructureClass ), pointer :: galacticStructure_ => null() double precision , allocatable , dimension(:) :: randomErrorPolynomialCoefficient , systematicErrorPolynomialCoefficient, & & massStellarSystematicErrorPolynomialCoefficient integer :: covarianceBinomialBinsPerDecade @@ -64,7 +63,6 @@ function localGroupOccupationFractionConstructorParameters(parameters) result(se Constructor for the ``localGroupOccupationFraction'' output analysis class which takes a parameter set as input. !!} use :: Input_Parameters , only : inputParameter , inputParameters - use :: Galactic_Structure , only : galacticStructureClass use :: Output_Times , only : outputTimes , outputTimesClass use :: Galactic_Filters , only : enumerationPositionTypeEncode use :: Models_Likelihoods_Constants, only : logImprobable @@ -72,7 +70,6 @@ function localGroupOccupationFractionConstructorParameters(parameters) result(se type (outputAnalysisLocalGroupOccupationFraction) :: self type (inputParameters ), intent(inout) :: parameters class (outputTimesClass ), pointer :: outputTimes_ - class (galacticStructureClass ), pointer :: galacticStructure_ double precision , allocatable , dimension(:) :: randomErrorPolynomialCoefficient , systematicErrorPolynomialCoefficient, & & massStellarSystematicErrorPolynomialCoefficient integer :: covarianceBinomialBinsPerDecade @@ -147,10 +144,9 @@ function localGroupOccupationFractionConstructorParameters(parameters) result(se var_str('orbital') The type of position to use in survey geometry filters. - - + !!] - self=outputAnalysisLocalGroupOccupationFraction(outputTimes_,galacticStructure_,enumerationPositionTypeEncode(positionType,includesPrefix=.false.),randomErrorMinimum,randomErrorMaximum,randomErrorPolynomialCoefficient,systematicErrorPolynomialCoefficient,massStellarSystematicErrorPolynomialCoefficient,covarianceBinomialBinsPerDecade,covarianceBinomialMassHaloMinimum,covarianceBinomialMassHaloMaximum) + self=outputAnalysisLocalGroupOccupationFraction(outputTimes_,enumerationPositionTypeEncode(positionType,includesPrefix=.false.),randomErrorMinimum,randomErrorMaximum,randomErrorPolynomialCoefficient,systematicErrorPolynomialCoefficient,massStellarSystematicErrorPolynomialCoefficient,covarianceBinomialBinsPerDecade,covarianceBinomialMassHaloMinimum,covarianceBinomialMassHaloMaximum) !![ @@ -158,7 +154,7 @@ function localGroupOccupationFractionConstructorParameters(parameters) result(se return end function localGroupOccupationFractionConstructorParameters - function localGroupOccupationFractionConstructorInternal(outputTimes_,galacticStructure_,positionType,randomErrorMinimum,randomErrorMaximum,randomErrorPolynomialCoefficient,systematicErrorPolynomialCoefficient,massStellarSystematicErrorPolynomialCoefficient,covarianceBinomialBinsPerDecade,covarianceBinomialMassHaloMinimum,covarianceBinomialMassHaloMaximum) result (self) + function localGroupOccupationFractionConstructorInternal(outputTimes_,positionType,randomErrorMinimum,randomErrorMaximum,randomErrorPolynomialCoefficient,systematicErrorPolynomialCoefficient,massStellarSystematicErrorPolynomialCoefficient,covarianceBinomialBinsPerDecade,covarianceBinomialMassHaloMinimum,covarianceBinomialMassHaloMaximum) result (self) !!{ Constructor for the ``localGroupOccupationFraction'' output analysis class for internal use. !!} @@ -188,7 +184,6 @@ function localGroupOccupationFractionConstructorInternal(outputTimes_,galacticSt & massStellarSystematicErrorPolynomialCoefficient type (enumerationPositionTypeType ), intent(in ) :: positionType class (outputTimesClass ), intent(inout), target :: outputTimes_ - class (galacticStructureClass ), intent(in ), target :: galacticStructure_ type (nodePropertyExtractorMassBasic ) , pointer :: nodePropertyExtractor_ type (nodePropertyExtractorMassStellar ) , pointer :: outputAnalysisWeightPropertyExtractor_ type (outputAnalysisPropertyOperatorSystmtcPolynomial ) , pointer :: outputAnalysisPropertyOperatorSystmtcPolynomial_ , outputAnalysisWeightPropertyOperatorSystmtcPolynomial_ @@ -218,7 +213,7 @@ function localGroupOccupationFractionConstructorInternal(outputTimes_,galacticSt integer (c_size_t ) :: i , bufferCount type (hdf5Object ) :: fileData !![ - + !!] ! Construct the target distribution. @@ -243,7 +238,7 @@ function localGroupOccupationFractionConstructorInternal(outputTimes_,galacticSt ! Create a stellar mass weight property extractor. allocate(outputAnalysisWeightPropertyExtractor_ ) !![ - + !!] ! Build a size weight property operator. allocate(outputAnalysisWeightPropertyOperatorSystmtcPolynomial_) @@ -446,9 +441,8 @@ subroutine localGroupOccupationFractionDestructor(self) type(outputAnalysisLocalGroupOccupationFraction), intent(inout) :: self !![ - - - + + !!] return end subroutine localGroupOccupationFractionDestructor diff --git a/source/output.analyses.Local_Group.stellar_mass_function.F90 b/source/output.analyses.Local_Group.stellar_mass_function.F90 index 4c93d2a768..976c9b79e9 100644 --- a/source/output.analyses.Local_Group.stellar_mass_function.F90 +++ b/source/output.analyses.Local_Group.stellar_mass_function.F90 @@ -39,7 +39,6 @@ private type (outputAnalysisVolumeFunction1D), pointer :: volumeFunctionSatellites => null(), volumeFunctionCentrals => null() class (outputTimesClass ), pointer :: outputTimes_ => null() - class (galacticStructureClass ), pointer :: galacticStructure_ => null() double precision , allocatable, dimension(: ) :: randomErrorPolynomialCoefficient , systematicErrorPolynomialCoefficient double precision , allocatable, dimension(: ) :: masses , massFunction , & & massFunctionTarget @@ -80,7 +79,6 @@ function localGroupStellarMassFunctionConstructorParameters(parameters) result(s Constructor for the ``localGroupStellarMassFunction'' output analysis class which takes a parameter set as input. !!} use :: Input_Parameters , only : inputParameter , inputParameters - use :: Galactic_Structure , only : galacticStructureClass use :: Output_Times , only : outputTimes , outputTimesClass use :: Galactic_Filters , only : enumerationPositionTypeEncode use :: Models_Likelihoods_Constants, only : logImprobable @@ -88,7 +86,6 @@ function localGroupStellarMassFunctionConstructorParameters(parameters) result(s type (outputAnalysisLocalGroupStellarMassFunction) :: self type (inputParameters ), intent(inout) :: parameters class (outputTimesClass ), pointer :: outputTimes_ - class (galacticStructureClass ), pointer :: galacticStructure_ double precision , allocatable , dimension(:) :: randomErrorPolynomialCoefficient , systematicErrorPolynomialCoefficient integer :: covarianceBinomialBinsPerDecade double precision :: covarianceBinomialMassHaloMinimum, covarianceBinomialMassHaloMaximum , & @@ -176,10 +173,9 @@ The maximum halo mass to consider when constructing Local Group ste logImprobable The log-likelihood to assign to bins where the model expectation is zero. - - + !!] - self=outputAnalysisLocalGroupStellarMassFunction(outputTimes_,galacticStructure_,enumerationPositionTypeEncode(positionType,includesPrefix=.false.),negativeBinomialScatterFractional,randomErrorMinimum,randomErrorMaximum,randomErrorPolynomialCoefficient,systematicErrorPolynomialCoefficient,covarianceBinomialBinsPerDecade,covarianceBinomialMassHaloMinimum,covarianceBinomialMassHaloMaximum,logLikelihoodZero) + self=outputAnalysisLocalGroupStellarMassFunction(outputTimes_,enumerationPositionTypeEncode(positionType,includesPrefix=.false.),negativeBinomialScatterFractional,randomErrorMinimum,randomErrorMaximum,randomErrorPolynomialCoefficient,systematicErrorPolynomialCoefficient,covarianceBinomialBinsPerDecade,covarianceBinomialMassHaloMinimum,covarianceBinomialMassHaloMaximum,logLikelihoodZero) !![ @@ -187,7 +183,7 @@ The maximum halo mass to consider when constructing Local Group ste return end function localGroupStellarMassFunctionConstructorParameters - function localGroupStellarMassFunctionConstructorInternal(outputTimes_,galacticStructure_,positionType,negativeBinomialScatterFractional,randomErrorMinimum,randomErrorMaximum,randomErrorPolynomialCoefficient,systematicErrorPolynomialCoefficient,covarianceBinomialBinsPerDecade,covarianceBinomialMassHaloMinimum,covarianceBinomialMassHaloMaximum,logLikelihoodZero) result (self) + function localGroupStellarMassFunctionConstructorInternal(outputTimes_,positionType,negativeBinomialScatterFractional,randomErrorMinimum,randomErrorMaximum,randomErrorPolynomialCoefficient,systematicErrorPolynomialCoefficient,covarianceBinomialBinsPerDecade,covarianceBinomialMassHaloMinimum,covarianceBinomialMassHaloMaximum,logLikelihoodZero) result (self) !!{ Constructor for the ``localGroupStellarMassFunction'' output analysis class for internal use. !!} @@ -217,7 +213,6 @@ function localGroupStellarMassFunctionConstructorInternal(outputTimes_,galacticS double precision , intent(in ), dimension(: ) :: randomErrorPolynomialCoefficient , systematicErrorPolynomialCoefficient type (enumerationPositionTypeType ), intent(in ) :: positionType class (outputTimesClass ), intent(inout), target :: outputTimes_ - class (galacticStructureClass ), intent(in ), target :: galacticStructure_ type (nodePropertyExtractorMassStellar ) , pointer :: nodePropertyExtractor_ type (outputAnalysisPropertyOperatorSystmtcPolynomial ) , pointer :: outputAnalysisPropertyOperatorSystmtcPolynomial_ type (outputAnalysisPropertyOperatorLog10 ) , pointer :: outputAnalysisPropertyOperatorLog10_ @@ -251,7 +246,7 @@ function localGroupStellarMassFunctionConstructorInternal(outputTimes_,galacticS & bufferCountSatellites type (localGroupDB ) :: localGroupDB_ !![ - + !!] ! Initialize. @@ -284,7 +279,7 @@ function localGroupStellarMassFunctionConstructorInternal(outputTimes_,galacticS ! Create a stellar mass property extractor. allocate(nodePropertyExtractor_ ) !![ - + !!] ! Create property operators and unoperators to perform conversion to/from logarithmic mass. allocate(outputAnalysisPropertyOperatorLog10_ ) @@ -523,7 +518,6 @@ subroutine localGroupStellarMassFunctionDestructor(self) - !!] return end subroutine localGroupStellarMassFunctionDestructor diff --git a/source/output.analyses.Local_Group.stellar_mass_halo_mass_relation.F90 b/source/output.analyses.Local_Group.stellar_mass_halo_mass_relation.F90 index c0cba16a18..7d6d3db05b 100644 --- a/source/output.analyses.Local_Group.stellar_mass_halo_mass_relation.F90 +++ b/source/output.analyses.Local_Group.stellar_mass_halo_mass_relation.F90 @@ -34,7 +34,6 @@ private class (outputAnalysisClass ), pointer :: outputAnalysis_ => null() class (outputTimesClass ), pointer :: outputTimes_ => null() - class (galacticStructureClass ), pointer :: galacticStructure_ => null() double precision , allocatable , dimension(:) :: randomErrorPolynomialCoefficient , systematicErrorPolynomialCoefficient, & & massStellarSystematicErrorPolynomialCoefficient integer :: covarianceBinomialBinsPerDecade @@ -64,7 +63,6 @@ function localGroupStellarMassHaloMassRelationConstructorParameters(parameters) Constructor for the ``localGroupStellarMassHaloMassRelation'' output analysis class which takes a parameter set as input. !!} use :: Input_Parameters , only : inputParameter , inputParameters - use :: Galactic_Structure , only : galacticStructureClass use :: Output_Times , only : outputTimes , outputTimesClass use :: Galactic_Filters , only : enumerationPositionTypeEncode use :: Models_Likelihoods_Constants, only : logImprobable @@ -72,7 +70,6 @@ function localGroupStellarMassHaloMassRelationConstructorParameters(parameters) type (outputAnalysisLocalGroupStellarMassHaloMassRelation) :: self type (inputParameters ), intent(inout) :: parameters class (outputTimesClass ), pointer :: outputTimes_ - class (galacticStructureClass ), pointer :: galacticStructure_ double precision , allocatable , dimension(:) :: randomErrorPolynomialCoefficient , systematicErrorPolynomialCoefficient, & & massStellarSystematicErrorPolynomialCoefficient integer :: covarianceBinomialBinsPerDecade @@ -147,10 +144,9 @@ function localGroupStellarMassHaloMassRelationConstructorParameters(parameters) var_str('orbital') The type of position to use in survey geometry filters. - - + !!] - self=outputAnalysisLocalGroupStellarMassHaloMassRelation(outputTimes_,galacticStructure_,enumerationPositionTypeEncode(positionType,includesPrefix=.false.),randomErrorMinimum,randomErrorMaximum,randomErrorPolynomialCoefficient,systematicErrorPolynomialCoefficient,massStellarSystematicErrorPolynomialCoefficient,covarianceBinomialBinsPerDecade,covarianceBinomialMassHaloMinimum,covarianceBinomialMassHaloMaximum) + self=outputAnalysisLocalGroupStellarMassHaloMassRelation(outputTimes_,enumerationPositionTypeEncode(positionType,includesPrefix=.false.),randomErrorMinimum,randomErrorMaximum,randomErrorPolynomialCoefficient,systematicErrorPolynomialCoefficient,massStellarSystematicErrorPolynomialCoefficient,covarianceBinomialBinsPerDecade,covarianceBinomialMassHaloMinimum,covarianceBinomialMassHaloMaximum) !![ @@ -158,7 +154,7 @@ function localGroupStellarMassHaloMassRelationConstructorParameters(parameters) return end function localGroupStellarMassHaloMassRelationConstructorParameters - function localGroupStellarMassHaloMassRelationConstructorInternal(outputTimes_,galacticStructure_,positionType,randomErrorMinimum,randomErrorMaximum,randomErrorPolynomialCoefficient,systematicErrorPolynomialCoefficient,massStellarSystematicErrorPolynomialCoefficient,covarianceBinomialBinsPerDecade,covarianceBinomialMassHaloMinimum,covarianceBinomialMassHaloMaximum) result (self) + function localGroupStellarMassHaloMassRelationConstructorInternal(outputTimes_,positionType,randomErrorMinimum,randomErrorMaximum,randomErrorPolynomialCoefficient,systematicErrorPolynomialCoefficient,massStellarSystematicErrorPolynomialCoefficient,covarianceBinomialBinsPerDecade,covarianceBinomialMassHaloMinimum,covarianceBinomialMassHaloMaximum) result (self) !!{ Constructor for the ``localGroupStellarMassHaloMassRelation'' output analysis class for internal use. !!} @@ -188,7 +184,6 @@ function localGroupStellarMassHaloMassRelationConstructorInternal(outputTimes_,g & massStellarSystematicErrorPolynomialCoefficient type (enumerationPositionTypeType ), intent(in ) :: positionType class (outputTimesClass ), intent(inout), target :: outputTimes_ - class (galacticStructureClass ), intent(in ), target :: galacticStructure_ type (nodePropertyExtractorMassBasic ) , pointer :: nodePropertyExtractor_ type (nodePropertyExtractorMassStellar ) , pointer :: outputAnalysisWeightPropertyExtractor_ type (outputAnalysisPropertyOperatorSystmtcPolynomial ) , pointer :: outputAnalysisPropertyOperatorSystmtcPolynomial_ , outputAnalysisWeightPropertyOperatorSystmtcPolynomial_ @@ -218,7 +213,7 @@ function localGroupStellarMassHaloMassRelationConstructorInternal(outputTimes_,g integer (c_size_t ) :: i , bufferCount type (hdf5Object ) :: fileData !![ - + !!] ! Construct the target distribution. @@ -247,7 +242,7 @@ function localGroupStellarMassHaloMassRelationConstructorInternal(outputTimes_,g ! Create a stellar mass weight property extractor. allocate(outputAnalysisWeightPropertyExtractor_ ) !![ - + !!] ! Build a size weight property operator. allocate(outputAnalysisWeightPropertyOperatorSystmtcPolynomial_) @@ -443,9 +438,8 @@ subroutine localGroupStellarMassHaloMassRelationDestructor(self) type(outputAnalysisLocalGroupStellarMassHaloMassRelation), intent(inout) :: self !![ - - - + + !!] return end subroutine localGroupStellarMassHaloMassRelationDestructor diff --git a/source/output.analyses.Sunyaev-Zeldovich_Planck2013.F90 b/source/output.analyses.Sunyaev-Zeldovich_Planck2013.F90 index 8ec9dd4427..21647d5508 100644 --- a/source/output.analyses.Sunyaev-Zeldovich_Planck2013.F90 +++ b/source/output.analyses.Sunyaev-Zeldovich_Planck2013.F90 @@ -21,8 +21,6 @@ Implements a thermal Sunyaev-Zeldovich signal vs. stellar mass analysis class. !!} - use :: Galactic_Structure, only : galacticStructureClass - !![ A thermal Sunyaev-Zeldovich signal vs. stellar mass analysis class using the results of \cite{planck_collaboration_planck_2013}. @@ -33,15 +31,12 @@ A thermal Sunyaev-Zeldovich signal vs stellar mass analysis class using the results of \cite{planck_collaboration_planck_2013}. !!} private - double precision , allocatable , dimension(:) :: systematicErrorPolynomialCoefficient , randomErrorPolynomialCoefficient - class (cosmologyParametersClass ), pointer :: cosmologyParameters_ => null() - class (cosmologyFunctionsClass ), pointer :: cosmologyFunctions_ => null() - class (darkMatterHaloScaleClass ), pointer :: darkMatterHaloScale_ => null() - class (hotHaloMassDistributionClass ), pointer :: hotHaloMassDistribution_ => null() - class (hotHaloTemperatureProfileClass), pointer :: hotHaloTemperatureProfile_ => null() - class (chemicalStateClass ), pointer :: chemicalState_ => null() - class (galacticStructureClass ), pointer :: galacticStructure_ => null() - double precision :: randomErrorMinimum , randomErrorMaximum + double precision , allocatable, dimension(:) :: systematicErrorPolynomialCoefficient , randomErrorPolynomialCoefficient + class (cosmologyParametersClass), pointer :: cosmologyParameters_ => null() + class (cosmologyFunctionsClass ), pointer :: cosmologyFunctions_ => null() + class (darkMatterHaloScaleClass), pointer :: darkMatterHaloScale_ => null() + class (chemicalStateClass ), pointer :: chemicalState_ => null() + double precision :: randomErrorMinimum , randomErrorMaximum contains final :: sunyaevZeldovichPlanck2013Destructor end type outputAnalysisSunyaevZeldovichPlanck2013 @@ -60,8 +55,7 @@ function sunyaevZeldovichPlanck2013ConstructorParameters(parameters) result (sel !!{ Constructor for the ``sunyaevZeldovichPlanck2013'' output analysis class which takes a parameter set as input. !!} - use :: Input_Parameters , only : inputParameter , inputParameters - use :: Galactic_Structure, only : galacticStructureClass + use :: Input_Parameters, only : inputParameter, inputParameters implicit none type (outputAnalysisSunyaevZeldovichPlanck2013) :: self type (inputParameters ), intent(inout) :: parameters @@ -69,11 +63,8 @@ function sunyaevZeldovichPlanck2013ConstructorParameters(parameters) result (sel class (cosmologyParametersClass ), pointer :: cosmologyParameters_ class (cosmologyFunctionsClass ), pointer :: cosmologyFunctions_ class (darkMatterHaloScaleClass ), pointer :: darkMatterHaloScale_ - class (hotHaloMassDistributionClass ), pointer :: hotHaloMassDistribution_ - class (hotHaloTemperatureProfileClass ), pointer :: hotHaloTemperatureProfile_ class (chemicalStateClass ), pointer :: chemicalState_ class (outputTimesClass ), pointer :: outputTimes_ - class (galacticStructureClass ), pointer :: galacticStructure_ double precision :: randomErrorMinimum , randomErrorMaximum allocate(systematicErrorPolynomialCoefficient(max(1,parameters%count('systematicErrorPolynomialCoefficient',zeroIfNotPresent=.true.)))) @@ -110,29 +101,23 @@ function sunyaevZeldovichPlanck2013ConstructorParameters(parameters) result (sel - - - !!] ! Build the object. - self=outputAnalysisSunyaevZeldovichPlanck2013(systematicErrorPolynomialCoefficient,randomErrorPolynomialCoefficient,randomErrorMinimum,randomErrorMaximum,cosmologyParameters_,cosmologyFunctions_,darkMatterHaloScale_,hotHaloMassDistribution_,hotHaloTemperatureProfile_,chemicalState_,outputTimes_,galacticStructure_) + self=outputAnalysisSunyaevZeldovichPlanck2013(systematicErrorPolynomialCoefficient,randomErrorPolynomialCoefficient,randomErrorMinimum,randomErrorMaximum,cosmologyParameters_,cosmologyFunctions_,darkMatterHaloScale_,chemicalState_,outputTimes_) !![ - - - - - - - - + + + + + !!] return end function sunyaevZeldovichPlanck2013ConstructorParameters - function sunyaevZeldovichPlanck2013ConstructorInternal(systematicErrorPolynomialCoefficient,randomErrorPolynomialCoefficient,randomErrorMinimum,randomErrorMaximum,cosmologyParameters_,cosmologyFunctions_,darkMatterHaloScale_,hotHaloMassDistribution_,hotHaloTemperatureProfile_,chemicalState_,outputTimes_,galacticStructure_) result (self) + function sunyaevZeldovichPlanck2013ConstructorInternal(systematicErrorPolynomialCoefficient,randomErrorPolynomialCoefficient,randomErrorMinimum,randomErrorMaximum,cosmologyParameters_,cosmologyFunctions_,darkMatterHaloScale_,chemicalState_,outputTimes_) result (self) !!{ Constructor for the ``sunyaevZeldovichPlanck2013'' output analysis class for internal use. !!} @@ -158,11 +143,8 @@ function sunyaevZeldovichPlanck2013ConstructorInternal(systematicErrorPolynomial class (cosmologyParametersClass ), intent(inout), target :: cosmologyParameters_ class (cosmologyFunctionsClass ), intent(inout), target :: cosmologyFunctions_ class (darkMatterHaloScaleClass ), intent(in ), target :: darkMatterHaloScale_ - class (hotHaloMassDistributionClass ), intent(in ), target :: hotHaloMassDistribution_ - class (hotHaloTemperatureProfileClass ), intent(in ), target :: hotHaloTemperatureProfile_ class (chemicalStateClass ), intent(in ), target :: chemicalState_ class (outputTimesClass ), intent(inout), target :: outputTimes_ - class (galacticStructureClass ), intent(in ), target :: galacticStructure_ integer , parameter :: covarianceBinomialBinsPerDecade =10 double precision , parameter :: covarianceBinomialMassHaloMinimum = 1.000d08, covarianceBinomialMassHaloMaximum=1.0d16 double precision , allocatable , dimension(: ) :: masses , functionValueTarget , & @@ -190,7 +172,7 @@ function sunyaevZeldovichPlanck2013ConstructorInternal(systematicErrorPolynomial integer (c_size_t ), parameter :: bufferCount =10 integer (c_size_t ) :: iBin , binCount !![ - + !!] ! Construct the target data. @@ -314,7 +296,7 @@ function sunyaevZeldovichPlanck2013ConstructorInternal(systematicErrorPolynomial ! Create a stellar mass property extractor. allocate(nodePropertyExtractor_ ) !![ - + !!] ! Create a thermal Sunyaev-Zeldovich property extractor. allocate(outputAnalysisWeightPropertyExtractor_) @@ -325,10 +307,7 @@ function sunyaevZeldovichPlanck2013ConstructorInternal(systematicErrorPolynomial & cosmologyParameters_ , & & cosmologyFunctions_ , & & darkMatterHaloScale_ , & - & hotHaloMassDistribution_ , & - & hotHaloTemperatureProfile_ , & & chemicalState_ , & - & galacticStructure_ , & & densityContrast =500.0d0 , & & densityContrastRelativeTo=densityCosmologicalCritical, & & distanceAngular =500.0d0 & @@ -404,14 +383,11 @@ subroutine sunyaevZeldovichPlanck2013Destructor(self) type(outputAnalysisSunyaevZeldovichPlanck2013), intent(inout) :: self !![ - - - - - - - - + + + + + !!] return end subroutine sunyaevZeldovichPlanck2013Destructor diff --git a/source/output.analyses.black_hole_bulge_relation.F90 b/source/output.analyses.black_hole_bulge_relation.F90 index f9ce39c29e..8ffcf0629d 100644 --- a/source/output.analyses.black_hole_bulge_relation.F90 +++ b/source/output.analyses.black_hole_bulge_relation.F90 @@ -21,8 +21,6 @@ Contains a module which implements a black hole-bulge mass relation analysis class. !!} - use :: Galactic_Structure, only : galacticStructureClass - !![ A black hole-bulge mass relation output analysis class. @@ -33,7 +31,6 @@ A black hole-bulge mass relation output analysis class. !!} private - class (galacticStructureClass ), pointer :: galacticStructure_ => null() class (cosmologyFunctionsClass), pointer :: cosmologyFunctions_ => null() double precision , allocatable , dimension(:) :: systematicErrorPolynomialCoefficient , randomErrorPolynomialCoefficient double precision :: randomErrorMinimum , randomErrorMaximum @@ -63,7 +60,6 @@ function blackHoleBulgeRelationConstructorParameters(parameters) result (self) double precision , allocatable , dimension(:) :: systematicErrorPolynomialCoefficient, randomErrorPolynomialCoefficient class (cosmologyFunctionsClass ), pointer :: cosmologyFunctions_ class (outputTimesClass ), pointer :: outputTimes_ - class (galacticStructureClass ), pointer :: galacticStructure_ double precision :: randomErrorMinimum , randomErrorMaximum @@ -101,20 +97,18 @@ function blackHoleBulgeRelationConstructorParameters(parameters) result (self) - !!] ! Build the object. - self=outputAnalysisBlackHoleBulgeRelation(systematicErrorPolynomialCoefficient,randomErrorPolynomialCoefficient,randomErrorMinimum,randomErrorMaximum,cosmologyFunctions_,outputTimes_,galacticStructure_) + self=outputAnalysisBlackHoleBulgeRelation(systematicErrorPolynomialCoefficient,randomErrorPolynomialCoefficient,randomErrorMinimum,randomErrorMaximum,cosmologyFunctions_,outputTimes_) !![ - !!] return end function blackHoleBulgeRelationConstructorParameters - function blackHoleBulgeRelationConstructorInternal(systematicErrorPolynomialCoefficient,randomErrorPolynomialCoefficient,randomErrorMinimum,randomErrorMaximum,cosmologyFunctions_,outputTimes_,galacticStructure_) result (self) + function blackHoleBulgeRelationConstructorInternal(systematicErrorPolynomialCoefficient,randomErrorPolynomialCoefficient,randomErrorMinimum,randomErrorMaximum,cosmologyFunctions_,outputTimes_) result (self) !!{ Constructor for the ``blackHoleBulgeRelation'' output analysis class for internal use. !!} @@ -140,7 +134,6 @@ function blackHoleBulgeRelationConstructorInternal(systematicErrorPolynomialCoef double precision , intent(in ), dimension(: ) :: systematicErrorPolynomialCoefficient , randomErrorPolynomialCoefficient class (cosmologyFunctionsClass ), intent(inout), target :: cosmologyFunctions_ class (outputTimesClass ), intent(inout), target :: outputTimes_ - class (galacticStructureClass ), intent(inout), target :: galacticStructure_ integer , parameter :: covarianceBinomialBinsPerDecade =10 double precision , parameter :: covarianceBinomialMassHaloMinimum = 1.0d08, covarianceBinomialMassHaloMaximum=1.0d16 double precision , allocatable , dimension(: ) :: masses , functionValueTarget , & @@ -167,7 +160,7 @@ function blackHoleBulgeRelationConstructorInternal(systematicErrorPolynomialCoef type (hdf5Object ) :: dataFile type (varying_string ) :: targetLabel !![ - + !!] !$ call hdf5Access%set() @@ -285,7 +278,7 @@ function blackHoleBulgeRelationConstructorInternal(systematicErrorPolynomialCoef ! Create a stellar mass property extractor. allocate(nodePropertyExtractor_ ) !![ - + !!] ! Create an ISM metallicity weight property extractor. allocate(outputAnalysisWeightPropertyExtractor_ ) @@ -360,7 +353,6 @@ subroutine blackHoleBulgeRelationDestructor(self) type(outputAnalysisBlackHoleBulgeRelation), intent(inout) :: self !![ - !!] return diff --git a/source/output.analyses.black_hole_velocity_dispersion_relation.F90 b/source/output.analyses.black_hole_velocity_dispersion_relation.F90 index 6df2688ab2..40c3664000 100644 --- a/source/output.analyses.black_hole_velocity_dispersion_relation.F90 +++ b/source/output.analyses.black_hole_velocity_dispersion_relation.F90 @@ -24,7 +24,6 @@ !!} use :: Cosmology_Functions , only : cosmologyFunctionsClass - use :: Galactic_Structure , only : galacticStructureClass use :: Dark_Matter_Halo_Scales, only : darkMatterHaloScaleClass !![ @@ -37,7 +36,6 @@ !!} private class (darkMatterHaloScaleClass), pointer :: darkMatterHaloScale_ => null() - class (galacticStructureClass ), pointer :: galacticStructure_ => null() class (cosmologyFunctionsClass ), pointer :: cosmologyFunctions_ => null() double precision , allocatable , dimension(:) :: systematicErrorPolynomialCoefficient , randomErrorPolynomialCoefficient double precision :: randomErrorMinimum , randomErrorMaximum @@ -68,7 +66,6 @@ function blackHoleVelocityDispersionRelationConstructorParameters(parameters) re double precision , allocatable , dimension(:) :: systematicErrorPolynomialCoefficient , randomErrorPolynomialCoefficient class (cosmologyFunctionsClass ), pointer :: cosmologyFunctions_ class (outputTimesClass ), pointer :: outputTimes_ - class (galacticStructureClass ), pointer :: galacticStructure_ class (darkMatterHaloScaleClass ), pointer :: darkMatterHaloScale_ double precision :: randomErrorMinimum , randomErrorMaximum double precision , parameter :: toleranceRelative =1.0d-3 @@ -107,21 +104,19 @@ function blackHoleVelocityDispersionRelationConstructorParameters(parameters) re - !!] ! Build the object. - self=outputAnalysisBlackHoleVelocityDispersionRelation(systematicErrorPolynomialCoefficient,randomErrorPolynomialCoefficient,randomErrorMinimum,randomErrorMaximum,cosmologyFunctions_,outputTimes_,toleranceRelative=1.0d-3,darkMatterHaloScale_=darkMatterHaloScale_,galacticStructure_=galacticStructure_) + self=outputAnalysisBlackHoleVelocityDispersionRelation(systematicErrorPolynomialCoefficient,randomErrorPolynomialCoefficient,randomErrorMinimum,randomErrorMaximum,cosmologyFunctions_,outputTimes_,toleranceRelative=1.0d-3,darkMatterHaloScale_=darkMatterHaloScale_) !![ - !!] return end function blackHoleVelocityDispersionRelationConstructorParameters - function blackHoleVelocityDispersionRelationConstructorInternal(systematicErrorPolynomialCoefficient,randomErrorPolynomialCoefficient,randomErrorMinimum,randomErrorMaximum,cosmologyFunctions_,outputTimes_,toleranceRelative,darkMatterHaloScale_,galacticStructure_) result (self) + function blackHoleVelocityDispersionRelationConstructorInternal(systematicErrorPolynomialCoefficient,randomErrorPolynomialCoefficient,randomErrorMinimum,randomErrorMaximum,cosmologyFunctions_,outputTimes_,toleranceRelative,darkMatterHaloScale_) result (self) !!{ Constructor for the ``blackHoleVelocityDispersionRelation'' output analysis class for internal use. !!} @@ -149,7 +144,6 @@ function blackHoleVelocityDispersionRelationConstructorInternal(systematicErrorP double precision , intent(in ), dimension(: ) :: systematicErrorPolynomialCoefficient , randomErrorPolynomialCoefficient class (cosmologyFunctionsClass ), intent(inout), target :: cosmologyFunctions_ class (outputTimesClass ), intent(inout), target :: outputTimes_ - class (galacticStructureClass ), intent(inout), target :: galacticStructure_ class (darkMatterHaloScaleClass ), intent(in ), target :: darkMatterHaloScale_ integer , parameter :: covarianceBinomialBinsPerDecade =10 double precision , parameter :: covarianceBinomialMassHaloMinimum = 1.0d08, covarianceBinomialMassHaloMaximum =1.0d16 @@ -180,7 +174,7 @@ function blackHoleVelocityDispersionRelationConstructorInternal(systematicErrorP type (varying_string ) :: targetLabel type (varying_string ) , dimension(1 ) :: radiusSpecifiers !![ - + !!] !$ call hdf5Access%set() @@ -276,11 +270,11 @@ function blackHoleVelocityDispersionRelationConstructorInternal(systematicErrorP ! Build weight property operators. allocate(outputAnalysisWeightPropertyOperatorLog10_ ) !![ - + !!] allocate(outputAnalysisWeightPropertyOperatorMinMax_ ) !![ - + !!] allocate(weightPropertyOperators_ ) allocate(weightPropertyOperators_%next ) @@ -288,12 +282,12 @@ function blackHoleVelocityDispersionRelationConstructorInternal(systematicErrorP weightPropertyOperators_%next %operator_ => outputAnalysisWeightPropertyOperatorLog10_ allocate(outputAnalysisWeightPropertyOperator_ ) !![ - + !!] ! Build anti-log10() property operator. allocate(outputAnalysisPropertyUnoperator_ ) !![ - + !!] ! Create a velocity dispersion property extractor. allocate(nodePropertyExtractorVelocityDispersion_ ) @@ -301,16 +295,16 @@ function blackHoleVelocityDispersionRelationConstructorInternal(systematicErrorP includeRadii =.false. integrationFailureIsFatal =.false. !![ - + !!] allocate(nodePropertyExtractor_ ) !![ - + !!] ! Create an ISM metallicity weight property extractor. allocate(outputAnalysisWeightPropertyExtractor_ ) !![ - + !!] ! Build the object. self%outputAnalysisMeanFunction1D=outputAnalysisMeanFunction1D( & @@ -363,6 +357,7 @@ function blackHoleVelocityDispersionRelationConstructorInternal(systematicErrorP + @@ -380,7 +375,6 @@ subroutine blackHoleVelocityDispersionRelationDestructor(self) type(outputAnalysisBlackHoleVelocityDispersionRelation), intent(inout) :: self !![ - !!] diff --git a/source/output.analyses.concentration_distribution.CDM.COCO.F90 b/source/output.analyses.concentration_distribution.CDM.COCO.F90 index c8d92fdc97..d4288391e3 100644 --- a/source/output.analyses.concentration_distribution.CDM.COCO.F90 +++ b/source/output.analyses.concentration_distribution.CDM.COCO.F90 @@ -61,7 +61,6 @@ function concentrationDistributionCDMCOCOConstructorParameters(parameters) resul class (outputTimesClass ), pointer :: outputTimes_ class (nbodyHaloMassErrorClass ), pointer :: nbodyHaloMassError_ class (virialDensityContrastClass ), pointer :: virialDensityContrast_ - class (darkMatterProfileDMOClass ), pointer :: darkMatterProfileDMO_ integer :: distributionNumber double precision :: rootVarianceFractionalMinimum @@ -79,12 +78,11 @@ function concentrationDistributionCDMCOCOConstructorParameters(parameters) resul - !!] - self=outputAnalysisConcentrationDistributionCDMCOCO(distributionNumber,rootVarianceFractionalMinimum,cosmologyParameters_,cosmologyFunctions_,nbodyHaloMassError_,outputTimes_,darkMatterProfileDMO_,virialDensityContrast_) + self=outputAnalysisConcentrationDistributionCDMCOCO(distributionNumber,rootVarianceFractionalMinimum,cosmologyParameters_,cosmologyFunctions_,nbodyHaloMassError_,outputTimes_,virialDensityContrast_) !![ @@ -92,19 +90,17 @@ function concentrationDistributionCDMCOCOConstructorParameters(parameters) resul - !!] return end function concentrationDistributionCDMCOCOConstructorParameters - function concentrationDistributionCDMCOCOConstructorInternal(distributionNumber,rootVarianceFractionalMinimum,cosmologyParameters_,cosmologyFunctions_,nbodyHaloMassError_,outputTimes_,darkMatterProfileDMO_,virialDensityContrast_) result(self) + function concentrationDistributionCDMCOCOConstructorInternal(distributionNumber,rootVarianceFractionalMinimum,cosmologyParameters_,cosmologyFunctions_,nbodyHaloMassError_,outputTimes_,virialDensityContrast_) result(self) !!{ Internal constructor for the ``concentrationDistributionCDMCOCO'' output analysis class. !!} use :: Error , only : Error_Report use :: Cosmology_Functions , only : cosmologyFunctionsClass use :: Cosmology_Parameters , only : cosmologyParametersClass - use :: Dark_Matter_Profiles_DMO , only : darkMatterProfileDMOClass use :: Input_Paths , only : inputPath , pathTypeDataStatic use :: Output_Times , only : outputTimesClass use :: Statistics_NBody_Halo_Mass_Errors, only : nbodyHaloMassErrorClass @@ -115,7 +111,6 @@ function concentrationDistributionCDMCOCOConstructorInternal(distributionNumber, class (cosmologyParametersClass ), target , intent(in ) :: cosmologyParameters_ class (cosmologyFunctionsClass ), target , intent(inout) :: cosmologyFunctions_ class (virialDensityContrastClass ), target , intent(in ) :: virialDensityContrast_ - class (darkMatterProfileDMOClass ), target , intent(in ) :: darkMatterProfileDMO_ class (outputTimesClass ), target , intent(inout) :: outputTimes_ class (nbodyHaloMassErrorClass ), target , intent(in ) :: nbodyHaloMassError_ integer , intent(in ) :: distributionNumber @@ -151,7 +146,6 @@ function concentrationDistributionCDMCOCOConstructorInternal(distributionNumber, & cosmologyParameters_ , & & cosmologyFunctions_ , & & nbodyHaloMassError_ , & - & darkMatterProfileDMO_ , & & virialDensityContrast_ , & & virialDensityContrastDefinition_ , & & outputTimes_ & diff --git a/source/output.analyses.concentration_distribution.F90 b/source/output.analyses.concentration_distribution.F90 index 4aea35718b..80af2ac900 100644 --- a/source/output.analyses.concentration_distribution.F90 +++ b/source/output.analyses.concentration_distribution.F90 @@ -34,7 +34,6 @@ A concentration distribution function output analysis class. null() class (cosmologyFunctionsClass ), pointer :: cosmologyFunctions_ => null() - class (darkMatterProfileDMOClass ), pointer :: darkMatterProfileDMO_ => null() class (nbodyHaloMassErrorClass ), pointer :: nbodyHaloMassError_ => null() class (virialDensityContrastClass), pointer :: virialDensityContrastDefinition_ => null(), virialDensityContrast_ => null() double precision :: rootVarianceFractionalMinimum , redshift , & @@ -71,7 +70,6 @@ function concentrationDistributionConstructorParameters(parameters) result (self type (inputParameters ), intent(inout) :: parameters class (cosmologyParametersClass ), pointer :: cosmologyParameters_ class (cosmologyFunctionsClass ), pointer :: cosmologyFunctions_ - class (darkMatterProfileDMOClass ), pointer :: darkMatterProfileDMO_ class (outputTimesClass ), pointer :: outputTimes_ class (nbodyHaloMassErrorClass ), pointer :: nbodyHaloMassError_ class (virialDensityContrastClass ), pointer :: virialDensityContrastDefinition_, virialDensityContrast_ @@ -91,7 +89,6 @@ function concentrationDistributionConstructorParameters(parameters) result (self - @@ -119,7 +116,7 @@ The name of the file from which to read concentration distribution A label for this analysis. !!] - self=outputAnalysisConcentrationDistribution(char(fileName),label,comment,rootVarianceFractionalMinimum,cosmologyParameters_,cosmologyFunctions_,nbodyHaloMassError_,darkMatterProfileDMO_,virialDensityContrast_,virialDensityContrastDefinition_,outputTimes_) + self=outputAnalysisConcentrationDistribution(char(fileName),label,comment,rootVarianceFractionalMinimum,cosmologyParameters_,cosmologyFunctions_,nbodyHaloMassError_,virialDensityContrast_,virialDensityContrastDefinition_,outputTimes_) else !![ @@ -231,7 +228,6 @@ The target function covariance for likelihood calculations.The target function covariance for likelihood calculations. - !!] return end function concentrationDistributionConstructorParameters - function concentrationDistributionConstructorFile(fileName,label,comment,rootVarianceFractionalMinimum,cosmologyParameters_,cosmologyFunctions_,nbodyHaloMassError_,darkMatterProfileDMO_,virialDensityContrast_,virialDensityContrastDefinition_,outputTimes_) result(self) + function concentrationDistributionConstructorFile(fileName,label,comment,rootVarianceFractionalMinimum,cosmologyParameters_,cosmologyFunctions_,nbodyHaloMassError_,virialDensityContrast_,virialDensityContrastDefinition_,outputTimes_) result(self) !!{ Constructor for the ``progenitorMassFunction'' output analysis class which reads all required properties from file. !!} @@ -275,7 +270,6 @@ function concentrationDistributionConstructorFile(fileName,label,comment,rootVar class (outputTimesClass ), intent(inout) :: outputTimes_ class (cosmologyParametersClass ), intent(in ) :: cosmologyParameters_ class (cosmologyFunctionsClass ), intent(inout) :: cosmologyFunctions_ - class (darkMatterProfileDMOClass ), intent(in ) :: darkMatterProfileDMO_ class (nbodyHaloMassErrorClass ), intent(in ) :: nbodyHaloMassError_ class (virialDensityContrastClass ), intent(in ) :: virialDensityContrastDefinition_, virialDensityContrast_ double precision , allocatable , dimension(: ) :: functionValueTarget , concentration @@ -322,14 +316,14 @@ function concentrationDistributionConstructorFile(fileName,label,comment,rootVar ! Convert redshift to time. time=cosmologyFunctions_%cosmicTime(cosmologyFunctions_%expansionFactorFromRedshift(redshift)) ! Build the object. - self=outputAnalysisConcentrationDistribution(label,comment,time,massMinimum,massMaximum,concentration(1),concentration(size(concentration)),size(concentration,kind=c_size_t),timeRecent,massParticle,rootVarianceFractionalMinimum,cosmologyParameters_,cosmologyFunctions_,nbodyHaloMassError_,darkMatterProfileDMO_,virialDensityContrast_,virialDensityContrastDefinition_,outputTimes_,targetLabel,functionValueTarget,functionCovarianceTarget) + self=outputAnalysisConcentrationDistribution(label,comment,time,massMinimum,massMaximum,concentration(1),concentration(size(concentration)),size(concentration,kind=c_size_t),timeRecent,massParticle,rootVarianceFractionalMinimum,cosmologyParameters_,cosmologyFunctions_,nbodyHaloMassError_,virialDensityContrast_,virialDensityContrastDefinition_,outputTimes_,targetLabel,functionValueTarget,functionCovarianceTarget) !![ !!] return end function concentrationDistributionConstructorFile - function concentrationDistributionConstructorInternal(label,comment,time,massMinimum,massMaximum,concentrationMinimum,concentrationMaximum,countConcentrations,timeRecent,massParticle,rootVarianceFractionalMinimum,cosmologyParameters_,cosmologyFunctions_,nbodyHaloMassError_,darkMatterProfileDMO_,virialDensityContrast_,virialDensityContrastDefinition_,outputTimes_,targetLabel,functionValueTarget,functionCovarianceTarget) result(self) + function concentrationDistributionConstructorInternal(label,comment,time,massMinimum,massMaximum,concentrationMinimum,concentrationMaximum,countConcentrations,timeRecent,massParticle,rootVarianceFractionalMinimum,cosmologyParameters_,cosmologyFunctions_,nbodyHaloMassError_,virialDensityContrast_,virialDensityContrastDefinition_,outputTimes_,targetLabel,functionValueTarget,functionCovarianceTarget) result(self) !!{ Internal constructor for the ``concentrationDistribution'' output analysis class. !!} @@ -353,7 +347,6 @@ function concentrationDistributionConstructorInternal(label,comment,time,massMin type (varying_string ) , intent(in ) :: label , comment class (cosmologyParametersClass ), target , intent(in ) :: cosmologyParameters_ class (cosmologyFunctionsClass ), target , intent(in ) :: cosmologyFunctions_ - class (darkMatterProfileDMOClass ), target , intent(in ) :: darkMatterProfileDMO_ class (outputTimesClass ), target , intent(inout) :: outputTimes_ class (nbodyHaloMassErrorClass ), target , intent(in ) :: nbodyHaloMassError_ class (virialDensityContrastClass ), target , intent(in ) :: virialDensityContrastDefinition_ , virialDensityContrast_ @@ -393,7 +386,7 @@ function concentrationDistributionConstructorInternal(label,comment,time,massMin double precision , parameter :: covarianceBinomialMassHaloMinimum = +3.000d+11, covarianceBinomialMassHaloMaximum=1.0d15 integer (c_size_t ) :: iOutput , bufferCount !![ - + !!] ! Set parameters needed for descriptor. @@ -479,7 +472,6 @@ function concentrationDistributionConstructorInternal(label,comment,time,massMin & .false. , & & cosmologyParameters_ , & & cosmologyFunctions_ , & - & darkMatterProfileDMO_ , & & virialDensityContrast_ , & & virialDensityContrastDefinition_ & & ) @@ -495,7 +487,6 @@ function concentrationDistributionConstructorInternal(label,comment,time,massMin & .false. , & & cosmologyFunctions_ , & & cosmologyParameters_ , & - & darkMatterProfileDMO_ , & & virialDensityContrast_ , & & virialDensityContrastDefinition_ & & ) @@ -681,7 +672,6 @@ subroutine concentrationDistributionDestructor(self) - !!] diff --git a/source/output.analyses.concentration_vs_mass_relation.CDM.Ludlow_2016.F90 b/source/output.analyses.concentration_vs_mass_relation.CDM.Ludlow_2016.F90 index c839fbe7ba..97f7af5beb 100644 --- a/source/output.analyses.concentration_vs_mass_relation.CDM.Ludlow_2016.F90 +++ b/source/output.analyses.concentration_vs_mass_relation.CDM.Ludlow_2016.F90 @@ -36,7 +36,6 @@ class(cosmologyParametersClass ), pointer :: cosmologyParameters_ => null() class(cosmologyFunctionsClass ), pointer :: cosmologyFunctions_ => null() class(virialDensityContrastClass), pointer :: virialDensityContrast_ => null() - class(darkMatterProfileDMOClass ), pointer :: darkMatterProfileDMO_ => null() class(nbodyHaloMassErrorClass ), pointer :: nbodyHaloMassError_ => null() contains final :: concentrationVsHaloMassCDMLudlow2016Destructor @@ -65,7 +64,6 @@ function concentrationVsHaloMassCDMLudlow2016ConstructorParameters(parameters) r class(cosmologyParametersClass ), pointer :: cosmologyParameters_ class(cosmologyFunctionsClass ), pointer :: cosmologyFunctions_ class(virialDensityContrastClass ), pointer :: virialDensityContrast_ - class(darkMatterProfileDMOClass ), pointer :: darkMatterProfileDMO_ class(outputTimesClass ), pointer :: outputTimes_ class(nbodyHaloMassErrorClass ), pointer :: nbodyHaloMassError_ @@ -75,9 +73,8 @@ function concentrationVsHaloMassCDMLudlow2016ConstructorParameters(parameters) r - !!] - self=outputAnalysisConcentrationVsHaloMassCDMLudlow2016(cosmologyParameters_,cosmologyFunctions_,virialDensityContrast_,darkMatterProfileDMO_,nbodyHaloMassError_,outputTimes_) + self=outputAnalysisConcentrationVsHaloMassCDMLudlow2016(cosmologyParameters_,cosmologyFunctions_,virialDensityContrast_,nbodyHaloMassError_,outputTimes_) !![ !!] @@ -90,13 +87,12 @@ function concentrationVsHaloMassCDMLudlow2016ConstructorParameters(parameters) r - !!] return end function concentrationVsHaloMassCDMLudlow2016ConstructorParameters - function concentrationVsHaloMassCDMLudlow2016ConstructorInternal(cosmologyParameters_,cosmologyFunctions_,virialDensityContrast_,darkMatterProfileDMO_,nbodyHaloMassError_,outputTimes_) result (self) + function concentrationVsHaloMassCDMLudlow2016ConstructorInternal(cosmologyParameters_,cosmologyFunctions_,virialDensityContrast_,nbodyHaloMassError_,outputTimes_) result (self) !!{ Constructor for the ``concentrationVsHaloMassCDMLudlow2016'' output analysis class for internal use. !!} @@ -123,7 +119,6 @@ function concentrationVsHaloMassCDMLudlow2016ConstructorInternal(cosmologyParame class (cosmologyParametersClass ), target , intent(in ) :: cosmologyParameters_ class (cosmologyFunctionsClass ), target , intent(in ) :: cosmologyFunctions_ class (virialDensityContrastClass ), target , intent(in ) :: virialDensityContrast_ - class (darkMatterProfileDMOClass ), target , intent(in ) :: darkMatterProfileDMO_ class (nbodyHaloMassErrorClass ), target , intent(in ) :: nbodyHaloMassError_ class (outputTimesClass ), target , intent(inout) :: outputTimes_ integer (c_size_t ), parameter :: massHaloCount =26 @@ -146,7 +141,7 @@ function concentrationVsHaloMassCDMLudlow2016ConstructorInternal(cosmologyParame integer (c_size_t ) :: iOutput type (hdf5Object ) :: dataFile !![ - + !!] ! Construct mass bins matched to those used by Ludlow et al. (2016). @@ -176,28 +171,28 @@ function concentrationVsHaloMassCDMLudlow2016ConstructorInternal(cosmologyParame galacticFilterAll_ = galacticFilterAll ( filters_) ! Build N-body mass error distribution operator. allocate(outputAnalysisDistributionOperator_ ) - outputAnalysisDistributionOperator_ = outputAnalysisDistributionOperatorRndmErrNbodyMass(nbodyHaloMassError_ ) + outputAnalysisDistributionOperator_ = outputAnalysisDistributionOperatorRndmErrNbodyMass(nbodyHaloMassError_ ) ! Build identity weight operator. allocate(outputAnalysisWeightOperator_ ) - outputAnalysisWeightOperator_ = outputAnalysisWeightOperatorIdentity ( ) + outputAnalysisWeightOperator_ = outputAnalysisWeightOperatorIdentity ( ) ! Build log10() property operator. allocate(outputAnalysisPropertyOperator_ ) - outputAnalysisPropertyOperator_ = outputAnalysisPropertyOperatorLog10 ( ) + outputAnalysisPropertyOperator_ = outputAnalysisPropertyOperatorLog10 ( ) ! Build a log10 weight property operators. allocate(outputAnalysisWeightPropertyOperator_ ) - outputAnalysisWeightPropertyOperator_ = outputAnalysisPropertyOperatorLog10 ( ) + outputAnalysisWeightPropertyOperator_ = outputAnalysisPropertyOperatorLog10 ( ) ! Build anti-log10() property operator. allocate(outputAnalysisPropertyUnoperator_ ) - outputAnalysisPropertyUnoperator_ = outputAnalysisPropertyOperatorAntiLog10 ( ) + outputAnalysisPropertyUnoperator_ = outputAnalysisPropertyOperatorAntiLog10 ( ) ! Create a virial density contrast object matched to the definition used by Ludlow et al. (2016). allocate(virialDensityContrastDefinition_ ) - virialDensityContrastDefinition_ = virialDensityContrastFixed (200.0d0,fixedDensityTypeCritical,2.0d0,cosmologyParameters_,cosmologyFunctions_ ) + virialDensityContrastDefinition_ = virialDensityContrastFixed (200.0d0,fixedDensityTypeCritical,2.0d0,cosmologyParameters_,cosmologyFunctions_ ) ! Create a concentration weight property extractor. allocate(outputAnalysisWeightPropertyExtractor_ ) - outputAnalysisWeightPropertyExtractor_ = nodePropertyExtractorConcentration (.false.,cosmologyParameters_,cosmologyFunctions_,darkMatterProfileDMO_,virialDensityContrast_,virialDensityContrastDefinition_) + outputAnalysisWeightPropertyExtractor_ = nodePropertyExtractorConcentration (.false.,cosmologyParameters_,cosmologyFunctions_,virialDensityContrast_,virialDensityContrastDefinition_) ! Create a halo mass property extractor. allocate(nodePropertyExtractor_ ) - nodePropertyExtractor_ = nodePropertyExtractorMassHalo (.false.,cosmologyFunctions_,cosmologyParameters_,darkMatterProfileDMO_,virialDensityContrast_,virialDensityContrastDefinition_) + nodePropertyExtractor_ = nodePropertyExtractorMassHalo (.false.,cosmologyFunctions_,cosmologyParameters_,virialDensityContrast_,virialDensityContrastDefinition_) ! Build the object. self%outputAnalysisMeanFunction1D=outputAnalysisMeanFunction1D( & & var_str('concentrationHaloMassRelationCDMLudlow2016'), & @@ -255,7 +250,6 @@ subroutine concentrationVsHaloMassCDMLudlow2016Destructor(self) - !!] return diff --git a/source/output.analyses.correlation_function.F90 b/source/output.analyses.correlation_function.F90 index 70cff77c59..d0c8191901 100644 --- a/source/output.analyses.correlation_function.F90 +++ b/source/output.analyses.correlation_function.F90 @@ -21,19 +21,20 @@ Contains a module which implements a generic two-point correlation function output analysis class. !!} - use :: Cosmology_Functions , only : cosmologyFunctions , cosmologyFunctionsClass - use :: Dark_Matter_Halo_Biases , only : darkMatterHaloBias , darkMatterHaloBiasClass - use :: Dark_Matter_Profiles_DMO , only : darkMatterProfileDMO , darkMatterProfileDMOClass - use :: Galactic_Filters , only : galacticFilter , galacticFilterClass - use :: Geometry_Surveys , only : surveyGeometry , surveyGeometryClass - use :: Halo_Model_Power_Spectrum_Modifiers , only : haloModelPowerSpectrumModifier , haloModelPowerSpectrumModifierClass + use :: Cosmology_Functions , only : cosmologyFunctionsClass + use :: Dark_Matter_Halo_Biases , only : darkMatterHaloBiasClass + use :: Dark_Matter_Halo_Scales , only : darkMatterHaloScaleClass + use :: Dark_Matter_Profiles_DMO , only : darkMatterProfileDMOClass + use :: Galactic_Filters , only : galacticFilterClass + use :: Geometry_Surveys , only : surveyGeometryClass + use :: Halo_Model_Power_Spectrum_Modifiers , only : haloModelPowerSpectrumModifierClass use , intrinsic :: ISO_C_Binding , only : c_size_t - use :: Node_Property_Extractors , only : nodePropertyExtractor , nodePropertyExtractorClass + use :: Node_Property_Extractors , only : nodePropertyExtractorClass !$ use :: OMP_Lib , only : omp_lock_kind - use :: Output_Analysis_Distribution_Operators, only : outputAnalysisDistributionOperator, outputAnalysisDistributionOperatorClass - use :: Output_Analysis_Property_Operators , only : outputAnalysisPropertyOperator , outputAnalysisPropertyOperatorClass - use :: Output_Times , only : outputTimes , outputTimesClass - use :: Power_Spectra , only : powerSpectrum , powerSpectrumClass + use :: Output_Analysis_Distribution_Operators, only : outputAnalysisDistributionOperatorClass + use :: Output_Analysis_Property_Operators , only : outputAnalysisPropertyOperatorClass + use :: Output_Times , only : outputTimesClass + use :: Power_Spectra , only : powerSpectrumClass !![ @@ -79,6 +80,7 @@ A generic two-point correlation function output analysis class. class (nodePropertyExtractorClass ), pointer :: massPropertyExtractor_ => null() class (darkMatterProfileDMOClass ), pointer :: darkMatterProfileDMO_ => null() class (darkMatterHaloBiasClass ), pointer :: darkMatterHaloBias_ => null() + class (darkMatterHaloScaleClass ), pointer :: darkMatterHaloScale_ => null() class (haloModelPowerSpectrumModifierClass ), pointer :: haloModelPowerSpectrumModifier_ => null() class (powerSpectrumClass ), pointer :: powerSpectrum_ => null() double precision , allocatable, dimension(: ) :: separations , wavenumber , & @@ -150,6 +152,7 @@ function correlationFunctionConstructorParameters(parameters) result(self) class (nodePropertyExtractorClass ), pointer :: massPropertyExtractor_ class (darkMatterProfileDMOClass ), pointer :: darkMatterProfileDMO_ class (darkMatterHaloBiasClass ), pointer :: darkMatterHaloBias_ + class (darkMatterHaloScaleClass ), pointer :: darkMatterHaloScale_ class (haloModelPowerSpectrumModifierClass ), pointer :: haloModelPowerSpectrumModifier_ class (powerSpectrumClass ), pointer :: powerSpectrum_ double precision , allocatable , dimension(: ) :: separations , massMinima , & @@ -287,6 +290,7 @@ The target function covariance for likelihood calculations. + @@ -316,6 +320,7 @@ The target function covariance for likelihood calculations.The target function covariance for likelihood calculations. + @@ -346,7 +352,7 @@ The target function covariance for likelihood calculations. + !!] ! Assign 1D versions of target for use in descriptor. @@ -507,6 +515,7 @@ subroutine correlationFunctionDestructor(self) !![ + @@ -612,15 +621,17 @@ subroutine correlationFunctionAccumulateHalo(self,indexOutput,node) !!} use :: Galacticus_Nodes , only : nodeComponentBasic , treeNode use :: Halo_Model_Power_Spectrum_Modifiers, only : haloModelTermOneHalo , haloModelTermTwoHalo + use :: Mass_Distributions , only : massDistributionClass use :: Math_Distributions_Poisson_Binomial, only : Poisson_Binomial_Distribution_Mean, Poisson_Binomial_Distribution_Mean_Pairs , Poisson_Binomial_Distribution_Mean_Pairs_Jacobian use :: Output_Analyses_Options , only : outputAnalysisPropertyTypeLinear , enumerationOutputAnalysisPropertyTypeType use :: Vectors , only : Vector_Outer_Product - use :: Linear_Algebra , only : assignment(=) , matrix , operator(*) + use :: Linear_Algebra , only : assignment(=) , matrix , operator(*) implicit none class (outputAnalysisCorrelationFunction ), intent(inout) :: self integer (c_size_t ), intent(in ) :: indexOutput type (treeNode ), intent(inout) :: node class (nodeComponentBasic ), pointer :: basic , basicRoot + class (massDistributionClass ), pointer :: massDistribution_ double precision , dimension(self%wavenumberCount,self%massCount) :: oneHaloTerm , twoHaloTerm double precision , dimension( self%massCount) :: galaxyDensity logical , dimension( self%massCount) :: oneHaloTermActive , twoHaloTermActive @@ -630,7 +641,8 @@ subroutine correlationFunctionAccumulateHalo(self,indexOutput,node) & fourierProfile , wavenumber double precision :: countSatellitePairsMean , countSatellitesMean , & & haloWeightOutput , expansionFactor , & - & biasHalo , massHalo + & biasHalo , massHalo , & + & radiusVirial integer (c_size_t ) :: i , j , & & indexOneHalo , indexTwoHalo , & & indexDensity @@ -642,7 +654,9 @@ subroutine correlationFunctionAccumulateHalo(self,indexOutput,node) ! Return immediately if no nodes have been accumulated. if (all(self%probabilityCentral == 0.0d0) .and. self%countSatellites == 0) return ! Construct the Fourier profile of the host halo. We include the weighting by the square-root of the power spectrum here. - expansionFactor=self%cosmologyFunctions_%expansionFactor(self%outputTimes_%time(indexOutput)) + massDistribution_ => self%darkMatterProfileDMO_%get ( node ) + radiusVirial = self%darkMatterHaloScale_ %radiusVirial ( node ) + expansionFactor = self%cosmologyFunctions_ %expansionFactor(self%outputTimes_%time(indexOutput)) allocate(wavenumber (self%wavenumberCount)) allocate(fourierProfile(self%wavenumberCount)) do i=1,self%wavenumberCount @@ -650,14 +664,17 @@ subroutine correlationFunctionAccumulateHalo(self,indexOutput,node) scaleType =outputAnalysisPropertyTypeLinear wavenumber (i)=+1.0d0 & & /self%separationPropertyOperator_%operate(1.0d0/self%waveNumber(i),node,scaleType,indexOutput) - fourierProfile(i)=+ self%darkMatterProfileDMO_%kSpace( & - & node , & - & wavenumber(i)/expansionFactor & - & ) & - & *sqrt( & - & +self%powerSpectrum_%power (self%wavenumber(i ),self%outputTimes_%time(indexOutput)) & + fourierProfile(i)=+ massDistribution_%fourierTransform( & + & radiusVirial , & + & wavenumber (i)/expansionFactor & + & ) & + & *sqrt( & + & +self%powerSpectrum_%power (self%wavenumber (i),self%outputTimes_%time(indexOutput)) & & ) end do + !![ + + !!] ! Get the mass of this halo. basic => node %basic() massHalo = basic%mass () diff --git a/source/output.analyses.correlation_function.Hearin2014_SDSS.F90 b/source/output.analyses.correlation_function.Hearin2014_SDSS.F90 index 3488171d1e..f4f6dad580 100644 --- a/source/output.analyses.correlation_function.Hearin2014_SDSS.F90 +++ b/source/output.analyses.correlation_function.Hearin2014_SDSS.F90 @@ -31,11 +31,8 @@ A correlation function output analysis class for the \cite{hearin_d A correlation function function output analysis class for the \cite{hearin_dark_2013} analysis. !!} private - class (galacticStructureClass), pointer :: galacticStructure_ => null() double precision , allocatable, dimension(:) :: randomErrorPolynomialCoefficient , systematicErrorPolynomialCoefficient double precision :: randomErrorMinimum , randomErrorMaximum - contains - final :: correlationFunctionHearin2013SDSSDestructor end type outputAnalysisCorrelationFunctionHearin2013SDSS interface outputAnalysisCorrelationFunctionHearin2013SDSS @@ -53,8 +50,7 @@ function correlationFunctionHearin2013SDSSConstructorParameters(parameters) resu Constructor for the ``correlationFunctionHearin2013SDSS'' output analysis class which takes a parameter set as input. !!} use, intrinsic :: ISO_C_Binding , only : c_size_t - use :: Input_Parameters , only : inputParameter , inputParameters - use :: Galactic_Structure, only : galacticStructureClass + use :: Input_Parameters , only : inputParameter, inputParameters implicit none type (outputAnalysisCorrelationFunctionHearin2013SDSS) :: self type (inputParameters ), intent(inout) :: parameters @@ -62,9 +58,9 @@ function correlationFunctionHearin2013SDSSConstructorParameters(parameters) resu class (outputTimesClass ), pointer :: outputTimes_ class (darkMatterProfileDMOClass ), pointer :: darkMatterProfileDMO_ class (darkMatterHaloBiasClass ), pointer :: darkMatterHaloBias_ + class (darkMatterHaloScaleClass ), pointer :: darkMatterHaloScale_ class (haloModelPowerSpectrumModifierClass ), pointer :: haloModelPowerSpectrumModifier_ class (powerSpectrumClass ), pointer :: powerSpectrum_ - class (galacticStructureClass ), pointer :: galacticStructure_ double precision , allocatable , dimension(:) :: randomErrorPolynomialCoefficient, systematicErrorPolynomialCoefficient double precision :: massHaloMinimum , massHaloMaximum , & & randomErrorMinimum , randomErrorMaximum @@ -132,25 +128,25 @@ The maximum halo mass to consider when constructing the mass functi + - !!] - self=outputAnalysisCorrelationFunctionHearin2013SDSS(massHaloBinsPerDecade,massHaloMinimum, massHaloMaximum,randomErrorMinimum,randomErrorMaximum,randomErrorPolynomialCoefficient,systematicErrorPolynomialCoefficient,cosmologyFunctions_,outputTimes_,darkMatterProfileDMO_,darkMatterHaloBias_,haloModelPowerSpectrumModifier_,powerSpectrum_,galacticStructure_) + self=outputAnalysisCorrelationFunctionHearin2013SDSS(massHaloBinsPerDecade,massHaloMinimum, massHaloMaximum,randomErrorMinimum,randomErrorMaximum,randomErrorPolynomialCoefficient,systematicErrorPolynomialCoefficient,cosmologyFunctions_,outputTimes_,darkMatterProfileDMO_,darkMatterHaloBias_,darkMatterHaloScale_,haloModelPowerSpectrumModifier_,powerSpectrum_) !![ + - !!] return end function correlationFunctionHearin2013SDSSConstructorParameters - function correlationFunctionHearin2013SDSSConstructorInternal(massHaloBinsPerDecade,massHaloMinimum,massHaloMaximum,randomErrorMinimum,randomErrorMaximum,randomErrorPolynomialCoefficient,systematicErrorPolynomialCoefficient,cosmologyFunctions_,outputTimes_,darkMatterProfileDMO_,darkMatterHaloBias_,haloModelPowerSpectrumModifier_,powerSpectrum_,galacticStructure_) result (self) + function correlationFunctionHearin2013SDSSConstructorInternal(massHaloBinsPerDecade,massHaloMinimum,massHaloMaximum,randomErrorMinimum,randomErrorMaximum,randomErrorPolynomialCoefficient,systematicErrorPolynomialCoefficient,cosmologyFunctions_,outputTimes_,darkMatterProfileDMO_,darkMatterHaloBias_,darkMatterHaloScale_,haloModelPowerSpectrumModifier_,powerSpectrum_) result (self) !!{ Constructor for the ``correlationFunctionHearin2013SDSS'' output analysis class for internal use. !!} @@ -174,9 +170,9 @@ function correlationFunctionHearin2013SDSSConstructorInternal(massHaloBinsPerDec class (outputTimesClass ), intent(in ), target :: outputTimes_ class (darkMatterProfileDMOClass ), intent(in ), target :: darkMatterProfileDMO_ class (darkMatterHaloBiasClass ), intent(in ), target :: darkMatterHaloBias_ + class (darkMatterHaloScaleClass ), intent(in ), target :: darkMatterHaloScale_ class (haloModelPowerSpectrumModifierClass ), intent(in ), target :: haloModelPowerSpectrumModifier_ class (powerSpectrumClass ), intent(in ), target :: powerSpectrum_ - class (galacticStructureClass ), intent(in ), target :: galacticStructure_ type (cosmologyParametersSimple ), pointer :: cosmologyParametersData_ type (cosmologyFunctionsMatterLambda ), pointer :: cosmologyFunctionsData_ type (galacticFilterStellarMass ), pointer :: galacticFilter_ @@ -194,7 +190,7 @@ function correlationFunctionHearin2013SDSSConstructorInternal(massHaloBinsPerDec double precision , parameter :: wavenumberMinimum = 1.0d-3, wavenumberMaximum=1.0d+4 logical , parameter :: halfIntegral =.false. !![ - + !!] ! Build a filter which selects galaxies above some minimum stellar mass. @@ -233,7 +229,7 @@ function correlationFunctionHearin2013SDSSConstructorInternal(massHaloBinsPerDec ! Stellar mass property extractor. allocate(massPropertyExtractor_ ) !![ - + !!] ! Sequence of property operators to correct for cosmological model, convert to logarithm, and apply systematic errors. allocate(massPropertyOperatorCsmlgyLmnstyDstnc_) @@ -297,6 +293,7 @@ function correlationFunctionHearin2013SDSSConstructorInternal(massHaloBinsPerDec & outputTimes_ , & & darkMatterProfileDMO_ , & & darkMatterHaloBias_ , & + & darkMatterHaloScale_ , & & haloModelPowerSpectrumModifier_ , & & powerSpectrum_ , & & massDistributionOperator_ , & @@ -321,16 +318,3 @@ function correlationFunctionHearin2013SDSSConstructorInternal(massHaloBinsPerDec nullify(propertyOperators_) return end function correlationFunctionHearin2013SDSSConstructorInternal - - subroutine correlationFunctionHearin2013SDSSDestructor(self) - !!{ - Destructor for the ``correlationFunctionHearin2013SDSS'' output analysis class. - !!} - implicit none - type(outputAnalysisCorrelationFunctionHearin2013SDSS), intent(inout) :: self - - !![ - - !!] - return - end subroutine correlationFunctionHearin2013SDSSDestructor diff --git a/source/output.analyses.galaxy_sizes_SDSS.F90 b/source/output.analyses.galaxy_sizes_SDSS.F90 index b6cd420b2d..bb1844f221 100644 --- a/source/output.analyses.galaxy_sizes_SDSS.F90 +++ b/source/output.analyses.galaxy_sizes_SDSS.F90 @@ -46,7 +46,6 @@ sizes and masses are then used to construct a mass-dependent radius function by double precision :: massStellarRatio , sizeSourceLensing class (cosmologyFunctionsClass ), pointer :: cosmologyFunctions_ => null() class (gravitationalLensingClass), pointer :: gravitationalLensing_ => null() - class (galacticStructureClass ), pointer :: galacticStructure_ => null() contains final :: galaxySizesSDSSDestructor end type outputAnalysisGalaxySizesSDSS @@ -65,15 +64,13 @@ function galaxySizesSDSSConstructorParameters(parameters) result (self) !!{ Constructor for the ``galaxySizesSDSS'' output analysis class which takes a parameter set as input. !!} - use :: Input_Parameters , only : inputParameter , inputParameters - use :: Galactic_Structure, only : galacticStructureClass + use :: Input_Parameters, only : inputParameter, inputParameters implicit none type (outputAnalysisGalaxySizesSDSS) :: self type (inputParameters ), intent(inout) :: parameters class (cosmologyFunctionsClass ), pointer :: cosmologyFunctions_ class (outputTimesClass ), pointer :: outputTimes_ class (gravitationalLensingClass ), pointer :: gravitationalLensing_ - class (galacticStructureClass ), pointer :: galacticStructure_ double precision :: massStellarRatio , sizeSourceLensing integer :: distributionNumber @@ -99,20 +96,18 @@ function galaxySizesSDSSConstructorParameters(parameters) result (self) - !!] - self=outputAnalysisGalaxySizesSDSS(distributionNumber,massStellarRatio,sizeSourceLensing,cosmologyFunctions_,outputTimes_,gravitationalLensing_,galacticStructure_) + self=outputAnalysisGalaxySizesSDSS(distributionNumber,massStellarRatio,sizeSourceLensing,cosmologyFunctions_,outputTimes_,gravitationalLensing_) !![ - !!] return end function galaxySizesSDSSConstructorParameters - function galaxySizesSDSSConstructorInternal(distributionNumber,massStellarRatio,sizeSourceLensing,cosmologyFunctions_,outputTimes_,gravitationalLensing_,galacticStructure_) result(self) + function galaxySizesSDSSConstructorInternal(distributionNumber,massStellarRatio,sizeSourceLensing,cosmologyFunctions_,outputTimes_,gravitationalLensing_) result(self) !!{ Internal constructor for the ``galaxySizesSDSS'' output analysis class. !!} @@ -146,7 +141,6 @@ function galaxySizesSDSSConstructorInternal(distributionNumber,massStellarRatio, class (cosmologyFunctionsClass ), target , intent(in ) :: cosmologyFunctions_ class (outputTimesClass ), target , intent(inout) :: outputTimes_ class (gravitationalLensingClass ), target , intent(in ) :: gravitationalLensing_ - class (galacticStructureClass ), target , intent(in ) :: galacticStructure_ type (cosmologyParametersSimple ), pointer :: cosmologyParametersData type (cosmologyFunctionsMatterLambda ), pointer :: cosmologyFunctionsData type (nodePropertyExtractorRadiusHalfMassStellar ), pointer :: nodePropertyExtractor_ @@ -191,7 +185,7 @@ function galaxySizesSDSSConstructorInternal(distributionNumber,massStellarRatio, type (varying_string ) :: description logical :: isLateType !![ - + !!] ! Validate input. @@ -257,12 +251,12 @@ function galaxySizesSDSSConstructorInternal(distributionNumber,massStellarRatio, ! Create a half-mass radius property extractor. allocate(nodePropertyExtractor_ ) !![ - + !!] ! Create a stellar mass property extractor. allocate(outputAnalysisWeightPropertyExtractor_ ) !![ - + !!] ! Create multiply, log10, cosmological angular distance, and cosmological luminosity distance property operators. allocate(outputAnalysisPropertyOperatorMultiply_ ) @@ -506,7 +500,6 @@ subroutine galaxySizesSDSSDestructor(self) !![ - !!] return end subroutine galaxySizesSDSSDestructor diff --git a/source/output.analyses.mass_function_HI.ALFALFA_Martin2010.F90 b/source/output.analyses.mass_function_HI.ALFALFA_Martin2010.F90 index 09e3203c10..1701ca6de8 100644 --- a/source/output.analyses.mass_function_HI.ALFALFA_Martin2010.F90 +++ b/source/output.analyses.mass_function_HI.ALFALFA_Martin2010.F90 @@ -62,7 +62,6 @@ function massFunctionHIALFALFAMartin2010ConstructorParameters(parameters) result Constructor for the ``massFunctionHIALFALFAMartin2010'' output analysis class which takes a parameter set as input. !!} use :: Cosmology_Parameters , only : cosmologyParameters , cosmologyParametersClass - use :: Galactic_Structure , only : galacticStructureClass use :: Input_Parameters , only : inputParameter , inputParameters use :: Output_Analysis_Molecular_Ratios, only : outputAnalysisMolecularRatio, outputAnalysisMolecularRatioClass implicit none @@ -74,7 +73,6 @@ function massFunctionHIALFALFAMartin2010ConstructorParameters(parameters) result class (gravitationalLensingClass ), pointer :: gravitationalLensing_ class (outputAnalysisMolecularRatioClass ), pointer :: outputAnalysisMolecularRatio_ class (outputAnalysisDistributionOperatorClass ), pointer :: outputAnalysisDistributionOperatorRandomError_ - class (galacticStructureClass ), pointer :: galacticStructure_ double precision , allocatable , dimension(:) :: systematicErrorPolynomialCoefficient integer :: covarianceBinomialBinsPerDecade double precision :: covarianceBinomialMassHaloMinimum , covarianceBinomialMassHaloMaximum, & @@ -128,10 +126,9 @@ The maximum halo mass to consider when constructing ALFALFA HI mass - !!] ! Build the object. - self=outputAnalysisMassFunctionHIALFALFAMartin2010(cosmologyFunctions_,cosmologyParameters_,outputAnalysisDistributionOperatorRandomError_,outputAnalysisMolecularRatio_,gravitationalLensing_,outputTimes_,galacticStructure_,systematicErrorPolynomialCoefficient,covarianceBinomialBinsPerDecade,covarianceBinomialMassHaloMinimum,covarianceBinomialMassHaloMaximum,sizeSourceLensing) + self=outputAnalysisMassFunctionHIALFALFAMartin2010(cosmologyFunctions_,cosmologyParameters_,outputAnalysisDistributionOperatorRandomError_,outputAnalysisMolecularRatio_,gravitationalLensing_,outputTimes_,systematicErrorPolynomialCoefficient,covarianceBinomialBinsPerDecade,covarianceBinomialMassHaloMinimum,covarianceBinomialMassHaloMaximum,sizeSourceLensing) !![ @@ -140,12 +137,11 @@ The maximum halo mass to consider when constructing ALFALFA HI mass - !!] return end function massFunctionHIALFALFAMartin2010ConstructorParameters - function massFunctionHIALFALFAMartin2010ConstructorInternal(cosmologyFunctions_,cosmologyParameters_,outputAnalysisDistributionOperatorRandomError_,outputAnalysisMolecularRatio_,gravitationalLensing_,outputTimes_,galacticStructure_,systematicErrorPolynomialCoefficient,covarianceBinomialBinsPerDecade,covarianceBinomialMassHaloMinimum,covarianceBinomialMassHaloMaximum,sizeSourceLensing) result (self) + function massFunctionHIALFALFAMartin2010ConstructorInternal(cosmologyFunctions_,cosmologyParameters_,outputAnalysisDistributionOperatorRandomError_,outputAnalysisMolecularRatio_,gravitationalLensing_,outputTimes_,systematicErrorPolynomialCoefficient,covarianceBinomialBinsPerDecade,covarianceBinomialMassHaloMinimum,covarianceBinomialMassHaloMaximum,sizeSourceLensing) result (self) !!{ Constructor for the ``massFunctionHIALFALFAMartin2010'' output analysis class for internal use. !!} @@ -166,7 +162,6 @@ function massFunctionHIALFALFAMartin2010ConstructorInternal(cosmologyFunctions_, class (gravitationalLensingClass ), intent(in ), target :: gravitationalLensing_ class (outputAnalysisMolecularRatioClass ), intent(in ), target :: outputAnalysisMolecularRatio_ class (outputAnalysisDistributionOperatorClass ), intent(in ), target :: outputAnalysisDistributionOperatorRandomError_ - class (galacticStructureClass ), intent(in ), target :: galacticStructure_ double precision , intent(in ) :: sizeSourceLensing double precision , intent(in ), dimension(:) :: systematicErrorPolynomialCoefficient integer , intent(in ) :: covarianceBinomialBinsPerDecade @@ -265,7 +260,6 @@ function massFunctionHIALFALFAMartin2010ConstructorInternal(cosmologyFunctions_, & outputAnalysisDistributionOperator_ , & & outputAnalysisMolecularRatio_ , & & outputTimes_ , & - & galacticStructure_ , & & covarianceBinomialBinsPerDecade , & & covarianceBinomialMassHaloMinimum , & & covarianceBinomialMassHaloMaximum & diff --git a/source/output.analyses.mass_function_HI.F90 b/source/output.analyses.mass_function_HI.F90 index cf0905d481..4ccc322b77 100644 --- a/source/output.analyses.mass_function_HI.F90 +++ b/source/output.analyses.mass_function_HI.F90 @@ -36,7 +36,6 @@ An HI mass function output analysis class. private class (surveyGeometryClass ), pointer :: surveyGeometry_ => null() class (cosmologyFunctionsClass ), pointer :: cosmologyFunctions_ => null(), cosmologyFunctionsData => null() - class (galacticStructureClass ), pointer :: galacticStructure_ => null() class (outputAnalysisMolecularRatioClass), pointer :: outputAnalysisMolecularRatio_ => null() double precision , allocatable, dimension(:) :: masses contains @@ -60,7 +59,6 @@ function massFunctionHIConstructorParameters(parameters) result (self) !!} use :: Error , only : Error_Report use :: Input_Parameters , only : inputParameter , inputParameters - use :: Galactic_Structure , only : galacticStructureClass use :: Output_Analysis_Molecular_Ratios, only : outputAnalysisMolecularRatio, outputAnalysisMolecularRatioClass implicit none type (outputAnalysisMassFunctionHI ) :: self @@ -69,7 +67,6 @@ function massFunctionHIConstructorParameters(parameters) result (self) class (surveyGeometryClass ), pointer :: surveyGeometry_ class (cosmologyFunctionsClass ), pointer :: cosmologyFunctions_ , cosmologyFunctionsData class (outputTimesClass ), pointer :: outputTimes_ - class (galacticStructureClass ), pointer :: galacticStructure_ class (outputAnalysisDistributionOperatorClass), pointer :: outputAnalysisDistributionOperator_ class (outputAnalysisPropertyOperatorClass ), pointer :: outputAnalysisPropertyOperator_ class (outputAnalysisMolecularRatioClass ), pointer :: outputAnalysisMolecularRatio_ @@ -168,9 +165,8 @@ The target function covariance for likelihood calculations. - - self=outputAnalysisMassFunctionHI(label,comment,masses,galacticFilter_,surveyGeometry_,cosmologyFunctions_,cosmologyFunctionsData,outputAnalysisPropertyOperator_,outputAnalysisDistributionOperator_,outputAnalysisMolecularRatio_,outputTimes_,galacticStructure_,covarianceBinomialBinsPerDecade,covarianceBinomialMassHaloMinimum,covarianceBinomialMassHaloMaximum{conditions}) + self=outputAnalysisMassFunctionHI(label,comment,masses,galacticFilter_,surveyGeometry_,cosmologyFunctions_,cosmologyFunctionsData,outputAnalysisPropertyOperator_,outputAnalysisDistributionOperator_,outputAnalysisMolecularRatio_,outputTimes_,covarianceBinomialBinsPerDecade,covarianceBinomialMassHaloMinimum,covarianceBinomialMassHaloMaximum{conditions}) @@ -184,12 +180,11 @@ The target function covariance for likelihood calculations. - !!] return end function massFunctionHIConstructorParameters - function massFunctionHIConstructorFile(label,comment,fileName,galacticFilter_,surveyGeometry_,cosmologyFunctions_,cosmologyFunctionsData,outputAnalysisPropertyOperator_,outputAnalysisDistributionOperator_,outputAnalysisMolecularRatio_,outputTimes_,galacticStructure_,covarianceBinomialBinsPerDecade,covarianceBinomialMassHaloMinimum,covarianceBinomialMassHaloMaximum) result (self) + function massFunctionHIConstructorFile(label,comment,fileName,galacticFilter_,surveyGeometry_,cosmologyFunctions_,cosmologyFunctionsData,outputAnalysisPropertyOperator_,outputAnalysisDistributionOperator_,outputAnalysisMolecularRatio_,outputTimes_,covarianceBinomialBinsPerDecade,covarianceBinomialMassHaloMinimum,covarianceBinomialMassHaloMaximum) result (self) !!{ Constructor for the ``massFunctionHI'' output analysis class which reads bin information from a standard format file. !!} @@ -207,7 +202,6 @@ function massFunctionHIConstructorFile(label,comment,fileName,galacticFilter_,su class (outputAnalysisPropertyOperatorClass ), intent(inout) , target :: outputAnalysisPropertyOperator_ class (outputAnalysisDistributionOperatorClass), intent(in ) , target :: outputAnalysisDistributionOperator_ class (outputAnalysisMolecularRatioClass ), intent(in ) , target :: outputAnalysisMolecularRatio_ - class (galacticStructureClass ), intent(in ) , target :: galacticStructure_ double precision , dimension(: ), allocatable :: masses , functionValueTarget double precision , dimension(:,:), allocatable :: functionCovarianceTarget integer , intent(in ) :: covarianceBinomialBinsPerDecade @@ -230,7 +224,7 @@ function massFunctionHIConstructorFile(label,comment,fileName,galacticFilter_,su ! Construct the object. !![ - self=outputAnalysisMassFunctionHI(label,comment,masses,galacticFilter_,surveyGeometry_,cosmologyFunctions_,cosmologyFunctionsData,outputAnalysisPropertyOperator_,outputAnalysisDistributionOperator_,outputAnalysisMolecularRatio_,outputTimes_,galacticStructure_,covarianceBinomialBinsPerDecade,covarianceBinomialMassHaloMinimum,covarianceBinomialMassHaloMaximum{conditions}) + self=outputAnalysisMassFunctionHI(label,comment,masses,galacticFilter_,surveyGeometry_,cosmologyFunctions_,cosmologyFunctionsData,outputAnalysisPropertyOperator_,outputAnalysisDistributionOperator_,outputAnalysisMolecularRatio_,outputTimes_,covarianceBinomialBinsPerDecade,covarianceBinomialMassHaloMinimum,covarianceBinomialMassHaloMaximum{conditions}) @@ -239,7 +233,7 @@ function massFunctionHIConstructorFile(label,comment,fileName,galacticFilter_,su return end function massFunctionHIConstructorFile - function massFunctionHIConstructorInternal(label,comment,masses,galacticFilter_,surveyGeometry_,cosmologyFunctions_,cosmologyFunctionsData,outputAnalysisPropertyOperator_,outputAnalysisDistributionOperator_,outputAnalysisMolecularRatio_,outputTimes_,galacticStructure_,covarianceBinomialBinsPerDecade,covarianceBinomialMassHaloMinimum,covarianceBinomialMassHaloMaximum,targetLabel,functionValueTarget,functionCovarianceTarget) result(self) + function massFunctionHIConstructorInternal(label,comment,masses,galacticFilter_,surveyGeometry_,cosmologyFunctions_,cosmologyFunctionsData,outputAnalysisPropertyOperator_,outputAnalysisDistributionOperator_,outputAnalysisMolecularRatio_,outputTimes_,covarianceBinomialBinsPerDecade,covarianceBinomialMassHaloMinimum,covarianceBinomialMassHaloMaximum,targetLabel,functionValueTarget,functionCovarianceTarget) result(self) !!{ Constructor for the ``massFunctionHI'' output analysis class which takes a parameter set as input. !!} @@ -270,7 +264,6 @@ function massFunctionHIConstructorInternal(label,comment,masses,galacticFilter_, class (outputAnalysisPropertyOperatorClass ), intent(inout), target :: outputAnalysisPropertyOperator_ class (outputAnalysisDistributionOperatorClass ), intent(in ), target :: outputAnalysisDistributionOperator_ class (outputAnalysisMolecularRatioClass ), intent(in ), target :: outputAnalysisMolecularRatio_ - class (galacticStructureClass ), intent(in ), target :: galacticStructure_ integer , intent(in ) :: covarianceBinomialBinsPerDecade double precision , intent(in ) :: covarianceBinomialMassHaloMinimum , covarianceBinomialMassHaloMaximum type (varying_string ), intent(in ), optional :: targetLabel @@ -293,7 +286,7 @@ function massFunctionHIConstructorInternal(label,comment,masses,galacticFilter_, integer (c_size_t ), parameter :: bufferCountMinimum =5 integer (c_size_t ) :: iBin , bufferCount !![ - + !!] ! Compute weights that apply to each output redshift. @@ -305,7 +298,7 @@ function massFunctionHIConstructorInternal(label,comment,masses,galacticFilter_, ! Create a HI mass property extractor. allocate(nodePropertyExtractor_) !![ - + !!] ! Prepend log10, cosmological luminosity distance, and HI mass property operators. allocate(outputAnalysisPropertyOperatorHIMass_ ) @@ -434,7 +427,6 @@ subroutine massFunctionHIDestructor(self) !![ - diff --git a/source/output.analyses.mass_function_stellar.Bernardi_SDSS.F90 b/source/output.analyses.mass_function_stellar.Bernardi_SDSS.F90 index 2ee01a4a71..4db9713fe4 100644 --- a/source/output.analyses.mass_function_stellar.Bernardi_SDSS.F90 +++ b/source/output.analyses.mass_function_stellar.Bernardi_SDSS.F90 @@ -54,14 +54,12 @@ function massFunctionStellarBernardi2013SDSSConstructorParameters(parameters) re !!{ Constructor for the ``massFunctionStellarBernardi2013SDSS'' output analysis class which takes a parameter set as input. !!} - use :: Input_Parameters , only : inputParameter , inputParameters - use :: Galactic_Structure, only : galacticStructureClass + use :: Input_Parameters, only : inputParameter, inputParameters implicit none type (outputAnalysisMassFunctionStellarBernardi2013SDSS) :: self type (inputParameters ), intent(inout) :: parameters class (cosmologyFunctionsClass ), pointer :: cosmologyFunctions_ class (outputTimesClass ), pointer :: outputTimes_ - class (galacticStructureClass ), pointer :: galacticStructure_ class (gravitationalLensingClass ), pointer :: gravitationalLensing_ double precision , allocatable , dimension(:) :: randomErrorPolynomialCoefficient , systematicErrorPolynomialCoefficient integer :: covarianceBinomialBinsPerDecade @@ -140,21 +138,19 @@ The maximum halo mass to consider when constructing \cite{bernardi_ - !!] ! Build the object. - self=outputAnalysisMassFunctionStellarBernardi2013SDSS(cosmologyFunctions_,gravitationalLensing_,outputTimes_,galacticStructure_,randomErrorMinimum,randomErrorMaximum,randomErrorPolynomialCoefficient,systematicErrorPolynomialCoefficient,covarianceBinomialBinsPerDecade,covarianceBinomialMassHaloMinimum,covarianceBinomialMassHaloMaximum,sizeSourceLensing) + self=outputAnalysisMassFunctionStellarBernardi2013SDSS(cosmologyFunctions_,gravitationalLensing_,outputTimes_,randomErrorMinimum,randomErrorMaximum,randomErrorPolynomialCoefficient,systematicErrorPolynomialCoefficient,covarianceBinomialBinsPerDecade,covarianceBinomialMassHaloMinimum,covarianceBinomialMassHaloMaximum,sizeSourceLensing) !![ - !!] return end function massFunctionStellarBernardi2013SDSSConstructorParameters - function massFunctionStellarBernardi2013SDSSConstructorInternal(cosmologyFunctions_,gravitationalLensing_,outputTimes_,galacticStructure_,randomErrorMinimum,randomErrorMaximum,randomErrorPolynomialCoefficient,systematicErrorPolynomialCoefficient,covarianceBinomialBinsPerDecade,covarianceBinomialMassHaloMinimum,covarianceBinomialMassHaloMaximum,sizeSourceLensing) result (self) + function massFunctionStellarBernardi2013SDSSConstructorInternal(cosmologyFunctions_,gravitationalLensing_,outputTimes_,randomErrorMinimum,randomErrorMaximum,randomErrorPolynomialCoefficient,systematicErrorPolynomialCoefficient,covarianceBinomialBinsPerDecade,covarianceBinomialMassHaloMinimum,covarianceBinomialMassHaloMaximum,sizeSourceLensing) result (self) !!{ Constructor for the ``massFunctionStellarBernardi2013SDSS'' output analysis class for internal use. !!} @@ -171,7 +167,6 @@ function massFunctionStellarBernardi2013SDSSConstructorInternal(cosmologyFunctio class (cosmologyFunctionsClass ), intent(in ), target :: cosmologyFunctions_ class (outputTimesClass ), intent(inout), target :: outputTimes_ class (gravitationalLensingClass ), intent(in ), target :: gravitationalLensing_ - class (galacticStructureClass ), intent(in ), target :: galacticStructure_ double precision , intent(in ) :: randomErrorMinimum , randomErrorMaximum , & & sizeSourceLensing double precision , intent(in ), dimension(:) :: randomErrorPolynomialCoefficient , systematicErrorPolynomialCoefficient @@ -285,7 +280,6 @@ function massFunctionStellarBernardi2013SDSSConstructorInternal(cosmologyFunctio & outputAnalysisPropertyOperator_ , & & outputAnalysisDistributionOperator_ , & & outputTimes_ , & - & galacticStructure_ , & & covarianceBinomialBinsPerDecade , & & covarianceBinomialMassHaloMinimum , & & covarianceBinomialMassHaloMaximum & @@ -313,7 +307,6 @@ subroutine massFunctionStellarBernardi2013SDSSDestructor(self) type(outputAnalysisMassFunctionStellarBernardi2013SDSS), intent(inout) :: self !![ - !!] return diff --git a/source/output.analyses.mass_function_stellar.F90 b/source/output.analyses.mass_function_stellar.F90 index cb6e863c89..7ccf539a78 100644 --- a/source/output.analyses.mass_function_stellar.F90 +++ b/source/output.analyses.mass_function_stellar.F90 @@ -36,7 +36,6 @@ A stellar mass function output analysis class. private class (surveyGeometryClass ), pointer :: surveyGeometry_ => null() class (cosmologyFunctionsClass), pointer :: cosmologyFunctions_ => null(), cosmologyFunctionsData => null() - class (galacticStructureClass ), pointer :: galacticStructure_ => null() double precision , allocatable, dimension(:) :: masses contains final :: massFunctionStellarDestructor @@ -57,9 +56,8 @@ function massFunctionStellarConstructorParameters(parameters) result (self) !!{ Constructor for the ``massFunctionStellar'' output analysis class which takes a parameter set as input. !!} - use :: Error , only : Error_Report - use :: Galactic_Structure, only : galacticStructureClass - use :: Input_Parameters , only : inputParameter , inputParameters + use :: Error , only : Error_Report + use :: Input_Parameters, only : inputParameter, inputParameters implicit none type (outputAnalysisMassFunctionStellar ) :: self type (inputParameters ), intent(inout) :: parameters @@ -69,7 +67,6 @@ function massFunctionStellarConstructorParameters(parameters) result (self) class (outputAnalysisDistributionOperatorClass), pointer :: outputAnalysisDistributionOperator_ class (outputAnalysisPropertyOperatorClass ), pointer :: outputAnalysisPropertyOperator_ class (outputTimesClass ), pointer :: outputTimes_ - class (galacticStructureClass ), pointer :: galacticStructure_ double precision , dimension(: ), allocatable :: masses , functionValueTarget , & & functionCovarianceTarget1D double precision , dimension(:,:), allocatable :: functionCovarianceTarget @@ -164,9 +161,8 @@ The target function covariance for likelihood calculations. - - self=outputAnalysisMassFunctionStellar(label,comment,masses,galacticFilter_,surveyGeometry_,cosmologyFunctions_,cosmologyFunctionsData,outputAnalysisPropertyOperator_,outputAnalysisDistributionOperator_,outputTimes_,galacticStructure_,covarianceBinomialBinsPerDecade,covarianceBinomialMassHaloMinimum,covarianceBinomialMassHaloMaximum{conditions}) + self=outputAnalysisMassFunctionStellar(label,comment,masses,galacticFilter_,surveyGeometry_,cosmologyFunctions_,cosmologyFunctionsData,outputAnalysisPropertyOperator_,outputAnalysisDistributionOperator_,outputTimes_,covarianceBinomialBinsPerDecade,covarianceBinomialMassHaloMinimum,covarianceBinomialMassHaloMaximum{conditions}) @@ -178,13 +174,12 @@ The target function covariance for likelihood calculations. - !!] return end function massFunctionStellarConstructorParameters - function massFunctionStellarConstructorFile(label,comment,fileName,galacticFilter_,surveyGeometry_,cosmologyFunctions_,cosmologyFunctionsData,outputAnalysisPropertyOperator_,outputAnalysisDistributionOperator_,outputTimes_,galacticStructure_,covarianceBinomialBinsPerDecade,covarianceBinomialMassHaloMinimum,covarianceBinomialMassHaloMaximum) result (self) + function massFunctionStellarConstructorFile(label,comment,fileName,galacticFilter_,surveyGeometry_,cosmologyFunctions_,cosmologyFunctionsData,outputAnalysisPropertyOperator_,outputAnalysisDistributionOperator_,outputTimes_,covarianceBinomialBinsPerDecade,covarianceBinomialMassHaloMinimum,covarianceBinomialMassHaloMaximum) result (self) !!{ Constructor for the ``massFunctionStellar'' output analysis class which reads bin information from a standard format file. !!} @@ -200,7 +195,6 @@ function massFunctionStellarConstructorFile(label,comment,fileName,galacticFilte class (outputAnalysisPropertyOperatorClass ), intent(inout) , target :: outputAnalysisPropertyOperator_ class (outputAnalysisDistributionOperatorClass), intent(in ) , target :: outputAnalysisDistributionOperator_ class (outputTimesClass ), intent(inout) , target :: outputTimes_ - class (galacticStructureClass ), intent(in ) , target :: galacticStructure_ double precision , dimension(: ), allocatable :: masses , functionValueTarget double precision , dimension(:,:), allocatable :: functionCovarianceTarget integer , intent(in ) :: covarianceBinomialBinsPerDecade @@ -223,7 +217,7 @@ function massFunctionStellarConstructorFile(label,comment,fileName,galacticFilte ! Construct the object. !![ - self=outputAnalysisMassFunctionStellar(label,comment,masses,galacticFilter_,surveyGeometry_,cosmologyFunctions_,cosmologyFunctionsData,outputAnalysisPropertyOperator_,outputAnalysisDistributionOperator_,outputTimes_,galacticStructure_,covarianceBinomialBinsPerDecade,covarianceBinomialMassHaloMinimum,covarianceBinomialMassHaloMaximum{conditions}) + self=outputAnalysisMassFunctionStellar(label,comment,masses,galacticFilter_,surveyGeometry_,cosmologyFunctions_,cosmologyFunctionsData,outputAnalysisPropertyOperator_,outputAnalysisDistributionOperator_,outputTimes_,covarianceBinomialBinsPerDecade,covarianceBinomialMassHaloMinimum,covarianceBinomialMassHaloMaximum{conditions}) @@ -232,7 +226,7 @@ function massFunctionStellarConstructorFile(label,comment,fileName,galacticFilte return end function massFunctionStellarConstructorFile - function massFunctionStellarConstructorInternal(label,comment,masses,galacticFilter_,surveyGeometry_,cosmologyFunctions_,cosmologyFunctionsData,outputAnalysisPropertyOperator_,outputAnalysisDistributionOperator_,outputTimes_,galacticStructure_,covarianceBinomialBinsPerDecade,covarianceBinomialMassHaloMinimum,covarianceBinomialMassHaloMaximum,targetLabel,functionValueTarget,functionCovarianceTarget) result(self) + function massFunctionStellarConstructorInternal(label,comment,masses,galacticFilter_,surveyGeometry_,cosmologyFunctions_,cosmologyFunctionsData,outputAnalysisPropertyOperator_,outputAnalysisDistributionOperator_,outputTimes_,covarianceBinomialBinsPerDecade,covarianceBinomialMassHaloMinimum,covarianceBinomialMassHaloMaximum,targetLabel,functionValueTarget,functionCovarianceTarget) result(self) !!{ Constructor for the ``massFunctionStellar'' output analysis class which takes a parameter set as input. !!} @@ -257,7 +251,6 @@ function massFunctionStellarConstructorInternal(label,comment,masses,galacticFil class (outputAnalysisPropertyOperatorClass ), intent(inout), target :: outputAnalysisPropertyOperator_ class (outputAnalysisDistributionOperatorClass ), intent(in ), target :: outputAnalysisDistributionOperator_ class (outputTimesClass ), intent(inout), target :: outputTimes_ - class (galacticStructureClass ), intent(in ), target :: galacticStructure_ integer , intent(in ) :: covarianceBinomialBinsPerDecade double precision , intent(in ) :: covarianceBinomialMassHaloMinimum , covarianceBinomialMassHaloMaximum type (varying_string ), intent(in ), optional :: targetLabel @@ -279,7 +272,7 @@ function massFunctionStellarConstructorInternal(label,comment,masses,galacticFil integer (c_size_t ), parameter :: bufferCountMinimum =5 integer (c_size_t ) :: iBin , bufferCount !![ - + !!] ! Compute weights that apply to each output redshift. @@ -291,7 +284,7 @@ function massFunctionStellarConstructorInternal(label,comment,masses,galacticFil ! Create a stellar mass property extractor. allocate(nodePropertyExtractor_) !![ - + !!] ! Prepend log10 and cosmological luminosity distance property operators. allocate(outputAnalysisPropertyOperatorLog10_ ) @@ -414,7 +407,6 @@ subroutine massFunctionStellarDestructor(self) - !!] return end subroutine massFunctionStellarDestructor diff --git a/source/output.analyses.mass_function_stellar.GAMA.F90 b/source/output.analyses.mass_function_stellar.GAMA.F90 index 230e549212..da28325f97 100644 --- a/source/output.analyses.mass_function_stellar.GAMA.F90 +++ b/source/output.analyses.mass_function_stellar.GAMA.F90 @@ -76,8 +76,7 @@ function massFunctionStellarBaldry2012GAMAConstructorParameters(parameters) resu !!{ Constructor for the ``massFunctionStellarBaldry2012GAMA'' output analysis class which takes a parameter set as input. !!} - use :: Input_Parameters , only : inputParameter , inputParameters - use :: Galactic_Structure, only : galacticStructureClass + use :: Input_Parameters, only : inputParameter, inputParameters implicit none type (outputAnalysisMassFunctionStellarBaldry2012GAMA) :: self type (inputParameters ), intent(inout) :: parameters @@ -85,7 +84,6 @@ function massFunctionStellarBaldry2012GAMAConstructorParameters(parameters) resu class (outputTimesClass ), pointer :: outputTimes_ class (gravitationalLensingClass ), pointer :: gravitationalLensing_ class (massFunctionIncompletenessClass ), pointer :: massFunctionIncompleteness_ - class (galacticStructureClass ), pointer :: galacticStructure_ double precision , allocatable , dimension(:) :: randomErrorPolynomialCoefficient , systematicErrorPolynomialCoefficient integer :: covarianceBinomialBinsPerDecade double precision :: covarianceBinomialMassHaloMinimum, covarianceBinomialMassHaloMaximum , & @@ -164,22 +162,20 @@ The maximum halo mass to consider when constructing \cite{baldry_ga - !!] ! Build the object. - self=outputAnalysisMassFunctionStellarBaldry2012GAMA(cosmologyFunctions_,gravitationalLensing_,massFunctionIncompleteness_,outputTimes_,galacticStructure_,randomErrorMinimum,randomErrorMaximum,randomErrorPolynomialCoefficient,systematicErrorPolynomialCoefficient,covarianceBinomialBinsPerDecade,covarianceBinomialMassHaloMinimum,covarianceBinomialMassHaloMaximum,sizeSourceLensing) + self=outputAnalysisMassFunctionStellarBaldry2012GAMA(cosmologyFunctions_,gravitationalLensing_,massFunctionIncompleteness_,outputTimes_,randomErrorMinimum,randomErrorMaximum,randomErrorPolynomialCoefficient,systematicErrorPolynomialCoefficient,covarianceBinomialBinsPerDecade,covarianceBinomialMassHaloMinimum,covarianceBinomialMassHaloMaximum,sizeSourceLensing) !![ - !!] return end function massFunctionStellarBaldry2012GAMAConstructorParameters - function massFunctionStellarBaldry2012GAMAConstructorInternal(cosmologyFunctions_,gravitationalLensing_,massFunctionIncompleteness_,outputTimes_,galacticStructure_,randomErrorMinimum,randomErrorMaximum,randomErrorPolynomialCoefficient,systematicErrorPolynomialCoefficient,covarianceBinomialBinsPerDecade,covarianceBinomialMassHaloMinimum,covarianceBinomialMassHaloMaximum,sizeSourceLensing) result (self) + function massFunctionStellarBaldry2012GAMAConstructorInternal(cosmologyFunctions_,gravitationalLensing_,massFunctionIncompleteness_,outputTimes_,randomErrorMinimum,randomErrorMaximum,randomErrorPolynomialCoefficient,systematicErrorPolynomialCoefficient,covarianceBinomialBinsPerDecade,covarianceBinomialMassHaloMinimum,covarianceBinomialMassHaloMaximum,sizeSourceLensing) result (self) !!{ Constructor for the ``massFunctionStellarBaldry2012GAMA'' output analysis class for internal use. !!} @@ -199,7 +195,6 @@ function massFunctionStellarBaldry2012GAMAConstructorInternal(cosmologyFunctions class (outputTimesClass ), intent(inout), target :: outputTimes_ class (gravitationalLensingClass ), intent(in ), target :: gravitationalLensing_ class (massFunctionIncompletenessClass ), intent(in ), target :: massFunctionIncompleteness_ - class (galacticStructureClass ), intent(in ), target :: galacticStructure_ double precision , intent(in ) :: randomErrorMinimum , randomErrorMaximum , & & sizeSourceLensing double precision , intent(in ), dimension(:) :: randomErrorPolynomialCoefficient , systematicErrorPolynomialCoefficient @@ -327,7 +322,6 @@ function massFunctionStellarBaldry2012GAMAConstructorInternal(cosmologyFunctions & outputAnalysisPropertyOperator_ , & & outputAnalysisDistributionOperator_ , & & outputTimes_ , & - & galacticStructure_ , & & covarianceBinomialBinsPerDecade , & & covarianceBinomialMassHaloMinimum , & & covarianceBinomialMassHaloMaximum & @@ -356,7 +350,6 @@ subroutine massFunctionStellarBaldry2012GAMADestructor(self) type(outputAnalysisMassFunctionStellarBaldry2012GAMA), intent(inout) :: self !![ - !!] return diff --git a/source/output.analyses.mass_function_stellar.PRIMUS.F90 b/source/output.analyses.mass_function_stellar.PRIMUS.F90 index b5a1781da2..410601d7be 100644 --- a/source/output.analyses.mass_function_stellar.PRIMUS.F90 +++ b/source/output.analyses.mass_function_stellar.PRIMUS.F90 @@ -77,15 +77,13 @@ function massFunctionStellarPRIMUSConstructorParameters(parameters) result (self !!{ Constructor for the ``massFunctionStellarPRIMUS'' output analysis class which takes a parameter set as input. !!} - use :: Input_Parameters , only : inputParameter , inputParameters - use :: Galactic_Structure, only : galacticStructureClass + use :: Input_Parameters, only : inputParameter, inputParameters implicit none type (outputAnalysisMassFunctionStellarPRIMUS) :: self type (inputParameters ), intent(inout) :: parameters class (cosmologyFunctionsClass ), pointer :: cosmologyFunctions_ class (outputTimesClass ), pointer :: outputTimes_ class (gravitationalLensingClass ), pointer :: gravitationalLensing_ - class (galacticStructureClass ), pointer :: galacticStructure_ double precision , allocatable , dimension(:) :: randomErrorPolynomialCoefficient , systematicErrorPolynomialCoefficient integer :: covarianceBinomialBinsPerDecade , redshiftInterval double precision :: covarianceBinomialMassHaloMinimum, covarianceBinomialMassHaloMaximum , & @@ -169,21 +167,19 @@ The maximum halo mass to consider when constructing PRIMUS stellar - !!] ! Build the object. - self=outputAnalysisMassFunctionStellarPRIMUS(cosmologyFunctions_,gravitationalLensing_,outputTimes_,galacticStructure_,redshiftInterval,randomErrorMinimum,randomErrorMaximum,randomErrorPolynomialCoefficient,systematicErrorPolynomialCoefficient,covarianceBinomialBinsPerDecade,covarianceBinomialMassHaloMinimum,covarianceBinomialMassHaloMaximum,sizeSourceLensing) + self=outputAnalysisMassFunctionStellarPRIMUS(cosmologyFunctions_,gravitationalLensing_,outputTimes_,redshiftInterval,randomErrorMinimum,randomErrorMaximum,randomErrorPolynomialCoefficient,systematicErrorPolynomialCoefficient,covarianceBinomialBinsPerDecade,covarianceBinomialMassHaloMinimum,covarianceBinomialMassHaloMaximum,sizeSourceLensing) !![ - !!] return end function massFunctionStellarPRIMUSConstructorParameters - function massFunctionStellarPRIMUSConstructorInternal(cosmologyFunctions_,gravitationalLensing_,outputTimes_,galacticStructure_,redshiftInterval,randomErrorMinimum,randomErrorMaximum,randomErrorPolynomialCoefficient,systematicErrorPolynomialCoefficient,covarianceBinomialBinsPerDecade,covarianceBinomialMassHaloMinimum,covarianceBinomialMassHaloMaximum,sizeSourceLensing) result (self) + function massFunctionStellarPRIMUSConstructorInternal(cosmologyFunctions_,gravitationalLensing_,outputTimes_,redshiftInterval,randomErrorMinimum,randomErrorMaximum,randomErrorPolynomialCoefficient,systematicErrorPolynomialCoefficient,covarianceBinomialBinsPerDecade,covarianceBinomialMassHaloMinimum,covarianceBinomialMassHaloMaximum,sizeSourceLensing) result (self) !!{ Constructor for the ``massFunctionStellarPRIMUS'' output analysis class for internal use. !!} @@ -203,7 +199,6 @@ function massFunctionStellarPRIMUSConstructorInternal(cosmologyFunctions_,gravit class (cosmologyFunctionsClass ), intent(in ), target :: cosmologyFunctions_ class (outputTimesClass ), intent(inout), target :: outputTimes_ class (gravitationalLensingClass ), intent(in ), target :: gravitationalLensing_ - class (galacticStructureClass ), intent(in ), target :: galacticStructure_ integer , intent(in ) :: redshiftInterval double precision , intent(in ) :: randomErrorMinimum , randomErrorMaximum , & & sizeSourceLensing @@ -356,7 +351,6 @@ function massFunctionStellarPRIMUSConstructorInternal(cosmologyFunctions_,gravit & outputAnalysisPropertyOperator_ , & & outputAnalysisDistributionOperator_ , & & outputTimes_ , & - & galacticStructure_ , & & covarianceBinomialBinsPerDecade , & & covarianceBinomialMassHaloMinimum , & & covarianceBinomialMassHaloMaximum & @@ -384,7 +378,6 @@ subroutine massFunctionStellarPRIMUSDestructor(self) type(outputAnalysisMassFunctionStellarPRIMUS), intent(inout) :: self !![ - !!] return diff --git a/source/output.analyses.mass_function_stellar.SDSS.F90 b/source/output.analyses.mass_function_stellar.SDSS.F90 index 7a40dfe3a1..5c4cea65b6 100644 --- a/source/output.analyses.mass_function_stellar.SDSS.F90 +++ b/source/output.analyses.mass_function_stellar.SDSS.F90 @@ -81,15 +81,13 @@ function massFunctionStellarSDSSConstructorParameters(parameters) result (self) !!{ Constructor for the ``massFunctionStellarSDSS'' output analysis class which takes a parameter set as input. !!} - use :: Input_Parameters , only : inputParameter , inputParameters - use :: Galactic_Structure, only : galacticStructureClass + use :: Input_Parameters, only : inputParameter, inputParameters implicit none type (outputAnalysisMassFunctionStellarSDSS) :: self type (inputParameters ), intent(inout) :: parameters class (cosmologyFunctionsClass ), pointer :: cosmologyFunctions_ class (outputTimesClass ), pointer :: outputTimes_ class (gravitationalLensingClass ), pointer :: gravitationalLensing_ - class (galacticStructureClass ), pointer :: galacticStructure_ double precision , allocatable , dimension(:) :: randomErrorPolynomialCoefficient , systematicErrorPolynomialCoefficient integer :: covarianceBinomialBinsPerDecade double precision :: covarianceBinomialMassHaloMinimum, covarianceBinomialMassHaloMaximum , & @@ -167,21 +165,19 @@ The maximum halo mass to consider when constructing SDSS stellar ma - !!] ! Build the object. - self=outputAnalysisMassFunctionStellarSDSS(cosmologyFunctions_,gravitationalLensing_,outputTimes_,galacticStructure_,randomErrorMinimum,randomErrorMaximum,randomErrorPolynomialCoefficient,systematicErrorPolynomialCoefficient,covarianceBinomialBinsPerDecade,covarianceBinomialMassHaloMinimum,covarianceBinomialMassHaloMaximum,sizeSourceLensing) + self=outputAnalysisMassFunctionStellarSDSS(cosmologyFunctions_,gravitationalLensing_,outputTimes_,randomErrorMinimum,randomErrorMaximum,randomErrorPolynomialCoefficient,systematicErrorPolynomialCoefficient,covarianceBinomialBinsPerDecade,covarianceBinomialMassHaloMinimum,covarianceBinomialMassHaloMaximum,sizeSourceLensing) !![ - !!] return end function massFunctionStellarSDSSConstructorParameters - function massFunctionStellarSDSSConstructorInternal(cosmologyFunctions_,gravitationalLensing_,outputTimes_,galacticStructure_,randomErrorMinimum,randomErrorMaximum,randomErrorPolynomialCoefficient,systematicErrorPolynomialCoefficient,covarianceBinomialBinsPerDecade,covarianceBinomialMassHaloMinimum,covarianceBinomialMassHaloMaximum,sizeSourceLensing) result (self) + function massFunctionStellarSDSSConstructorInternal(cosmologyFunctions_,gravitationalLensing_,outputTimes_,randomErrorMinimum,randomErrorMaximum,randomErrorPolynomialCoefficient,systematicErrorPolynomialCoefficient,covarianceBinomialBinsPerDecade,covarianceBinomialMassHaloMinimum,covarianceBinomialMassHaloMaximum,sizeSourceLensing) result (self) !!{ Constructor for the ``massFunctionStellarSDSS'' output analysis class for internal use. !!} @@ -198,7 +194,6 @@ function massFunctionStellarSDSSConstructorInternal(cosmologyFunctions_,gravitat class (cosmologyFunctionsClass ), intent(in ), target :: cosmologyFunctions_ class (outputTimesClass ), intent(inout), target :: outputTimes_ class (gravitationalLensingClass ), intent(in ), target :: gravitationalLensing_ - class (galacticStructureClass ), intent(in ) , target :: galacticStructure_ double precision , intent(in ) :: randomErrorMinimum , randomErrorMaximum , & & sizeSourceLensing double precision , intent(in ), dimension(:) :: randomErrorPolynomialCoefficient , systematicErrorPolynomialCoefficient @@ -312,7 +307,6 @@ function massFunctionStellarSDSSConstructorInternal(cosmologyFunctions_,gravitat & outputAnalysisPropertyOperator_ , & & outputAnalysisDistributionOperator_ , & & outputTimes_ , & - & galacticStructure_ , & & covarianceBinomialBinsPerDecade , & & covarianceBinomialMassHaloMinimum , & & covarianceBinomialMassHaloMaximum & @@ -340,7 +334,6 @@ subroutine massFunctionStellarSDSSDestructor(self) type(outputAnalysisMassFunctionStellarSDSS), intent(inout) :: self !![ - !!] return diff --git a/source/output.analyses.mass_function_stellar.UKIDSS_UDS.F90 b/source/output.analyses.mass_function_stellar.UKIDSS_UDS.F90 index e19a4c9b0b..184e0b405e 100644 --- a/source/output.analyses.mass_function_stellar.UKIDSS_UDS.F90 +++ b/source/output.analyses.mass_function_stellar.UKIDSS_UDS.F90 @@ -77,15 +77,13 @@ function massFunctionStellarUKIDSSUDSConstructorParameters(parameters) result (s !!{ Constructor for the ``massFunctionStellarUKIDSSUDS'' output analysis class which takes a parameter set as input. !!} - use :: Input_Parameters , only : inputParameter , inputParameters - use :: Galactic_Structure, only : galacticStructureClass + use :: Input_Parameters, only : inputParameter, inputParameters implicit none type (outputAnalysisMassFunctionStellarUKIDSSUDS) :: self type (inputParameters ), intent(inout) :: parameters class (cosmologyFunctionsClass ), pointer :: cosmologyFunctions_ class (outputTimesClass ), pointer :: outputTimes_ class (gravitationalLensingClass ), pointer :: gravitationalLensing_ - class (galacticStructureClass ), pointer :: galacticStructure_ double precision , allocatable , dimension(:) :: randomErrorPolynomialCoefficient , systematicErrorPolynomialCoefficient integer :: covarianceBinomialBinsPerDecade , redshiftInterval double precision :: covarianceBinomialMassHaloMinimum, covarianceBinomialMassHaloMaximum , & @@ -169,21 +167,19 @@ The maximum halo mass to consider when constructing UKIDSS UDS stel - !!] ! Build the object. - self=outputAnalysisMassFunctionStellarUKIDSSUDS(cosmologyFunctions_,gravitationalLensing_,outputTimes_,galacticStructure_,redshiftInterval,randomErrorMinimum,randomErrorMaximum,randomErrorPolynomialCoefficient,systematicErrorPolynomialCoefficient,covarianceBinomialBinsPerDecade,covarianceBinomialMassHaloMinimum,covarianceBinomialMassHaloMaximum,sizeSourceLensing) + self=outputAnalysisMassFunctionStellarUKIDSSUDS(cosmologyFunctions_,gravitationalLensing_,outputTimes_,redshiftInterval,randomErrorMinimum,randomErrorMaximum,randomErrorPolynomialCoefficient,systematicErrorPolynomialCoefficient,covarianceBinomialBinsPerDecade,covarianceBinomialMassHaloMinimum,covarianceBinomialMassHaloMaximum,sizeSourceLensing) !![ - !!] return end function massFunctionStellarUKIDSSUDSConstructorParameters - function massFunctionStellarUKIDSSUDSConstructorInternal(cosmologyFunctions_,gravitationalLensing_,outputTimes_,galacticStructure_,redshiftInterval,randomErrorMinimum,randomErrorMaximum,randomErrorPolynomialCoefficient,systematicErrorPolynomialCoefficient,covarianceBinomialBinsPerDecade,covarianceBinomialMassHaloMinimum,covarianceBinomialMassHaloMaximum,sizeSourceLensing) result (self) + function massFunctionStellarUKIDSSUDSConstructorInternal(cosmologyFunctions_,gravitationalLensing_,outputTimes_,redshiftInterval,randomErrorMinimum,randomErrorMaximum,randomErrorPolynomialCoefficient,systematicErrorPolynomialCoefficient,covarianceBinomialBinsPerDecade,covarianceBinomialMassHaloMinimum,covarianceBinomialMassHaloMaximum,sizeSourceLensing) result (self) !!{ Constructor for the ``massFunctionStellarUKIDSSUDS'' output analysis class for internal use. !!} @@ -203,7 +199,6 @@ function massFunctionStellarUKIDSSUDSConstructorInternal(cosmologyFunctions_,gra class (cosmologyFunctionsClass ), intent(in ), target :: cosmologyFunctions_ class (outputTimesClass ), intent(inout), target :: outputTimes_ class (gravitationalLensingClass ), intent(in ), target :: gravitationalLensing_ - class (galacticStructureClass ), intent(in ), target :: galacticStructure_ integer , intent(in ) :: redshiftInterval double precision , intent(in ) :: randomErrorMinimum , randomErrorMaximum , & & sizeSourceLensing @@ -334,7 +329,6 @@ function massFunctionStellarUKIDSSUDSConstructorInternal(cosmologyFunctions_,gra & outputAnalysisPropertyOperator_ , & & outputAnalysisDistributionOperator_ , & & outputTimes_ , & - & galacticStructure_ , & & covarianceBinomialBinsPerDecade , & & covarianceBinomialMassHaloMinimum , & & covarianceBinomialMassHaloMaximum & @@ -362,7 +356,6 @@ subroutine massFunctionStellarUKIDSSUDSDestructor(self) type(outputAnalysisMassFunctionStellarUKIDSSUDS), intent(inout) :: self !![ - !!] return diff --git a/source/output.analyses.mass_function_stellar.ULTRAVISTA.F90 b/source/output.analyses.mass_function_stellar.ULTRAVISTA.F90 index 0a3df76a36..83af0d5292 100644 --- a/source/output.analyses.mass_function_stellar.ULTRAVISTA.F90 +++ b/source/output.analyses.mass_function_stellar.ULTRAVISTA.F90 @@ -77,15 +77,13 @@ function massFunctionStellarULTRAVISTAConstructorParameters(parameters) result ( !!{ Constructor for the ``massFunctionStellarULTRAVISTA'' output analysis class which takes a parameter set as input. !!} - use :: Input_Parameters , only : inputParameter , inputParameters - use :: Galactic_Structure, only : galacticStructureClass + use :: Input_Parameters, only : inputParameter, inputParameters implicit none type (outputAnalysisMassFunctionStellarULTRAVISTA) :: self type (inputParameters ), intent(inout) :: parameters class (cosmologyFunctionsClass ), pointer :: cosmologyFunctions_ class (outputTimesClass ), pointer :: outputTimes_ class (gravitationalLensingClass ), pointer :: gravitationalLensing_ - class (galacticStructureClass ), pointer :: galacticStructure_ double precision , allocatable , dimension(:) :: randomErrorPolynomialCoefficient , systematicErrorPolynomialCoefficient integer :: covarianceBinomialBinsPerDecade , redshiftInterval double precision :: covarianceBinomialMassHaloMinimum, covarianceBinomialMassHaloMaximum , & @@ -169,21 +167,19 @@ The maximum halo mass to consider when constructing ULTRAVISTA stel - !!] ! Build the object. - self=outputAnalysisMassFunctionStellarULTRAVISTA(cosmologyFunctions_,gravitationalLensing_,outputTimes_,galacticStructure_,redshiftInterval,randomErrorMinimum,randomErrorMaximum,randomErrorPolynomialCoefficient,systematicErrorPolynomialCoefficient,covarianceBinomialBinsPerDecade,covarianceBinomialMassHaloMinimum,covarianceBinomialMassHaloMaximum,sizeSourceLensing) + self=outputAnalysisMassFunctionStellarULTRAVISTA(cosmologyFunctions_,gravitationalLensing_,outputTimes_,redshiftInterval,randomErrorMinimum,randomErrorMaximum,randomErrorPolynomialCoefficient,systematicErrorPolynomialCoefficient,covarianceBinomialBinsPerDecade,covarianceBinomialMassHaloMinimum,covarianceBinomialMassHaloMaximum,sizeSourceLensing) !![ - !!] return end function massFunctionStellarULTRAVISTAConstructorParameters - function massFunctionStellarULTRAVISTAConstructorInternal(cosmologyFunctions_,gravitationalLensing_,outputTimes_,galacticStructure_,redshiftInterval,randomErrorMinimum,randomErrorMaximum,randomErrorPolynomialCoefficient,systematicErrorPolynomialCoefficient,covarianceBinomialBinsPerDecade,covarianceBinomialMassHaloMinimum,covarianceBinomialMassHaloMaximum,sizeSourceLensing) result (self) + function massFunctionStellarULTRAVISTAConstructorInternal(cosmologyFunctions_,gravitationalLensing_,outputTimes_,redshiftInterval,randomErrorMinimum,randomErrorMaximum,randomErrorPolynomialCoefficient,systematicErrorPolynomialCoefficient,covarianceBinomialBinsPerDecade,covarianceBinomialMassHaloMinimum,covarianceBinomialMassHaloMaximum,sizeSourceLensing) result (self) !!{ Constructor for the ``massFunctionStellarULTRAVISTA'' output analysis class for internal use. !!} @@ -203,7 +199,6 @@ function massFunctionStellarULTRAVISTAConstructorInternal(cosmologyFunctions_,gr class (cosmologyFunctionsClass ), intent(in ), target :: cosmologyFunctions_ class (outputTimesClass ), intent(inout), target :: outputTimes_ class (gravitationalLensingClass ), intent(in ), target :: gravitationalLensing_ - class (galacticStructureClass ), intent(in ) , target :: galacticStructure_ integer , intent(in ) :: redshiftInterval double precision , intent(in ) :: randomErrorMinimum , randomErrorMaximum , & & sizeSourceLensing @@ -353,8 +348,7 @@ function massFunctionStellarULTRAVISTAConstructorInternal(cosmologyFunctions_,gr & cosmologyFunctionsData , & & outputAnalysisPropertyOperator_ , & & outputAnalysisDistributionOperator_ , & - & outputTimes_ , & - & galacticStructure_ , & + & outputTimes_ , & & covarianceBinomialBinsPerDecade , & & covarianceBinomialMassHaloMinimum , & & covarianceBinomialMassHaloMaximum & @@ -382,7 +376,6 @@ subroutine massFunctionStellarULTRAVISTADestructor(self) type(outputAnalysisMassFunctionStellarULTRAVISTA), intent(inout) :: self !![ - !!] return diff --git a/source/output.analyses.mass_function_stellar.VIPERS.F90 b/source/output.analyses.mass_function_stellar.VIPERS.F90 index f7cd7c6d62..43ddf7aa21 100644 --- a/source/output.analyses.mass_function_stellar.VIPERS.F90 +++ b/source/output.analyses.mass_function_stellar.VIPERS.F90 @@ -77,15 +77,13 @@ function massFunctionStellarVIPERSConstructorParameters(parameters) result (self !!{ Constructor for the ``massFunctionStellarVIPERS'' output analysis class which takes a parameter set as input. !!} - use :: Input_Parameters , only : inputParameter , inputParameters - use :: Galactic_Structure, only : galacticStructureClass + use :: Input_Parameters, only : inputParameter, inputParameters implicit none type (outputAnalysisMassFunctionStellarVIPERS) :: self type (inputParameters ), intent(inout) :: parameters class (cosmologyFunctionsClass ), pointer :: cosmologyFunctions_ class (outputTimesClass ), pointer :: outputTimes_ class (gravitationalLensingClass ), pointer :: gravitationalLensing_ - class (galacticStructureClass ), pointer :: galacticStructure_ double precision , allocatable , dimension(:) :: randomErrorPolynomialCoefficient , systematicErrorPolynomialCoefficient integer :: covarianceBinomialBinsPerDecade , redshiftInterval double precision :: covarianceBinomialMassHaloMinimum, covarianceBinomialMassHaloMaximum , & @@ -169,21 +167,19 @@ The maximum halo mass to consider when constructing VIPERS stellar - !!] ! Build the object. - self=outputAnalysisMassFunctionStellarVIPERS(cosmologyFunctions_,gravitationalLensing_,outputTimes_,galacticStructure_,redshiftInterval,randomErrorMinimum,randomErrorMaximum,randomErrorPolynomialCoefficient,systematicErrorPolynomialCoefficient,covarianceBinomialBinsPerDecade,covarianceBinomialMassHaloMinimum,covarianceBinomialMassHaloMaximum,sizeSourceLensing) + self=outputAnalysisMassFunctionStellarVIPERS(cosmologyFunctions_,gravitationalLensing_,outputTimes_,redshiftInterval,randomErrorMinimum,randomErrorMaximum,randomErrorPolynomialCoefficient,systematicErrorPolynomialCoefficient,covarianceBinomialBinsPerDecade,covarianceBinomialMassHaloMinimum,covarianceBinomialMassHaloMaximum,sizeSourceLensing) !![ - !!] return end function massFunctionStellarVIPERSConstructorParameters - function massFunctionStellarVIPERSConstructorInternal(cosmologyFunctions_,gravitationalLensing_,outputTimes_,galacticStructure_,redshiftInterval,randomErrorMinimum,randomErrorMaximum,randomErrorPolynomialCoefficient,systematicErrorPolynomialCoefficient,covarianceBinomialBinsPerDecade,covarianceBinomialMassHaloMinimum,covarianceBinomialMassHaloMaximum,sizeSourceLensing) result (self) + function massFunctionStellarVIPERSConstructorInternal(cosmologyFunctions_,gravitationalLensing_,outputTimes_,redshiftInterval,randomErrorMinimum,randomErrorMaximum,randomErrorPolynomialCoefficient,systematicErrorPolynomialCoefficient,covarianceBinomialBinsPerDecade,covarianceBinomialMassHaloMinimum,covarianceBinomialMassHaloMaximum,sizeSourceLensing) result (self) !!{ Constructor for the ``massFunctionStellarVIPERS'' output analysis class for internal use. !!} @@ -203,7 +199,6 @@ function massFunctionStellarVIPERSConstructorInternal(cosmologyFunctions_,gravit class (cosmologyFunctionsClass ), intent(in ), target :: cosmologyFunctions_ class (outputTimesClass ), intent(inout), target :: outputTimes_ class (gravitationalLensingClass ), intent(in ), target :: gravitationalLensing_ - class (galacticStructureClass ), intent(in ), target :: galacticStructure_ integer , intent(in ) :: redshiftInterval double precision , intent(in ) :: randomErrorMinimum , randomErrorMaximum , & & sizeSourceLensing @@ -334,7 +329,6 @@ function massFunctionStellarVIPERSConstructorInternal(cosmologyFunctions_,gravit & outputAnalysisPropertyOperator_ , & & outputAnalysisDistributionOperator_ , & & outputTimes_ , & - & galacticStructure_ , & & covarianceBinomialBinsPerDecade , & & covarianceBinomialMassHaloMinimum , & & covarianceBinomialMassHaloMaximum & @@ -362,7 +356,6 @@ subroutine massFunctionStellarVIPERSDestructor(self) type(outputAnalysisMassFunctionStellarVIPERS), intent(inout) :: self !![ - !!] return diff --git a/source/output.analyses.mass_function_stellar.ZFOURGE.F90 b/source/output.analyses.mass_function_stellar.ZFOURGE.F90 index 281806504e..ca3b640436 100644 --- a/source/output.analyses.mass_function_stellar.ZFOURGE.F90 +++ b/source/output.analyses.mass_function_stellar.ZFOURGE.F90 @@ -78,7 +78,6 @@ function massFunctionStellarZFOURGEConstructorParameters(parameters) result (sel Constructor for the ``massFunctionStellarZFOURGE'' output analysis class which takes a parameter set as input. !!} use :: Gravitational_Lensing, only : gravitationalLensingClass - use :: Galactic_Structure , only : galacticStructureClass use :: Input_Parameters , only : inputParameter , inputParameters implicit none type (outputAnalysisMassFunctionStellarZFOURGE) :: self @@ -86,7 +85,6 @@ function massFunctionStellarZFOURGEConstructorParameters(parameters) result (sel class (cosmologyFunctionsClass ), pointer :: cosmologyFunctions_ class (outputTimesClass ), pointer :: outputTimes_ class (gravitationalLensingClass ), pointer :: gravitationalLensing_ - class (galacticStructureClass ), pointer :: galacticStructure_ double precision , allocatable , dimension(:) :: randomErrorPolynomialCoefficient , systematicErrorPolynomialCoefficient integer :: covarianceBinomialBinsPerDecade , redshiftInterval double precision :: covarianceBinomialMassHaloMinimum, covarianceBinomialMassHaloMaximum , & @@ -170,21 +168,19 @@ The maximum halo mass to consider when constructing ZFOURGE stellar - !!] ! Build the object. - self=outputAnalysisMassFunctionStellarZFOURGE(cosmologyFunctions_,gravitationalLensing_,outputTimes_,galacticStructure_,redshiftInterval,randomErrorMinimum,randomErrorMaximum,randomErrorPolynomialCoefficient,systematicErrorPolynomialCoefficient,covarianceBinomialBinsPerDecade,covarianceBinomialMassHaloMinimum,covarianceBinomialMassHaloMaximum,sizeSourceLensing) + self=outputAnalysisMassFunctionStellarZFOURGE(cosmologyFunctions_,gravitationalLensing_,outputTimes_,redshiftInterval,randomErrorMinimum,randomErrorMaximum,randomErrorPolynomialCoefficient,systematicErrorPolynomialCoefficient,covarianceBinomialBinsPerDecade,covarianceBinomialMassHaloMinimum,covarianceBinomialMassHaloMaximum,sizeSourceLensing) !![ - !!] return end function massFunctionStellarZFOURGEConstructorParameters - function massFunctionStellarZFOURGEConstructorInternal(cosmologyFunctions_,gravitationalLensing_,outputTimes_,galacticStructure_,redshiftInterval,randomErrorMinimum,randomErrorMaximum,randomErrorPolynomialCoefficient,systematicErrorPolynomialCoefficient,covarianceBinomialBinsPerDecade,covarianceBinomialMassHaloMinimum,covarianceBinomialMassHaloMaximum,sizeSourceLensing) result (self) + function massFunctionStellarZFOURGEConstructorInternal(cosmologyFunctions_,gravitationalLensing_,outputTimes_,redshiftInterval,randomErrorMinimum,randomErrorMaximum,randomErrorPolynomialCoefficient,systematicErrorPolynomialCoefficient,covarianceBinomialBinsPerDecade,covarianceBinomialMassHaloMinimum,covarianceBinomialMassHaloMaximum,sizeSourceLensing) result (self) !!{ Constructor for the ``massFunctionStellarZFOURGE'' output analysis class for internal use. !!} @@ -204,7 +200,6 @@ function massFunctionStellarZFOURGEConstructorInternal(cosmologyFunctions_,gravi class (cosmologyFunctionsClass ), intent(in ), target :: cosmologyFunctions_ class (outputTimesClass ), intent(inout), target :: outputTimes_ class (gravitationalLensingClass ), intent(in ), target :: gravitationalLensing_ - class (galacticStructureClass ), intent(in ), target :: galacticStructure_ integer , intent(in ) :: redshiftInterval double precision , intent(in ) :: randomErrorMinimum , randomErrorMaximum , & & sizeSourceLensing @@ -367,7 +362,6 @@ function massFunctionStellarZFOURGEConstructorInternal(cosmologyFunctions_,gravi & outputAnalysisPropertyOperator_ , & & outputAnalysisDistributionOperator_ , & & outputTimes_ , & - & galacticStructure_ , & & covarianceBinomialBinsPerDecade , & & covarianceBinomialMassHaloMinimum , & & covarianceBinomialMassHaloMaximum & @@ -395,7 +389,6 @@ subroutine massFunctionStellarZFOURGEDestructor(self) type(outputAnalysisMassFunctionStellarZFOURGE), intent(inout) :: self !![ - !!] return diff --git a/source/output.analyses.mass_metallicity_relation.Andrews2013.F90 b/source/output.analyses.mass_metallicity_relation.Andrews2013.F90 index 1ae002e9b5..14b5144b32 100644 --- a/source/output.analyses.mass_metallicity_relation.Andrews2013.F90 +++ b/source/output.analyses.mass_metallicity_relation.Andrews2013.F90 @@ -36,7 +36,6 @@ class (cosmologyFunctionsClass ), pointer :: cosmologyFunctions_ => null() class (starFormationRateDisksClass ), pointer :: starFormationRateDisks_ => null() class (starFormationRateSpheroidsClass), pointer :: starFormationRateSpheroids_ => null() - class (galacticStructureClass ), pointer :: galacticStructure_ => null() double precision :: randomErrorMinimum , randomErrorMaximum , & & fractionGasThreshold contains @@ -61,7 +60,6 @@ function massMetallicityAndrews2013ConstructorParameters(parameters) result (sel use :: Input_Parameters , only : inputParameter , inputParameters use :: Star_Formation_Rates_Disks , only : starFormationRateDisksClass use :: Star_Formation_Rates_Spheroids, only : starFormationRateSpheroidsClass - use :: Galactic_Structure , only : galacticStructureClass implicit none type (outputAnalysisMassMetallicityAndrews2013) :: self type (inputParameters ), intent(inout) :: parameters @@ -71,7 +69,6 @@ function massMetallicityAndrews2013ConstructorParameters(parameters) result (sel class (outputTimesClass ), pointer :: outputTimes_ class (starFormationRateDisksClass ), pointer :: starFormationRateDisks_ class (starFormationRateSpheroidsClass ), pointer :: starFormationRateSpheroids_ - class (galacticStructureClass ), pointer :: galacticStructure_ double precision :: randomErrorMinimum , randomErrorMaximum , & & fractionGasThreshold @@ -125,22 +122,20 @@ function massMetallicityAndrews2013ConstructorParameters(parameters) result (sel - !!] ! Build the object. - self=outputAnalysisMassMetallicityAndrews2013(metallicitySystematicErrorPolynomialCoefficient,systematicErrorPolynomialCoefficient,randomErrorPolynomialCoefficient,randomErrorMinimum,randomErrorMaximum,fractionGasThreshold,cosmologyFunctions_,outputTimes_,starFormationRateDisks_,starFormationRateSpheroids_,galacticStructure_) + self=outputAnalysisMassMetallicityAndrews2013(metallicitySystematicErrorPolynomialCoefficient,systematicErrorPolynomialCoefficient,randomErrorPolynomialCoefficient,randomErrorMinimum,randomErrorMaximum,fractionGasThreshold,cosmologyFunctions_,outputTimes_,starFormationRateDisks_,starFormationRateSpheroids_) !![ - !!] return end function massMetallicityAndrews2013ConstructorParameters - function massMetallicityAndrews2013ConstructorInternal(metallicitySystematicErrorPolynomialCoefficient,systematicErrorPolynomialCoefficient,randomErrorPolynomialCoefficient,randomErrorMinimum,randomErrorMaximum,fractionGasThreshold,cosmologyFunctions_,outputTimes_,starFormationRateDisks_,starFormationRateSpheroids_,galacticStructure_) result (self) + function massMetallicityAndrews2013ConstructorInternal(metallicitySystematicErrorPolynomialCoefficient,systematicErrorPolynomialCoefficient,randomErrorPolynomialCoefficient,randomErrorMinimum,randomErrorMaximum,fractionGasThreshold,cosmologyFunctions_,outputTimes_,starFormationRateDisks_,starFormationRateSpheroids_) result (self) !!{ Constructor for the ``massMetallicityAndrews2013'' output analysis class for internal use. !!} @@ -177,7 +172,6 @@ function massMetallicityAndrews2013ConstructorInternal(metallicitySystematicErro class (outputTimesClass ), intent(inout), target :: outputTimes_ class (starFormationRateDisksClass ), intent(in ), target :: starFormationRateDisks_ class (starFormationRateSpheroidsClass ), intent(in ), target :: starFormationRateSpheroids_ - class (galacticStructureClass ), intent(in ), target :: galacticStructure_ integer , parameter :: covarianceBinomialBinsPerDecade =10 double precision , parameter :: covarianceBinomialMassHaloMinimum = 1.0d08, covarianceBinomialMassHaloMaximum =1.0d16 double precision , allocatable , dimension(: ) :: masses , functionValueTarget @@ -210,7 +204,7 @@ function massMetallicityAndrews2013ConstructorInternal(metallicitySystematicErro type (hdf5Object ) :: dataFile integer :: indexOxygen !![ - + !!] ! Read masses at which fraction was measured. @@ -375,7 +369,7 @@ function massMetallicityAndrews2013ConstructorInternal(metallicitySystematicErro ! Create a stellar mass property extractor. allocate(nodePropertyExtractor_ ) !![ - + !!] ! Find the index for the oxygen abundance. indexOxygen=Abundances_Index_From_Name("O") @@ -472,7 +466,6 @@ subroutine massMetallicityAndrews2013Destructor(self) - !!] return end subroutine massMetallicityAndrews2013Destructor diff --git a/source/output.analyses.mass_metallicity_relation.Blanc2019.F90 b/source/output.analyses.mass_metallicity_relation.Blanc2019.F90 index 75850ac91a..e06a692d5c 100644 --- a/source/output.analyses.mass_metallicity_relation.Blanc2019.F90 +++ b/source/output.analyses.mass_metallicity_relation.Blanc2019.F90 @@ -36,7 +36,6 @@ class (cosmologyFunctionsClass ), pointer :: cosmologyFunctions_ => null() class (starFormationRateDisksClass ), pointer :: starFormationRateDisks_ => null() class (starFormationRateSpheroidsClass), pointer :: starFormationRateSpheroids_ => null() - class (galacticStructureClass ), pointer :: galacticStructure_ => null() double precision :: randomErrorMinimum , randomErrorMaximum , & & fractionGasThreshold contains @@ -61,7 +60,6 @@ function massMetallicityBlanc2019ConstructorParameters(parameters) result (self) use :: Input_Parameters , only : inputParameter , inputParameters use :: Star_Formation_Rates_Disks , only : starFormationRateDisksClass use :: Star_Formation_Rates_Spheroids, only : starFormationRateSpheroidsClass - use :: Galactic_Structure , only : galacticStructureClass implicit none type (outputAnalysisMassMetallicityBlanc2019) :: self type (inputParameters ), intent(inout) :: parameters @@ -71,7 +69,6 @@ function massMetallicityBlanc2019ConstructorParameters(parameters) result (self) class (outputTimesClass ), pointer :: outputTimes_ class (starFormationRateDisksClass ), pointer :: starFormationRateDisks_ class (starFormationRateSpheroidsClass ), pointer :: starFormationRateSpheroids_ - class (galacticStructureClass ), pointer :: galacticStructure_ double precision :: randomErrorMinimum , randomErrorMaximum , & & fractionGasThreshold @@ -125,22 +122,20 @@ function massMetallicityBlanc2019ConstructorParameters(parameters) result (self) - !!] ! Build the object. - self=outputAnalysisMassMetallicityBlanc2019(metallicitySystematicErrorPolynomialCoefficient,systematicErrorPolynomialCoefficient,randomErrorPolynomialCoefficient,randomErrorMinimum,randomErrorMaximum,fractionGasThreshold,cosmologyFunctions_,outputTimes_,starFormationRateDisks_,starFormationRateSpheroids_,galacticStructure_) + self=outputAnalysisMassMetallicityBlanc2019(metallicitySystematicErrorPolynomialCoefficient,systematicErrorPolynomialCoefficient,randomErrorPolynomialCoefficient,randomErrorMinimum,randomErrorMaximum,fractionGasThreshold,cosmologyFunctions_,outputTimes_,starFormationRateDisks_,starFormationRateSpheroids_) !![ - !!] return end function massMetallicityBlanc2019ConstructorParameters - function massMetallicityBlanc2019ConstructorInternal(metallicitySystematicErrorPolynomialCoefficient,systematicErrorPolynomialCoefficient,randomErrorPolynomialCoefficient,randomErrorMinimum,randomErrorMaximum,fractionGasThreshold,cosmologyFunctions_,outputTimes_,starFormationRateDisks_,starFormationRateSpheroids_,galacticStructure_) result (self) + function massMetallicityBlanc2019ConstructorInternal(metallicitySystematicErrorPolynomialCoefficient,systematicErrorPolynomialCoefficient,randomErrorPolynomialCoefficient,randomErrorMinimum,randomErrorMaximum,fractionGasThreshold,cosmologyFunctions_,outputTimes_,starFormationRateDisks_,starFormationRateSpheroids_) result (self) !!{ Constructor for the ``massMetallicityBlanc2019'' output analysis class for internal use. !!} @@ -177,7 +172,6 @@ function massMetallicityBlanc2019ConstructorInternal(metallicitySystematicErrorP class (outputTimesClass ), intent(inout), target :: outputTimes_ class (starFormationRateDisksClass ), intent(in ), target :: starFormationRateDisks_ class (starFormationRateSpheroidsClass ), intent(in ), target :: starFormationRateSpheroids_ - class (galacticStructureClass ), intent(in ), target :: galacticStructure_ integer , parameter :: covarianceBinomialBinsPerDecade =10 double precision , parameter :: covarianceBinomialMassHaloMinimum = 1.0d08, covarianceBinomialMassHaloMaximum =1.0d16 double precision , allocatable , dimension(: ) :: masses , functionValueTarget , & @@ -211,7 +205,7 @@ function massMetallicityBlanc2019ConstructorInternal(metallicitySystematicErrorP type (hdf5Object ) :: dataFile integer :: indexOxygen !![ - + !!] ! Read masses at which fraction was measured. @@ -380,7 +374,7 @@ function massMetallicityBlanc2019ConstructorInternal(metallicitySystematicErrorP ! Create a stellar mass property extractor. allocate(nodePropertyExtractor_ ) !![ - + !!] ! Find the index for the oxygen abundance. indexOxygen=Abundances_Index_From_Name("O") @@ -476,7 +470,6 @@ subroutine massMetallicityBlanc2019Destructor(self) - !!] return end subroutine massMetallicityBlanc2019Destructor diff --git a/source/output.analyses.morphological_fraction.GAMA_Moffett2016.F90 b/source/output.analyses.morphological_fraction.GAMA_Moffett2016.F90 index d14dae7e7a..0f1ec040bb 100644 --- a/source/output.analyses.morphological_fraction.GAMA_Moffett2016.F90 +++ b/source/output.analyses.morphological_fraction.GAMA_Moffett2016.F90 @@ -35,7 +35,6 @@ & functionErrorLowerTarget , functionErrorUpperTarget , & & systematicErrorPolynomialCoefficient , randomErrorPolynomialCoefficient class (cosmologyFunctionsClass), pointer :: cosmologyFunctions_ => null() - class (galacticStructureClass ), pointer :: galacticStructure_ => null() double precision :: ratioEarlyType , ratioEarlyTypeError , & & randomErrorMinimum , randomErrorMaximum contains @@ -58,15 +57,13 @@ function morphologicalFractionGAMAMoffett2016ConstructorParameters(parameters) r !!{ Constructor for the ``morphologicalFractionGAMAMoffett2016'' output analysis class which takes a parameter set as input. !!} - use :: Cosmology_Functions, only : cosmologyFunctions , cosmologyFunctionsClass - use :: Galactic_Structure , only : galacticStructureClass - use :: Input_Parameters , only : inputParameter , inputParameters + use :: Cosmology_Functions, only : cosmologyFunctions, cosmologyFunctionsClass + use :: Input_Parameters , only : inputParameter , inputParameters implicit none type (outputAnalysisMorphologicalFractionGAMAMoffett2016) :: self type (inputParameters ), intent(inout) :: parameters double precision , allocatable , dimension(:) :: systematicErrorPolynomialCoefficient, randomErrorPolynomialCoefficient class (cosmologyFunctionsClass ), pointer :: cosmologyFunctions_ - class (galacticStructureClass ), pointer :: galacticStructure_ class (outputTimesClass ), pointer :: outputTimes_ double precision :: ratioEarlyType , ratioEarlyTypeError , & & randomErrorMinimum , randomErrorMaximum @@ -118,20 +115,18 @@ function morphologicalFractionGAMAMoffett2016ConstructorParameters(parameters) r - !!] ! Build the object. - self=outputAnalysisMorphologicalFractionGAMAMoffett2016(ratioEarlyType,ratioEarlyTypeError,systematicErrorPolynomialCoefficient,randomErrorPolynomialCoefficient,randomErrorMinimum,randomErrorMaximum,cosmologyFunctions_,outputTimes_,galacticStructure_) + self=outputAnalysisMorphologicalFractionGAMAMoffett2016(ratioEarlyType,ratioEarlyTypeError,systematicErrorPolynomialCoefficient,randomErrorPolynomialCoefficient,randomErrorMinimum,randomErrorMaximum,cosmologyFunctions_,outputTimes_) !![ - !!] return end function morphologicalFractionGAMAMoffett2016ConstructorParameters - function morphologicalFractionGAMAMoffett2016ConstructorInternal(ratioEarlyType,ratioEarlyTypeError,systematicErrorPolynomialCoefficient,randomErrorPolynomialCoefficient,randomErrorMinimum,randomErrorMaximum,cosmologyFunctions_,outputTimes_,galacticStructure_) result (self) + function morphologicalFractionGAMAMoffett2016ConstructorInternal(ratioEarlyType,ratioEarlyTypeError,systematicErrorPolynomialCoefficient,randomErrorPolynomialCoefficient,randomErrorMinimum,randomErrorMaximum,cosmologyFunctions_,outputTimes_) result (self) !!{ Constructor for the ``morphologicalFractionGAMAMoffett2016'' output analysis class for internal use. !!} @@ -159,7 +154,6 @@ function morphologicalFractionGAMAMoffett2016ConstructorInternal(ratioEarlyType, double precision , intent(in ), dimension(: ) :: systematicErrorPolynomialCoefficient , randomErrorPolynomialCoefficient class (cosmologyFunctionsClass ), intent(inout), target :: cosmologyFunctions_ class (outputTimesClass ), intent(inout), target :: outputTimes_ - class (galacticStructureClass ), intent(in ), target :: galacticStructure_ integer , parameter :: covarianceBinomialBinsPerDecade =10 double precision , parameter :: covarianceBinomialMassHaloMinimum = 1.000d08 , covarianceBinomialMassHaloMaximum=1.0d16 double precision , allocatable , dimension(: ) :: masses , functionValueTarget @@ -189,7 +183,7 @@ function morphologicalFractionGAMAMoffett2016ConstructorInternal(ratioEarlyType, type (hdf5Object ) :: dataFile double precision :: probit,sqrtArg !![ - + !!] ! Read masses at which fraction was measured. @@ -356,12 +350,12 @@ function morphologicalFractionGAMAMoffett2016ConstructorInternal(ratioEarlyType, ! Create a stellar mass property extractor. allocate(nodePropertyExtractor_ ) !![ - + !!] ! Create a morphology weight property extractor. allocate(outputAnalysisWeightPropertyExtractor_ ) !![ - + !!] ! Build the object. self%outputAnalysisMeanFunction1D=outputAnalysisMeanFunction1D( & @@ -431,7 +425,6 @@ subroutine morphologicalFractionGAMAMoffett2016Destructor(self) !![ - !!] return end subroutine morphologicalFractionGAMAMoffett2016Destructor diff --git a/source/output.analyses.progenitor_mass_functions.F90 b/source/output.analyses.progenitor_mass_functions.F90 index 22aee95e3e..f197adaa73 100644 --- a/source/output.analyses.progenitor_mass_functions.F90 +++ b/source/output.analyses.progenitor_mass_functions.F90 @@ -46,7 +46,6 @@ A dark matter halo progenitor mass function output analysis class. class (cosmologyParametersClass ), pointer :: cosmologyParameters_ => null() class (nbodyHaloMassErrorClass ), pointer :: nbodyHaloMassError_ => null() class (virialDensityContrastClass ), pointer :: virialDensityContrastDefinition_ => null(), virialDensityContrast_ => null() - class (darkMatterProfileDMOClass ), pointer :: darkMatterProfileDMO_ => null() type (galacticFilterAll ), pointer :: galacticFilterParentMass_ => null() type (outputAnalysisWeightOperatorNbodyMass), pointer :: outputAnalysisWeightOperatorNbodyMass_ => null() type (nodePropertyExtractorMassHalo ), pointer :: nodePropertyExtractorMassParent_ => null() @@ -99,7 +98,6 @@ function progenitorMassFunctionConstructorParameters(parameters) result (self) class (cosmologyParametersClass ), pointer :: cosmologyParameters_ class (nbodyHaloMassErrorClass ), pointer :: nbodyHaloMassError_ class (outputTimesClass ), pointer :: outputTimes_ - class (darkMatterProfileDMOClass ), pointer :: darkMatterProfileDMO_ class (virialDensityContrastClass ), pointer :: virialDensityContrastDefinition_, virialDensityContrast_ double precision , dimension(: ), allocatable :: functionValueTarget , functionCovarianceTarget1D, & & rootVarianceTargetFractional @@ -119,7 +117,6 @@ function progenitorMassFunctionConstructorParameters(parameters) result (self) !![ - @@ -204,7 +201,7 @@ The name of the file from which to read progenitor mass function pa Label for the target dataset. !!] - self=outputAnalysisProgenitorMassFunction(char(fileName),label,comment,targetLabel,indexParent,indexRedshift,redshiftParent,massRatioLikelihoodMinimum,massRatioLikelihoodMaximum,covarianceDiagonalize,covarianceTargetOnly,rootVarianceTargetFractional,likelihoodInLog,alwaysIsolatedOnly,cosmologyFunctions_,cosmologyParameters_,darkMatterProfileDMO_,virialDensityContrast_,virialDensityContrastDefinition_,nbodyHaloMassError_,outputTimes_) + self=outputAnalysisProgenitorMassFunction(char(fileName),label,comment,targetLabel,indexParent,indexRedshift,redshiftParent,massRatioLikelihoodMinimum,massRatioLikelihoodMaximum,covarianceDiagonalize,covarianceTargetOnly,rootVarianceTargetFractional,likelihoodInLog,alwaysIsolatedOnly,cosmologyFunctions_,cosmologyParameters_,virialDensityContrast_,virialDensityContrastDefinition_,nbodyHaloMassError_,outputTimes_) else !![ @@ -308,7 +305,6 @@ The target function covariance for likelihood calculations.The target function covariance for likelihood calculations. - @@ -335,7 +330,7 @@ The target function covariance for likelihood calculations. !!] return end function progenitorMassFunctionConstructorFile - function progenitorMassFunctionConstructorInternal(label,comment,massRatioMinimum,massRatioMaximum,countMassRatio,massParentMinimum,massParentMaximum,timeProgenitor,timeParent,alwaysIsolatedOnly,massRatioLikelihoodMinimum,massRatioLikelihoodMaximum,covarianceDiagonalize,covarianceTargetOnly,rootVarianceTargetFractional,likelihoodInLog,cosmologyParameters_,cosmologyFunctions_,darkMatterProfileDMO_,virialDensityContrast_,virialDensityContrastDefinition_,nbodyHaloMassError_,outputTimes_,targetLabel,functionValueTarget,functionCovarianceTarget) result(self) + function progenitorMassFunctionConstructorInternal(label,comment,massRatioMinimum,massRatioMaximum,countMassRatio,massParentMinimum,massParentMaximum,timeProgenitor,timeParent,alwaysIsolatedOnly,massRatioLikelihoodMinimum,massRatioLikelihoodMaximum,covarianceDiagonalize,covarianceTargetOnly,rootVarianceTargetFractional,likelihoodInLog,cosmologyParameters_,cosmologyFunctions_,virialDensityContrast_,virialDensityContrastDefinition_,nbodyHaloMassError_,outputTimes_,targetLabel,functionValueTarget,functionCovarianceTarget) result(self) !!{ Internal constructor for the ``progenitorMassFunction'' output analysis class. !!} @@ -453,7 +447,6 @@ function progenitorMassFunctionConstructorInternal(label,comment,massRatioMinimu double precision , intent(in ) :: massRatioLikelihoodMinimum , massRatioLikelihoodMaximum class (cosmologyParametersClass ), intent(inout), target :: cosmologyParameters_ class (cosmologyFunctionsClass ), intent(inout), target :: cosmologyFunctions_ - class (darkMatterProfileDMOClass ), intent(in ), target :: darkMatterProfileDMO_ class (outputTimesClass ), intent(inout), target :: outputTimes_ class (nbodyHaloMassErrorClass ), intent(in ), target :: nbodyHaloMassError_ class (virialDensityContrastClass ), intent(in ), target :: virialDensityContrastDefinition_ , virialDensityContrast_ @@ -492,7 +485,7 @@ function progenitorMassFunctionConstructorInternal(label,comment,massRatioMinimu type (outputAnalysisPropertyOperatorIdentity ), pointer :: outputAnalysisPropertyIdentity_ integer (c_size_t ) :: iOutput , bufferCount !![ - + !!] ! Initialize state. @@ -547,9 +540,9 @@ function progenitorMassFunctionConstructorInternal(label,comment,massRatioMinimu end if !![ - - - + + + @@ -561,10 +554,10 @@ function progenitorMassFunctionConstructorInternal(label,comment,massRatioMinimu allocate( nodePropertyExtractorMassRatio_ ) allocate( nodePropertyExtractorParentNode_ ) !![ - - - - + + + + !!] ! Create a distribution normalizer which normalizes to bin width. allocate(outputAnalysisDistributionNormalizerBinWidth_ ) @@ -708,7 +701,6 @@ subroutine progenitorMassFunctionDestructor(self) - diff --git a/source/output.analyses.quiescent_fraction.F90 b/source/output.analyses.quiescent_fraction.F90 index 23c75739de..8209696040 100644 --- a/source/output.analyses.quiescent_fraction.F90 +++ b/source/output.analyses.quiescent_fraction.F90 @@ -41,7 +41,6 @@ class (cosmologyFunctionsClass ), pointer :: cosmologyFunctions_ => null(), cosmologyFunctionsData => null() class (starFormationRateDisksClass ), pointer :: starFormationRateDisks_ => null() class (starFormationRateSpheroidsClass), pointer :: starFormationRateSpheroids_ => null() - class (galacticStructureClass ), pointer :: galacticStructure_ => null() type (varying_string ) :: fileName double precision :: massMinimum , massMaximum , & & starFormationRateSpecificQuiescentLogarithmic , starFormationRateSpecificLogarithmicError , & @@ -63,10 +62,9 @@ function quiescentFractionConstructorParameters(parameters) result (self) !!{ Constructor for the ``quiescentFraction'' output analysis class which takes a parameter set as input. !!} - use :: Error , only : Error_Report - use :: Input_Parameters , only : inputParameter , inputParameters - use :: Numerical_Ranges , only : Make_Range , rangeTypeLogarithmic - use :: Galactic_Structure, only : galacticStructureClass + use :: Error , only : Error_Report + use :: Input_Parameters, only : inputParameter, inputParameters + use :: Numerical_Ranges, only : Make_Range , rangeTypeLogarithmic implicit none type (outputAnalysisQuiescentFraction ) :: self type (inputParameters ), intent(inout) :: parameters @@ -78,7 +76,6 @@ function quiescentFractionConstructorParameters(parameters) result (self) class (starFormationRateSpheroidsClass ), pointer :: starFormationRateSpheroids_ class (outputAnalysisPropertyOperatorClass ), pointer :: outputAnalysisPropertyOperator_ , outputAnalysisWeightPropertyOperator_ class (outputAnalysisDistributionOperatorClass), pointer :: outputAnalysisDistributionOperator_ - class (galacticStructureClass ), pointer :: galacticStructure_ double precision , dimension(: ), allocatable :: meanValueTarget , meanCovarianceTarget1D , & & massesStellar double precision , dimension(:,:), allocatable :: meanCovarianceTarget @@ -102,7 +99,6 @@ function quiescentFractionConstructorParameters(parameters) result (self) - starFormationRateSpecificQuiescentLogarithmic parameters @@ -132,7 +128,7 @@ The name of the file from which to read quiescent fraction function A label for this analysis. !!] - self=outputAnalysisQuiescentFraction(char(fileName),label,comment,starFormationRateSpecificQuiescentLogarithmic,starFormationRateSpecificLogarithmicError,galacticFilter_,surveyGeometry_,cosmologyFunctions_,cosmologyFunctionsData,outputTimes_,outputAnalysisPropertyOperator_,outputAnalysisDistributionOperator_,outputAnalysisWeightPropertyOperator_,starFormationRateDisks_,starFormationRateSpheroids_,galacticStructure_) + self=outputAnalysisQuiescentFraction(char(fileName),label,comment,starFormationRateSpecificQuiescentLogarithmic,starFormationRateSpecificLogarithmicError,galacticFilter_,surveyGeometry_,cosmologyFunctions_,cosmologyFunctionsData,outputTimes_,outputAnalysisPropertyOperator_,outputAnalysisDistributionOperator_,outputAnalysisWeightPropertyOperator_,starFormationRateDisks_,starFormationRateSpheroids_) else !![ @@ -221,8 +217,7 @@ The target function covariance for likelihood calculations. @@ -244,12 +239,11 @@ The target function covariance for likelihood calculations. - !!] return end function quiescentFractionConstructorParameters - function quiescentFractionConstructorFile(fileName,label,comment,starFormationRateSpecificQuiescentLogarithmic,starFormationRateSpecificLogarithmicError,galacticFilter_,surveyGeometry_,cosmologyFunctions_,cosmologyFunctionsData,outputTimes_,outputAnalysisPropertyOperator_,outputAnalysisDistributionOperator_,outputAnalysisWeightPropertyOperator_,starFormationRateDisks_,starFormationRateSpheroids_,galacticStructure_) result(self) + function quiescentFractionConstructorFile(fileName,label,comment,starFormationRateSpecificQuiescentLogarithmic,starFormationRateSpecificLogarithmicError,galacticFilter_,surveyGeometry_,cosmologyFunctions_,cosmologyFunctionsData,outputTimes_,outputAnalysisPropertyOperator_,outputAnalysisDistributionOperator_,outputAnalysisWeightPropertyOperator_,starFormationRateDisks_,starFormationRateSpheroids_) result(self) !!{ Constructor for the ``quiescentFraction'' output analysis class which reads all required properties from file. !!} @@ -269,7 +263,6 @@ function quiescentFractionConstructorFile(fileName,label,comment,starFormationRa class (outputAnalysisPropertyOperatorClass ), intent(inout), target :: outputAnalysisPropertyOperator_ class (outputAnalysisPropertyOperatorClass ), intent(inout), target :: outputAnalysisWeightPropertyOperator_ class (outputAnalysisDistributionOperatorClass), intent(inout), target :: outputAnalysisDistributionOperator_ - class (galacticStructureClass ), intent(in ), target :: galacticStructure_ double precision , intent(in ) :: starFormationRateSpecificQuiescentLogarithmic, starFormationRateSpecificLogarithmicError double precision , allocatable , dimension(: ) :: meanValueTarget , massesStellar double precision , allocatable , dimension(:,:) :: meanCovarianceTarget @@ -285,14 +278,14 @@ function quiescentFractionConstructorFile(fileName,label,comment,starFormationRa call dataFile%close ( ) !$ call hdf5Access%unset() ! Build the object. - self=quiescentFractionConstructorInternal(label,comment,massesStellar,starFormationRateSpecificQuiescentLogarithmic,starFormationRateSpecificLogarithmicError,galacticFilter_,surveyGeometry_,cosmologyFunctions_,cosmologyFunctionsData,outputTimes_,outputAnalysisPropertyOperator_,outputAnalysisDistributionOperator_,outputAnalysisWeightPropertyOperator_,starFormationRateDisks_,starFormationRateSpheroids_,galacticStructure_,targetLabel,meanValueTarget,meanCovarianceTarget) + self=quiescentFractionConstructorInternal(label,comment,massesStellar,starFormationRateSpecificQuiescentLogarithmic,starFormationRateSpecificLogarithmicError,galacticFilter_,surveyGeometry_,cosmologyFunctions_,cosmologyFunctionsData,outputTimes_,outputAnalysisPropertyOperator_,outputAnalysisDistributionOperator_,outputAnalysisWeightPropertyOperator_,starFormationRateDisks_,starFormationRateSpheroids_,targetLabel,meanValueTarget,meanCovarianceTarget) !![ !!] return end function quiescentFractionConstructorFile - function quiescentFractionConstructorInternal(label,comment,massesStellar,starFormationRateSpecificQuiescentLogarithmic,starFormationRateSpecificLogarithmicError,galacticFilter_,surveyGeometry_,cosmologyFunctions_,cosmologyFunctionsData,outputTimes_,outputAnalysisPropertyOperator_,outputAnalysisDistributionOperator_,outputAnalysisWeightPropertyOperator_,starFormationRateDisks_,starFormationRateSpheroids_,galacticStructure_,targetLabel,meanValueTarget,meanCovarianceTarget) result(self) + function quiescentFractionConstructorInternal(label,comment,massesStellar,starFormationRateSpecificQuiescentLogarithmic,starFormationRateSpecificLogarithmicError,galacticFilter_,surveyGeometry_,cosmologyFunctions_,cosmologyFunctionsData,outputTimes_,outputAnalysisPropertyOperator_,outputAnalysisDistributionOperator_,outputAnalysisWeightPropertyOperator_,starFormationRateDisks_,starFormationRateSpheroids_,targetLabel,meanValueTarget,meanCovarianceTarget) result(self) !!{ Internal constructor for the ``quiescentFraction'' output analysis class. !!} @@ -319,7 +312,6 @@ function quiescentFractionConstructorInternal(label,comment,massesStellar,starFo class (outputAnalysisDistributionOperatorClass ), intent(inout), target :: outputAnalysisDistributionOperator_ class (starFormationRateDisksClass ), intent(in ), target :: starFormationRateDisks_ class (starFormationRateSpheroidsClass ), intent(in ), target :: starFormationRateSpheroids_ - class (galacticStructureClass ), intent(in ), target :: galacticStructure_ type (varying_string ), optional , intent(in ) :: targetLabel double precision , optional , dimension(: ), intent(in ) :: meanValueTarget , massesStellar double precision , optional , dimension(:,:), intent(in ) :: meanCovarianceTarget @@ -342,7 +334,7 @@ function quiescentFractionConstructorInternal(label,comment,massesStellar,starFo integer (c_size_t ) :: iBin , bufferCount , & & countMasses !![ - + !!] ! Set properties needed for descriptor. @@ -430,7 +422,7 @@ function quiescentFractionConstructorInternal(label,comment,massesStellar,starFo !![ - nodePropertyExtractorMassStellar(galacticStructure_) + nodePropertyExtractorMassStellar() !!] diff --git a/source/output.analyses.quiescent_fraction.Wagner2016.F90 b/source/output.analyses.quiescent_fraction.Wagner2016.F90 index e8e44fab5f..e2282744c1 100644 --- a/source/output.analyses.quiescent_fraction.Wagner2016.F90 +++ b/source/output.analyses.quiescent_fraction.Wagner2016.F90 @@ -46,7 +46,6 @@ !!} private class (cosmologyParametersClass ), pointer :: cosmologyParameters_ => null() - class (darkMatterProfileDMOClass ), pointer :: darkMatterProfileDMO_ => null() class (virialDensityContrastClass ), pointer :: virialDensityContrast_ => null() double precision , allocatable , dimension(:) :: randomErrorPolynomialCoefficient , systematicErrorPolynomialCoefficient, & & weightSystematicErrorPolynomialCoefficient @@ -70,12 +69,10 @@ function quiescentFractionWagner2016ConstructorParameters(parameters) result (se !!{ Constructor for the ``quiescentFractionWagner2016'' output analysis class which takes a parameter set as input. !!} - use :: Cosmology_Parameters , only : cosmologyParameters , cosmologyParametersClass - use :: Cosmology_Functions , only : cosmologyFunctions , cosmologyFunctionsClass - use :: Dark_Matter_Profiles_DMO, only : darkMatterProfileDMOClass - use :: Virial_Density_Contrast , only : virialDensityContrastClass - use :: Galactic_Structure , only : galacticStructureClass - use :: Input_Parameters , only : inputParameter , inputParameters + use :: Cosmology_Parameters , only : cosmologyParameters , cosmologyParametersClass + use :: Cosmology_Functions , only : cosmologyFunctions , cosmologyFunctionsClass + use :: Virial_Density_Contrast, only : virialDensityContrastClass + use :: Input_Parameters , only : inputParameter , inputParameters implicit none type (outputAnalysisQuiescentFractionWagner2016) :: self type (inputParameters ), intent(inout) :: parameters @@ -84,9 +81,7 @@ function quiescentFractionWagner2016ConstructorParameters(parameters) result (se class (outputTimesClass ), pointer :: outputTimes_ class (starFormationRateDisksClass ), pointer :: starFormationRateDisks_ class (starFormationRateSpheroidsClass ), pointer :: starFormationRateSpheroids_ - class (darkMatterProfileDMOClass ), pointer :: darkMatterProfileDMO_ class (virialDensityContrastClass ), pointer :: virialDensityContrast_ - class (galacticStructureClass ), pointer :: galacticStructure_ double precision , allocatable , dimension(:) :: randomErrorPolynomialCoefficient , systematicErrorPolynomialCoefficient, & & weightSystematicErrorPolynomialCoefficient double precision :: randomErrorMinimum , randomErrorMaximum @@ -150,36 +145,31 @@ function quiescentFractionWagner2016ConstructorParameters(parameters) result (se - - !!] - self=outputAnalysisQuiescentFractionWagner2016(enumerationWagner2016QuiescentRedshiftRangeEncode(char(redshiftRange),includesPrefix=.false.),randomErrorMinimum,randomErrorMaximum,randomErrorPolynomialCoefficient,systematicErrorPolynomialCoefficient,weightSystematicErrorPolynomialCoefficient,cosmologyParameters_,cosmologyFunctions_,darkMatterProfileDMO_,virialDensityContrast_,outputTimes_,starFormationRateDisks_,starFormationRateSpheroids_,galacticStructure_) + self=outputAnalysisQuiescentFractionWagner2016(enumerationWagner2016QuiescentRedshiftRangeEncode(char(redshiftRange),includesPrefix=.false.),randomErrorMinimum,randomErrorMaximum,randomErrorPolynomialCoefficient,systematicErrorPolynomialCoefficient,weightSystematicErrorPolynomialCoefficient,cosmologyParameters_,cosmologyFunctions_,virialDensityContrast_,outputTimes_,starFormationRateDisks_,starFormationRateSpheroids_) !![ - - !!] return end function quiescentFractionWagner2016ConstructorParameters - function quiescentFractionWagner2016ConstructorInternal(redshiftRange,randomErrorMinimum,randomErrorMaximum,randomErrorPolynomialCoefficient,systematicErrorPolynomialCoefficient,weightSystematicErrorPolynomialCoefficient,cosmologyParameters_,cosmologyFunctions_,darkMatterProfileDMO_,virialDensityContrast_,outputTimes_,starFormationRateDisks_,starFormationRateSpheroids_,galacticStructure_) result(self) + function quiescentFractionWagner2016ConstructorInternal(redshiftRange,randomErrorMinimum,randomErrorMaximum,randomErrorPolynomialCoefficient,systematicErrorPolynomialCoefficient,weightSystematicErrorPolynomialCoefficient,cosmologyParameters_,cosmologyFunctions_,virialDensityContrast_,outputTimes_,starFormationRateDisks_,starFormationRateSpheroids_) result(self) !!{ Internal constructor for the ``quiescentFractionWagner2016'' output analysis class. !!} use :: Error , only : Error_Report use :: Cosmology_Functions , only : cosmologyFunctionsMatterLambda use :: Cosmology_Parameters , only : cosmologyParametersSimple - use :: Dark_Matter_Profiles_DMO , only : darkMatterProfileDMOClass use :: Input_Paths , only : inputPath , pathTypeDataStatic use :: Output_Times , only : outputTimesClass use :: Statistics_NBody_Halo_Mass_Errors , only : nbodyHaloMassErrorClass @@ -203,8 +193,6 @@ function quiescentFractionWagner2016ConstructorInternal(redshiftRange,randomErro class (starFormationRateDisksClass ), intent(in ), target :: starFormationRateDisks_ class (starFormationRateSpheroidsClass ), intent(in ), target :: starFormationRateSpheroids_ class (virialDensityContrastClass ), intent(in ), target :: virialDensityContrast_ - class (darkMatterProfileDMOClass ), intent(in ), target :: darkMatterProfileDMO_ - class (galacticStructureClass ), intent(in ), target :: galacticStructure_ type (galacticFilterHaloNotIsolated ) , pointer :: galacticFilterIsSubhalo_ type (galacticFilterHighPass ) , pointer :: galacticFilterHostHaloMass_ type (galacticFilterStellarMass ) , pointer :: galacticFilterStellarMass_ @@ -228,7 +216,7 @@ function quiescentFractionWagner2016ConstructorInternal(redshiftRange,randomErro type (varying_string ) :: fileName , label , & & description !![ - + !!] ! Construct file name and label for the analysis. @@ -282,7 +270,7 @@ function quiescentFractionWagner2016ConstructorInternal(redshiftRange,randomErro !!] allocate(nodePropertyExtractorHostMass_) !![ - + !!] allocate(nodePropertyExtractorHost_) !![ @@ -371,8 +359,7 @@ function quiescentFractionWagner2016ConstructorInternal(redshiftRange,randomErro & outputAnalysisDistributionOperator_ , & & outputAnalysisWeightPropertyOperator_ , & & starFormationRateDisks_ , & - & starFormationRateSpheroids_ , & - & galacticStructure_ & + & starFormationRateSpheroids_ & & ) !![ @@ -399,7 +386,6 @@ subroutine quiescentFractionWagner2016Destructor(self) !![ - !!] return diff --git a/source/output.analyses.satellite_radius_velocity_maximum.F90 b/source/output.analyses.satellite_radius_velocity_maximum.F90 index 040f281f5d..94652c2fc4 100644 --- a/source/output.analyses.satellite_radius_velocity_maximum.F90 +++ b/source/output.analyses.satellite_radius_velocity_maximum.F90 @@ -180,17 +180,26 @@ subroutine satelliteRadiusVelocityMaximumAnalyze(self,node,iOutput) Analyze the maximum velocity tidal track. !!} use :: Numerical_Constants_Math, only : Pi - !$ use :: OMP_Lib , only : OMP_Set_Lock, OMP_Unset_Lock + use :: Mass_Distributions , only : massDistributionClass + !$ use :: OMP_Lib , only : OMP_Set_Lock , OMP_Unset_Lock implicit none class (outputAnalysisSatelliteRadiusVelocityMaximum), intent(inout) :: self type (treeNode ), intent(inout) :: node integer (c_size_t ), intent(in ) :: iOutput + class (massDistributionClass ), pointer :: massDistributionUnheated_ , massDistribution_ double precision :: fractionRadiusVelocityMaximum, varianceFractionRadiusVelocityMaximum ! Skip non-satellites. if (.not.node%isSatellite()) return ! Extract the maximum circular velocity fraction. - fractionRadiusVelocityMaximum=self%darkMatterProfileDMO_%radiusCircularVelocityMaximum(node)/self%darkMatterProfileDMOUnheated%radiusCircularVelocityMaximum(node) + massDistribution_ => self%darkMatterProfileDMO_ %get(node) + massDistributionUnheated_ => self%darkMatterProfileDMOUnheated%get(node) + fractionRadiusVelocityMaximum=+massDistribution_ %radiusRotationCurveMaximum() & + & /massDistributionUnheated_%radiusRotationCurveMaximum() + !![ + + + !!] !$ call OMP_Set_Lock(self%accumulateLock) self%fractionRadiusVelocityMaximum(iOutput)=fractionRadiusVelocityMaximum ! Add model uncertainty. diff --git a/source/output.analyses.satellite_velocity_maximum.F90 b/source/output.analyses.satellite_velocity_maximum.F90 index 7264df3a13..19b321353e 100644 --- a/source/output.analyses.satellite_velocity_maximum.F90 +++ b/source/output.analyses.satellite_velocity_maximum.F90 @@ -180,18 +180,26 @@ subroutine satelliteVelocityMaximumAnalyze(self,node,iOutput) Analyze the maximum velocity tidal track. !!} use :: Numerical_Constants_Math, only : Pi - !$ use :: OMP_Lib , only : OMP_Set_Lock, OMP_Unset_Lock + use :: Mass_Distributions , only : massDistributionClass + !$ use :: OMP_Lib , only : OMP_Set_Lock , OMP_Unset_Lock implicit none class (outputAnalysisSatelliteVelocityMaximum), intent(inout) :: self type (treeNode ), intent(inout) :: node integer (c_size_t ), intent(in ) :: iOutput - double precision :: fractionVelocityMaximum, varianceFractionVelocityMaximum + class (massDistributionClass ), pointer :: massDistributionUnheated_, massDistribution_ + double precision :: fractionVelocityMaximum , varianceFractionVelocityMaximum ! Skip non-satellites. if (.not.node%isSatellite()) return ! Extract the maximum circular velocity fraction. - fractionVelocityMaximum=self%darkMatterProfileDMO_%circularVelocityMaximum(node)/self%darkMatterProfileDMOUnheated%circularVelocityMaximum(node) - !$ call OMP_Set_Lock(self%accumulateLock) + massDistribution_ => self%darkMatterProfileDMO_ %get(node) + massDistributionUnheated_ => self%darkMatterProfileDMOUnheated%get(node) + fractionVelocityMaximum=+massDistribution_ %velocityRotationCurveMaximum() & + & /massDistributionUnheated_%velocityRotationCurveMaximum() + !![ + + + !!] self%fractionVelocityMaximum(iOutput)=fractionVelocityMaximum ! Add model uncertainty. varianceFractionVelocityMaximum =+ self%varianceFractionVelocityMaximumTarget(iOutput) & diff --git a/source/output.analyses.spin_distribution.Bett2007.F90 b/source/output.analyses.spin_distribution.Bett2007.F90 index 028d9de054..616682ea00 100644 --- a/source/output.analyses.spin_distribution.Bett2007.F90 +++ b/source/output.analyses.spin_distribution.Bett2007.F90 @@ -60,7 +60,6 @@ function spinDistributionBett2007ConstructorParameters(parameters) result (self) class (nbodyHaloMassErrorClass ), pointer :: nbodyHaloMassError_ class (haloMassFunctionClass ), pointer :: haloMassFunction_ class (darkMatterHaloScaleClass ), pointer :: darkMatterHaloScale_ - class (darkMatterProfileDMOClass ), pointer :: darkMatterProfileDMO_ class (darkMatterProfileScaleRadiusClass ), pointer :: darkMatterProfileScaleRadius_ class (virialDensityContrastClass ), pointer :: virialDensityContrast_ class (* ), pointer :: percolationObjects_ @@ -87,12 +86,11 @@ function spinDistributionBett2007ConstructorParameters(parameters) result (self) - !!] percolationObjects_ => Virial_Density_Contrast_Percolation_Objects_Constructor_(parameters) - self = outputAnalysisSpinDistributionBett2007(logNormalRange,errorTolerant,cosmologyParameters_,cosmologyFunctions_,nbodyHaloMassError_,haloMassFunction_,darkMatterHaloScale_,darkMatterProfileDMO_,darkMatterProfileScaleRadius_,virialDensityContrast_,outputTimes_,percolationObjects_) + self = outputAnalysisSpinDistributionBett2007(logNormalRange,errorTolerant,cosmologyParameters_,cosmologyFunctions_,nbodyHaloMassError_,haloMassFunction_,darkMatterHaloScale_,darkMatterProfileScaleRadius_,virialDensityContrast_,outputTimes_,percolationObjects_) !![ @@ -101,14 +99,13 @@ function spinDistributionBett2007ConstructorParameters(parameters) result (self) - !!] return end function spinDistributionBett2007ConstructorParameters - function spinDistributionBett2007ConstructorInternal(logNormalRange,errorTolerant,cosmologyParameters_,cosmologyFunctions_,nbodyHaloMassError_,haloMassFunction_,darkMatterHaloScale_,darkMatterProfileDMO_,darkMatterProfileScaleRadius_,virialDensityContrast_,outputTimes_,percolationObjects_) result(self) + function spinDistributionBett2007ConstructorInternal(logNormalRange,errorTolerant,cosmologyParameters_,cosmologyFunctions_,nbodyHaloMassError_,haloMassFunction_,darkMatterHaloScale_,darkMatterProfileScaleRadius_,virialDensityContrast_,outputTimes_,percolationObjects_) result(self) !!{ Internal constructor for the ``spinDistributionBett2007'' output analysis class. !!} @@ -129,7 +126,6 @@ function spinDistributionBett2007ConstructorInternal(logNormalRange,errorToleran class (nbodyHaloMassErrorClass ), target , intent(in ) :: nbodyHaloMassError_ class (haloMassFunctionClass ), target , intent(in ) :: haloMassFunction_ class (darkMatterHaloScaleClass ), target , intent(in ) :: darkMatterHaloScale_ - class (darkMatterProfileDMOClass ), target , intent(in ) :: darkMatterProfileDMO_ class (darkMatterProfileScaleRadiusClass ), target , intent(in ) :: darkMatterProfileScaleRadius_ class (virialDensityContrastClass ), target , intent(in ) :: virialDensityContrast_ class (* ), target , intent(in ) :: percolationObjects_ @@ -151,7 +147,6 @@ function spinDistributionBett2007ConstructorInternal(logNormalRange,errorToleran & nbodyHaloMassError_ , & & haloMassFunction_ , & & darkMatterHaloScale_ , & - & darkMatterProfileDMO_ , & & darkMatterProfileScaleRadius_ , & & outputTimes_ , & & virialDensityContrast_ , & diff --git a/source/output.analyses.spin_distribution.F90 b/source/output.analyses.spin_distribution.F90 index 557a6e156a..7f5ae7f61c 100644 --- a/source/output.analyses.spin_distribution.F90 +++ b/source/output.analyses.spin_distribution.F90 @@ -39,7 +39,6 @@ A stellar mass function output analysis class. class (nbodyHaloMassErrorClass ), pointer :: nbodyHaloMassError_ => null() class (haloMassFunctionClass ), pointer :: haloMassFunction_ => null() class (darkMatterHaloScaleClass ), pointer :: darkMatterHaloScale_ => null() - class (darkMatterProfileDMOClass ), pointer :: darkMatterProfileDMO_ => null() class (darkMatterProfileScaleRadiusClass), pointer :: darkMatterProfileScaleRadius_ => null() class (virialDensityContrastClass ), pointer :: virialDensityContrast_ => null(), virialDensityContrastDefinition_ => null() double precision :: timeRecent , logNormalRange , & @@ -82,7 +81,6 @@ function spinDistributionConstructorParameters(parameters) result (self) class (nbodyHaloMassErrorClass ), pointer :: nbodyHaloMassError_ class (haloMassFunctionClass ), pointer :: haloMassFunction_ class (darkMatterHaloScaleClass ), pointer :: darkMatterHaloScale_ - class (darkMatterProfileDMOClass ), pointer :: darkMatterProfileDMO_ class (darkMatterProfileScaleRadiusClass), pointer :: darkMatterProfileScaleRadius_ class (virialDensityContrastClass ), pointer :: virialDensityContrast_ , virialDensityContrastDefinition_ double precision , dimension(: ), allocatable :: functionValueTarget , functionCovarianceTarget1D @@ -118,7 +116,6 @@ function spinDistributionConstructorParameters(parameters) result (self) - @@ -142,7 +139,7 @@ The name of the file from which to read spin distribution function A label for this analysis. !!] - self=outputAnalysisSpinDistribution(char(fileName),label,comment,logNormalRange,errorTolerant,cosmologyParameters_,cosmologyFunctions_,nbodyHaloMassError_,haloMassFunction_,darkMatterHaloScale_,darkMatterProfileDMO_,darkMatterProfileScaleRadius_,outputTimes_,virialDensityContrast_,virialDensityContrastDefinition_) + self=outputAnalysisSpinDistribution(char(fileName),label,comment,logNormalRange,errorTolerant,cosmologyParameters_,cosmologyFunctions_,nbodyHaloMassError_,haloMassFunction_,darkMatterHaloScale_,darkMatterProfileScaleRadius_,outputTimes_,virialDensityContrast_,virialDensityContrastDefinition_) else !![ @@ -269,7 +266,6 @@ The target function covariance for likelihood calculations.The target function covariance for likelihood calculations. - @@ -299,7 +294,7 @@ The target function covariance for likelihood calculations. + !!] ! Build grid of spins. @@ -477,7 +470,6 @@ function spinDistributionConstructorInternal(label,comment,time,massMinimum,mass & cosmologyFunctions_ =cosmologyFunctions_ , & & haloMassFunction_ =haloMassFunction_ , & & darkMatterHaloScale_ =darkMatterHaloScale_ , & - & darkMatterProfileDMO_ =darkMatterProfileDMO_ , & & darkMatterProfileScaleRadius_ =darkMatterProfileScaleRadius_ & & ) @@ -486,7 +478,7 @@ function spinDistributionConstructorInternal(label,comment,time,massMinimum,mass ! Create a spin parameter property extractor. allocate(nodePropertyExtractor_ ) !![ - + !!] ! Create a log10 property operator. allocate(outputAnalysisPropertyOperator_ ) @@ -515,7 +507,7 @@ function spinDistributionConstructorInternal(label,comment,time,massMinimum,mass !!] allocate(galacticFilterHaloMassRange_ ) !![ - + !!] allocate(galacticFilterNodeMajorMergerRecent_ ) !![ @@ -694,7 +686,6 @@ subroutine spinDistributionDestructor(self) - diff --git a/source/output.analyses.star_formation_rate_function.F90 b/source/output.analyses.star_formation_rate_function.F90 index de9d072d6f..296b56d2a4 100755 --- a/source/output.analyses.star_formation_rate_function.F90 +++ b/source/output.analyses.star_formation_rate_function.F90 @@ -39,7 +39,6 @@ A stellar mass function output analysis class. private class (surveyGeometryClass ), pointer :: surveyGeometry_ => null() class (cosmologyFunctionsClass ), pointer :: cosmologyFunctions_ => null(), cosmologyFunctionsData => null() - class (galacticStructureClass ), pointer :: galacticStructure_ => null() class (starFormationRateDisksClass ), pointer :: starFormationRateDisks_ => null() class (starFormationRateSpheroidsClass), pointer :: starFormationRateSpheroids_ => null() double precision , allocatable, dimension(:) :: starFormationRates @@ -62,9 +61,8 @@ function starFormationRateFunctionConstructorParameters(parameters) result (self !!{ Constructor for the ``starFormationRateFunction'' output analysis class which takes a parameter set as input. !!} - use :: Error , only : Error_Report - use :: Galactic_Structure, only : galacticStructureClass - use :: Input_Parameters , only : inputParameter , inputParameters + use :: Error , only : Error_Report + use :: Input_Parameters, only : inputParameter, inputParameters implicit none type (outputAnalysisStarFormationRateFunction) :: self type (inputParameters ), intent(inout) :: parameters @@ -74,7 +72,6 @@ function starFormationRateFunctionConstructorParameters(parameters) result (self class (outputAnalysisDistributionOperatorClass), pointer :: outputAnalysisDistributionOperator_ class (outputAnalysisPropertyOperatorClass ), pointer :: outputAnalysisPropertyOperator_ class (outputTimesClass ), pointer :: outputTimes_ - class (galacticStructureClass ), pointer :: galacticStructure_ class (starFormationRateDisksClass ), pointer :: starFormationRateDisks_ class (starFormationRateSpheroidsClass ), pointer :: starFormationRateSpheroids_ double precision , dimension(: ), allocatable :: starFormationRates , functionValueTarget , & @@ -171,11 +168,10 @@ The target function covariance for likelihood calculations. - - self=outputAnalysisStarFormationRateFunction(label,comment,starFormationRates,galacticFilter_,surveyGeometry_,cosmologyFunctions_,cosmologyFunctionsData,outputAnalysisPropertyOperator_,outputAnalysisDistributionOperator_,outputTimes_,galacticStructure_,starFormationRateDisks_,starFormationRateSpheroids_,covarianceBinomialBinsPerDecade,covarianceBinomialMassHaloMinimum,covarianceBinomialMassHaloMaximum{conditions}) + self=outputAnalysisStarFormationRateFunction(label,comment,starFormationRates,galacticFilter_,surveyGeometry_,cosmologyFunctions_,cosmologyFunctionsData,outputAnalysisPropertyOperator_,outputAnalysisDistributionOperator_,outputTimes_,starFormationRateDisks_,starFormationRateSpheroids_,covarianceBinomialBinsPerDecade,covarianceBinomialMassHaloMinimum,covarianceBinomialMassHaloMaximum{conditions}) @@ -187,7 +183,6 @@ The target function covariance for likelihood calculations. - @@ -195,7 +190,7 @@ The target function covariance for likelihood calculations. - self=outputAnalysisStarFormationRateFunction(label,comment,starFormationRates,galacticFilter_,surveyGeometry_,cosmologyFunctions_,cosmologyFunctionsData,outputAnalysisPropertyOperator_,outputAnalysisDistributionOperator_,outputTimes_,galacticStructure_,starFormationRateDisks_,starFormationRateSpheroids_,covarianceBinomialBinsPerDecade,covarianceBinomialMassHaloMinimum,covarianceBinomialMassHaloMaximum{conditions}) + self=outputAnalysisStarFormationRateFunction(label,comment,starFormationRates,galacticFilter_,surveyGeometry_,cosmologyFunctions_,cosmologyFunctionsData,outputAnalysisPropertyOperator_,outputAnalysisDistributionOperator_,outputTimes_,starFormationRateDisks_,starFormationRateSpheroids_,covarianceBinomialBinsPerDecade,covarianceBinomialMassHaloMinimum,covarianceBinomialMassHaloMaximum{conditions}) @@ -245,7 +239,7 @@ function starFormationRateFunctionConstructorFile(label,comment,fileName,galacti return end function starFormationRateFunctionConstructorFile - function starFormationRateFunctionConstructorInternal(label,comment,starFormationRates,galacticFilter_,surveyGeometry_,cosmologyFunctions_,cosmologyFunctionsData,outputAnalysisPropertyOperator_,outputAnalysisDistributionOperator_,outputTimes_,galacticStructure_,starFormationRateDisks_,starFormationRateSpheroids_,covarianceBinomialBinsPerDecade,covarianceBinomialMassHaloMinimum,covarianceBinomialMassHaloMaximum,targetLabel,functionValueTarget,functionCovarianceTarget) result(self) + function starFormationRateFunctionConstructorInternal(label,comment,starFormationRates,galacticFilter_,surveyGeometry_,cosmologyFunctions_,cosmologyFunctionsData,outputAnalysisPropertyOperator_,outputAnalysisDistributionOperator_,outputTimes_,starFormationRateDisks_,starFormationRateSpheroids_,covarianceBinomialBinsPerDecade,covarianceBinomialMassHaloMinimum,covarianceBinomialMassHaloMaximum,targetLabel,functionValueTarget,functionCovarianceTarget) result(self) !!{ Constructor for the ``starFormationRateFunction'' output analysis class which takes a parameter set as input. !!} @@ -270,7 +264,6 @@ function starFormationRateFunctionConstructorInternal(label,comment,starFormatio class (outputAnalysisPropertyOperatorClass ), intent(inout), target :: outputAnalysisPropertyOperator_ class (outputAnalysisDistributionOperatorClass ), intent(in ), target :: outputAnalysisDistributionOperator_ class (outputTimesClass ), intent(inout), target :: outputTimes_ - class (galacticStructureClass ), intent(in ), target :: galacticStructure_ class (starFormationRateDisksClass ), intent(in ), target :: starFormationRateDisks_ class (starFormationRateSpheroidsClass ), intent(in ), target :: starFormationRateSpheroids_ integer , intent(in ) :: covarianceBinomialBinsPerDecade @@ -294,7 +287,7 @@ function starFormationRateFunctionConstructorInternal(label,comment,starFormatio integer (c_size_t ), parameter :: bufferCountMinimum =5 integer (c_size_t ) :: iBin , bufferCount !![ - + !!] ! Compute weights that apply to each output redshift. @@ -429,7 +422,6 @@ subroutine starFormationRateFunctionDestructor(self) - !!] diff --git a/source/output.analyses.star_formation_rate_function.Robotham2011.F90 b/source/output.analyses.star_formation_rate_function.Robotham2011.F90 index bcfd27f8c7..9e17f7149e 100755 --- a/source/output.analyses.star_formation_rate_function.Robotham2011.F90 +++ b/source/output.analyses.star_formation_rate_function.Robotham2011.F90 @@ -56,14 +56,12 @@ function starFormationRateFunctionRobotham2011ConstructorParameters(parameters) !!{ Constructor for the ``StarFormationRateFunctionRobotham2011'' output analysis class which takes a parameter set as input. !!} - use :: Input_Parameters , only : inputParameter , inputParameters - use :: Galactic_Structure, only : galacticStructureClass + use :: Input_Parameters, only : inputParameter, inputParameters implicit none type (outputAnalysisStarFormationRateFunctionRobotham2011) :: self type (inputParameters ), intent(inout) :: parameters class (cosmologyFunctionsClass ), pointer :: cosmologyFunctions_ class (outputTimesClass ), pointer :: outputTimes_ - class (galacticStructureClass ), pointer :: galacticStructure_ class (starFormationRateDisksClass ), pointer :: starFormationRateDisks_ class (starFormationRateSpheroidsClass ), pointer :: starFormationRateSpheroids_ class (gravitationalLensingClass ), pointer :: gravitationalLensing_ @@ -144,25 +142,23 @@ The maximum halo mass to consider when constructing \cite{robotham_ - !!] ! Build the object. - self=outputAnalysisStarFormationRateFunctionRobotham2011(cosmologyFunctions_,gravitationalLensing_,outputTimes_,galacticStructure_,starFormationRateDisks_,starFormationRateSpheroids_,randomErrorMinimum,randomErrorMaximum,randomErrorPolynomialCoefficient,systematicErrorPolynomialCoefficient,covarianceBinomialBinsPerDecade,covarianceBinomialMassHaloMinimum,covarianceBinomialMassHaloMaximum,sizeSourceLensing) + self=outputAnalysisStarFormationRateFunctionRobotham2011(cosmologyFunctions_,gravitationalLensing_,outputTimes_,starFormationRateDisks_,starFormationRateSpheroids_,randomErrorMinimum,randomErrorMaximum,randomErrorPolynomialCoefficient,systematicErrorPolynomialCoefficient,covarianceBinomialBinsPerDecade,covarianceBinomialMassHaloMinimum,covarianceBinomialMassHaloMaximum,sizeSourceLensing) !![ - !!] return end function starFormationRateFunctionRobotham2011ConstructorParameters - function starFormationRateFunctionRobotham2011ConstructorInternal(cosmologyFunctions_,gravitationalLensing_,outputTimes_,galacticStructure_,starFormationRateDisks_,starFormationRateSpheroids_,randomErrorMinimum,randomErrorMaximum,randomErrorPolynomialCoefficient,systematicErrorPolynomialCoefficient,covarianceBinomialBinsPerDecade,covarianceBinomialMassHaloMinimum,covarianceBinomialMassHaloMaximum,sizeSourceLensing) result (self) + function starFormationRateFunctionRobotham2011ConstructorInternal(cosmologyFunctions_,gravitationalLensing_,outputTimes_,starFormationRateDisks_,starFormationRateSpheroids_,randomErrorMinimum,randomErrorMaximum,randomErrorPolynomialCoefficient,systematicErrorPolynomialCoefficient,covarianceBinomialBinsPerDecade,covarianceBinomialMassHaloMinimum,covarianceBinomialMassHaloMaximum,sizeSourceLensing) result (self) !!{ Constructor for the ``StarFormationRateFunctionRobotham2011'' output analysis class for internal use. !!} @@ -179,7 +175,6 @@ function starFormationRateFunctionRobotham2011ConstructorInternal(cosmologyFunct class (cosmologyFunctionsClass ), intent(in ), target :: cosmologyFunctions_ class (outputTimesClass ), intent(inout), target :: outputTimes_ class (gravitationalLensingClass ), intent(in ), target :: gravitationalLensing_ - class (galacticStructureClass ), intent(in ), target :: galacticStructure_ class (starFormationRateDisksClass ), intent(in ), target :: starFormationRateDisks_ class (starFormationRateSpheroidsClass ), intent(in ), target :: starFormationRateSpheroids_ double precision , intent(in ) :: randomErrorMinimum , randomErrorMaximum , & @@ -295,7 +290,6 @@ function starFormationRateFunctionRobotham2011ConstructorInternal(cosmologyFunct & outputAnalysisPropertyOperator_ , & & outputAnalysisDistributionOperator_ , & & outputTimes_ , & - & galacticStructure_ , & & starFormationRateDisks_ , & & starFormationRateSpheroids_ , & & covarianceBinomialBinsPerDecade , & @@ -325,7 +319,6 @@ subroutine starFormationRateFunctionRobotham2011Destructor(self) type(outputAnalysisStarFormationRateFunctionRobotham2011), intent(inout) :: self !![ - !!] return diff --git a/source/output.analyses.star_forming_main_sequence.F90 b/source/output.analyses.star_forming_main_sequence.F90 index 49fc80e69e..0685dd903b 100644 --- a/source/output.analyses.star_forming_main_sequence.F90 +++ b/source/output.analyses.star_forming_main_sequence.F90 @@ -41,7 +41,6 @@ class (cosmologyFunctionsClass ), pointer :: cosmologyFunctions_ => null(), cosmologyFunctionsData => null() class (starFormationRateDisksClass ), pointer :: starFormationRateDisks_ => null() class (starFormationRateSpheroidsClass), pointer :: starFormationRateSpheroids_ => null() - class (galacticStructureClass ), pointer :: galacticStructure_ => null() type (varying_string ) :: fileName double precision :: massMinimum , massMaximum , & & countMassesPerDecade @@ -63,9 +62,8 @@ function starFormingMainSequenceConstructorParameters(parameters) result (self) Constructor for the ``starFormingMainSequence'' output analysis class which takes a parameter set as input. !!} use :: Error , only : Error_Report - use :: Input_Parameters , only : inputParameter , inputParameters - use :: Numerical_Ranges , only : Make_Range , rangeTypeLogarithmic - use :: Galactic_Structure, only : galacticStructureClass + use :: Input_Parameters , only : inputParameter, inputParameters + use :: Numerical_Ranges , only : Make_Range , rangeTypeLogarithmic implicit none type (outputAnalysisStarFormingMainSequence ) :: self type (inputParameters ), intent(inout) :: parameters @@ -77,7 +75,6 @@ function starFormingMainSequenceConstructorParameters(parameters) result (self) class (starFormationRateSpheroidsClass ), pointer :: starFormationRateSpheroids_ class (outputAnalysisPropertyOperatorClass ), pointer :: outputAnalysisPropertyOperator_ , outputAnalysisWeightPropertyOperator_ class (outputAnalysisDistributionOperatorClass), pointer :: outputAnalysisDistributionOperator_ - class (galacticStructureClass ), pointer :: galacticStructure_ double precision , dimension(: ), allocatable :: meanValueTarget , meanCovarianceTarget1D , & & massesStellar double precision , dimension(:,:), allocatable :: meanCovarianceTarget @@ -100,7 +97,6 @@ function starFormingMainSequenceConstructorParameters(parameters) result (self) - !!] if (parameters%isPresent('fileName')) then !![ @@ -120,7 +116,7 @@ The name of the file from which to read star forming main sequence A label for this analysis. !!] - self=outputAnalysisStarFormingMainSequence(char(fileName),label,comment,galacticFilter_,surveyGeometry_,cosmologyFunctions_,cosmologyFunctionsData,outputTimes_,outputAnalysisPropertyOperator_,outputAnalysisDistributionOperator_,outputAnalysisWeightPropertyOperator_,starFormationRateDisks_,starFormationRateSpheroids_,galacticStructure_) + self=outputAnalysisStarFormingMainSequence(char(fileName),label,comment,galacticFilter_,surveyGeometry_,cosmologyFunctions_,cosmologyFunctionsData,outputTimes_,outputAnalysisPropertyOperator_,outputAnalysisDistributionOperator_,outputAnalysisWeightPropertyOperator_,starFormationRateDisks_,starFormationRateSpheroids_) else !![ @@ -208,8 +204,7 @@ The target function covariance for likelihood calculations. @@ -231,12 +226,11 @@ The target function covariance for likelihood calculations. - !!] return end function starFormingMainSequenceConstructorParameters - function starFormingMainSequenceConstructorFile(fileName,label,comment,galacticFilter_,surveyGeometry_,cosmologyFunctions_,cosmologyFunctionsData,outputTimes_,outputAnalysisPropertyOperator_,outputAnalysisDistributionOperator_,outputAnalysisWeightPropertyOperator_,starFormationRateDisks_,starFormationRateSpheroids_,galacticStructure_) result(self) + function starFormingMainSequenceConstructorFile(fileName,label,comment,galacticFilter_,surveyGeometry_,cosmologyFunctions_,cosmologyFunctionsData,outputTimes_,outputAnalysisPropertyOperator_,outputAnalysisDistributionOperator_,outputAnalysisWeightPropertyOperator_,starFormationRateDisks_,starFormationRateSpheroids_) result(self) !!{ Constructor for the ``starFormingMainSequence'' output analysis class which reads all required properties from file. !!} @@ -256,7 +250,6 @@ function starFormingMainSequenceConstructorFile(fileName,label,comment,galacticF class (outputAnalysisPropertyOperatorClass ), intent(inout), target :: outputAnalysisPropertyOperator_ class (outputAnalysisPropertyOperatorClass ), intent(inout), target :: outputAnalysisWeightPropertyOperator_ class (outputAnalysisDistributionOperatorClass), intent(inout), target :: outputAnalysisDistributionOperator_ - class (galacticStructureClass ), intent(in ), target :: galacticStructure_ double precision , allocatable , dimension(: ) :: meanValueTarget , massesStellar double precision , allocatable , dimension(:,:) :: meanCovarianceTarget double precision :: massesStellarBinWidthLogarithmic @@ -287,7 +280,7 @@ function starFormingMainSequenceConstructorFile(fileName,label,comment,galacticF ! Build the object. !![ - self=starFormingMainSequenceConstructorInternal(label,comment,massesStellar,galacticFilter_,surveyGeometry_,cosmologyFunctions_,cosmologyFunctionsData,outputTimes_,outputAnalysisPropertyOperator_,outputAnalysisDistributionOperator_,outputAnalysisWeightPropertyOperator_,starFormationRateDisks_,starFormationRateSpheroids_,galacticStructure_,targetLabel,meanValueTarget,meanCovarianceTarget{conditions}) + self=starFormingMainSequenceConstructorInternal(label,comment,massesStellar,galacticFilter_,surveyGeometry_,cosmologyFunctions_,cosmologyFunctionsData,outputTimes_,outputAnalysisPropertyOperator_,outputAnalysisDistributionOperator_,outputAnalysisWeightPropertyOperator_,starFormationRateDisks_,starFormationRateSpheroids_,targetLabel,meanValueTarget,meanCovarianceTarget{conditions}) @@ -295,7 +288,7 @@ function starFormingMainSequenceConstructorFile(fileName,label,comment,galacticF return end function starFormingMainSequenceConstructorFile - function starFormingMainSequenceConstructorInternal(label,comment,massesStellar,galacticFilter_,surveyGeometry_,cosmologyFunctions_,cosmologyFunctionsData,outputTimes_,outputAnalysisPropertyOperator_,outputAnalysisDistributionOperator_,outputAnalysisWeightPropertyOperator_,starFormationRateDisks_,starFormationRateSpheroids_,galacticStructure_,targetLabel,meanValueTarget,meanCovarianceTarget,massesStellarBinWidthLogarithmic) result(self) + function starFormingMainSequenceConstructorInternal(label,comment,massesStellar,galacticFilter_,surveyGeometry_,cosmologyFunctions_,cosmologyFunctionsData,outputTimes_,outputAnalysisPropertyOperator_,outputAnalysisDistributionOperator_,outputAnalysisWeightPropertyOperator_,starFormationRateDisks_,starFormationRateSpheroids_,targetLabel,meanValueTarget,meanCovarianceTarget,massesStellarBinWidthLogarithmic) result(self) !!{ Internal constructor for the ``starFormingMainSequence'' output analysis class. !!} @@ -323,7 +316,6 @@ function starFormingMainSequenceConstructorInternal(label,comment,massesStellar, class (outputAnalysisDistributionOperatorClass ), intent(inout), target :: outputAnalysisDistributionOperator_ class (starFormationRateDisksClass ), intent(in ), target :: starFormationRateDisks_ class (starFormationRateSpheroidsClass ), intent(in ), target :: starFormationRateSpheroids_ - class (galacticStructureClass ), intent(in ), target :: galacticStructure_ type (varying_string ), optional , intent(in ) :: targetLabel double precision , optional , dimension(: ), intent(in ) :: meanValueTarget , massesStellar double precision , optional , dimension(:,:), intent(in ) :: meanCovarianceTarget @@ -345,7 +337,7 @@ function starFormingMainSequenceConstructorInternal(label,comment,massesStellar, integer (c_size_t ) :: iBin , bufferCount , & & countMasses !![ - + !!] ! Set properties needed for descriptor. @@ -415,7 +407,7 @@ function starFormingMainSequenceConstructorInternal(label,comment,massesStellar, !![ - nodePropertyExtractorMassStellar(galacticStructure_) + nodePropertyExtractorMassStellar() !!] diff --git a/source/output.analyses.star_forming_main_sequence.Schreiber2015.F90 b/source/output.analyses.star_forming_main_sequence.Schreiber2015.F90 index 04f5e407e2..b901773bf0 100644 --- a/source/output.analyses.star_forming_main_sequence.Schreiber2015.F90 +++ b/source/output.analyses.star_forming_main_sequence.Schreiber2015.F90 @@ -56,7 +56,6 @@ function starFormingMainSequenceSchreiber2015ConstructorParameters(parameters) r !!} use :: Cosmology_Parameters, only : cosmologyParameters , cosmologyParametersClass use :: Cosmology_Functions , only : cosmologyFunctions , cosmologyFunctionsClass - use :: Galactic_Structure , only : galacticStructureClass use :: Input_Parameters , only : inputParameter , inputParameters implicit none type (outputAnalysisStarFormingMainSequenceSchreiber2015) :: self @@ -66,7 +65,6 @@ function starFormingMainSequenceSchreiber2015ConstructorParameters(parameters) r class (outputTimesClass ), pointer :: outputTimes_ class (starFormationRateDisksClass ), pointer :: starFormationRateDisks_ class (starFormationRateSpheroidsClass ), pointer :: starFormationRateSpheroids_ - class (galacticStructureClass ), pointer :: galacticStructure_ double precision , allocatable , dimension(:) :: randomErrorPolynomialCoefficient , systematicErrorPolynomialCoefficient, & & weightSystematicErrorPolynomialCoefficient double precision :: randomErrorMinimum , randomErrorMaximum @@ -133,9 +131,8 @@ function starFormingMainSequenceSchreiber2015ConstructorParameters(parameters) r - !!] - self=outputAnalysisStarFormingMainSequenceSchreiber2015(redshiftIndex,randomErrorMinimum,randomErrorMaximum,randomErrorPolynomialCoefficient,systematicErrorPolynomialCoefficient,weightSystematicErrorPolynomialCoefficient,cosmologyParameters_,cosmologyFunctions_,outputTimes_,starFormationRateDisks_,starFormationRateSpheroids_,galacticStructure_) + self=outputAnalysisStarFormingMainSequenceSchreiber2015(redshiftIndex,randomErrorMinimum,randomErrorMaximum,randomErrorPolynomialCoefficient,systematicErrorPolynomialCoefficient,weightSystematicErrorPolynomialCoefficient,cosmologyParameters_,cosmologyFunctions_,outputTimes_,starFormationRateDisks_,starFormationRateSpheroids_) !![ @@ -143,12 +140,11 @@ function starFormingMainSequenceSchreiber2015ConstructorParameters(parameters) r - !!] return end function starFormingMainSequenceSchreiber2015ConstructorParameters - function starFormingMainSequenceSchreiber2015ConstructorInternal(redshiftIndex,randomErrorMinimum,randomErrorMaximum,randomErrorPolynomialCoefficient,systematicErrorPolynomialCoefficient,weightSystematicErrorPolynomialCoefficient,cosmologyParameters_,cosmologyFunctions_,outputTimes_,starFormationRateDisks_,starFormationRateSpheroids_,galacticStructure_) result(self) + function starFormingMainSequenceSchreiber2015ConstructorInternal(redshiftIndex,randomErrorMinimum,randomErrorMaximum,randomErrorPolynomialCoefficient,systematicErrorPolynomialCoefficient,weightSystematicErrorPolynomialCoefficient,cosmologyParameters_,cosmologyFunctions_,outputTimes_,starFormationRateDisks_,starFormationRateSpheroids_) result(self) !!{ Internal constructor for the ``starFormingMainSequenceSchreiber2015'' output analysis class. !!} @@ -174,7 +170,6 @@ function starFormingMainSequenceSchreiber2015ConstructorInternal(redshiftIndex,r class (outputTimesClass ), intent(inout), target :: outputTimes_ class (starFormationRateDisksClass ), intent(in ), target :: starFormationRateDisks_ class (starFormationRateSpheroidsClass ), intent(in ), target :: starFormationRateSpheroids_ - class (galacticStructureClass ), intent(in ), target :: galacticStructure_ type (galacticFilterStellarMass ) , pointer :: galacticFilterStellarMass_ type (galacticFilterStarFormationRate ) , pointer :: galacticFilterStarFormationRate_ type (galacticFilterAll ) , pointer :: galacticFilter_ @@ -332,8 +327,7 @@ function starFormingMainSequenceSchreiber2015ConstructorInternal(redshiftIndex,r & outputAnalysisDistributionOperator_ , & & outputAnalysisWeightPropertyOperator_, & & starFormationRateDisks_ , & - & starFormationRateSpheroids_ , & - & galacticStructure_ & + & starFormationRateSpheroids_ & & ) !![ diff --git a/source/output.analyses.star_forming_main_sequence.Wagner2016.F90 b/source/output.analyses.star_forming_main_sequence.Wagner2016.F90 index ff941d2f8d..0a8b7ec7b7 100644 --- a/source/output.analyses.star_forming_main_sequence.Wagner2016.F90 +++ b/source/output.analyses.star_forming_main_sequence.Wagner2016.F90 @@ -54,7 +54,6 @@ !!} private class (cosmologyParametersClass ), pointer :: cosmologyParameters_ => null() - class (darkMatterProfileDMOClass ), pointer :: darkMatterProfileDMO_ => null() class (virialDensityContrastClass ), pointer :: virialDensityContrast_ => null() double precision , allocatable , dimension(:) :: randomErrorPolynomialCoefficient , systematicErrorPolynomialCoefficient double precision :: randomErrorMinimum , randomErrorMaximum @@ -78,23 +77,19 @@ function starFormingMainSequenceWagner2016ConstructorParameters(parameters) resu !!{ Constructor for the ``starFormingMainSequenceWagner2016'' output analysis class which takes a parameter set as input. !!} - use :: Cosmology_Parameters , only : cosmologyParameters , cosmologyParametersClass - use :: Cosmology_Functions , only : cosmologyFunctions , cosmologyFunctionsClass - use :: Dark_Matter_Profiles_DMO, only : darkMatterProfileDMOClass - use :: Virial_Density_Contrast , only : virialDensityContrastClass - use :: Galactic_Structure , only : galacticStructureClass - use :: Input_Parameters , only : inputParameter , inputParameters + use :: Cosmology_Parameters , only : cosmologyParameters , cosmologyParametersClass + use :: Cosmology_Functions , only : cosmologyFunctions , cosmologyFunctionsClass + use :: Virial_Density_Contrast, only : virialDensityContrastClass + use :: Input_Parameters , only : inputParameter , inputParameters implicit none type (outputAnalysisStarFormingMainSequenceWagner2016) :: self type (inputParameters ), intent(inout) :: parameters class (cosmologyParametersClass ), pointer :: cosmologyParameters_ class (cosmologyFunctionsClass ), pointer :: cosmologyFunctions_ - class (darkMatterProfileDMOClass ), pointer :: darkMatterProfileDMO_ class (virialDensityContrastClass ), pointer :: virialDensityContrast_ class (outputTimesClass ), pointer :: outputTimes_ class (starFormationRateDisksClass ), pointer :: starFormationRateDisks_ class (starFormationRateSpheroidsClass ), pointer :: starFormationRateSpheroids_ - class (galacticStructureClass ), pointer :: galacticStructure_ double precision , allocatable , dimension(:) :: randomErrorPolynomialCoefficient , systematicErrorPolynomialCoefficient, & & weightSystematicErrorPolynomialCoefficient double precision :: randomErrorMinimum , randomErrorMaximum @@ -156,36 +151,31 @@ function starFormingMainSequenceWagner2016ConstructorParameters(parameters) resu - - !!] - self=outputAnalysisStarFormingMainSequenceWagner2016(enumerationWagner2016SSFRRedshiftRangeEncode(char(redshiftRange),includesPrefix=.false.),enumerationWagner2016SSFRGalaxyTypeEncode(char(galaxyType),includesPrefix=.false.),randomErrorMinimum,randomErrorMaximum,randomErrorPolynomialCoefficient,systematicErrorPolynomialCoefficient,weightSystematicErrorPolynomialCoefficient,cosmologyParameters_,cosmologyFunctions_,darkMatterProfileDMO_,virialDensityContrast_,outputTimes_,starFormationRateDisks_,starFormationRateSpheroids_,galacticStructure_) + self=outputAnalysisStarFormingMainSequenceWagner2016(enumerationWagner2016SSFRRedshiftRangeEncode(char(redshiftRange),includesPrefix=.false.),enumerationWagner2016SSFRGalaxyTypeEncode(char(galaxyType),includesPrefix=.false.),randomErrorMinimum,randomErrorMaximum,randomErrorPolynomialCoefficient,systematicErrorPolynomialCoefficient,weightSystematicErrorPolynomialCoefficient,cosmologyParameters_,cosmologyFunctions_,virialDensityContrast_,outputTimes_,starFormationRateDisks_,starFormationRateSpheroids_) !![ - - !!] return end function starFormingMainSequenceWagner2016ConstructorParameters - function starFormingMainSequenceWagner2016ConstructorInternal(redshiftRange,galaxyType,randomErrorMinimum,randomErrorMaximum,randomErrorPolynomialCoefficient,systematicErrorPolynomialCoefficient,weightSystematicErrorPolynomialCoefficient,cosmologyParameters_,cosmologyFunctions_,darkMatterProfileDMO_,virialDensityContrast_,outputTimes_,starFormationRateDisks_,starFormationRateSpheroids_,galacticStructure_) result(self) + function starFormingMainSequenceWagner2016ConstructorInternal(redshiftRange,galaxyType,randomErrorMinimum,randomErrorMaximum,randomErrorPolynomialCoefficient,systematicErrorPolynomialCoefficient,weightSystematicErrorPolynomialCoefficient,cosmologyParameters_,cosmologyFunctions_,virialDensityContrast_,outputTimes_,starFormationRateDisks_,starFormationRateSpheroids_) result(self) !!{ Internal constructor for the ``starFormingMainSequenceWagner2016'' output analysis class. !!} use :: Error , only : Error_Report use :: Cosmology_Functions , only : cosmologyFunctionsMatterLambda use :: Cosmology_Parameters , only : cosmologyParametersSimple - use :: Dark_Matter_Profiles_DMO , only : darkMatterProfileDMOClass use :: Input_Paths , only : inputPath , pathTypeDataStatic use :: Output_Times , only : outputTimesClass use :: Statistics_NBody_Halo_Mass_Errors , only : nbodyHaloMassErrorClass @@ -211,8 +201,6 @@ function starFormingMainSequenceWagner2016ConstructorInternal(redshiftRange,gala class (starFormationRateDisksClass ), intent(in ), target :: starFormationRateDisks_ class (starFormationRateSpheroidsClass ), intent(in ), target :: starFormationRateSpheroids_ class (virialDensityContrastClass ), intent(in ), target :: virialDensityContrast_ - class (darkMatterProfileDMOClass ), intent(in ), target :: darkMatterProfileDMO_ - class (galacticStructureClass ), intent(in ), target :: galacticStructure_ type (galacticFilterHaloNotIsolated ) , pointer :: galacticFilterIsSubhalo_ type (galacticFilterHighPass ) , pointer :: galacticFilterHostHaloMass_ type (galacticFilterStellarMass ) , pointer :: galacticFilterStellarMass_ @@ -235,7 +223,7 @@ function starFormingMainSequenceWagner2016ConstructorInternal(redshiftRange,gala type (varying_string ) :: fileName , label , & & description !![ - + !!] ! Construct file name and label for the analysis. @@ -322,7 +310,7 @@ function starFormingMainSequenceWagner2016ConstructorInternal(redshiftRange,gala !!] allocate(nodePropertyExtractorHostMass_) !![ - + !!] allocate(nodePropertyExtractorHost_) !![ @@ -413,8 +401,7 @@ function starFormingMainSequenceWagner2016ConstructorInternal(redshiftRange,gala & outputAnalysisDistributionOperator_ , & & outputAnalysisWeightPropertyOperator_, & & starFormationRateDisks_ , & - & starFormationRateSpheroids_ , & - & galacticStructure_ & + & starFormationRateSpheroids_ & & ) !![ @@ -443,7 +430,6 @@ subroutine starFormingMainSequenceWagner2016Destructor(self) !![ - !!] return diff --git a/source/output.analyses.stellar_vs_halo_mass_relation.COSMOS_Leauthaud2012.F90 b/source/output.analyses.stellar_vs_halo_mass_relation.COSMOS_Leauthaud2012.F90 index e942156d28..c0ccf61222 100644 --- a/source/output.analyses.stellar_vs_halo_mass_relation.COSMOS_Leauthaud2012.F90 +++ b/source/output.analyses.stellar_vs_halo_mass_relation.COSMOS_Leauthaud2012.F90 @@ -36,8 +36,6 @@ class (outputAnalysisClass ), pointer :: outputAnalysis_ => null() class (cosmologyParametersClass ), pointer :: cosmologyParameters_ => null() class (cosmologyFunctionsClass ), pointer :: cosmologyFunctions_ => null() - class (darkMatterProfileDMOClass ), pointer :: darkMatterProfileDMO_ => null() - class (galacticStructureClass ), pointer :: galacticStructure_ => null() class (virialDensityContrastClass), pointer :: virialDensityContrast_ => null() class (outputTimesClass ), pointer :: outputTimes_ => null() logical :: computeScatter @@ -70,7 +68,6 @@ function stellarVsHaloMassRelationLeauthaud2012ConstructorParameters(parameters) use :: Cosmology_Functions , only : cosmologyFunctionsClass use :: Cosmology_Parameters , only : cosmologyParametersClass use :: Dark_Matter_Profiles_DMO, only : darkMatterProfileDMOClass - use :: Galactic_Structure , only : galacticStructureClass use :: Virial_Density_Contrast , only : virialDensityContrastClass use :: Input_Parameters , only : inputParameters implicit none @@ -79,8 +76,6 @@ function stellarVsHaloMassRelationLeauthaud2012ConstructorParameters(parameters) double precision , allocatable , dimension(:) :: systematicErrorPolynomialCoefficient, systematicErrorMassHaloPolynomialCoefficient class (cosmologyParametersClass ), pointer :: cosmologyParameters_ class (cosmologyFunctionsClass ), pointer :: cosmologyFunctions_ - class (darkMatterProfileDMOClass ), pointer :: darkMatterProfileDMO_ - class (galacticStructureClass ), pointer :: galacticStructure_ class (virialDensityContrastClass ), pointer :: virialDensityContrast_ class (outputTimesClass ), pointer :: outputTimes_ integer :: redshiftInterval @@ -140,26 +135,22 @@ function stellarVsHaloMassRelationLeauthaud2012ConstructorParameters(parameters) - - !!] ! Build the object. - self=outputAnalysisStellarVsHaloMassRelationLeauthaud2012(redshiftInterval,likelihoodBins,computeScatter,systematicErrorPolynomialCoefficient,systematicErrorMassHaloPolynomialCoefficient,cosmologyParameters_,cosmologyFunctions_,darkMatterProfileDMO_,virialDensityContrast_,galacticStructure_,outputTimes_) + self=outputAnalysisStellarVsHaloMassRelationLeauthaud2012(redshiftInterval,likelihoodBins,computeScatter,systematicErrorPolynomialCoefficient,systematicErrorMassHaloPolynomialCoefficient,cosmologyParameters_,cosmologyFunctions_,virialDensityContrast_,outputTimes_) !![ - - !!] return end function stellarVsHaloMassRelationLeauthaud2012ConstructorParameters - function stellarVsHaloMassRelationLeauthaud2012ConstructorInternal(redshiftInterval,likelihoodBins,computeScatter,systematicErrorPolynomialCoefficient,systematicErrorMassHaloPolynomialCoefficient,cosmologyParameters_,cosmologyFunctions_,darkMatterProfileDMO_,virialDensityContrast_,galacticStructure_,outputTimes_) result (self) + function stellarVsHaloMassRelationLeauthaud2012ConstructorInternal(redshiftInterval,likelihoodBins,computeScatter,systematicErrorPolynomialCoefficient,systematicErrorMassHaloPolynomialCoefficient,cosmologyParameters_,cosmologyFunctions_,virialDensityContrast_,outputTimes_) result (self) !!{ Constructor for the ``stellarVsHaloMassRelationLeauthaud2012'' output analysis class for internal use. !!} @@ -195,8 +186,6 @@ function stellarVsHaloMassRelationLeauthaud2012ConstructorInternal(redshiftInter class (cosmologyParametersClass ), intent(in ), target :: cosmologyParameters_ class (cosmologyFunctionsClass ), intent(inout), target :: cosmologyFunctions_ class (virialDensityContrastClass ), intent(in ), target :: virialDensityContrast_ - class (darkMatterProfileDMOClass ), intent(in ), target :: darkMatterProfileDMO_ - class (galacticStructureClass ), intent(in ), target :: galacticStructure_ class (outputTimesClass ), intent(inout), target :: outputTimes_ integer (c_size_t ), parameter :: massHaloCount =26 double precision , allocatable , dimension(: ) :: massHalo , massStellarDataLogarithmic , & @@ -241,7 +230,7 @@ function stellarVsHaloMassRelationLeauthaud2012ConstructorInternal(redshiftInter type (table1DGeneric ) :: interpolator character (len=4 ) :: redshiftMinimumLabel , redshiftMaximumLabel !![ - + !!] ! Construct survey geometry. @@ -441,34 +430,34 @@ function stellarVsHaloMassRelationLeauthaud2012ConstructorInternal(redshiftInter ! Create a stellar mass weight property extractor. allocate(outputAnalysisWeightPropertyExtractor_ ) !![ - + !!] allocate(outputAnalysisWeightPropertyOperator_ ) !![ - + !!] allocate(outputAnalysisWeightPropertyOperatorNormalized_ ) !![ - + !!] ! Build weight operator. allocate (outputAnalysisWeightOperator_ ) !![ - + !!] ! Build anti-log10() property operator. allocate(outputAnalysisPropertyUnoperator_ ) !![ - + !!] ! Create a halo mass weight property extractor. allocate(virialDensityContrastDefinition_ ) !![ - + !!] allocate(nodePropertyExtractor_ ) !![ - + !!] ! Build the object. if (computeScatter) then @@ -633,8 +622,6 @@ subroutine stellarVsHaloMassRelationLeauthaud2012Destructor(self) - - !!] diff --git a/source/output.analyses.subhalo_mass_function.F90 b/source/output.analyses.subhalo_mass_function.F90 index 9404452f60..9a16ac577e 100644 --- a/source/output.analyses.subhalo_mass_function.F90 +++ b/source/output.analyses.subhalo_mass_function.F90 @@ -44,7 +44,6 @@ class (outputTimesClass ), pointer :: outputTimes_ => null() class (virialDensityContrastClass ), pointer :: virialDensityContrast_ => null(), virialDensityContrastDefinition_ => null() class (cosmologyParametersClass ), pointer :: cosmologyParameters_ => null() - class (darkMatterProfileDMOClass ), pointer :: darkMatterProfileDMO_ => null() type (outputAnalysisVolumeFunction1D), pointer :: volumeFunctionsSubHalos => null(), volumeFunctionsHostHalos => null() double precision , allocatable, dimension(: ) :: massRatios , massFunction , & & massFunctionTarget @@ -95,7 +94,6 @@ function subhaloMassFunctionConstructorParameters(parameters) result(self) class (cosmologyFunctionsClass ), pointer :: cosmologyFunctions_ class (outputTimesClass ), pointer :: outputTimes_ class (virialDensityContrastClass ), pointer :: virialDensityContrast_, virialDensityContrastDefinition_ - class (darkMatterProfileDMOClass ), pointer :: darkMatterProfileDMO_ double precision :: massRatioMinimum , massRatioMaximum , & & redshift , negativeBinomialScatterFractional integer (c_size_t ) :: countMassRatios @@ -150,31 +148,29 @@ function subhaloMassFunctionConstructorParameters(parameters) result(self) - !!] if (parameters%isPresent('fileName')) then !![ - self=outputAnalysisSubhaloMassFunction(outputTimes_,virialDensityContrastDefinition_,cosmologyParameters_,cosmologyFunctions_,virialDensityContrast_,darkMatterProfileDMO_,fileName,negativeBinomialScatterFractional{conditions}) + self=outputAnalysisSubhaloMassFunction(outputTimes_,virialDensityContrastDefinition_,cosmologyParameters_,cosmologyFunctions_,virialDensityContrast_,fileName,negativeBinomialScatterFractional{conditions}) !!] else - self=outputAnalysisSubhaloMassFunction(outputTimes_,virialDensityContrastDefinition_,cosmologyParameters_,cosmologyFunctions_,virialDensityContrast_,darkMatterProfileDMO_,cosmologyFunctions_%cosmicTime(cosmologyFunctions_%expansionFactorFromRedshift(redshift)),massRatioMinimum,massRatioMaximum,countMassRatios,negativeBinomialScatterFractional) + self=outputAnalysisSubhaloMassFunction(outputTimes_,virialDensityContrastDefinition_,cosmologyParameters_,cosmologyFunctions_,virialDensityContrast_,cosmologyFunctions_%cosmicTime(cosmologyFunctions_%expansionFactorFromRedshift(redshift)),massRatioMinimum,massRatioMaximum,countMassRatios,negativeBinomialScatterFractional) end if !![ - !!] return end function subhaloMassFunctionConstructorParameters - function subhaloMassFunctionConstructorFile(outputTimes_,virialDensityContrastDefinition_,cosmologyParameters_,cosmologyFunctions_,virialDensityContrast_,darkMatterProfileDMO_,fileName,negativeBinomialScatterFractional,redshift) result (self) + function subhaloMassFunctionConstructorFile(outputTimes_,virialDensityContrastDefinition_,cosmologyParameters_,cosmologyFunctions_,virialDensityContrast_,fileName,negativeBinomialScatterFractional,redshift) result (self) !!{ Constructor for the ``subhaloMassFunction'' output analysis class for internal use. !!} @@ -192,7 +188,6 @@ function subhaloMassFunctionConstructorFile(outputTimes_,virialDensityContrastDe class (virialDensityContrastClass ), intent(in ) :: virialDensityContrast_ , virialDensityContrastDefinition_ class (cosmologyParametersClass ), intent(inout) :: cosmologyParameters_ class (cosmologyFunctionsClass ), intent(inout), target :: cosmologyFunctions_ - class (darkMatterProfileDMOClass ), intent(in ) :: darkMatterProfileDMO_ double precision , intent(in ), optional :: redshift double precision , allocatable , dimension(: ) :: massRatiosTarget , massFunctionTarget , & & massFunctionErrorTarget @@ -227,14 +222,14 @@ function subhaloMassFunctionConstructorFile(outputTimes_,virialDensityContrastDe do i=1_c_size_t,countMassRatios massFunctionCovarianceTarget(i,i)=massFunctionErrorTarget(i)**2 end do - self=outputAnalysisSubhaloMassFunction(outputTimes_,virialDensityContrastDefinition_,cosmologyParameters_,cosmologyFunctions_,virialDensityContrast_,darkMatterProfileDMO_,time,massRatioMinimum,massRatioMaximum,countMassRatios,negativeBinomialScatterFractional,massFunctionTarget,massFunctionCovarianceTarget,labelTarget) + self=outputAnalysisSubhaloMassFunction(outputTimes_,virialDensityContrastDefinition_,cosmologyParameters_,cosmologyFunctions_,virialDensityContrast_,time,massRatioMinimum,massRatioMaximum,countMassRatios,negativeBinomialScatterFractional,massFunctionTarget,massFunctionCovarianceTarget,labelTarget) !![ !!] return end function subhaloMassFunctionConstructorFile - function subhaloMassFunctionConstructorInternal(outputTimes_,virialDensityContrastDefinition_,cosmologyParameters_,cosmologyFunctions_,virialDensityContrast_,darkMatterProfileDMO_,time,massRatioMinimum,massRatioMaximum,countMassRatios,negativeBinomialScatterFractional,massFunctionTarget,massFunctionCovarianceTarget,labelTarget) result (self) + function subhaloMassFunctionConstructorInternal(outputTimes_,virialDensityContrastDefinition_,cosmologyParameters_,cosmologyFunctions_,virialDensityContrast_,time,massRatioMinimum,massRatioMaximum,countMassRatios,negativeBinomialScatterFractional,massFunctionTarget,massFunctionCovarianceTarget,labelTarget) result (self) !!{ Constructor for the ``subhaloMassFunction'' output analysis class for internal use. !!} @@ -260,7 +255,6 @@ function subhaloMassFunctionConstructorInternal(outputTimes_,virialDensityContra class (virialDensityContrastClass ), intent(in ), target :: virialDensityContrast_ , virialDensityContrastDefinition_ class (cosmologyParametersClass ), intent(in ), target :: cosmologyParameters_ class (cosmologyFunctionsClass ), intent(in ), target :: cosmologyFunctions_ - class (darkMatterProfileDMOClass ), intent(in ), target :: darkMatterProfileDMO_ double precision , intent(in ), dimension(:) , optional :: massFunctionTarget double precision , intent(in ), dimension(:,:), optional :: massFunctionCovarianceTarget type (varying_string ), intent(in ) , optional :: labelTarget @@ -286,7 +280,7 @@ function subhaloMassFunctionConstructorInternal(outputTimes_,virialDensityContra double precision , parameter :: massHostLogarithmicMaximum =1.0d2 integer (c_size_t ) :: i !![ - + !!] ! Initialize. @@ -311,8 +305,8 @@ function subhaloMassFunctionConstructorInternal(outputTimes_,virialDensityContra !![ - - + + @@ -470,7 +464,6 @@ subroutine subhaloMassFunctionDestructor(self) - !!] diff --git a/source/output.analyses.subhalo_radial_distribution.F90 b/source/output.analyses.subhalo_radial_distribution.F90 index d2fa51ff8a..6fe7f4a810 100644 --- a/source/output.analyses.subhalo_radial_distribution.F90 +++ b/source/output.analyses.subhalo_radial_distribution.F90 @@ -44,7 +44,6 @@ class (outputTimesClass ), pointer :: outputTimes_ => null() class (virialDensityContrastClass ), pointer :: virialDensityContrast_ => null(), virialDensityContrastDefinition_ => null() class (cosmologyParametersClass ), pointer :: cosmologyParameters_ => null() - class (darkMatterProfileDMOClass ), pointer :: darkMatterProfileDMO_ => null() type (outputAnalysisVolumeFunction1D), pointer :: volumeFunctionsSubHalos => null(), volumeFunctionsHostHalos => null() double precision , allocatable, dimension(: ) :: radiiFractional , radialDistribution , & & radialDistributionTarget @@ -95,7 +94,6 @@ function subhaloRadialDistributionConstructorParameters(parameters) result(self) class (cosmologyFunctionsClass ), pointer :: cosmologyFunctions_ class (outputTimesClass ), pointer :: outputTimes_ class (virialDensityContrastClass ), pointer :: virialDensityContrastDefinition_ , virialDensityContrast_ - class (darkMatterProfileDMOClass ), pointer :: darkMatterProfileDMO_ double precision :: radiusFractionMinimum , radiusFractionMaximum , & & redshift , negativeBinomialScatterFractional, & & massRatioThreshold @@ -157,31 +155,29 @@ function subhaloRadialDistributionConstructorParameters(parameters) result(self) - !!] if (parameters%isPresent('fileName')) then !![ - self=outputAnalysisSubhaloRadialDistribution(outputTimes_,virialDensityContrastDefinition_,cosmologyParameters_,cosmologyFunctions_,virialDensityContrast_,darkMatterProfileDMO_,fileName,negativeBinomialScatterFractional{conditions}) + self=outputAnalysisSubhaloRadialDistribution(outputTimes_,virialDensityContrastDefinition_,cosmologyParameters_,cosmologyFunctions_,virialDensityContrast_,fileName,negativeBinomialScatterFractional{conditions}) !!] else - self=outputAnalysisSubhaloRadialDistribution(outputTimes_,virialDensityContrastDefinition_,cosmologyParameters_,cosmologyFunctions_,virialDensityContrast_,darkMatterProfileDMO_,cosmologyFunctions_%cosmicTime(cosmologyFunctions_%expansionFactorFromRedshift(redshift)),radiusFractionMinimum,radiusFractionMaximum,countRadiiFractional,massRatioThreshold,negativeBinomialScatterFractional) + self=outputAnalysisSubhaloRadialDistribution(outputTimes_,virialDensityContrastDefinition_,cosmologyParameters_,cosmologyFunctions_,virialDensityContrast_,cosmologyFunctions_%cosmicTime(cosmologyFunctions_%expansionFactorFromRedshift(redshift)),radiusFractionMinimum,radiusFractionMaximum,countRadiiFractional,massRatioThreshold,negativeBinomialScatterFractional) end if !![ - !!] return end function subhaloRadialDistributionConstructorParameters - function subhaloRadialDistributionConstructorFile(outputTimes_,virialDensityContrastDefinition_,cosmologyParameters_,cosmologyFunctions_,virialDensityContrast_,darkMatterProfileDMO_,fileName,negativeBinomialScatterFractional,redshift) result (self) + function subhaloRadialDistributionConstructorFile(outputTimes_,virialDensityContrastDefinition_,cosmologyParameters_,cosmologyFunctions_,virialDensityContrast_,fileName,negativeBinomialScatterFractional,redshift) result (self) !!{ Constructor for the ``subhaloRadialDistribution'' output analysis class for internal use. !!} @@ -199,7 +195,6 @@ function subhaloRadialDistributionConstructorFile(outputTimes_,virialDensityCont class (virialDensityContrastClass ), intent(in ), target :: virialDensityContrast_ , virialDensityContrastDefinition_ class (cosmologyParametersClass ), intent(inout), target :: cosmologyParameters_ class (cosmologyFunctionsClass ), intent(inout), target :: cosmologyFunctions_ - class (darkMatterProfileDMOClass ), intent(in ), target :: darkMatterProfileDMO_ double precision , intent(in ), optional :: redshift double precision , allocatable , dimension(: ) :: radiiFractionalTarget , radialDistributionTarget , & & radialDistributionErrorTarget @@ -236,14 +231,14 @@ function subhaloRadialDistributionConstructorFile(outputTimes_,virialDensityCont do i=1_c_size_t,countRadiiFractional radialDistributionCovarianceTarget(i,i)=radialDistributionErrorTarget(i)**2 end do - self=outputAnalysisSubhaloRadialDistribution(outputTimes_,virialDensityContrastDefinition_,cosmologyParameters_,cosmologyFunctions_,virialDensityContrast_,darkMatterProfileDMO_,time,radiusFractionMinimum,radiusFractionMaximum,countRadiiFractional,massRatioThreshold,negativeBinomialScatterFractional,radialDistributionTarget,radialDistributionCovarianceTarget,labelTarget) + self=outputAnalysisSubhaloRadialDistribution(outputTimes_,virialDensityContrastDefinition_,cosmologyParameters_,cosmologyFunctions_,virialDensityContrast_,time,radiusFractionMinimum,radiusFractionMaximum,countRadiiFractional,massRatioThreshold,negativeBinomialScatterFractional,radialDistributionTarget,radialDistributionCovarianceTarget,labelTarget) !![ !!] return end function subhaloRadialDistributionConstructorFile - function subhaloRadialDistributionConstructorInternal(outputTimes_,virialDensityContrastDefinition_,cosmologyParameters_,cosmologyFunctions_,virialDensityContrast_,darkMatterProfileDMO_,time,radiusFractionMinimum,radiusFractionMaximum,countRadiiFractional,massRatioThreshold,negativeBinomialScatterFractional,radialDistributionTarget,radialDistributionCovarianceTarget,labelTarget) result (self) + function subhaloRadialDistributionConstructorInternal(outputTimes_,virialDensityContrastDefinition_,cosmologyParameters_,cosmologyFunctions_,virialDensityContrast_,time,radiusFractionMinimum,radiusFractionMaximum,countRadiiFractional,massRatioThreshold,negativeBinomialScatterFractional,radialDistributionTarget,radialDistributionCovarianceTarget,labelTarget) result (self) !!{ Constructor for the ``subhaloRadialDistribution'' output analysis class for internal use. !!} @@ -270,7 +265,6 @@ function subhaloRadialDistributionConstructorInternal(outputTimes_,virialDensity class (virialDensityContrastClass ), intent(in ), target :: virialDensityContrast_ , virialDensityContrastDefinition_ class (cosmologyParametersClass ), intent(in ), target :: cosmologyParameters_ class (cosmologyFunctionsClass ), intent(in ), target :: cosmologyFunctions_ - class (darkMatterProfileDMOClass ), intent(in ), target :: darkMatterProfileDMO_ double precision , intent(in ), dimension(:) , optional :: radialDistributionTarget double precision , intent(in ), dimension(:,:), optional :: radialDistributionCovarianceTarget type (varying_string ), intent(in ) , optional :: labelTarget @@ -296,7 +290,7 @@ function subhaloRadialDistributionConstructorInternal(outputTimes_,virialDensity double precision , parameter :: massHostLogarithmicMaximum =1.0d2 integer (c_size_t ) :: i !![ - + !!] ! Initialize. @@ -322,8 +316,8 @@ function subhaloRadialDistributionConstructorInternal(outputTimes_,virialDensity !![ - - + + @@ -481,7 +475,6 @@ subroutine subhaloRadialDistributionDestructor(self) - !!] diff --git a/source/output.analyses.subhalo_velocity_maximum_vs_mass.F90 b/source/output.analyses.subhalo_velocity_maximum_vs_mass.F90 index c95f18d12b..7e0a330d48 100644 --- a/source/output.analyses.subhalo_velocity_maximum_vs_mass.F90 +++ b/source/output.analyses.subhalo_velocity_maximum_vs_mass.F90 @@ -174,7 +174,7 @@ function subhaloVMaxVsMassConstructorFile(outputTimes_,virialDensityContrastDefi type (varying_string ) :: labelTarget type (hdf5Object ) :: file , velocityMaximumVsMassGroup !![ - + !!] ! Read properties from the file. @@ -274,7 +274,7 @@ function subhaloVMaxVsMassConstructorInternal(outputTimes_,virialDensityContrast !![ - + diff --git a/source/output.analyses.tidal_tracks.velocity_maximum.F90 b/source/output.analyses.tidal_tracks.velocity_maximum.F90 index 0f599473d8..71615117d4 100644 --- a/source/output.analyses.tidal_tracks.velocity_maximum.F90 +++ b/source/output.analyses.tidal_tracks.velocity_maximum.F90 @@ -135,14 +135,16 @@ subroutine tidalTracksVelocityMaximumAnalyze(self,node,iOutput) !!{ Analyze the maximum velocity tidal track. !!} - use :: Galacticus_Nodes, only : nodeComponentBasic, nodeComponentSatellite - !$ use :: OMP_Lib , only : OMP_Set_Lock , OMP_Unset_Lock + use :: Galacticus_Nodes , only : nodeComponentBasic , nodeComponentSatellite + use :: Mass_Distributions, only : massDistributionClass + !$ use :: OMP_Lib , only : OMP_Set_Lock , OMP_Unset_Lock implicit none class (outputAnalysisTidalTracksVelocityMaximum), intent(inout) :: self type (treeNode ), intent(inout) :: node integer (c_size_t ), intent(in ) :: iOutput class (nodeComponentBasic ), pointer :: basic class (nodeComponentSatellite ), pointer :: satellite + class (massDistributionClass ), pointer :: massDistribution_ , massDistributionUnheated double precision , dimension(:), allocatable :: fractionMassBound_ , fractionVelocityMaximum_ , & & fractionVelocityMaximumTarget_ double precision :: fractionMassBound , fractionVelocityMaximum , & @@ -151,10 +153,16 @@ subroutine tidalTracksVelocityMaximumAnalyze(self,node,iOutput) ! Skip non-satellites. if (.not.node%isSatellite()) return ! Extract the bound mass and maximum velocity fractions. - basic => node%basic() - satellite => node%satellite() - fractionMassBound = satellite %boundMass ( )/basic %mass ( ) - fractionVelocityMaximum = self %darkMatterProfileDMO_%circularVelocityMaximum(node)/self %darkMatterProfileDMOUnheated%circularVelocityMaximum(node) + basic => node %basic ( ) + satellite => node %satellite ( ) + massDistribution_ => self %darkMatterProfileDMO_ %get(node) + massDistributionUnheated => self %darkMatterProfileDMOUnheated%get(node) + fractionMassBound = satellite %boundMass ( )/basic %mass () + fractionVelocityMaximum = massDistribution_%velocityRotationCurveMaximum ( )/massDistributionUnheated%velocityRotationCurveMaximum() + !![ + + + !!] ! Evaluate the target value. Uses the Penarrubia et al. (2010) fitting function. fractionVelocityMaximumTarget =+ 2.0d0 **self%mu & & * fractionMassBound **self%eta & diff --git a/source/ram_pressure_stripping.mass_loss_rate.simple_cylindrical.F90 b/source/ram_pressure_stripping.mass_loss_rate.simple_cylindrical.F90 index 3f41ad94e5..c1f5a35631 100644 --- a/source/ram_pressure_stripping.mass_loss_rate.simple_cylindrical.F90 +++ b/source/ram_pressure_stripping.mass_loss_rate.simple_cylindrical.F90 @@ -22,7 +22,6 @@ !!} use :: Hot_Halo_Ram_Pressure_Forces, only : hotHaloRamPressureForceClass - use :: Galactic_Structure , only : galacticStructureClass !![ @@ -50,7 +49,6 @@ !!} private class (hotHaloRamPressureForceClass), pointer :: hotHaloRamPressureForce_ => null() - class (galacticStructureClass ), pointer :: galacticStructure_ => null() double precision :: rateFractionalMaximum , beta contains final :: simpleCylindricalDestructor @@ -77,7 +75,6 @@ function simpleCylindricalConstructorParameters(parameters) result(self) type (ramPressureStrippingSimpleCylindrical) :: self type (inputParameters ), intent(inout) :: parameters class (hotHaloRamPressureForceClass ), pointer :: hotHaloRamPressureForce_ - class (galacticStructureClass ), pointer :: galacticStructure_ double precision :: rateFractionalMaximum , beta !![ @@ -94,18 +91,16 @@ function simpleCylindricalConstructorParameters(parameters) result(self) parameters - !!] - self=ramPressureStrippingSimpleCylindrical(rateFractionalMaximum,beta,hotHaloRamPressureForce_,galacticStructure_) + self=ramPressureStrippingSimpleCylindrical(rateFractionalMaximum,beta,hotHaloRamPressureForce_) !![ - !!] return end function simpleCylindricalConstructorParameters - function simpleCylindricalConstructorInternal(rateFractionalMaximum,beta,hotHaloRamPressureForce_,galacticStructure_) result(self) + function simpleCylindricalConstructorInternal(rateFractionalMaximum,beta,hotHaloRamPressureForce_) result(self) !!{ Internal constructor for the {\normalfont \ttfamily simpleCylindrical} model of ram pressure stripping class. !!} @@ -113,9 +108,8 @@ function simpleCylindricalConstructorInternal(rateFractionalMaximum,beta,hotHalo type (ramPressureStrippingSimpleCylindrical) :: self double precision , intent(in ) :: rateFractionalMaximum , beta class (hotHaloRamPressureForceClass ), intent(in ), target :: hotHaloRamPressureForce_ - class (galacticStructureClass ), intent(in ), target :: galacticStructure_ !![ - + !!] return @@ -130,7 +124,6 @@ subroutine simpleCylindricalDestructor(self) !![ - !!] return end subroutine simpleCylindricalDestructor @@ -152,23 +145,26 @@ double precision function simpleCylindricalRateMassLoss(self,component) \end{equation} is the gravitational restoring force at the half-mass radius, $r_\mathrm{1/2}$. !!} - use :: Display , only : displayGreen , displayBlue , displayMagenta, displayReset - use :: Galactic_Structure_Options , only : componentTypeDisk , coordinateSystemCylindrical , massTypeAll , massTypeGaseous, & - & enumerationComponentTypeType - use :: Galacticus_Nodes , only : nodeComponentDisk , treeNode - use :: Numerical_Constants_Astronomical, only : gigaYear , gravitationalConstantGalacticus, megaParsec + use :: Coordinates , only : coordinateCylindrical, assignment(=) + use :: Display , only : displayGreen , displayBlue , displayMagenta, displayReset + use :: Galactic_Structure_Options , only : componentTypeDisk , enumerationComponentTypeType , massTypeAll , massTypeGaseous + use :: Galacticus_Nodes , only : nodeComponentDisk , treeNode + use :: Mass_Distributions , only : massDistributionClass + use :: Numerical_Constants_Astronomical, only : gigaYear , gravitationalConstantGalacticus, megaParsec use :: Numerical_Constants_Math , only : Pi use :: Numerical_Constants_Prefixes , only : kilo implicit none class (ramPressureStrippingSimpleCylindrical), intent(inout) :: self class (nodeComponent ), intent(inout) :: component type (treeNode ), pointer :: node + class (massDistributionClass ), pointer :: massDistributionGaseous, massDistributionTotal type (enumerationComponentTypeType ) :: componentType - double precision :: forceGravitational , forceRamPressure , & - & rateMassLossFractional, radiusHalfMass , & - & surfaceDensityGas , surfaceDensityTotal, & - & timeDynamical , radius , & - & radiusHalfMass , velocity , & + type (coordinateCylindrical ) :: coordinates + double precision :: forceGravitational , forceRamPressure , & + & rateMassLossFractional , radiusHalfMass , & + & surfaceDensityGas , surfaceDensityTotal, & + & timeDynamical , radius , & + & radiusHalfMass , velocity , & & massGas ! Assume no mass loss rate due to ram pressure by default. @@ -198,20 +194,15 @@ double precision function simpleCylindricalRateMassLoss(self,component) & ) end select ! Compute the surface densities at the half mass radius. - surfaceDensityGas = self%galacticStructure_%surfaceDensity( & - & node , & - & [radiusHalfMass,0.0d0,0.0d0] , & - & coordinateSystem=coordinateSystemCylindrical, & - & massType =massTypeGaseous , & - & componentType =componentType & - & ) - surfaceDensityTotal = self%galacticStructure_%surfaceDensity( & - & node , & - & [radiusHalfMass,0.0d0,0.0d0] , & - & coordinateSystem=coordinateSystemCylindrical, & - & massType =massTypeAll , & - & componentType =componentType & - & ) + coordinates = [radiusHalfMass,0.0d0,0.0d0] + massDistributionGaseous => node %massDistribution(massType=massTypeGaseous,componentType=componentType) + massDistributionTotal => node %massDistribution(massType=massTypeAll ,componentType=componentType) + surfaceDensityGas = massDistributionGaseous%surfaceDensity ( coordinates ) + surfaceDensityTotal = massDistributionTotal %surfaceDensity ( coordinates ) + !![ + + + !!] ! Compute the gravitational restoring force in the midplane. forceGravitational = +2.0d0 & & *Pi & diff --git a/source/ram_pressure_stripping.mass_loss_rate.simple_spherical.F90 b/source/ram_pressure_stripping.mass_loss_rate.simple_spherical.F90 index 299e98679c..c845ce5da0 100644 --- a/source/ram_pressure_stripping.mass_loss_rate.simple_spherical.F90 +++ b/source/ram_pressure_stripping.mass_loss_rate.simple_spherical.F90 @@ -22,7 +22,6 @@ !!} use :: Hot_Halo_Ram_Pressure_Forces, only : hotHaloRamPressureForceClass - use :: Galactic_Structure , only : galacticStructureClass !![ @@ -51,7 +50,6 @@ !!} private class (hotHaloRamPressureForceClass), pointer :: hotHaloRamPressureForce_ => null() - class (galacticStructureClass ), pointer :: galacticStructure_ => null() double precision :: rateFractionalMaximum , beta contains final :: simpleSphericalDestructor @@ -78,7 +76,6 @@ function simpleSphericalConstructorParameters(parameters) result(self) type (ramPressureStrippingSimpleSpherical) :: self type (inputParameters ), intent(inout) :: parameters class (hotHaloRamPressureForceClass ), pointer :: hotHaloRamPressureForce_ - class (galacticStructureClass ), pointer :: galacticStructure_ double precision :: rateFractionalMaximum , beta !![ @@ -95,18 +92,16 @@ function simpleSphericalConstructorParameters(parameters) result(self) parameters - !!] - self=ramPressureStrippingSimpleSpherical(rateFractionalMaximum,beta,hotHaloRamPressureForce_,galacticStructure_) + self=ramPressureStrippingSimpleSpherical(rateFractionalMaximum,beta,hotHaloRamPressureForce_) !![ - !!] return end function simpleSphericalConstructorParameters - function simpleSphericalConstructorInternal(rateFractionalMaximum,beta,hotHaloRamPressureForce_,galacticStructure_) result(self) + function simpleSphericalConstructorInternal(rateFractionalMaximum,beta,hotHaloRamPressureForce_) result(self) !!{ Internal constructor for the {\normalfont \ttfamily simpleSpherical} model of ram pressure stripping of spheroids class. !!} @@ -114,9 +109,8 @@ function simpleSphericalConstructorInternal(rateFractionalMaximum,beta,hotHaloRa type (ramPressureStrippingSimpleSpherical) :: self double precision , intent(in ) :: rateFractionalMaximum , beta class (hotHaloRamPressureForceClass ), intent(in ), target :: hotHaloRamPressureForce_ - class (galacticStructureClass ), intent(in ), target :: galacticStructure_ !![ - + !!] return @@ -131,7 +125,6 @@ subroutine simpleSphericalDestructor(self) !![ - !!] return end subroutine simpleSphericalDestructor @@ -153,22 +146,25 @@ double precision function simpleSphericalRateMassLoss(self,component) \end{equation} is the gravitational restoring force at the half-mass radius, $r_\mathrm{1/2}$ \citep{takeda_ram_1984}. !!} - use :: Display , only : displayGreen , displayBlue , displayMagenta, displayReset - use :: Galactic_Structure_Options , only : componentTypeSpheroid , coordinateSystemSpherical , massTypeAll , massTypeGaseous, & - & enumerationComponentTypeType - use :: Galacticus_Nodes , only : nodeComponentSpheroid , treeNode - use :: Numerical_Constants_Astronomical, only : gigaYear , gravitationalConstantGalacticus, megaParsec + use :: Coordinates , only : coordinateSpherical , assignment(=) + use :: Display , only : displayGreen , displayBlue , displayMagenta, displayReset + use :: Galactic_Structure_Options , only : componentTypeSpheroid, enumerationComponentTypeType , massTypeAll , massTypeGaseous + use :: Galacticus_Nodes , only : nodeComponentSpheroid, treeNode + use :: Mass_Distributions , only : massDistributionClass + use :: Numerical_Constants_Astronomical, only : gigaYear , gravitationalConstantGalacticus, megaParsec use :: Numerical_Constants_Prefixes , only : kilo implicit none class (ramPressureStrippingSimpleSpherical), intent(inout) :: self class (nodeComponent ), intent(inout) :: component type (treeNode ), pointer :: node + class (massDistributionClass ), pointer :: massDistributionGaseous, massDistributionTotal type (enumerationComponentTypeType ) :: componentType - double precision :: forceGravitational , forceRamPressure, & - & rateMassLossFractional, radiusHalfMass , & - & densityGas , massHalf , & - & timeDynamical , radius , & - & radiusHalfMass , velocity , & + type (coordinateSpherical ) :: coordinates + double precision :: forceGravitational , forceRamPressure, & + & rateMassLossFractional , radiusHalfMass , & + & densityGas , massHalf , & + & timeDynamical , radius , & + & radiusHalfMass , velocity , & & massGas ! Assume no mass loss rate due to ram pressure by default. @@ -198,19 +194,15 @@ double precision function simpleSphericalRateMassLoss(self,component) & ) end select ! Compute the densities at the half mass radius. - densityGas=self%galacticStructure_%density ( & - & node , & - & [radiusHalfMass,0.0d0,0.0d0] , & - & coordinateSystem=coordinateSystemSpherical, & - & massType =massTypeGaseous , & - & componentType =componentType & - & ) - massHalf =self%galacticStructure_%massEnclosed( & - & node , & - & radiusHalfMass , & - & massType =massTypeAll , & - & componentType =componentType & - & ) + coordinates = [radiusHalfMass,0.0d0,0.0d0] + massDistributionGaseous => node %massDistribution (massType=massTypeGaseous,componentType=componentType) + massDistributionTotal => node %massDistribution (massType=massTypeAll ,componentType=componentType) + densityGas = massDistributionGaseous%density ( coordinates ) + massHalf = massDistributionTotal %massEnclosedBySphere( radiusHalfMass ) + !![ + + + !!] ! Compute the gravitational restoring force. if (massHalf > 0.0d0 .and. densityGas > 0.0d0) then forceGravitational = +4.0d0 & diff --git a/source/satellites.deceleration_SIDM.Kummer2018.F90 b/source/satellites.deceleration_SIDM.Kummer2018.F90 index e714051dc6..7b46f06817 100644 --- a/source/satellites.deceleration_SIDM.Kummer2018.F90 +++ b/source/satellites.deceleration_SIDM.Kummer2018.F90 @@ -25,7 +25,6 @@ use :: Dark_Matter_Particles , only : darkMatterParticleClass use :: Dark_Matter_Profiles_DMO, only : darkMatterProfileDMOClass use :: Numerical_Interpolation , only : interpolator - use :: Galactic_Structure , only : galacticStructureClass !![ @@ -40,7 +39,6 @@ private class (darkMatterParticleClass ), pointer :: darkMatterParticle_ => null() class (darkMatterProfileDMOClass), pointer :: darkMatterProfileDMO_ => null() - class (galacticStructureClass ), pointer :: galacticStructure_ => null() type (interpolator ), allocatable :: decelerationFactor double precision :: rateScatteringNormalization , xMaximum contains @@ -75,24 +73,21 @@ function kummer2018ConstructorParameters(parameters) result(self) type (inputParameters ), intent(inout) :: parameters class(darkMatterParticleClass ), pointer :: darkMatterParticle_ class(darkMatterProfileDMOClass ), pointer :: darkMatterProfileDMO_ - class(galacticStructureClass ), pointer :: galacticStructure_ !![ - !!] - self=satelliteDecelerationSIDMKummer2018(darkMatterParticle_,darkMatterProfileDMO_,galacticStructure_) + self=satelliteDecelerationSIDMKummer2018(darkMatterParticle_,darkMatterProfileDMO_) !![ - !!] return end function kummer2018ConstructorParameters - function kummer2018ConstructorInternal(darkMatterParticle_,darkMatterProfileDMO_,galacticStructure_) result(self) + function kummer2018ConstructorInternal(darkMatterParticle_,darkMatterProfileDMO_) result(self) !!{ Internal constructor for the {\normalfont \ttfamily kummer2018} satellite deceleration due to dark matter self-interactions class. @@ -104,9 +99,8 @@ function kummer2018ConstructorInternal(darkMatterParticle_,darkMatterProfileDMO_ type (satelliteDecelerationSIDMKummer2018) :: self class(darkMatterParticleClass ), intent(in ), target :: darkMatterParticle_ class(darkMatterProfileDMOClass ), intent(in ), target :: darkMatterProfileDMO_ - class(galacticStructureClass ), intent(in ), target :: galacticStructure_ !![ - + !!] select type (darkMatterParticle_ => self%darkMatterParticle_) @@ -138,7 +132,6 @@ subroutine kummer2018Destructor(self) !![ - !!] return end subroutine kummer2018Destructor @@ -147,8 +140,10 @@ function kummer2018Acceleration(self,node) !!{ Return a deceleration for satellites due to dark matter self-interactions using the formulation of \cite{kummer_effective_2018}. !!} + use :: Coordinates , only : coordinateSpherical , coordinateCartesian , assignment(=) use :: Galactic_Structure_Options , only : coordinateSystemCartesian , radiusLarge use :: Galacticus_Nodes , only : nodeComponentSatellite , nodeComponentBasic + use :: Mass_Distributions , only : massDistributionClass , kinematicsDistributionClass use :: Numerical_Constants_Astronomical, only : gravitationalConstantGalacticus use :: Vectors , only : Vector_Magnitude implicit none @@ -158,29 +153,38 @@ function kummer2018Acceleration(self,node) class (nodeComponentSatellite ), pointer :: satellite class (nodeComponentBasic ), pointer :: basic type (treeNode ), pointer :: nodeHost + class (massDistributionClass ), pointer :: massDistribution_ , massDistributionHost_ + class (kinematicsDistributionClass ), pointer :: kinematics_ , kinematicsHost_ double precision , dimension(3) :: position , velocity double precision :: radiusOrbital , speedOrbital , & & densityHost , rateScattering , & - & potentialHalfMass , potentialBoundary , & & massBoundary , radiusBoundary , & & velocityEscape , speedHalfMass , & & velocityDispersionHost, velocityDispersionSatellite, & & x , radiusHalfMass , & & velocityDispersion , dispersionFactor , & & potentialEscape - + type (coordinateSpherical ) :: coordinates , coordinatesHost , & + & coordinatesBoundary , coordinatesHalfMass + type (coordinateCartesian ) :: coordinatesCartesian + ! Set zero acceleration by default. kummer2018Acceleration=0.0d0 ! If the scattering cross section is zero, we can return immediately. if (self%rateScatteringNormalization == 0.0d0) return ! Evaluate satellite and host properties. - nodeHost => node %mergesWith( ) - satellite => node %satellite ( ) - position = satellite %position ( ) - velocity = satellite %velocity ( ) - radiusOrbital = Vector_Magnitude ( position ) - speedOrbital = Vector_Magnitude ( velocity ) - densityHost = self %galacticStructure_%density (nodeHost,position,coordinateSystemCartesian) + nodeHost => node %mergesWith ( ) + satellite => node %satellite ( ) + massDistributionHost_ => nodeHost %massDistribution( ) + position = satellite %position ( ) + velocity = satellite %velocity ( ) + radiusOrbital = Vector_Magnitude ( position) + speedOrbital = Vector_Magnitude ( velocity) + coordinatesCartesian = position + densityHost = massDistributionHost_%density (coordinatesCartesian) + !![ + + !!] ! Find the escape velocity from the half-mass radius of the subhalo. This is equal to the potential difference between the ! half-mass radius and outer boundary of the subhalo, plus the potential difference from the outer boundary to infinity (for ! which we can treat the subhalo as a point mass). @@ -195,16 +199,16 @@ function kummer2018Acceleration(self,node) & basic % mass() & & ) if (massBoundary > 0.0d0) then - radiusBoundary =self%galacticStructure_%radiusEnclosingMass(node,mass = massBoundary ) - radiusHalfMass =self%galacticStructure_%radiusEnclosingMass(node,mass =0.5d0*massBoundary ) + massDistribution_ => node %massDistribution ( ) + radiusBoundary = massDistribution_%radiusEnclosingMass(mass= massBoundary) + radiusHalfMass = massDistribution_%radiusEnclosingMass(mass=0.5d0*massBoundary) if (radiusBoundary < 0.5d0*radiusLarge) then - potentialBoundary=self%galacticStructure_%potential (node,radius= radiusBoundary) - potentialHalfMass=self%galacticStructure_%potential (node,radius= radiusHalfMass) - potentialEscape =+potentialBoundary & - & -potentialHalfMass & - & +gravitationalConstantGalacticus & - & *massBoundary & - & /radiusBoundary + coordinatesBoundary=[radiusBoundary,0.0d0,0.0d0] + coordinatesHalfMass=[radiusHalfMass,0.0d0,0.0d0] + potentialEscape =+massDistribution_%potentialDifference(coordinatesBoundary,coordinatesHalfMass) & + & +gravitationalConstantGalacticus & + & *massBoundary & + & /radiusBoundary if (potentialEscape > 0.0d0) then velocityEscape=sqrt(2.0d0*potentialEscape) else @@ -213,6 +217,9 @@ function kummer2018Acceleration(self,node) else velocityEscape=0.0d0 end if + !![ + + !!] ! Get the speed of a host particle at the half-mass radius of the subhalo - this is the sum of the kinetic energy or host ! particles in the rest-frame of the subhalo, plus the energy they gain by falling in to the half-mass radius of the ! subhalo. @@ -225,20 +232,32 @@ function kummer2018Acceleration(self,node) if (x > self%xMaximum) call self%tabulate(x+1.0d0) ! Find the combined velocity dispersion of satellite and host, and evaluate the correction factor given in Appendix A of ! Kummer et al. (2018). - velocityDispersionHost =+self%darkMatterProfileDMO_%radialVelocityDispersion(nodeHost,radiusOrbital ) - velocityDispersionSatellite=+self%darkMatterProfileDMO_%radialVelocityDispersion(node ,radiusHalfMass) - velocityDispersion =+sqrt( & - & +velocityDispersionHost **2 & - & +velocityDispersionSatellite**2 & - & ) - dispersionFactor =+1.0d0 & - & /( & - & +1.0d0 & - & +( & - & +velocityDispersion & - & /speedHalfMass & - & )**3 & - & ) + massDistribution_ => self %darkMatterProfileDMO_%get (node ) + massDistributionHost_ => self %darkMatterProfileDMO_%get (nodeHost ) + kinematics_ => massDistribution_ %kinematicsDistribution( ) + kinematicsHost_ => massDistributionHost_ %kinematicsDistribution( ) + coordinates = [radiusHalfMass,0.0d0,0.0d0] + coordinatesHost = [radiusOrbital ,0.0d0,0.0d0] + velocityDispersionHost = +kinematicsHost_ %velocityDispersion1D (coordinatesHost,massDistributionHost_) + velocityDispersionSatellite = +kinematics_ %velocityDispersion1D (coordinates ,massDistribution_ ) + velocityDispersion = +sqrt( & + & +velocityDispersionHost **2 & + & +velocityDispersionSatellite**2 & + & ) + dispersionFactor = +1.0d0 & + & /( & + & +1.0d0 & + & +( & + & +velocityDispersion & + & /speedHalfMass & + & )**3 & + & ) + !![ + + + + + !!] ! Evaluate the scattering rate and acceleration. rateScattering = + speedOrbital & & * densityHost & diff --git a/source/satellites.dynamical_friction.acceleration.Chandrasekhar1943.F90 b/source/satellites.dynamical_friction.acceleration.Chandrasekhar1943.F90 index e6d19b77cd..76c6f5bf5b 100644 --- a/source/satellites.dynamical_friction.acceleration.Chandrasekhar1943.F90 +++ b/source/satellites.dynamical_friction.acceleration.Chandrasekhar1943.F90 @@ -25,7 +25,6 @@ use :: Dark_Matter_Halo_Scales , only : darkMatterHaloScaleClass use :: Dark_Matter_Profiles_DMO, only : darkMatterProfileDMOClass - use :: Galactic_Structure , only : galacticStructureClass !![ @@ -51,7 +50,6 @@ private class (darkMatterHaloScaleClass ), pointer :: darkMatterHaloScale_ => null() class (darkMatterProfileDMOClass), pointer :: darkMatterProfileDMO_ => null() - class (galacticStructureClass ), pointer :: galacticStructure_ => null() double precision :: logarithmCoulomb contains !![ @@ -84,7 +82,6 @@ function chandrasekhar1943ConstructorParameters(parameters) result(self) type (inputParameters ), intent(inout) :: parameters class (darkMatterHaloScaleClass ), pointer :: darkMatterHaloScale_ class (darkMatterProfileDMOClass ), pointer :: darkMatterProfileDMO_ - class (galacticStructureClass ), pointer :: galacticStructure_ double precision :: logarithmCoulomb !![ @@ -96,19 +93,17 @@ function chandrasekhar1943ConstructorParameters(parameters) result(self) - !!] - self=satelliteDynamicalFrictionChandrasekhar1943(logarithmCoulomb,darkMatterHaloScale_,darkMatterProfileDMO_,galacticStructure_) + self=satelliteDynamicalFrictionChandrasekhar1943(logarithmCoulomb,darkMatterHaloScale_,darkMatterProfileDMO_) !![ - !!] return end function chandrasekhar1943ConstructorParameters - function chandrasekhar1943ConstructorInternal(logarithmCoulomb,darkMatterHaloScale_,darkMatterProfileDMO_,galacticStructure_) result(self) + function chandrasekhar1943ConstructorInternal(logarithmCoulomb,darkMatterHaloScale_,darkMatterProfileDMO_) result(self) !!{ Internal constructor for the {\normalfont \ttfamily chandrasekhar1943} satellite dynamical friction class. !!} @@ -116,10 +111,9 @@ function chandrasekhar1943ConstructorInternal(logarithmCoulomb,darkMatterHaloSca type (satelliteDynamicalFrictionChandrasekhar1943) :: self class (darkMatterHaloScaleClass ), intent(in ), target :: darkMatterHaloScale_ class (darkMatterProfileDMOClass ), intent(in ), target :: darkMatterProfileDMO_ - class (galacticStructureClass ), intent(in ), target :: galacticStructure_ double precision , intent(in ) :: logarithmCoulomb !![ - + !!] return @@ -135,7 +129,6 @@ subroutine chandrasekhar1943Destructor(self) !![ - !!] return end subroutine chandrasekhar1943Destructor @@ -144,9 +137,11 @@ function chandrasekhar1943Acceleration(self,node) !!{ Return an acceleration for satellites due to dynamical friction using the formulation of \cite{chandrasekhar_dynamical_1943}. !!} + use :: Coordinates , only : coordinateCartesian , assignment(=) use :: Error_Functions , only : Error_Function use :: Galactic_Structure_Options , only : coordinateSystemCartesian, componentTypeDarkHalo , massTypeDark use :: Galacticus_Nodes , only : nodeComponentSatellite , nodeComponentBasic , treeNode + use :: Mass_Distributions , only : massDistributionClass use :: Numerical_Constants_Astronomical, only : gigaYear , gravitationalConstantGalacticus, megaParsec use :: Numerical_Constants_Math , only : Pi use :: Numerical_Constants_Prefixes , only : kilo @@ -158,24 +153,31 @@ function chandrasekhar1943Acceleration(self,node) class (nodeComponentBasic ), pointer :: basic class (nodeComponentSatellite ), pointer :: satellite type (treeNode ), pointer :: nodeHost - double precision , dimension(3) :: position , velocity + class (massDistributionClass ), pointer :: massDistribution_ , massDistributionHost_ double precision :: massSatellite - - nodeHost => node %mergesWith ( ) - basic => node %basic ( ) - satellite => node %satellite ( ) - massSatellite = satellite %boundMass ( ) - position = satellite %position ( ) - velocity = satellite %velocity ( ) - chandrasekhar1943Acceleration = +4.0d0 & - & *Pi & - & *self%galacticStructure_%chandrasekharIntegral(nodeHost,node,position,velocity) & - & *self %coulombLogarithm ( node ) & - & *gravitationalConstantGalacticus**2 & - & *massSatellite & - & *kilo & - & *gigaYear & + type (coordinateCartesian ) :: position , velocity + + nodeHost => node %mergesWith () + basic => node %basic () + satellite => node %satellite () + massSatellite = satellite%boundMass () + position = satellite%position () + velocity = satellite%velocity () + massDistribution_ => node %massDistribution() + massDistributionHost_ => nodeHost %massDistribution() + chandrasekhar1943Acceleration = +4.0d0 & + & *Pi & + & *massDistributionHost_%chandrasekharIntegral(massDistributionHost_,massDistribution_,massSatellite,position,velocity) & + & *self %coulombLogarithm (node ) & + & *gravitationalConstantGalacticus**2 & + & *massSatellite & + & *kilo & + & *gigaYear & & /megaParsec + !![ + + + !!] return end function chandrasekhar1943Acceleration diff --git a/source/satellites.dynamical_friction.acceleration.Kaur2018.F90 b/source/satellites.dynamical_friction.acceleration.Kaur2018.F90 index 8e56ba1579..e5b51d2b42 100644 --- a/source/satellites.dynamical_friction.acceleration.Kaur2018.F90 +++ b/source/satellites.dynamical_friction.acceleration.Kaur2018.F90 @@ -26,7 +26,6 @@ use :: Dark_Matter_Profiles_DMO, only : darkMatterProfileDMOClass use :: Numerical_Interpolation , only : interpolator - use :: Galactic_Structure , only : galacticStructureClass !![ @@ -56,7 +55,6 @@ private class (satelliteDynamicalFrictionClass), pointer :: satelliteDynamicalFriction_ => null() class (darkMatterProfileDMOClass ), pointer :: darkMatterProfileDMO_ => null() - class (galacticStructureClass ), pointer :: galacticStructure_ => null() type (interpolator ) :: factorSuppressionLeadingLogarithmic , factorSuppressionTrailingLogarithmic double precision :: radiusDimensionlessMaximum contains @@ -88,24 +86,21 @@ function kaur2018ConstructorParameters(parameters) result(self) type (inputParameters ), intent(inout) :: parameters class(darkMatterProfileDMOClass ), pointer :: darkMatterProfileDMO_ class(satelliteDynamicalFrictionClass ), pointer :: satelliteDynamicalFriction_ - class(galacticStructureClass ), pointer :: galacticStructure_ !![ - !!] - self=satelliteDynamicalFrictionKaur2018(satelliteDynamicalFriction_,darkMatterProfileDMO_,galacticStructure_) + self=satelliteDynamicalFrictionKaur2018(satelliteDynamicalFriction_,darkMatterProfileDMO_) !![ - !!] return end function kaur2018ConstructorParameters - function kaur2018ConstructorInternal(satelliteDynamicalFriction_,darkMatterProfileDMO_,galacticStructure_) result(self) + function kaur2018ConstructorInternal(satelliteDynamicalFriction_,darkMatterProfileDMO_) result(self) !!{ Internal constructor for the {\normalfont \ttfamily kaur2018} satellite dynamical friction class. !!} @@ -114,9 +109,8 @@ function kaur2018ConstructorInternal(satelliteDynamicalFriction_,darkMatterProfi type (satelliteDynamicalFrictionKaur2018) :: self class(darkMatterProfileDMOClass ), intent(in ), target :: darkMatterProfileDMO_ class(satelliteDynamicalFrictionClass ), intent(in ), target :: satelliteDynamicalFriction_ - class(galacticStructureClass ), intent(in ), target :: galacticStructure_ !![ - + !!] ! Build interpolators for the suppression factor as a function of radius. These are extracted directly from the arXiv source @@ -180,7 +174,6 @@ subroutine kaur2018Destructor(self) !![ - !!] return end subroutine kaur2018Destructor @@ -189,15 +182,18 @@ function kaur2018Acceleration(self,node) !!{ Return an acceleration for satellites due to dynamical friction using the core-stalling model of \cite{kaur_stalling_2018}. !!} - use :: Galacticus_Nodes , only : nodeComponentSatellite - use :: Galactic_Structure_Options , only : coordinateSystemCartesian - use :: Root_Finder , only : rangeExpandMultiplicative , rangeExpandSignExpectNegative, rangeExpandSignExpectPositive, rootFinder + use :: Coordinates , only : coordinateSpherical , assignment(=) + use :: Galacticus_Nodes , only : nodeComponentSatellite + use :: Galactic_Structure_Options, only : coordinateSystemCartesian + use :: Mass_Distributions , only : massDistributionClass + use :: Root_Finder , only : rangeExpandMultiplicative, rangeExpandSignExpectNegative, rangeExpandSignExpectPositive, rootFinder implicit none double precision , dimension(3) :: kaur2018Acceleration class (satelliteDynamicalFrictionKaur2018), intent(inout), target :: self type (treeNode ), intent(inout) :: node class (nodeComponentSatellite ), pointer :: satellite type (treeNode ), pointer :: nodeHost + class (massDistributionClass ), pointer :: massDistribution_ double precision , dimension(3) :: position double precision , parameter :: toleranceAbsolute =0.0d+0, toleranceRelative =1.0d-3 double precision :: massSatellite , densityHostCentral , & @@ -205,6 +201,7 @@ function kaur2018Acceleration(self,node) & radiusOrbital , radiusStalling , & & radiusDimensionless type (rootFinder ) :: finder + type (coordinateSpherical ) :: coordinates ! Compute the base acceleration. kaur2018Acceleration=+self%satelliteDynamicalFriction_%acceleration(node) @@ -218,22 +215,31 @@ function kaur2018Acceleration(self,node) if (massSatellite <= 0.0d0) return ! Check if the density profile has a finite density at the center. We do this by considering the logarithmic slope of the ! density profile. For cusped density profiles, we assume no stalling. - logSlopeDensityProfileDarkMatterHost=self%darkMatterProfileDMO_%densityLogSlope(nodeHost,radius=0.0d0) + coordinates = [0.0d0,0.0d0,0.0d0] + massDistribution_ => self %darkMatterProfileDMO_%get (nodeHost ) + logSlopeDensityProfileDarkMatterHost = massDistribution_ %densityGradientRadial(coordinates,logarithmic=.true.) + !![ + + !!] if (logSlopeDensityProfileDarkMatterHost < 0.0d0) return ! Find the stalling radius. - self_ => self - densityHostCentral=self%galacticStructure_%density(nodeHost,[0.0d0,0.0d0,0.0d0],coordinateSystemCartesian) - finder=rootFinder( & - & rootFunction =radiusStallingRoot , & - & toleranceAbsolute =toleranceAbsolute , & - & toleranceRelative =toleranceRelative , & - & rangeExpandDownward =0.5d0 , & - & rangeExpandUpward =2.0d0 , & - & rangeExpandType =rangeExpandMultiplicative , & - & rangeExpandDownwardSignExpect=rangeExpandSignExpectNegative, & - & rangeExpandUpwardSignExpect =rangeExpandSignExpectPositive & - & ) + self_ => self + massDistribution_ => nodeHost %massDistribution( ) + densityHostCentral = massDistribution_%density (coordinates) + finder = rootFinder( & + & rootFunction =radiusStallingRoot , & + & toleranceAbsolute =toleranceAbsolute , & + & toleranceRelative =toleranceRelative , & + & rangeExpandDownward =0.5d0 , & + & rangeExpandUpward =2.0d0 , & + & rangeExpandType =rangeExpandMultiplicative , & + & rangeExpandDownwardSignExpect=rangeExpandSignExpectNegative, & + & rangeExpandUpwardSignExpect =rangeExpandSignExpectPositive & + & ) radiusStalling=finder%find(rootGuess=radiusOrbital) + !![ + + !!] ! Compute the suppression factor. radiusDimensionless=+radiusOrbital & & /radiusStalling @@ -258,12 +264,12 @@ Root function used in finding the stalling radius. implicit none double precision, intent(in ) :: radiusStalling - radiusStallingRoot=+4.0d0 & - & *Pi & - & /3.0d0 & - & *densityHostCentral & - & *radiusStalling **3 & - & -self_%galacticStructure_%massEnclosed(nodeHost,radiusStalling) & + radiusStallingRoot=+4.0d0 & + & *Pi & + & /3.0d0 & + & *densityHostCentral & + & *radiusStalling **3 & + & -massDistribution_%massEnclosedBySphere(radiusStalling) & & -massSatellite return end function radiusStallingRoot diff --git a/source/satellites.dynamical_friction.acceleration.Petts2015.F90 b/source/satellites.dynamical_friction.acceleration.Petts2015.F90 index 1034acf43a..87acb7e1bb 100644 --- a/source/satellites.dynamical_friction.acceleration.Petts2015.F90 +++ b/source/satellites.dynamical_friction.acceleration.Petts2015.F90 @@ -64,7 +64,6 @@ function petts2015ConstructorParameters(parameters) result(self) class (darkMatterHaloScaleClass ), pointer :: darkMatterHaloScale_ class (darkMatterProfileDMOClass ), pointer :: darkMatterProfileDMO_ class (cosmologyParametersClass ), pointer :: cosmologyParameters_ - class (galacticStructureClass ), pointer :: galacticStructure_ logical :: logarithmCoulombApproximate !![ @@ -77,20 +76,18 @@ function petts2015ConstructorParameters(parameters) result(self) - !!] - self=satelliteDynamicalFrictionPetts2015(logarithmCoulombApproximate,cosmologyParameters_,darkMatterHaloScale_,darkMatterProfileDMO_,galacticStructure_) + self=satelliteDynamicalFrictionPetts2015(logarithmCoulombApproximate,cosmologyParameters_,darkMatterHaloScale_,darkMatterProfileDMO_) !![ - !!] return end function petts2015ConstructorParameters - function petts2015ConstructorInternal(logarithmCoulombApproximate,cosmologyParameters_,darkMatterHaloScale_,darkMatterProfileDMO_,galacticStructure_) result(self) + function petts2015ConstructorInternal(logarithmCoulombApproximate,cosmologyParameters_,darkMatterHaloScale_,darkMatterProfileDMO_) result(self) !!{ Internal constructor for the {\normalfont \ttfamily petts2015} satellite dynamical friction class. !!} @@ -99,10 +96,9 @@ function petts2015ConstructorInternal(logarithmCoulombApproximate,cosmologyParam class (cosmologyParametersClass ), intent(in ), target :: cosmologyParameters_ class (darkMatterHaloScaleClass ), intent(in ), target :: darkMatterHaloScale_ class (darkMatterProfileDMOClass ), intent(in ), target :: darkMatterProfileDMO_ - class (galacticStructureClass ), intent(in ), target :: galacticStructure_ logical , intent(in ) :: logarithmCoulombApproximate !![ - + !!] return @@ -119,7 +115,6 @@ subroutine petts2015Destructor(self) - !!] return end subroutine petts2015Destructor @@ -128,8 +123,10 @@ double precision function petts2015CoulombLogarithm(self,node) result(coulombLog !!{ Evaluate the Coulomb logarithm for the \cite{petts_semi-analytic_2015} dynamical friction model. !!} + use :: Coordinates , only : coordinateSpherical , assignment(=) use :: Galacticus_Nodes , only : nodeComponentBasic , nodeComponentSatellite, treeNode use :: Galactic_Structure_Options , only : componentTypeAll , massTypeDark + use :: Mass_Distributions , only : massDistributionClass use :: Numerical_Constants_Astronomical, only : gravitationalConstantGalacticus use :: Vectors , only : Vector_Magnitude implicit none @@ -138,12 +135,14 @@ double precision function petts2015CoulombLogarithm(self,node) result(coulombLog class (nodeComponentSatellite ), pointer :: satellite class (nodeComponentBasic ), pointer :: basic type (treeNode ), pointer :: nodeHost - double precision , dimension(3) :: position , velocity + class (massDistributionClass ), pointer :: massDistribution_ + double precision , dimension(3) :: position , velocity double precision :: speedOrbital , radiusOrbital , & & impactParameterMinimum , impactParameterMaximum , & & massSatellite , densitySlopeLogarithmic, & & radiusHalfMassSatellite, fractionDarkMatter , & & massHalfSatellite + type (coordinateSpherical ) :: coordinates nodeHost => node %mergesWith( ) satellite => node %satellite ( ) @@ -164,13 +163,17 @@ double precision function petts2015CoulombLogarithm(self,node) result(coulombLog & massSatellite, & & basic%mass() & & ) - radiusHalfMassSatellite = self%galacticStructure_%radiusEnclosingMass( & - & node , & - & mass =massHalfSatellite, & - & componentType=componentTypeAll , & - & massType =massTypeDark & - & ) - densitySlopeLogarithmic = abs(self%darkMatterProfileDMO_%densityLogSlope(nodeHost,radiusOrbital)) + massDistribution_ => node %massDistribution (componentType=componentTypeAll ,massType=massTypeDark) + radiusHalfMassSatellite = massDistribution_%radiusEnclosingMass(mass =massHalfSatellite ) + !![ + + !!] + coordinates = [radiusOrbital,0.0d0,0.0d0] + massDistribution_ => self %darkMatterProfileDMO_%get (nodeHost ) + densitySlopeLogarithmic = abs(massDistribution_ %densityGradientRadial(coordinates,logarithmic=.true.)) + !![ + + !!] ! Evaluate the minimum and maximum impact parameters. if (densitySlopeLogarithmic > 1.0d0) then impactParameterMaximum=radiusOrbital/densitySlopeLogarithmic diff --git a/source/satellites.evaporation_SIDM.Kummer2018.F90 b/source/satellites.evaporation_SIDM.Kummer2018.F90 index c0973ea2dd..cf13a5bf50 100644 --- a/source/satellites.evaporation_SIDM.Kummer2018.F90 +++ b/source/satellites.evaporation_SIDM.Kummer2018.F90 @@ -25,7 +25,6 @@ use :: Dark_Matter_Particles , only : darkMatterParticleClass use :: Dark_Matter_Profiles_DMO, only : darkMatterProfileDMOClass use :: Numerical_Interpolation , only : interpolator - use :: Galactic_Structure , only : galacticStructureClass !![ @@ -40,7 +39,6 @@ private class (darkMatterParticleClass ), pointer :: darkMatterParticle_ => null() class (darkMatterProfileDMOClass), pointer :: darkMatterProfileDMO_ => null() - class (galacticStructureClass ), pointer :: galacticStructure_ => null() type (interpolator ), allocatable :: evaporationFactor double precision :: rateScatteringNormalization , xMaximum contains @@ -75,24 +73,21 @@ function kummer2018ConstructorParameters(parameters) result(self) type (inputParameters ), intent(inout) :: parameters class(darkMatterParticleClass ), pointer :: darkMatterParticle_ class(darkMatterProfileDMOClass ), pointer :: darkMatterProfileDMO_ - class(galacticStructureClass ), pointer :: galacticStructure_ !![ - !!] - self=satelliteEvaporationSIDMKummer2018(darkMatterParticle_,darkMatterProfileDMO_,galacticStructure_) + self=satelliteEvaporationSIDMKummer2018(darkMatterParticle_,darkMatterProfileDMO_) !![ - !!] return end function kummer2018ConstructorParameters - function kummer2018ConstructorInternal(darkMatterParticle_,darkMatterProfileDMO_,galacticStructure_) result(self) + function kummer2018ConstructorInternal(darkMatterParticle_,darkMatterProfileDMO_) result(self) !!{ Internal constructor for the {\normalfont \ttfamily kummer2018} satellite evaporation due to dark matter self-interactions class. @@ -104,9 +99,8 @@ function kummer2018ConstructorInternal(darkMatterParticle_,darkMatterProfileDMO_ type (satelliteEvaporationSIDMKummer2018) :: self class(darkMatterParticleClass ), intent(in ), target :: darkMatterParticle_ class(darkMatterProfileDMOClass ), intent(in ), target :: darkMatterProfileDMO_ - class(galacticStructureClass ), intent(in ), target :: galacticStructure_ !![ - + !!] select type (darkMatterParticle_ => self%darkMatterParticle_) @@ -138,7 +132,6 @@ subroutine kummer2018Destructor(self) !![ - !!] return end subroutine kummer2018Destructor @@ -147,38 +140,49 @@ double precision function kummer2018MassLossRate(self,node) !!{ Return a evaporation for satellites due to dark matter self-interactions using the formulation of \cite{kummer_effective_2018}. !!} - use :: Galactic_Structure_Options , only : coordinateSystemCartesian , radiusLarge - use :: Galacticus_Nodes , only : nodeComponentSatellite,nodeComponentBasic + use :: Coordinates , only : coordinateSpherical , coordinateCartesian , assignment(=) + use :: Galactic_Structure_Options , only : coordinateSystemCartesian , radiusLarge + use :: Galacticus_Nodes , only : nodeComponentSatellite , nodeComponentBasic + use :: Mass_Distributions , only : massDistributionClass , kinematicsDistributionClass use :: Numerical_Constants_Astronomical, only : gravitationalConstantGalacticus use :: Vectors , only : Vector_Magnitude implicit none class (satelliteEvaporationSIDMKummer2018), intent(inout) :: self - type (treeNode ), intent(inout) :: node - class (nodeComponentSatellite ), pointer :: satellite - class (nodeComponentBasic ), pointer :: basic - type (treeNode ), pointer :: nodeHost - double precision , dimension(3) :: position , velocity - double precision :: radiusOrbital , speedOrbital , & - & densityHost , rateScattering , & - & potentialHalfMass , potentialBoundary , & - & massBoundary , radiusBoundary , & - & velocityEscape , speedHalfMass , & - & velocityDispersionHost, velocityDispersionSatellite, & - & x , radiusHalfMass , & - & velocityDispersion , potentialEscape + type (treeNode ), intent(inout) :: node + class (nodeComponentSatellite ), pointer :: satellite + class (nodeComponentBasic ), pointer :: basic + type (treeNode ), pointer :: nodeHost + class (massDistributionClass ), pointer :: massDistribution_ , massDistributionHost_ + class (kinematicsDistributionClass ), pointer :: kinematics_ , kinematicsHost_ + double precision , dimension(3) :: position , velocity + double precision :: radiusOrbital , speedOrbital , & + & densityHost , rateScattering , & + & massBoundary , radiusBoundary , & + & velocityEscape , speedHalfMass , & + & velocityDispersionHost, velocityDispersionSatellite, & + & x , radiusHalfMass , & + & velocityDispersion , potentialEscape + type (coordinateSpherical ) :: coordinates , coordinatesHost , & + & coordinatesBoundary , coordinatesHalfMass + type (coordinateCartesian ) :: coordinatesCartesian ! Set zero mass loss rate by default. kummer2018MassLossRate=0.0d0 ! If the scattering cross section is zero, we can return immediately. if (self%rateScatteringNormalization == 0.0d0) return ! Evaluate satellite and host properties. - nodeHost => node %mergesWith( ) - satellite => node %satellite ( ) - position = satellite %position ( ) - velocity = satellite %velocity ( ) - radiusOrbital = Vector_Magnitude ( position ) - speedOrbital = Vector_Magnitude ( velocity ) - densityHost = self %galacticStructure_%density (nodeHost,position,coordinateSystemCartesian) + nodeHost => node %mergesWith ( ) + satellite => node %satellite ( ) + massDistributionHost_ => nodeHost %massDistribution( ) + position = satellite %position ( ) + velocity = satellite %velocity ( ) + radiusOrbital = Vector_Magnitude ( position) + speedOrbital = Vector_Magnitude ( velocity) + coordinatesCartesian = position + densityHost = massDistributionHost_%density (coordinatesCartesian) + !![ + + !!] ! Find the escape velocity from the half-mass radius of the subhalo. This is equal to the potential difference between the ! half-mass radius and outer boundary of the subhalo, plus the potential difference from the outer boundary to infinity (for ! which we can treat the subhalo as a point mass). @@ -193,16 +197,16 @@ double precision function kummer2018MassLossRate(self,node) & basic % mass() & & ) if (massBoundary > 0.0d0) then - radiusBoundary =self%galacticStructure_%radiusEnclosingMass(node,mass = massBoundary ) - radiusHalfMass =self%galacticStructure_%radiusEnclosingMass(node,mass =0.5d0*massBoundary ) + massDistribution_ => node %massDistribution ( ) + radiusBoundary = massDistribution_%radiusEnclosingMass(mass= massBoundary) + radiusHalfMass = massDistribution_%radiusEnclosingMass(mass=0.5d0*massBoundary) if (radiusBoundary < 0.5d0*radiusLarge) then - potentialBoundary=self%galacticStructure_%potential (node,radius= radiusBoundary) - potentialHalfMass=self%galacticStructure_%potential (node,radius= radiusHalfMass) - potentialEscape =+potentialBoundary & - & -potentialHalfMass & - & +gravitationalConstantGalacticus & - & *massBoundary & - & /radiusBoundary + coordinatesBoundary=[radiusBoundary,0.0d0,0.0d0] + coordinatesHalfMass=[radiusHalfMass,0.0d0,0.0d0] + potentialEscape =+massDistribution_%potentialDifference(coordinatesBoundary,coordinatesHalfMass) & + & +gravitationalConstantGalacticus & + & *massBoundary & + & /radiusBoundary if (potentialEscape > 0.0d0) then velocityEscape=sqrt(2.0d0*potentialEscape) else @@ -211,13 +215,28 @@ double precision function kummer2018MassLossRate(self,node) else velocityEscape=0.0d0 end if + !![ + + !!] ! Find the combined velocity dispersion of satellite and host. - velocityDispersionHost =+self%darkMatterProfileDMO_%radialVelocityDispersion(nodeHost,radiusOrbital ) - velocityDispersionSatellite=+self%darkMatterProfileDMO_%radialVelocityDispersion(node ,radiusHalfMass) - velocityDispersion =+sqrt( & - & +velocityDispersionHost **2 & - & +velocityDispersionSatellite**2 & - & ) + massDistribution_ => self %darkMatterProfileDMO_%get (node ) + massDistributionHost_ => self %darkMatterProfileDMO_%get (nodeHost ) + kinematics_ => massDistribution_ %kinematicsDistribution( ) + kinematicsHost_ => massDistributionHost_ %kinematicsDistribution( ) + coordinates = [radiusHalfMass,0.0d0,0.0d0] + coordinatesHost = [radiusOrbital ,0.0d0,0.0d0] + velocityDispersionHost = +kinematicsHost_ %velocityDispersion1D (coordinatesHost,massDistributionHost_) + velocityDispersionSatellite = +kinematics_ %velocityDispersion1D (coordinates ,massDistribution_ ) + velocityDispersion = +sqrt( & + & +velocityDispersionHost **2 & + & +velocityDispersionSatellite**2 & + & ) + !![ + + + + + !!] ! Get the speed of a host particle at the half-mass radius of the subhalo - this is the sum of the kinetic energy or host ! particles in the rest-frame of the subhalo, plus the energy they gain by falling in to the half-mass radius of the ! subhalo. We include the correction factor of the velocity dispersion as suggested in equation (A4) of Kummer et diff --git a/source/satellites.merging.mass_movements.Baugh2005.F90 b/source/satellites.merging.mass_movements.Baugh2005.F90 index 21c2c831b9..0989324729 100644 --- a/source/satellites.merging.mass_movements.Baugh2005.F90 +++ b/source/satellites.merging.mass_movements.Baugh2005.F90 @@ -21,8 +21,7 @@ Implements a merger mass movements class using the \cite{baugh_can_2005} model. !!} - use :: Kind_Numbers , only : kind_int8 - use :: Galactic_Structure, only : galacticStructureClass + use :: Kind_Numbers, only : kind_int8 !![ @@ -51,14 +50,13 @@ A merger mass movements class which uses the \cite{baugh_can_2005} calculation. !!} private - class (galacticStructureClass ), pointer :: galacticStructure_ => null() - double precision :: massRatioMajorMerger , ratioMassBurst , & - & fractionGasCriticalBurst - type (enumerationDestinationMergerType) :: destinationGasMinorMerger - integer (kind=kind_int8 ) :: lastUniqueID - type (enumerationDestinationMergerType) :: destinationGasSatellite , destinationStarsSatellite, & - & destinationGasHost , destinationStarsHost - logical :: mergerIsMajor , movementsCalculated + double precision :: massRatioMajorMerger , ratioMassBurst , & + & fractionGasCriticalBurst + type (enumerationDestinationMergerType) :: destinationGasMinorMerger + integer (kind=kind_int8 ) :: lastUniqueID + type (enumerationDestinationMergerType) :: destinationGasSatellite , destinationStarsSatellite, & + & destinationGasHost , destinationStarsHost + logical :: mergerIsMajor , movementsCalculated contains final :: baugh2005Destructor procedure :: autoHook => baugh2005AutoHook @@ -83,7 +81,6 @@ function baugh2005ConstructorParameters(parameters) result(self) implicit none type (mergerMassMovementsBaugh2005) :: self type (inputParameters ), intent(inout) :: parameters - class (galacticStructureClass ), pointer :: galacticStructure_ double precision :: massRatioMajorMerger , ratioMassBurst, & & fractionGasCriticalBurst type (varying_string ) :: destinationGasMinorMerger @@ -113,28 +110,25 @@ function baugh2005ConstructorParameters(parameters) result(self) The component to which satellite galaxy gas moves to as a result of a minor merger. parameters - !!] - self=mergerMassMovementsBaugh2005(massRatioMajorMerger,enumerationDestinationMergerEncode(char(destinationGasMinorMerger),includesPrefix=.false.),ratioMassBurst,fractionGasCriticalBurst,galacticStructure_) + self=mergerMassMovementsBaugh2005(massRatioMajorMerger,enumerationDestinationMergerEncode(char(destinationGasMinorMerger),includesPrefix=.false.),ratioMassBurst,fractionGasCriticalBurst) !![ - !!] return end function baugh2005ConstructorParameters - function baugh2005ConstructorInternal(massRatioMajorMerger,destinationGasMinorMerger,ratioMassBurst,fractionGasCriticalBurst,galacticStructure_) result(self) + function baugh2005ConstructorInternal(massRatioMajorMerger,destinationGasMinorMerger,ratioMassBurst,fractionGasCriticalBurst) result(self) !!{ Internal constructor for the {\normalfont \ttfamily baugh2005} merger mass movements. !!} implicit none - type (mergerMassMovementsBaugh2005 ) :: self - class (galacticStructureClass ), intent(in ), target :: galacticStructure_ - double precision , intent(in ) :: massRatioMajorMerger , ratioMassBurst, & - & fractionGasCriticalBurst - type (enumerationDestinationMergerType), intent(in ) :: destinationGasMinorMerger + type (mergerMassMovementsBaugh2005 ) :: self + double precision , intent(in ) :: massRatioMajorMerger , ratioMassBurst, & + & fractionGasCriticalBurst + type (enumerationDestinationMergerType), intent(in ) :: destinationGasMinorMerger !![ - + !!] self%lastUniqueID =-huge(0_kind_int8) @@ -170,9 +164,6 @@ subroutine baugh2005Destructor(self) if (calculationResetEvent%isAttached(self,baugh2005CalculationReset)) call calculationResetEvent%detach(self,baugh2005CalculationReset) if (satelliteMergerEvent %isAttached(self,baugh2005GetHook )) call satelliteMergerEvent %detach(self,baugh2005GetHook ) - !![ - - !!] return end subroutine baugh2005Destructor @@ -224,16 +215,20 @@ subroutine baugh2005Get(self,node,destinationGasSatellite,destinationStarsSatell Determine how different mass components should be redistributed as the result of a merger according to the model of \cite{baugh_can_2005}. !!} - use :: Galactic_Structure_Options, only : componentTypeSpheroid, massTypeGalactic, massTypeGaseous + use :: Galactic_Structure_Options, only : componentTypeSpheroid, componentTypeDisk, massTypeGalactic, massTypeGaseous + use :: Mass_Distributions , only : massDistributionClass implicit none class (mergerMassMovementsBaugh2005 ), intent(inout) :: self type (treeNode ), intent(inout), target :: node - type (enumerationDestinationMergerType), intent( out) :: destinationGasSatellite, destinationGasHost , & - & destinationStarsHost , destinationStarsSatellite + type (enumerationDestinationMergerType), intent( out) :: destinationGasSatellite , destinationGasHost , & + & destinationStarsHost , destinationStarsSatellite logical , intent( out) :: mergerIsMajor type (treeNode ), pointer :: nodeHost - double precision :: massHost , massSatellite , & - & massSpheroidHost , massGasHost + class (massDistributionClass ), pointer :: massDistributionSatellite , massDistributionHost , & + & massDistributionHostDiskGas , massDistributionHostSpheroidGas, & + & massDistributionHostSpheroid + double precision :: massHost , massSatellite , & + & massSpheroidHost , massGasHost logical :: triggersBurst ! The calculation of how mass moves as a result of the merger is computed when first needed and then stored. This ensures that @@ -242,20 +237,33 @@ subroutine baugh2005Get(self,node,destinationGasSatellite,destinationStarsSatell if (node%uniqueID() /= self%lastUniqueID) call baugh2005CalculationReset(self,node,node%uniqueID()) if (.not.self%movementsCalculated) then self%movementsCalculated = .true. - nodeHost => node%mergesWith() - massSatellite = self%galacticStructure_%massEnclosed(node ,massType=massTypeGalactic) - massHost = self%galacticStructure_%massEnclosed(nodeHost ,massType=massTypeGalactic) - massGasHost = self%galacticStructure_%massEnclosed(nodeHost ,massType=massTypeGaseous ) - massSpheroidHost = self%galacticStructure_%massEnclosed(nodeHost,componentType=componentTypeSpheroid,massType=massTypeGalactic) - self%mergerIsMajor = massSatellite >= self%massRatioMajorMerger*massHost - triggersBurst = self%mergerIsMajor & - & .or. & - & ( & - & massSpheroidHost < self%ratioMassBurst *massHost & - & .and. & - & massGasHost >= self%fractionGasCriticalBurst*massHost & - & ) - if (self%mergerIsMajor) then + nodeHost => node %mergesWith ( ) + massDistributionSatellite => node %massDistribution( massType=massTypeGalactic) + massDistributionHost => nodeHost %massDistribution( massType=massTypeGalactic) + massDistributionHostSpheroid => nodeHost %massDistribution(componentType=componentTypeSpheroid,massType=massTypeGalactic) + massDistributionHostDiskGas => nodeHost %massDistribution(componentType=componentTypeDisk ,massType=massTypeGaseous ) + massDistributionHostSpheroidGas => nodeHost %massDistribution(componentType=componentTypeSpheroid,massType=massTypeGaseous ) + massSatellite = +massDistributionSatellite %massTotal ( ) + massHost = +massDistributionHost %massTotal ( ) + massGasHost = +massDistributionHostDiskGas %massTotal ( ) & + & +massDistributionHostSpheroidGas%massTotal ( ) + massSpheroidHost = +massDistributionHostSpheroid %massTotal ( ) + self%mergerIsMajor = massSatellite >= self%massRatioMajorMerger *massHost + triggersBurst = self%mergerIsMajor & + & .or. & + & ( & + & massSpheroidHost < self%ratioMassBurst *massHost & + & .and. & + & massGasHost >= self%fractionGasCriticalBurst*massHost & + & ) + !![ + + + + + + !!] + if (self%mergerIsMajor) then self%destinationGasSatellite = destinationMergerSpheroid self%destinationStarsSatellite = destinationMergerSpheroid self%destinationGasHost = destinationMergerSpheroid diff --git a/source/satellites.merging.mass_movements.simple.F90 b/source/satellites.merging.mass_movements.simple.F90 index f021b3be1e..8684fd3feb 100644 --- a/source/satellites.merging.mass_movements.simple.F90 +++ b/source/satellites.merging.mass_movements.simple.F90 @@ -21,8 +21,7 @@ Implements a merger mass movements class which uses a simple calculation. !!} - use :: Kind_Numbers , only : kind_int8 - use :: Galactic_Structure, only : galacticStructureClass + use :: Kind_Numbers, only : kind_int8 !![ @@ -45,13 +44,12 @@ A merger mass movements class which uses a simple calculation. !!} private - class (galacticStructureClass ), pointer :: galacticStructure_ => null() - double precision :: massRatioMajorMerger - type (enumerationDestinationMergerType) :: destinationGasMinorMerger , destinationStarsMinorMerger - integer (kind=kind_int8 ) :: lastUniqueID - type (enumerationDestinationMergerType) :: destinationGasSatellite , destinationStarsSatellite , & - & destinationGasHost , destinationStarsHost - logical :: mergerIsMajor , movementsCalculated + double precision :: massRatioMajorMerger + type (enumerationDestinationMergerType) :: destinationGasMinorMerger, destinationStarsMinorMerger + integer (kind=kind_int8 ) :: lastUniqueID + type (enumerationDestinationMergerType) :: destinationGasSatellite , destinationStarsSatellite , & + & destinationGasHost , destinationStarsHost + logical :: mergerIsMajor , movementsCalculated contains final :: simpleDestructor procedure :: autoHook => simpleAutoHook @@ -76,7 +74,6 @@ function simpleConstructorParameters(parameters) result(self) implicit none type (mergerMassMovementsSimple) :: self type (inputParameters ), intent(inout) :: parameters - class (galacticStructureClass ), pointer :: galacticStructure_ double precision :: massRatioMajorMerger type (varying_string ) :: destinationGasMinorMerger, destinationStarsMinorMerger @@ -99,27 +96,24 @@ function simpleConstructorParameters(parameters) result(self) The component to which satellite galaxy stars move to as a result of a minor merger. parameters - !!] - self=mergerMassMovementsSimple(massRatioMajorMerger,enumerationDestinationMergerEncode(char(destinationGasMinorMerger),includesPrefix=.false.),enumerationDestinationMergerEncode(char(destinationStarsMinorMerger),includesPrefix=.false.),galacticStructure_) + self=mergerMassMovementsSimple(massRatioMajorMerger,enumerationDestinationMergerEncode(char(destinationGasMinorMerger),includesPrefix=.false.),enumerationDestinationMergerEncode(char(destinationStarsMinorMerger),includesPrefix=.false.)) !![ - !!] return end function simpleConstructorParameters - function simpleConstructorInternal(massRatioMajorMerger,destinationGasMinorMerger,destinationStarsMinorMerger,galacticStructure_) result(self) + function simpleConstructorInternal(massRatioMajorMerger,destinationGasMinorMerger,destinationStarsMinorMerger) result(self) !!{ Internal constructor for the {\normalfont \ttfamily simple} merger mass movements class. !!} implicit none type (mergerMassMovementsSimple ) :: self - class (galacticStructureClass ), intent(in ), target :: galacticStructure_ double precision , intent(in ) :: massRatioMajorMerger type (enumerationDestinationMergerType), intent(in ) :: destinationGasMinorMerger, destinationStarsMinorMerger !![ - + !!] self%lastUniqueID =-huge(0_kind_int8) @@ -155,9 +149,6 @@ subroutine simpleDestructor(self) if (calculationResetEvent%isAttached(self,simpleCalculationReset)) call calculationResetEvent%detach(self,simpleCalculationReset) if (satelliteMergerEvent %isAttached(self,simpleGetHook )) call satelliteMergerEvent %detach(self,simpleGetHook ) - !![ - - !!] return end subroutine simpleDestructor @@ -208,16 +199,19 @@ subroutine simpleGet(self,node,destinationGasSatellite,destinationStarsSatellite !!{ Determine where stars and gas move as the result of a merger event using a simple algorithm. !!} - use :: Galactic_Structure_Options, only : componentTypeDisk, componentTypeSpheroid, massTypeGalactic + use :: Galactic_Structure_Options, only : componentTypeDisk , componentTypeSpheroid, massTypeGalactic + use :: Mass_Distributions , only : massDistributionClass implicit none class (mergerMassMovementsSimple ), intent(inout) :: self type (treeNode ), intent(inout), target :: node - type (enumerationDestinationMergerType), intent( out) :: destinationGasSatellite, destinationGasHost , & - & destinationStarsHost , destinationStarsSatellite + type (enumerationDestinationMergerType), intent( out) :: destinationGasSatellite , destinationGasHost , & + & destinationStarsHost , destinationStarsSatellite logical , intent( out) :: mergerIsMajor - type (treeNode ), pointer :: nodeHost , nodeMajor - double precision :: massHost , massSatellite , & - & massSpheroid , massDisk + type (treeNode ), pointer :: nodeHost , nodeMajor + class (massDistributionClass ), pointer :: massDistributionSatellite, massDistributionHost , & + & massDistributionDisk , massDistributionSpheroid + double precision :: massHost , massSatellite , & + & massSpheroid , massDisk type (enumerationDestinationMergerType) :: destinationDominant ! The calculation of how mass moves as a result of the merger is computed when first needed and then stored. This ensures that @@ -225,11 +219,17 @@ subroutine simpleGet(self,node,destinationGasSatellite,destinationStarsSatellite ! components are modified in response to the merger. if (node%uniqueID() /= self%lastUniqueID) call simpleCalculationReset(self,node,node%uniqueID()) if (.not.self%movementsCalculated) then - self%movementsCalculated = .true. - nodeHost => node%mergesWith() - massSatellite = self%galacticStructure_%massEnclosed(node ,massType=massTypeGalactic) - massHost = self%galacticStructure_%massEnclosed(nodeHost,massType=massTypeGalactic) - self%mergerIsMajor = massSatellite > 0.0d0 .and. massHost > 0.0d0 .and. min(massSatellite,massHost) >= self%massRatioMajorMerger*max(massSatellite,massHost) + self%movementsCalculated = .true. + nodeHost => node %mergesWith ( ) + massDistributionHost => nodeHost %massDistribution(massType=massTypeGalactic) + massDistributionSatellite => node %massDistribution(massType=massTypeGalactic) + massSatellite = massDistributionSatellite%massTotal ( ) + massHost = massDistributionHost %massTotal ( ) + self%mergerIsMajor = massSatellite > 0.0d0 .and. massHost > 0.0d0 .and. min(massSatellite,massHost) >= self%massRatioMajorMerger*max(massSatellite,massHost) + !![ + + + !!] if (self%mergerIsMajor) then self%destinationGasSatellite = destinationMergerSpheroid self%destinationStarsSatellite= destinationMergerSpheroid @@ -243,8 +243,14 @@ subroutine simpleGet(self,node,destinationGasSatellite,destinationStarsSatellite else nodeMajor => node end if - massDisk =self%galacticStructure_%massEnclosed(nodeMajor,massType=massTypeGalactic,componentType=componentTypeDisk ) - massSpheroid=self%galacticStructure_%massEnclosed(nodeMajor,massType=massTypeGalactic,componentType=componentTypeSpheroid) + massDistributionDisk => nodeMajor %massDistribution(massType=massTypeGalactic,componentType=componentTypeDisk ) + massDistributionSpheroid => nodeMajor %massDistribution(massType=massTypeGalactic,componentType=componentTypeSpheroid) + massDisk = massDistributionDisk %massTotal ( ) + massSpheroid = massDistributionSpheroid%massTotal ( ) + !![ + + + !!] if (massDisk > massSpheroid) then destinationDominant=destinationMergerDisk else @@ -252,12 +258,12 @@ subroutine simpleGet(self,node,destinationGasSatellite,destinationStarsSatellite end if end if if (self%destinationGasMinorMerger == destinationMergerDominant) then - self%destinationGasSatellite =destinationDominant + self%destinationGasSatellite = destinationDominant else self%destinationGasSatellite =self%destinationGasMinorMerger end if if (self%destinationStarsMinorMerger == destinationMergerDominant) then - self%destinationStarsSatellite=destinationDominant + self%destinationStarsSatellite= destinationDominant else self%destinationStarsSatellite=self%destinationStarsMinorMerger end if diff --git a/source/satellites.merging.mass_movements.very_simple.F90 b/source/satellites.merging.mass_movements.very_simple.F90 index a1ba06b9fb..0a6c470b3b 100644 --- a/source/satellites.merging.mass_movements.very_simple.F90 +++ b/source/satellites.merging.mass_movements.very_simple.F90 @@ -21,8 +21,7 @@ Implements a merger mass movements class which uses a simple calculation. !!} - use :: Kind_Numbers , only : kind_int8 - use :: Galactic_Structure, only : galacticStructureClass + use :: Kind_Numbers, only : kind_int8 !![ @@ -37,10 +36,9 @@ A merger mass movements class which uses a simple calculation. !!} private - class (galacticStructureClass), pointer :: galacticStructure_ => null() - double precision :: massRatioMajorMerger - integer (kind=kind_int8 ) :: lastUniqueID - logical :: mergerIsMajor , movementsCalculated + double precision :: massRatioMajorMerger + integer (kind=kind_int8) :: lastUniqueID + logical :: mergerIsMajor , movementsCalculated contains final :: verySimpleDestructor procedure :: autoHook => verySimpleAutoHook @@ -65,7 +63,6 @@ function verySimpleConstructorParameters(parameters) result(self) implicit none type (mergerMassMovementsVerySimple) :: self type (inputParameters ), intent(inout) :: parameters - class (galacticStructureClass ), pointer :: galacticStructure_ double precision :: massRatioMajorMerger !![ @@ -75,26 +72,23 @@ function verySimpleConstructorParameters(parameters) result(self) The mass ratio above which mergers are considered to be ``major''. parameters - !!] - self=mergerMassMovementsVerySimple(massRatioMajorMerger,galacticStructure_) + self=mergerMassMovementsVerySimple(massRatioMajorMerger) !![ - !!] return end function verySimpleConstructorParameters - function verySimpleConstructorInternal(massRatioMajorMerger,galacticStructure_) result(self) + function verySimpleConstructorInternal(massRatioMajorMerger) result(self) !!{ Internal constructor for the {\normalfont \ttfamily verySimple} merger mass movements. !!} implicit none - type (mergerMassMovementsVerySimple) :: self - class (galacticStructureClass ), intent(in ), target :: galacticStructure_ - double precision , intent(in ) :: massRatioMajorMerger + type (mergerMassMovementsVerySimple) :: self + double precision , intent(in ) :: massRatioMajorMerger !![ - + !!] self%lastUniqueID =-huge(0_kind_int8) @@ -126,9 +120,6 @@ subroutine verySimpleDestructor(self) if (calculationResetEvent%isAttached(self,verySimpleCalculationReset)) call calculationResetEvent%detach(self,verySimpleCalculationReset) if (satelliteMergerEvent %isAttached(self,verySimpleGetHook )) call satelliteMergerEvent %detach(self,verySimpleGetHook ) - !![ - - !!] return end subroutine verySimpleDestructor @@ -180,14 +171,16 @@ subroutine verySimpleGet(self,node,destinationGasSatellite,destinationStarsSatel Determine where stars and gas move as the result of a merger event using a very simple algorithm. !!} use :: Galactic_Structure_Options, only : massTypeGalactic + use :: Mass_Distributions , only : massDistributionClass implicit none class (mergerMassMovementsVerySimple ), intent(inout) :: self type (treeNode ), intent(inout), target :: node - type (enumerationDestinationMergerType), intent( out) :: destinationGasSatellite, destinationGasHost , & - & destinationStarsHost , destinationStarsSatellite + type (enumerationDestinationMergerType), intent( out) :: destinationGasSatellite , destinationGasHost , & + & destinationStarsHost , destinationStarsSatellite logical , intent( out) :: mergerIsMajor type (treeNode ), pointer :: nodeHost - double precision :: massHost , massSatellite + class (massDistributionClass ), pointer :: massDistributionSatellite, massDistributionHost + double precision :: massHost , massSatellite ! The calculation of how mass moves as a result of the merger is computed when first needed and then stored. This ensures that ! the results are determined by the properties of the merge target prior to any modification that will occur as node @@ -200,10 +193,16 @@ subroutine verySimpleGet(self,node,destinationGasSatellite,destinationStarsSatel else if (self%massRatioMajorMerger > 1.0d0) then self%mergerIsMajor=.false. else - nodeHost => node%mergesWith() - massSatellite = self%galacticStructure_%massEnclosed(node ,massType=massTypeGalactic) - massHost = self%galacticStructure_%massEnclosed(nodeHost,massType=massTypeGalactic) - self%mergerIsMajor = massSatellite >= self%massRatioMajorMerger*massHost + nodeHost => node %mergesWith ( ) + massDistributionHost => nodeHost %massDistribution(massType=massTypeGalactic) + massDistributionSatellite => node %massDistribution(massType=massTypeGalactic) + massSatellite = massDistributionSatellite%massTotal ( ) + massHost = massDistributionHost %massTotal ( ) + self%mergerIsMajor = massSatellite >= self%massRatioMajorMerger*massHost + !![ + + + !!] end if end if mergerIsMajor =self%mergerIsMajor diff --git a/source/satellites.merging.progenitor_properties.Cole2000.F90 b/source/satellites.merging.progenitor_properties.Cole2000.F90 index 686ade808c..b1f5d56975 100644 --- a/source/satellites.merging.progenitor_properties.Cole2000.F90 +++ b/source/satellites.merging.progenitor_properties.Cole2000.F90 @@ -23,7 +23,7 @@ use :: Root_Finder , only : rootFinder use :: Satellite_Merging_Mass_Movements, only : mergerMassMovementsClass, enumerationDestinationMergerType - use :: Galactic_Structure , only : galacticStructureClass + use :: Mass_Distributions , only : massDistributionClass !![ @@ -67,7 +67,6 @@ A merger progenitor properties class which uses the algorithm of \cite{cole_hierarchical_2000}. !!} private - class(galacticStructureClass ), pointer :: galacticStructure_ => null() class(mergerMassMovementsClass), pointer :: mergerMassMovements_ => null() type (rootFinder ) :: finder contains @@ -86,9 +85,11 @@ ! Module global variables used in root finding. class (mergerProgenitorPropertiesCole2000), pointer :: self_ type (treeNode ), pointer :: node_ - type (enumerationDestinationMergerType ) :: destinationGas_, destinationStars_ + class (massDistributionClass ), pointer :: massDistributionSpheroidStellar_, massDistributionDiskStellar_, & + & massDistributionSpheroidGaseous_, massDistributionDiskGaseous_ + type (enumerationDestinationMergerType ) :: destinationGas_ , destinationStars_ double precision :: massHalf_ - !$omp threadprivate(self_,node_,destinationGas_,destinationStars_,massHalf_) + !$omp threadprivate(self_,node_,destinationGas_,destinationStars_,massHalf_,massDistributionSpheroidStellar_,massDistributionDiskStellar_,massDistributionSpheroidGaseous_,massDistributionDiskGaseous_) contains @@ -104,7 +105,6 @@ function cole2000ConstructorParameters(parameters) result(self) type (mergerProgenitorPropertiesCole2000) :: self type (inputParameters ), intent(inout) :: parameters class(mergerMassMovementsClass ), pointer :: mergerMassMovements_ - class(galacticStructureClass ), pointer :: galacticStructure_ ! Ensure that required methods are supported. if ( & @@ -149,18 +149,16 @@ function cole2000ConstructorParameters(parameters) result(self) & ) !![ - !!] - self=mergerProgenitorPropertiesCole2000(mergerMassMovements_,galacticStructure_) + self=mergerProgenitorPropertiesCole2000(mergerMassMovements_) !![ - !!] return end function cole2000ConstructorParameters - function cole2000ConstructorInternal(mergerMassMovements_,galacticStructure_) result(self) + function cole2000ConstructorInternal(mergerMassMovements_) result(self) !!{ Internal constructor for the {\normalfont \ttfamily cole2000} merger progenitor properties class. !!} @@ -168,9 +166,8 @@ function cole2000ConstructorInternal(mergerMassMovements_,galacticStructure_) re implicit none type (mergerProgenitorPropertiesCole2000) :: self class(mergerMassMovementsClass ), intent(in ), target :: mergerMassMovements_ - class(galacticStructureClass ), intent(in ), target :: galacticStructure_ !![ - + !!] self%finder=rootFinder( & @@ -195,7 +192,6 @@ subroutine cole2000Destructor(self) !![ - !!] return end subroutine cole2000Destructor @@ -204,7 +200,8 @@ subroutine cole2000Get(self,nodeSatellite,nodeHost,massSatellite,massHost,massSp !!{ Computes various properties of the progenitor galaxies useful for calculations of merger remnant sizes. !!} - use :: Galactic_Structure_Options , only : massTypeGalactic , radiusLarge + use :: Galactic_Structure_Options , only : componentTypeDisk , componentTypeSpheroid , massTypeGaseous , massTypeStellar, & + & massTypeGalactic , radiusLarge use :: Error , only : Error_Report use :: Galacticus_Nodes , only : nodeComponentDisk , nodeComponentSpheroid , treeNode use :: Numerical_Constants_Astronomical, only : gravitationalConstantGalacticus @@ -219,6 +216,7 @@ subroutine cole2000Get(self,nodeSatellite,nodeHost,massSatellite,massHost,massSp & radiusSatellite , massSpheroidSatellite class (nodeComponentDisk ), pointer :: diskHost , diskSatelite class (nodeComponentSpheroid ), pointer :: spheroidHost , spheroidSatellite + class (massDistributionClass ), pointer :: massDistributionHost , massDistributionSatellite double precision :: massComponent , factorDarkMatterDiskHost , & & radiusHalfMassDiskHost , factorDarkMatterSpheroidHost , & & radiusHalfMassSpheroidHost , factorDarkMatterDiskSatellite , & @@ -236,8 +234,10 @@ subroutine cole2000Get(self,nodeSatellite,nodeHost,massSatellite,massHost,massSp diskSatelite => nodeSatellite%disk () spheroidSatellite => nodeSatellite%spheroid() ! Find the baryonic masses of the two galaxies. - massSatellite=self%galacticStructure_%massEnclosed(nodeSatellite,massType=massTypeGalactic) - massHost =self%galacticStructure_%massEnclosed( nodeHost,massType=massTypeGalactic) + massDistributionSatellite => nodeSatellite %massDistribution(massType=massTypeGalactic) + massDistributionHost => nodeHost %massDistribution(massType=massTypeGalactic) + massSatellite = massDistributionSatellite%massTotal ( ) + massHost = massDistributionHost %massTotal ( ) ! Compute dark matter factors. These are the specific angular momenta of components divided by sqrt(G M r) where M is the ! component mass and r its half-mass radius. We use a weighted average of these factors to infer the specific angular momentum ! of the remnant from its mass and radius. @@ -351,44 +351,54 @@ subroutine cole2000Get(self,nodeSatellite,nodeHost,massSatellite,massHost,massSp ! Compute the half-mass radii of the material that will end up in the remnant spheroid. ! Host node. if (massSpheroidHost > 0.0d0) then - self_ => self - node_ => nodeHost - destinationGas_ = destinationGasHost - destinationStars_= destinationStarsHost - massHalf_ = 0.0d0 ! Set to zero here so that cole2000HalfMassRadiusRoot() returns the actual half mass. - massHalf_ = 0.5d0*cole2000HalfMassRadiusRoot(radiusLarge) + self_ => self + node_ => nodeHost + massDistributionSpheroidStellar_ => nodeHost%massDistribution(componentType=componentTypeSpheroid,massType=massTypeStellar) + massDistributionDiskStellar_ => nodeHost%massDistribution(componentType=componentTypeDisk ,massType=massTypeStellar) + massDistributionSpheroidGaseous_ => nodeHost%massDistribution(componentType=componentTypeSpheroid,massType=massTypeGaseous) + massDistributionDiskGaseous_ => nodeHost%massDistribution(componentType=componentTypeDisk ,massType=massTypeGaseous) + destinationGas_ = destinationGasHost + destinationStars_ = destinationStarsHost + massHalf_ = 0.0d0 ! Set to zero here so that cole2000HalfMassRadiusRoot() returns the actual half mass. + massHalf_ = 0.5d0*cole2000HalfMassRadiusRoot(radiusLarge) if (cole2000HalfMassRadiusRoot(0.0d0) <= 0.0d0) then - radiusHost=self%finder%find(rootGuess=self%galacticStructure_%radiusEnclosingMass( & - & node_ , & - & massFractional=0.50d0 , & - & massType =massTypeGalactic & - & ) & - & ) + radiusHost=self%finder%find(rootGuess=massDistributionHost%radiusEnclosingMass(massFractional=0.50d0)) else radiusHost =0.0d0 massSpheroidHost=0.0d0 end if + !![ + + + + + !!] else radiusHost=0.0d0 end if if (massSpheroidSatellite > 0.0d0) then - self_ => self - node_ => nodeSatellite - destinationGas_ = destinationGasSatellite - destinationStars_ = destinationStarsSatellite - massHalf_ = 0.0d0 ! Set to zero here so that cole2000HalfMassRadiusRoot() returns the actual half mass. - massHalf_ = 0.50d0*cole2000HalfMassRadiusRoot(radiusLarge) + self_ => self + node_ => nodeSatellite + massDistributionSpheroidStellar_ => nodeSatellite%massDistribution(componentType=componentTypeSpheroid,massType=massTypeStellar) + massDistributionDiskStellar_ => nodeSatellite%massDistribution(componentType=componentTypeDisk ,massType=massTypeStellar) + massDistributionSpheroidGaseous_ => nodeSatellite%massDistribution(componentType=componentTypeSpheroid,massType=massTypeGaseous) + massDistributionDiskGaseous_ => nodeSatellite%massDistribution(componentType=componentTypeDisk ,massType=massTypeGaseous) + destinationGas_ = destinationGasSatellite + destinationStars_ = destinationStarsSatellite + massHalf_ = 0.0d0 ! Set to zero here so that cole2000HalfMassRadiusRoot() returns the actual half mass. + massHalf_ = 0.50d0*cole2000HalfMassRadiusRoot(radiusLarge) if (cole2000HalfMassRadiusRoot(0.0d0) <= 0.0d0) then - radiusSatellite=self%finder%find(rootGuess=self%galacticStructure_%radiusEnclosingMass( & - & node_ , & - & massFractional=0.50d0 , & - & massType =massTypeGalactic & - & ) & - & ) + radiusSatellite=self%finder%find(rootGuess=massDistributionSatellite%radiusEnclosingMass(massFractional=0.50d0)) else radiusSatellite =0.0d0 massSpheroidSatellite=0.0d0 end if + !![ + + + + + !!] else radiusSatellite=0.0d0 end if @@ -400,6 +410,11 @@ subroutine cole2000Get(self,nodeSatellite,nodeHost,massSatellite,massHost,massSp end if ! Compute the mass of the host spheroid before the merger. massSpheroidHostPreMerger=spheroidHost%massStellar()+spheroidHost%massGas() + ! Clean up. + !![ + + + !!] return end subroutine cole2000Get @@ -407,7 +422,6 @@ double precision function cole2000HalfMassRadiusRoot(radius) !!{ Function used in root finding for progenitor galaxy half-mass radii. !!} - use :: Galactic_Structure_Options , only : componentTypeDisk , componentTypeSpheroid , massTypeGaseous, massTypeStellar use :: Satellite_Merging_Mass_Movements, only : destinationMergerSpheroid, destinationMergerUnmoved implicit none double precision, intent(in ) :: radius @@ -417,22 +431,22 @@ Function used in root finding for progenitor galaxy half-mass radii. ! Account for gas mass. select case (destinationGas_%ID) case (destinationMergerSpheroid%ID) - cole2000HalfMassRadiusRoot=+cole2000HalfMassRadiusRoot & - & +self_%galacticStructure_%massEnclosed(node_,radius,componentType=componentTypeSpheroid,massType=massTypeGaseous) & - & +self_%galacticStructure_%massEnclosed(node_,radius,componentType=componentTypeDisk ,massType=massTypeGaseous) + cole2000HalfMassRadiusRoot=+cole2000HalfMassRadiusRoot & + & +massDistributionSpheroidGaseous_%massEnclosedBySphere(radius) & + & +massDistributionDiskGaseous_ %massEnclosedBySphere(radius) case (destinationMergerUnmoved %ID) - cole2000HalfMassRadiusRoot=+cole2000HalfMassRadiusRoot & - & +self_%galacticStructure_%massEnclosed(node_,radius,componentType=componentTypeSpheroid,massType=massTypeGaseous) + cole2000HalfMassRadiusRoot=+cole2000HalfMassRadiusRoot & + & +massDistributionSpheroidGaseous_%massEnclosedBySphere(radius) end select ! Account for stellar mass. select case (destinationStars_%ID) case (destinationMergerSpheroid%ID) - cole2000HalfMassRadiusRoot=+cole2000HalfMassRadiusRoot & - & +self_%galacticStructure_%massEnclosed(node_,radius,componentType=componentTypeSpheroid,massType=massTypeStellar) & - & +self_%galacticStructure_%massEnclosed(node_,radius,componentType=componentTypeDisk ,massType=massTypeStellar) + cole2000HalfMassRadiusRoot=+cole2000HalfMassRadiusRoot & + & +massDistributionSpheroidStellar_%massEnclosedBySphere(radius) & + & +massDistributionDiskStellar_ %massEnclosedBySphere(radius) case (destinationMergerUnmoved %ID) - cole2000HalfMassRadiusRoot=+cole2000HalfMassRadiusRoot & - & +self_%galacticStructure_%massEnclosed(node_,radius,componentType=componentTypeSpheroid,massType=massTypeStellar) + cole2000HalfMassRadiusRoot=+cole2000HalfMassRadiusRoot & + & +massDistributionSpheroidStellar_%massEnclosedBySphere(radius) end select return end function cole2000HalfMassRadiusRoot diff --git a/source/satellites.merging.progenitor_properties.simple.F90 b/source/satellites.merging.progenitor_properties.simple.F90 index ae015abbad..cbc03ddba2 100644 --- a/source/satellites.merging.progenitor_properties.simple.F90 +++ b/source/satellites.merging.progenitor_properties.simple.F90 @@ -22,7 +22,6 @@ !!} use :: Satellite_Merging_Mass_Movements, only : mergerMassMovementsClass - use :: Galactic_Structure , only : galacticStructureClass !![ @@ -34,7 +33,6 @@ A merger progenitor properties class which uses a simple calculation. !!} private - class(galacticStructureClass ), pointer :: galacticStructure_ => null() class(mergerMassMovementsClass), pointer :: mergerMassMovements_ => null() contains final :: simpleDestructor @@ -63,7 +61,6 @@ function simpleConstructorParameters(parameters) result(self) type (mergerProgenitorPropertiesSimple) :: self type (inputParameters ), intent(inout) :: parameters class(mergerMassMovementsClass ), pointer :: mergerMassMovements_ - class(galacticStructureClass ), pointer :: galacticStructure_ ! Ensure that required methods are supported. if ( & @@ -104,27 +101,24 @@ function simpleConstructorParameters(parameters) result(self) & ) !![ - !!] - self=mergerProgenitorPropertiesSimple(mergerMassMovements_,galacticStructure_) + self=mergerProgenitorPropertiesSimple(mergerMassMovements_) !![ - !!] return end function simpleConstructorParameters - function simpleConstructorInternal(mergerMassMovements_,galacticStructure_) result(self) + function simpleConstructorInternal(mergerMassMovements_) result(self) !!{ Internal constructor for the {\normalfont \ttfamily simple} merger progenitor properties class. !!} implicit none type (mergerProgenitorPropertiesSimple) :: self class(mergerMassMovementsClass ), intent(in ), target :: mergerMassMovements_ - class(galacticStructureClass ), intent(in ), target :: galacticStructure_ !![ - + !!] return @@ -139,7 +133,6 @@ subroutine simpleDestructor(self) !![ - !!] return end subroutine simpleDestructor @@ -152,6 +145,7 @@ subroutine simpleGet(self,nodeSatellite,nodeHost,massSatellite,massHost,massSphe use :: Error , only : Error_Report use :: Galacticus_Nodes , only : nodeComponentDisk , nodeComponentSpheroid , treeNode use :: Satellite_Merging_Mass_Movements, only : destinationMergerDisk, destinationMergerSpheroid, destinationMergerUnmoved, enumerationDestinationMergerType + use :: Mass_Distributions , only : massDistributionClass implicit none class (mergerProgenitorPropertiesSimple), intent(inout), target :: self type (treeNode ), intent(inout), target :: nodeSatellite , nodeHost @@ -160,6 +154,7 @@ subroutine simpleGet(self,nodeSatellite,nodeHost,massSatellite,massHost,massSphe & massSpheroidHostPreMerger, massGasSpheroidRemnant , & & massSpheroidRemnant , massSatellite , & & radiusSatellite , massSpheroidSatellite + class (massDistributionClass ), pointer :: massDistributionHost , massDistributionSatellite class (nodeComponentDisk ), pointer :: diskHost , diskSatelite class (nodeComponentSpheroid ), pointer :: spheroidHost , spheroidSatellite type (enumerationDestinationMergerType) :: destinationGasSatellite , destinationGasHost , & @@ -174,8 +169,10 @@ subroutine simpleGet(self,nodeSatellite,nodeHost,massSatellite,massHost,massSphe diskSatelite => nodeSatellite%disk () spheroidSatellite => nodeSatellite%spheroid() ! Find the baryonic masses of the two galaxies. - massSatellite=self%galacticStructure_%massEnclosed(nodeSatellite,massType=massTypeGalactic) - massHost =self%galacticStructure_%massEnclosed(nodeHost ,massType=massTypeGalactic) + massDistributionSatellite => nodeSatellite %massDistribution(massType=massTypeGalactic) + massDistributionHost => nodeHost %massDistribution(massType=massTypeGalactic) + massSatellite = massDistributionSatellite%massTotal ( ) + massHost = massDistributionHost %massTotal ( ) ! Find the masses of material that will end up in the spheroid component of the remnant. select case (destinationGasHost%ID) case (destinationMergerSpheroid%ID) @@ -250,5 +247,10 @@ subroutine simpleGet(self,nodeSatellite,nodeHost,massSatellite,massHost,massSphe factorAngularMomentum=1.0d0 ! Compute the mass of the host spheroid before the merger. massSpheroidHostPreMerger=spheroidHost%massStellar()+spheroidHost%massGas() + ! Clean up. + !![ + + + !!] return end subroutine simpleGet diff --git a/source/satellites.merging.progenitor_properties.standard.F90 b/source/satellites.merging.progenitor_properties.standard.F90 index f8f120a22e..00450a7b62 100644 --- a/source/satellites.merging.progenitor_properties.standard.F90 +++ b/source/satellites.merging.progenitor_properties.standard.F90 @@ -22,7 +22,6 @@ !!} use :: Satellite_Merging_Mass_Movements, only : mergerMassMovementsClass - use :: Galactic_Structure , only : galacticStructureClass !![ @@ -57,7 +56,6 @@ A merger progenitor properties class which uses a standard calculation. !!} private - class(galacticStructureClass ), pointer :: galacticStructure_ => null() class(mergerMassMovementsClass), pointer :: mergerMassMovements_ => null() contains final :: standardDestructor @@ -86,7 +84,6 @@ function standardConstructorParameters(parameters) result(self) type (mergerProgenitorPropertiesStandard) :: self type (inputParameters ), intent(inout) :: parameters class(mergerMassMovementsClass ), pointer :: mergerMassMovements_ - class(galacticStructureClass ), pointer :: galacticStructure_ if ( & & .not. & @@ -130,27 +127,24 @@ function standardConstructorParameters(parameters) result(self) & ) !![ - !!] - self=mergerProgenitorPropertiesStandard(mergerMassMovements_,galacticStructure_) + self=mergerProgenitorPropertiesStandard(mergerMassMovements_) !![ - !!] return end function standardConstructorParameters - function standardConstructorInternal(mergerMassMovements_,galacticStructure_) result(self) + function standardConstructorInternal(mergerMassMovements_) result(self) !!{ Internal constructor for the {\normalfont \ttfamily standard} merger progenitor properties class. !!} implicit none type (mergerProgenitorPropertiesStandard) :: self class(mergerMassMovementsClass ), intent(in ), target :: mergerMassMovements_ - class(galacticStructureClass ), intent(in ), target :: galacticStructure_ !![ - + !!] return @@ -165,7 +159,6 @@ subroutine standardDestructor(self) !![ - !!] return end subroutine standardDestructor @@ -179,6 +172,7 @@ subroutine standardGet(self,nodeSatellite,nodeHost,massSatellite,massHost,massSp use :: Galacticus_Nodes , only : nodeComponentDisk , nodeComponentSpheroid , treeNode use :: Numerical_Constants_Astronomical, only : gravitationalConstantGalacticus use :: Satellite_Merging_Mass_Movements, only : destinationMergerDisk , destinationMergerSpheroid, destinationMergerUnmoved, enumerationDestinationMergerType + use :: Mass_Distributions , only : massDistributionClass implicit none class (mergerProgenitorPropertiesStandard), intent(inout), target :: self type (treeNode ), intent(inout), target :: nodeSatellite , nodeHost @@ -187,6 +181,7 @@ subroutine standardGet(self,nodeSatellite,nodeHost,massSatellite,massHost,massSp & massSpheroidHostPreMerger , massGasSpheroidRemnant , & & massSpheroidRemnant , massSatellite , & & radiusSatellite , massSpheroidSatellite + class (massDistributionClass ), pointer :: massDistributionHost , massDistributionSatellite class (nodeComponentDisk ), pointer :: diskHost , diskSatellite class (nodeComponentSpheroid ), pointer :: spheroidHost , spheroidSatellite double precision , parameter :: massComponentMinimum=1.0d-30 @@ -207,8 +202,10 @@ subroutine standardGet(self,nodeSatellite,nodeHost,massSatellite,massHost,massSp diskSatellite => nodeSatellite%disk () spheroidSatellite => nodeSatellite%spheroid() ! Find the baryonic masses of the two galaxies. - massSatellite=self%galacticStructure_%massEnclosed(nodeSatellite,massType=massTypeGalactic) - massHost =self%galacticStructure_%massEnclosed(nodeHost ,massType=massTypeGalactic) + massDistributionSatellite => nodeSatellite %massDistribution(massType=massTypeGalactic) + massDistributionHost => nodeHost %massDistribution(massType=massTypeGalactic) + massSatellite = massDistributionSatellite%massTotal ( ) + massHost = massDistributionHost %massTotal ( ) ! Compute dark matter factors. These are the specific angular momenta of components divided by sqrt(G M r) where M is the ! component mass and r its half-mass radius. We use a weighted average of these factors to infer the specific angular momentum ! of the remnant from its mass and radius. @@ -346,5 +343,10 @@ subroutine standardGet(self,nodeSatellite,nodeHost,massSatellite,massHost,massSp ! Compute the mass of the host spheroid before the merger. massSpheroidHostPreMerger=spheroidHost%massStellar()+spheroidHost%massGas() if (radiusHost <= 0.0d0) massSpheroidHostPreMerger=0.0d0 + ! Clean up. + !![ + + + !!] return end subroutine standardGet diff --git a/source/satellites.merging.remnant_sizes.Cole2000.F90 b/source/satellites.merging.remnant_sizes.Cole2000.F90 index 2ceddda736..d1e18d46ae 100644 --- a/source/satellites.merging.remnant_sizes.Cole2000.F90 +++ b/source/satellites.merging.remnant_sizes.Cole2000.F90 @@ -23,7 +23,6 @@ use :: Kind_Numbers , only : kind_int8 use :: Satellite_Merging_Progenitor_Properties, only : mergerProgenitorPropertiesClass - use :: Galactic_Structure , only : galacticStructureClass !![ @@ -73,7 +72,6 @@ A merger remnant size class which uses the \cite{cole_hierarchical_2000} algorithm. !!} private - class (galacticStructureClass ), pointer :: galacticStructure_ => null() class (mergerProgenitorPropertiesClass), pointer :: mergerProgenitorProperties_ => null() double precision :: energyOrbital integer (kind=kind_int8 ) :: lastUniqueID @@ -105,7 +103,6 @@ function cole2000ConstructorParameters(parameters) result(self) type (mergerRemnantSizeCole2000 ) :: self type (inputParameters ), intent(inout) :: parameters class (mergerProgenitorPropertiesClass), pointer :: mergerProgenitorProperties_ - class (galacticStructureClass ), pointer :: galacticStructure_ double precision :: energyOrbital !![ @@ -116,18 +113,16 @@ function cole2000ConstructorParameters(parameters) result(self) parameters - !!] - self=mergerRemnantSizeCole2000(energyOrbital,mergerProgenitorProperties_,galacticStructure_) + self=mergerRemnantSizeCole2000(energyOrbital,mergerProgenitorProperties_) !![ - !!] return end function cole2000ConstructorParameters - function cole2000ConstructorInternal(energyOrbital,mergerProgenitorProperties_,galacticStructure_) result(self) + function cole2000ConstructorInternal(energyOrbital,mergerProgenitorProperties_) result(self) !!{ Internal constructor for the {\normalfont \ttfamily cole2000} merger remnant size class. !!} @@ -135,9 +130,8 @@ function cole2000ConstructorInternal(energyOrbital,mergerProgenitorProperties_,g type (mergerRemnantSizeCole2000 ) :: self double precision , intent(in ) :: energyOrbital class (mergerProgenitorPropertiesClass), intent(in ), target :: mergerProgenitorProperties_ - class (galacticStructureClass ), intent(in ), target :: galacticStructure_ !![ - + !!] self%propertiesCalculated =.false. @@ -170,7 +164,6 @@ subroutine cole2000Destructor(self) !![ - !!] if (calculationResetEvent%isAttached(self,cole2000CalculationReset)) call calculationResetEvent%detach(self,cole2000CalculationReset) if (satelliteMergerEvent %isAttached(self,cole2000GetHook )) call satelliteMergerEvent %detach(self,cole2000GetHook ) @@ -226,6 +219,7 @@ subroutine cole2000Get(self,node,radius,velocityCircular,angularMomentumSpecific use :: Display , only : displayMessage use :: Galactic_Structure_Options , only : massTypeDark use :: Error , only : Error_Report + use :: Mass_Distributions , only : massDistributionClass use :: Numerical_Comparison , only : Values_Agree use :: Numerical_Constants_Astronomical, only : gravitationalConstantGalacticus use :: String_Handling , only : operator(//) @@ -235,6 +229,7 @@ subroutine cole2000Get(self,node,radius,velocityCircular,angularMomentumSpecific double precision , intent( out) :: radius , velocityCircular , & & angularMomentumSpecific type (treeNode ), pointer :: nodeHost + class (massDistributionClass ), pointer :: massDistributionSatellite , massDistributionHost double precision , parameter :: formFactorEnergyBinding =0.5d+0 double precision , parameter :: toleranceMassAbsolute =1.0d+0 double precision , parameter :: toleranceMassRelative =1.0d-9 @@ -347,8 +342,14 @@ subroutine cole2000Get(self,node,radius,velocityCircular,angularMomentumSpecific ! Check if host has finite mass. if (massSpheroidSatellite+massSpheroidHost > 0.0d0) then ! Compute masses of dark matter within the host and satellite radii. - massDarkMatterHost =self%galacticStructure_%massEnclosed(nodeHost,radiusHost ,massType=massTypeDark) - massDarkMatterSatellite=self%galacticStructure_%massEnclosed(node ,radiusSatellite,massType=massTypeDark) + massDistributionHost => nodeHost%massDistribution(massType=massTypeDark) + massDistributionSatellite => node %massDistribution(massType=massTypeDark) + massDarkMatterHost = massDistributionHost %massEnclosedBySphere(radiusHost ) + massDarkMatterSatellite = massDistributionSatellite%massEnclosedBySphere(radiusSatellite) + !![ + + + !!] ! Combine baryonic and dark matter masses. massSpheroidHostTotal =+massSpheroidHost +2.0d0*massDarkMatterHost massSpheroidTotalSatellite=+massSpheroidSatellite+2.0d0*massDarkMatterSatellite diff --git a/source/satellites.merging.timescale.Boylan-Kolchin2008.F90 b/source/satellites.merging.timescale.Boylan-Kolchin2008.F90 index 5e97e4b82b..0fd0601127 100644 --- a/source/satellites.merging.timescale.Boylan-Kolchin2008.F90 +++ b/source/satellites.merging.timescale.Boylan-Kolchin2008.F90 @@ -23,7 +23,6 @@ use :: Dark_Matter_Halo_Scales , only : darkMatterHaloScaleClass use :: Dark_Matter_Profiles_DMO, only : darkMatterProfileDMOClass - use :: Galactic_Structure , only : galacticStructureClass !![ @@ -40,7 +39,6 @@ private class (darkMatterHaloScaleClass ), pointer :: darkMatterHaloScale_ => null() class (darkMatterProfileDMOClass), pointer :: darkMatterProfileDMO_ => null() - class (galacticStructureClass ), pointer :: galacticStructure_ => null() double precision :: timescaleMultiplier contains final :: boylanKolchin2008Destructor @@ -68,7 +66,6 @@ function boylanKolchin2008ConstructorParameters(parameters) result(self) type (inputParameters ), intent(inout) :: parameters class (darkMatterHaloScaleClass ), pointer :: darkMatterHaloScale_ class (darkMatterProfileDMOClass ), pointer :: darkMatterProfileDMO_ - class (galacticStructureClass ), pointer :: galacticStructure_ double precision :: timescaleMultiplier !![ @@ -80,19 +77,17 @@ function boylanKolchin2008ConstructorParameters(parameters) result(self) - !!] - self=satelliteMergingTimescalesBoylanKolchin2008(timescaleMultiplier,darkMatterHaloScale_,darkMatterProfileDMO_,galacticStructure_) + self=satelliteMergingTimescalesBoylanKolchin2008(timescaleMultiplier,darkMatterHaloScale_,darkMatterProfileDMO_) !![ - !!] return end function boylanKolchin2008ConstructorParameters - function boylanKolchin2008ConstructorInternal(timescaleMultiplier,darkMatterHaloScale_,darkMatterProfileDMO_,galacticStructure_) result(self) + function boylanKolchin2008ConstructorInternal(timescaleMultiplier,darkMatterHaloScale_,darkMatterProfileDMO_) result(self) !!{ Default constructor for the {\normalfont \ttfamily boylanKolchin2008} satellite merging timescale class. !!} @@ -101,9 +96,8 @@ function boylanKolchin2008ConstructorInternal(timescaleMultiplier,darkMatterHalo double precision , intent(in ) :: timescaleMultiplier class (darkMatterHaloScaleClass ), intent(in ), target :: darkMatterHaloScale_ class (darkMatterProfileDMOClass ), intent(in ), target :: darkMatterProfileDMO_ - class (galacticStructureClass ), intent(in ), target :: galacticStructure_ !![ - + !!] return @@ -119,7 +113,6 @@ subroutine boylanKolchin2008Destructor(self) !![ - !!] return end subroutine boylanKolchin2008Destructor @@ -128,16 +121,18 @@ double precision function boylanKolchin2008TimeUntilMerging(self,node,orbit) !!{ Return the timescale for merging satellites using the \cite{boylan-kolchin_dynamical_2008} method. !!} - use :: Error , only : Error_Report - use :: Galacticus_Nodes, only : nodeComponentBasic , treeNode - use :: Kepler_Orbits , only : keplerOrbit - use :: Satellite_Orbits, only : Satellite_Orbit_Equivalent_Circular_Orbit_Radius, errorCodeNoEquivalentOrbit, errorCodeOrbitUnbound, errorCodeSuccess + use :: Error , only : Error_Report + use :: Galacticus_Nodes , only : nodeComponentBasic , treeNode + use :: Mass_Distributions, only : massDistributionClass + use :: Kepler_Orbits , only : keplerOrbit + use :: Satellite_Orbits , only : Satellite_Orbit_Equivalent_Circular_Orbit_Radius, errorCodeNoEquivalentOrbit, errorCodeOrbitUnbound, errorCodeSuccess implicit none class (satelliteMergingTimescalesBoylanKolchin2008), intent(inout) :: self type (treeNode ), intent(inout) :: node type (keplerOrbit ), intent(inout) :: orbit type (treeNode ), pointer :: nodeHost class (nodeComponentBasic ), pointer :: basicHost , basic + class (massDistributionClass ), pointer :: massDistribution_ logical , parameter :: acceptUnboundOrbits =.false. double precision , parameter :: expArgumentMaximum =100.0d0 double precision , parameter :: A =0.216d0, b =1.3d0, & ! Fitting parameters from eqn. (6) of Boylan-Kolchin et al. @@ -158,7 +153,7 @@ double precision function boylanKolchin2008TimeUntilMerging(self,node,orbit) velocityScale=self%darkMatterHaloScale_%velocityVirial(nodeHost) radialScale =self%darkMatterHaloScale_%radiusVirial (nodeHost) ! Get the equivalent circular orbit. - equivalentCircularOrbitRadius=Satellite_Orbit_Equivalent_Circular_Orbit_Radius(nodeHost,orbit,self%darkMatterHaloScale_,self%darkMatterProfileDMO_,self%galacticStructure_,errorCode) + equivalentCircularOrbitRadius=Satellite_Orbit_Equivalent_Circular_Orbit_Radius(nodeHost,orbit,self%darkMatterHaloScale_,errorCode) ! Check error codes. select case (errorCode) case (errorCodeOrbitUnbound ) @@ -172,10 +167,13 @@ double precision function boylanKolchin2008TimeUntilMerging(self,node,orbit) return case (errorCodeSuccess ) ! Compute orbital circularity. - orbitalCircularity & - & =orbit%angularMomentum() & - & /equivalentCircularOrbitRadius & - & /self%darkMatterProfileDMO_%circularVelocity(nodeHost,equivalentCircularOrbitRadius) + massDistribution_ => self %darkMatterProfileDMO_%get (nodeHost ) + orbitalCircularity = +orbit %angularMomentum( ) & + & /massDistribution_ %rotationCurve (equivalentCircularOrbitRadius) & + & /equivalentCircularOrbitRadius + !![ + + !!] case default orbitalCircularity=0.0d0 call Error_Report('unrecognized error code'//{introspection:location}) diff --git a/source/satellites.merging.timescale.Jiang2008.F90 b/source/satellites.merging.timescale.Jiang2008.F90 index 301d533b3b..20a3542953 100644 --- a/source/satellites.merging.timescale.Jiang2008.F90 +++ b/source/satellites.merging.timescale.Jiang2008.F90 @@ -23,7 +23,6 @@ use :: Dark_Matter_Halo_Scales , only : darkMatterHaloScaleClass use :: Dark_Matter_Profiles_DMO, only : darkMatterProfileDMOClass - use :: Galactic_Structure , only : galacticStructureClass !![ @@ -46,7 +45,6 @@ private class (darkMatterHaloScaleClass ), pointer :: darkMatterHaloScale_ => null() class (darkMatterProfileDMOClass), pointer :: darkMatterProfileDMO_ => null() - class (galacticStructureClass ), pointer :: galacticStructure_ => null() double precision :: timescaleMultiplier ! Scatter (in log(T_merge)) to add to the merger times. double precision :: scatter @@ -77,7 +75,6 @@ function jiang2008ConstructorParameters(parameters) result(self) type (inputParameters ), intent(inout) :: parameters class (darkMatterHaloScaleClass ), pointer :: darkMatterHaloScale_ class (darkMatterProfileDMOClass ), pointer :: darkMatterProfileDMO_ - class (galacticStructureClass ), pointer :: galacticStructure_ double precision :: scatter , timescaleMultiplier if (.not.defaultBasicComponent%massIsGettable()) call Error_Report('this method requires that the "mass" property of the basic component be gettable'//{introspection:location}) @@ -96,19 +93,17 @@ function jiang2008ConstructorParameters(parameters) result(self) - !!] - self=satelliteMergingTimescalesJiang2008(timescaleMultiplier,scatter,darkMatterHaloScale_,darkMatterProfileDMO_,galacticStructure_) + self=satelliteMergingTimescalesJiang2008(timescaleMultiplier,scatter,darkMatterHaloScale_,darkMatterProfileDMO_) !![ - !!] return end function jiang2008ConstructorParameters - function jiang2008ConstructorInternal(timescaleMultiplier,scatter,darkMatterHaloScale_,darkMatterProfileDMO_,galacticStructure_) result(self) + function jiang2008ConstructorInternal(timescaleMultiplier,scatter,darkMatterHaloScale_,darkMatterProfileDMO_) result(self) !!{ Constructor for the \cite{jiang_fitting_2008} merging timescale class. !!} @@ -117,9 +112,8 @@ function jiang2008ConstructorInternal(timescaleMultiplier,scatter,darkMatterHalo double precision , intent(in ) :: timescaleMultiplier , scatter class (darkMatterHaloScaleClass ), intent(in ), target :: darkMatterHaloScale_ class (darkMatterProfileDMOClass ), intent(in ), target :: darkMatterProfileDMO_ - class (galacticStructureClass ), intent(in ), target :: galacticStructure_ !![ - + !!] return @@ -135,7 +129,6 @@ subroutine jiang2008Destructor(self) !![ - !!] return end subroutine jiang2008Destructor @@ -144,15 +137,17 @@ double precision function jiang2008TimeUntilMerging(self,node,orbit) !!{ Return the timescale for merging satellites using the \cite{jiang_fitting_2008} method. !!} - use :: Error , only : Error_Report - use :: Galacticus_Nodes, only : nodeComponentBasic , treeNode - use :: Satellite_Orbits, only : Satellite_Orbit_Equivalent_Circular_Orbit_Radius, errorCodeNoEquivalentOrbit, errorCodeOrbitUnbound, errorCodeSuccess + use :: Error , only : Error_Report + use :: Galacticus_Nodes , only : nodeComponentBasic , treeNode + use :: Mass_Distributions, only : massDistributionClass + use :: Satellite_Orbits , only : Satellite_Orbit_Equivalent_Circular_Orbit_Radius, errorCodeNoEquivalentOrbit, errorCodeOrbitUnbound, errorCodeSuccess implicit none class (satelliteMergingTimescalesJiang2008), intent(inout) :: self type (treeNode ), intent(inout) :: node type (keplerOrbit ), intent(inout) :: orbit type (treeNode ), pointer :: nodeHost class (nodeComponentBasic ), pointer :: basicHost , basic + class (massDistributionClass ), pointer :: massDistribution_ logical , parameter :: acceptUnboundOrbits =.false. double precision , parameter :: C =0.43d0 , a =0.94d0, & ! Fitting parameters from Jiang's paper. @@ -169,7 +164,7 @@ double precision function jiang2008TimeUntilMerging(self,node,orbit) nodeHost => node%parent%firstChild end if ! Get the equivalent circular orbit. - equivalentCircularOrbitRadius=Satellite_Orbit_Equivalent_Circular_Orbit_Radius(nodeHost,orbit,self%darkMatterHaloScale_,self%darkMatterProfileDMO_,self%galacticStructure_,errorCode) + equivalentCircularOrbitRadius=Satellite_Orbit_Equivalent_Circular_Orbit_Radius(nodeHost,orbit,self%darkMatterHaloScale_,errorCode) ! Check error codes. select case (errorCode) case (errorCodeOrbitUnbound ) @@ -187,9 +182,13 @@ double precision function jiang2008TimeUntilMerging(self,node,orbit) velocityScale=self%darkMatterHaloScale_%velocityVirial(nodeHost) radialScale =self%darkMatterHaloScale_%radiusVirial (nodeHost) ! Compute orbital circularity. - orbitalCircularity= orbit%angularMomentum() & - & /equivalentCircularOrbitRadius & - & /self%darkMatterProfileDMO_%circularVelocity(nodeHost,equivalentCircularOrbitRadius) + massDistribution_ => self %darkMatterProfileDMO_%get (nodeHost ) + orbitalCircularity = +orbit %angularMomentum( ) & + & /massDistribution_ %rotationCurve (equivalentCircularOrbitRadius) & + & /equivalentCircularOrbitRadius + !![ + + !!] ! Compute mass ratio (mass in host [not including satellite if the node is already a satellite] divided by mass in satellite). basic => node %basic() basicHost => nodeHost %basic() diff --git a/source/satellites.merging.virial_orbits.Benson2005.F90 b/source/satellites.merging.virial_orbits.Benson2005.F90 index 9b9dadb32c..1be98dbf86 100644 --- a/source/satellites.merging.virial_orbits.Benson2005.F90 +++ b/source/satellites.merging.virial_orbits.Benson2005.F90 @@ -21,11 +21,10 @@ An implementation of virial orbits using the \cite{benson_orbital_2005} orbital parameter distribution. !!} - use :: Cosmology_Parameters , only : cosmologyParametersClass - use :: Cosmology_Functions , only : cosmologyFunctionsClass - use :: Dark_Matter_Profiles_DMO, only : darkMatterProfileDMOClass - use :: Dark_Matter_Halo_Scales , only : darkMatterHaloScaleClass - use :: Virial_Density_Contrast , only : virialDensityContrastClass, virialDensityContrastSphericalCollapseClsnlssMttrCsmlgclCnstnt + use :: Cosmology_Parameters , only : cosmologyParametersClass + use :: Cosmology_Functions , only : cosmologyFunctionsClass + use :: Dark_Matter_Halo_Scales, only : darkMatterHaloScaleClass + use :: Virial_Density_Contrast, only : virialDensityContrastClass, virialDensityContrastSphericalCollapseClsnlssMttrCsmlgclCnstnt !![ @@ -49,7 +48,6 @@ !!} private class(darkMatterHaloScaleClass ), pointer :: darkMatterHaloScale_ => null() - class(darkMatterProfileDMOClass ), pointer :: darkMatterProfileDMO_ => null() class(virialDensityContrastClass ), pointer :: virialDensityContrast_ => null() class(cosmologyParametersClass ), pointer :: cosmologyParameters_ => null() class(cosmologyFunctionsClass ), pointer :: cosmologyFunctions_ => null() @@ -87,29 +85,26 @@ function benson2005ConstructorParameters(parameters) result(self) class(darkMatterHaloScaleClass ), pointer :: darkMatterHaloScale_ class(cosmologyFunctionsClass ), pointer :: cosmologyFunctions_ class(cosmologyParametersClass ), pointer :: cosmologyParameters_ - class(darkMatterProfileDMOClass ), pointer :: darkMatterProfileDMO_ class(virialDensityContrastClass), pointer :: virialDensityContrast_ !![ - !!] - self=virialOrbitBenson2005(darkMatterHaloScale_,cosmologyFunctions_,cosmologyParameters_,darkMatterProfileDMO_,virialDensityContrast_) + self=virialOrbitBenson2005(darkMatterHaloScale_,cosmologyFunctions_,cosmologyParameters_,virialDensityContrast_) !![ - !!] return end function benson2005ConstructorParameters - function benson2005ConstructorInternal(darkMatterHaloScale_,cosmologyFunctions_,cosmologyParameters_,darkMatterProfileDMO_,virialDensityContrast_) result(self) + function benson2005ConstructorInternal(darkMatterHaloScale_,cosmologyFunctions_,cosmologyParameters_,virialDensityContrast_) result(self) !!{ Internal constructor for the {\normalfont \ttfamily benson2005} virial orbits class. !!} @@ -120,9 +115,8 @@ function benson2005ConstructorInternal(darkMatterHaloScale_,cosmologyFunctions_, class(cosmologyParametersClass ), intent(in ), target :: cosmologyParameters_ class(cosmologyFunctionsClass ), intent(in ), target :: cosmologyFunctions_ class(virialDensityContrastClass), intent(in ), target :: virialDensityContrast_ - class(darkMatterProfileDMOClass ), intent(in ), target :: darkMatterProfileDMO_ !![ - + !!] allocate(self%virialDensityContrastDefinition_) @@ -144,7 +138,6 @@ subroutine benson2005Destructor(self) - !!] return @@ -190,7 +183,6 @@ function benson2005Orbit(self,node,host,acceptUnboundOrbits) & velocityHost , & & cosmologyParameters_ =self%cosmologyParameters_ , & & cosmologyFunctions_ =self%cosmologyFunctions_ , & - & darkMatterProfileDMO_ =self%darkMatterProfileDMO_ , & & virialDensityContrast_=self%virialDensityContrast_ & & ) massSatellite=Dark_Matter_Profile_Mass_Definition( & @@ -198,7 +190,6 @@ function benson2005Orbit(self,node,host,acceptUnboundOrbits) & self%virialDensityContrastDefinition_%densityContrast( basic%mass(), basic%timeLastIsolated()), & & cosmologyParameters_ =self%cosmologyParameters_ , & & cosmologyFunctions_ =self%cosmologyFunctions_ , & - & darkMatterProfileDMO_ =self%darkMatterProfileDMO_ , & & virialDensityContrast_=self%virialDensityContrast_ & & ) ! Select an orbit. @@ -275,7 +266,7 @@ double precision function benson2005VelocityTangentialMagnitudeMean(self,node,ho !$GLC attributes unused :: node basicHost => host%basic() - massHost = Dark_Matter_Profile_Mass_Definition(host,self%virialDensityContrastDefinition_%densityContrast(basicHost%mass(),basicHost%timeLastIsolated()),radiusHost,velocityHost,self%cosmologyParameters_,self%cosmologyFunctions_,self%darkMatterProfileDMO_,self%virialDensityContrast_) + massHost = Dark_Matter_Profile_Mass_Definition(host,self%virialDensityContrastDefinition_%densityContrast(basicHost%mass(),basicHost%timeLastIsolated()),radiusHost,velocityHost,self%cosmologyParameters_,self%cosmologyFunctions_,self%virialDensityContrast_) benson2005VelocityTangentialMagnitudeMean = +velocityTangentialMean & & *velocityHost return @@ -312,7 +303,7 @@ double precision function benson2005AngularMomentumMagnitudeMean(self,node,host) basic => node%basic() basicHost => host%basic() - massHost = Dark_Matter_Profile_Mass_Definition(host,self%virialDensityContrastDefinition_%densityContrast(basicHost%mass(),basicHost%timeLastIsolated()),radiusHost,velocityHost,self%cosmologyParameters_,self%cosmologyFunctions_,self%darkMatterProfileDMO_,self%virialDensityContrast_) + massHost = Dark_Matter_Profile_Mass_Definition(host,self%virialDensityContrastDefinition_%densityContrast(basicHost%mass(),basicHost%timeLastIsolated()),radiusHost,velocityHost,self%cosmologyParameters_,self%cosmologyFunctions_,self%virialDensityContrast_) benson2005AngularMomentumMagnitudeMean = +self%velocityTangentialMagnitudeMean(node,host) & & *radiusHost & & /( & ! Account for reduced mass. @@ -356,7 +347,7 @@ double precision function benson2005VelocityTotalRootMeanSquared(self,node,host) !$GLC attributes unused :: node basicHost => host%basic() - massHost = Dark_Matter_Profile_Mass_Definition(host,self%virialDensityContrastDefinition_%densityContrast(basicHost%mass(),basicHost%timeLastIsolated()),radiusHost,velocityHost,self%cosmologyParameters_,self%cosmologyFunctions_,self%darkMatterProfileDMO_,self%virialDensityContrast_) + massHost = Dark_Matter_Profile_Mass_Definition(host,self%virialDensityContrastDefinition_%densityContrast(basicHost%mass(),basicHost%timeLastIsolated()),radiusHost,velocityHost,self%cosmologyParameters_,self%cosmologyFunctions_,self%virialDensityContrast_) benson2005VelocityTotalRootMeanSquared = +velocityTotalRootMeanSquared & & *velocityHost return @@ -378,7 +369,7 @@ double precision function benson2005EnergyMean(self,node,host) basic => node%basic() basicHost => host%basic() - massHost = Dark_Matter_Profile_Mass_Definition(host,self%virialDensityContrastDefinition_%densityContrast(basicHost%mass(),basicHost%timeLastIsolated()),radiusHost,velocityHost,self%cosmologyParameters_,self%cosmologyFunctions_,self%darkMatterProfileDMO_,self%virialDensityContrast_) + massHost = Dark_Matter_Profile_Mass_Definition(host,self%virialDensityContrastDefinition_%densityContrast(basicHost%mass(),basicHost%timeLastIsolated()),radiusHost,velocityHost,self%cosmologyParameters_,self%cosmologyFunctions_,self%virialDensityContrast_) benson2005EnergyMean = +0.5d0 & & *self%velocityTotalRootMeanSquared(node,host)**2 & & /( & ! Account for reduced mass. diff --git a/source/satellites.merging.virial_orbits.Jiang2014.F90 b/source/satellites.merging.virial_orbits.Jiang2014.F90 index 503339a4fe..77155aaedf 100644 --- a/source/satellites.merging.virial_orbits.Jiang2014.F90 +++ b/source/satellites.merging.virial_orbits.Jiang2014.F90 @@ -21,13 +21,12 @@ An implementation of virial orbits using the \cite{jiang_orbital_2014} orbital parameter distribution. !!} - use :: Cosmology_Functions , only : cosmologyFunctionsClass - use :: Cosmology_Parameters , only : cosmologyParametersClass - use :: Dark_Matter_Profiles_DMO, only : darkMatterProfileDMOClass - use :: Dark_Matter_Halo_Scales , only : darkMatterHaloScaleClass - use :: Root_Finder , only : rootFinder - use :: Tables , only : table1DLinearLinear - use :: Virial_Density_Contrast , only : virialDensityContrastClass, virialDensityContrastFixed + use :: Cosmology_Functions , only : cosmologyFunctionsClass + use :: Cosmology_Parameters , only : cosmologyParametersClass + use :: Dark_Matter_Halo_Scales, only : darkMatterHaloScaleClass + use :: Root_Finder , only : rootFinder + use :: Tables , only : table1DLinearLinear + use :: Virial_Density_Contrast, only : virialDensityContrastClass, virialDensityContrastFixed !![ @@ -53,7 +52,6 @@ class (darkMatterHaloScaleClass ), pointer :: darkMatterHaloScale_ => null() class (cosmologyParametersClass ), pointer :: cosmologyParameters_ => null() class (cosmologyFunctionsClass ), pointer :: cosmologyFunctions_ => null() - class (darkMatterProfileDMOClass ), pointer :: darkMatterProfileDMO_ => null() class (virialDensityContrastClass), pointer :: virialDensityContrast_ => null() type (virialDensityContrastFixed), pointer :: virialDensityContrastDefinition_ => null() double precision , dimension(3,3) :: B , gamma , & @@ -111,7 +109,6 @@ function jiang2014ConstructorParameters(parameters) result(self) class (darkMatterHaloScaleClass ), pointer :: darkMatterHaloScale_ class (cosmologyParametersClass ), pointer :: cosmologyParameters_ class (cosmologyFunctionsClass ), pointer :: cosmologyFunctions_ - class (darkMatterProfileDMOClass ), pointer :: darkMatterProfileDMO_ class (virialDensityContrastClass), pointer :: virialDensityContrast_ double precision , dimension(3) :: bRatioLow , bRatioIntermediate , bRatioHigh , & & gammaRatioLow , gammaRatioIntermediate, gammaRatioHigh, & @@ -194,22 +191,20 @@ function jiang2014ConstructorParameters(parameters) result(self) - !!] - self=virialOrbitJiang2014(bRatioLow,bRatioIntermediate,bRatioHigh,gammaRatioLow,gammaRatioIntermediate,gammaRatioHigh,sigmaRatioLow,sigmaRatioIntermediate,sigmaRatioHigh,muRatioLow,muRatioIntermediate,muRatioHigh,darkMatterHaloScale_,cosmologyParameters_,cosmologyFunctions_,darkMatterProfileDMO_,virialDensityContrast_) + self=virialOrbitJiang2014(bRatioLow,bRatioIntermediate,bRatioHigh,gammaRatioLow,gammaRatioIntermediate,gammaRatioHigh,sigmaRatioLow,sigmaRatioIntermediate,sigmaRatioHigh,muRatioLow,muRatioIntermediate,muRatioHigh,darkMatterHaloScale_,cosmologyParameters_,cosmologyFunctions_,virialDensityContrast_) !![ - !!] return end function jiang2014ConstructorParameters - function jiang2014ConstructorInternal(bRatioLow,bRatioIntermediate,bRatioHigh,gammaRatioLow,gammaRatioIntermediate,gammaRatioHigh,sigmaRatioLow,sigmaRatioIntermediate,sigmaRatioHigh,muRatioLow,muRatioIntermediate,muRatioHigh,darkMatterHaloScale_,cosmologyParameters_,cosmologyFunctions_,darkMatterProfileDMO_,virialDensityContrast_) result(self) + function jiang2014ConstructorInternal(bRatioLow,bRatioIntermediate,bRatioHigh,gammaRatioLow,gammaRatioIntermediate,gammaRatioHigh,sigmaRatioLow,sigmaRatioIntermediate,sigmaRatioHigh,muRatioLow,muRatioIntermediate,muRatioHigh,darkMatterHaloScale_,cosmologyParameters_,cosmologyFunctions_,virialDensityContrast_) result(self) !!{ Internal constructor for the {\normalfont \ttfamily jiang2014} virial orbits class. !!} @@ -233,7 +228,6 @@ function jiang2014ConstructorInternal(bRatioLow,bRatioIntermediate,bRatioHigh,ga class (cosmologyParametersClass ), intent(in ), target :: cosmologyParameters_ class (cosmologyFunctionsClass ), intent(in ), target :: cosmologyFunctions_ class (virialDensityContrastClass ), intent(in ), target :: virialDensityContrast_ - class (darkMatterProfileDMOClass ), intent(in ), target :: darkMatterProfileDMO_ integer , parameter :: tableCount =1000 integer :: i , j , k , & & attempt @@ -248,7 +242,7 @@ function jiang2014ConstructorInternal(bRatioLow,bRatioIntermediate,bRatioHigh,ga type (hdf5Object ) :: file type (lockDescriptor ) :: fileLock !![ - + !!] ! Assign parameters of the distribution. @@ -455,7 +449,6 @@ subroutine jiang2014Destructor(self) - !!] @@ -503,7 +496,6 @@ function jiang2014Orbit(self,node,host,acceptUnboundOrbits) & velocityHost , & & cosmologyParameters_ =self%cosmologyParameters_ , & & cosmologyFunctions_ =self%cosmologyFunctions_ , & - & darkMatterProfileDMO_ =self%darkMatterProfileDMO_ , & & virialDensityContrast_=self%virialDensityContrast_ & & ) massSatellite=Dark_Matter_Profile_Mass_Definition( & @@ -511,7 +503,6 @@ function jiang2014Orbit(self,node,host,acceptUnboundOrbits) & virialDensityContrastDefinition_%densityContrast( basic%mass(), basic%timeLastIsolated()), & & cosmologyParameters_ =self%cosmologyParameters_ , & & cosmologyFunctions_ =self%cosmologyFunctions_ , & - & darkMatterProfileDMO_ =self%darkMatterProfileDMO_ , & & virialDensityContrast_=self%virialDensityContrast_ & & ) !![ @@ -645,7 +636,6 @@ double precision function jiang2014VelocityTangentialMagnitudeMean(self,node,hos & velocityHost , & & cosmologyParameters_ =self%cosmologyParameters_ , & & cosmologyFunctions_ =self%cosmologyFunctions_ , & - & darkMatterProfileDMO_ =self%darkMatterProfileDMO_ , & & virialDensityContrast_=self%virialDensityContrast_ & & ) massSatellite = Dark_Matter_Profile_Mass_Definition( & @@ -653,7 +643,6 @@ double precision function jiang2014VelocityTangentialMagnitudeMean(self,node,hos & virialDensityContrastDefinition_%densityContrast( basic%mass(), basic%timeLastIsolated()), & & cosmologyParameters_ =self%cosmologyParameters_ , & & cosmologyFunctions_ =self%cosmologyFunctions_ , & - & darkMatterProfileDMO_ =self%darkMatterProfileDMO_ , & & virialDensityContrast_=self%virialDensityContrast_ & & ) !![ @@ -703,7 +692,6 @@ double precision function jiang2014AngularMomentumMagnitudeMean(self,node,host) & velocityHost , & & cosmologyParameters_ =self%cosmologyParameters_ , & & cosmologyFunctions_ =self%cosmologyFunctions_ , & - & darkMatterProfileDMO_ =self%darkMatterProfileDMO_ , & & virialDensityContrast_=self%virialDensityContrast_ & & ) jiang2014AngularMomentumMagnitudeMean = +self%velocityTangentialMagnitudeMean(node,host) & @@ -759,7 +747,6 @@ double precision function jiang2014VelocityTotalRootMeanSquared(self,node,host) & velocityHost , & & cosmologyParameters_ =self%cosmologyParameters_ , & & cosmologyFunctions_ =self%cosmologyFunctions_ , & - & darkMatterProfileDMO_ =self%darkMatterProfileDMO_ , & & virialDensityContrast_=self%virialDensityContrast_ & & ) massSatellite = Dark_Matter_Profile_Mass_Definition( & @@ -767,7 +754,6 @@ double precision function jiang2014VelocityTotalRootMeanSquared(self,node,host) & self%virialDensityContrastDefinition_%densityContrast( basic%mass(), basic%timeLastIsolated()), & & cosmologyParameters_ =self%cosmologyParameters_ , & & cosmologyFunctions_ =self%cosmologyFunctions_ , & - & darkMatterProfileDMO_ =self%darkMatterProfileDMO_ , & & virialDensityContrast_=self%virialDensityContrast_ & & ) !![ @@ -802,7 +788,6 @@ double precision function jiang2014EnergyMean(self,node,host) & velocityHost , & & cosmologyParameters_ =self%cosmologyParameters_ , & & cosmologyFunctions_ =self%cosmologyFunctions_ , & - & darkMatterProfileDMO_ =self%darkMatterProfileDMO_ , & & virialDensityContrast_=self%virialDensityContrast_ & & ) jiang2014EnergyMean = +0.5d0 & diff --git a/source/satellites.merging.virial_orbits.Li2020.F90 b/source/satellites.merging.virial_orbits.Li2020.F90 index 93a2ef12f6..534f77e063 100644 --- a/source/satellites.merging.virial_orbits.Li2020.F90 +++ b/source/satellites.merging.virial_orbits.Li2020.F90 @@ -26,7 +26,6 @@ use :: Cosmological_Density_Field, only : cosmologicalMassVarianceClass , criticalOverdensityClass use :: Dark_Matter_Halo_Scales , only : darkMatterHaloScaleClass use :: Virial_Density_Contrast , only : virialDensityContrastBryanNorman1998, virialDensityContrastClass - use :: Dark_Matter_Profiles_DMO , only : darkMatterProfileDMOClass !![ @@ -49,7 +48,6 @@ class (cosmologyFunctionsClass ), pointer :: cosmologyFunctions_ => null() class (criticalOverdensityClass ), pointer :: criticalOverdensity_ => null() class (cosmologicalMassVarianceClass ), pointer :: cosmologicalMassVariance_ => null() - class (darkMatterProfileDMOClass ), pointer :: darkMatterProfileDMO_ => null() class (virialDensityContrastClass ), pointer :: virialDensityContrast_ => null() type (virialDensityContrastBryanNorman1998), pointer :: virialDensityContrastDefinition_ => null() double precision :: mu1 , mu2 , & @@ -100,7 +98,6 @@ function li2020ConstructorParameters(parameters) result(self) class (cosmologyFunctionsClass ), pointer :: cosmologyFunctions_ class (criticalOverdensityClass ), pointer :: criticalOverdensity_ class (cosmologicalMassVarianceClass), pointer :: cosmologicalMassVariance_ - class (darkMatterProfileDMOClass ), pointer :: darkMatterProfileDMO_ class (virialDensityContrastClass ), pointer :: virialDensityContrast_ double precision :: mu1 , mu2 , & & a0 , a1 , & @@ -191,10 +188,9 @@ function li2020ConstructorParameters(parameters) result(self) - !!] - self=virialOrbitLi2020(mu1,mu2,sigma1,a0,a1,a2,a3,b1,b2,c,propagateOrbits,darkMatterHaloScale_,cosmologyParameters_,cosmologyFunctions_,criticalOverdensity_,cosmologicalMassVariance_,darkMatterProfileDMO_,virialDensityContrast_) + self=virialOrbitLi2020(mu1,mu2,sigma1,a0,a1,a2,a3,b1,b2,c,propagateOrbits,darkMatterHaloScale_,cosmologyParameters_,cosmologyFunctions_,criticalOverdensity_,cosmologicalMassVariance_,virialDensityContrast_) !![ @@ -202,13 +198,12 @@ function li2020ConstructorParameters(parameters) result(self) - !!] return end function li2020ConstructorParameters - function li2020ConstructorInternal(mu1,mu2,sigma1,a0,a1,a2,a3,b1,b2,c,propagateOrbits,darkMatterHaloScale_,cosmologyParameters_,cosmologyFunctions_,criticalOverdensity_,cosmologicalMassVariance_,darkMatterProfileDMO_,virialDensityContrast_) result(self) + function li2020ConstructorInternal(mu1,mu2,sigma1,a0,a1,a2,a3,b1,b2,c,propagateOrbits,darkMatterHaloScale_,cosmologyParameters_,cosmologyFunctions_,criticalOverdensity_,cosmologicalMassVariance_,virialDensityContrast_) result(self) !!{ Internal constructor for the {\normalfont \ttfamily li2020} virial orbits class. !!} @@ -220,7 +215,6 @@ function li2020ConstructorInternal(mu1,mu2,sigma1,a0,a1,a2,a3,b1,b2,c,propagateO class (criticalOverdensityClass ), intent(in ), target :: criticalOverdensity_ class (cosmologicalMassVarianceClass), intent(in ), target :: cosmologicalMassVariance_ class (virialDensityContrastClass ), intent(in ), target :: virialDensityContrast_ - class (darkMatterProfileDMOClass ), intent(in ), target :: darkMatterProfileDMO_ double precision , intent(in ) :: mu1 , mu2 , & & a0 , a1 , & & a2 , a3 , & @@ -228,7 +222,7 @@ function li2020ConstructorInternal(mu1,mu2,sigma1,a0,a1,a2,a3,b1,b2,c,propagateO & c , sigma1 logical , intent(in ) :: propagateOrbits !![ - + !!] ! Create virial density contrast definition. @@ -260,7 +254,6 @@ subroutine li2020Destructor(self) - !!] @@ -307,7 +300,6 @@ function li2020Orbit(self,node,host,acceptUnboundOrbits) & velocityHost , & & cosmologyParameters_ =self%cosmologyParameters_ , & & cosmologyFunctions_ =self%cosmologyFunctions_ , & - & darkMatterProfileDMO_ =self%darkMatterProfileDMO_ , & & virialDensityContrast_=self%virialDensityContrast_ & & ) massSatellite=Dark_Matter_Profile_Mass_Definition( & @@ -315,7 +307,6 @@ function li2020Orbit(self,node,host,acceptUnboundOrbits) & self%virialDensityContrastDefinition_%densityContrast( basic%mass(), basic%timeLastIsolated()), & & cosmologyParameters_ =self%cosmologyParameters_ , & & cosmologyFunctions_ =self%cosmologyFunctions_ , & - & darkMatterProfileDMO_ =self%darkMatterProfileDMO_ , & & virialDensityContrast_=self%virialDensityContrast_ & & ) !![ @@ -436,7 +427,6 @@ function li2020VelocityDistributionFunction(self,node,host,velocityRadial,veloci & velocityHost , & & cosmologyParameters_ =self%cosmologyParameters_ , & & cosmologyFunctions_ =self%cosmologyFunctions_ , & - & darkMatterProfileDMO_ =self%darkMatterProfileDMO_ , & & virialDensityContrast_=self%virialDensityContrast_ & & ) massSatellite=Dark_Matter_Profile_Mass_Definition( & @@ -444,7 +434,6 @@ function li2020VelocityDistributionFunction(self,node,host,velocityRadial,veloci & self%virialDensityContrastDefinition_%densityContrast( basic%mass(), basic%timeLastIsolated()), & & cosmologyParameters_ =self%cosmologyParameters_ , & & cosmologyFunctions_ =self%cosmologyFunctions_ , & - & darkMatterProfileDMO_ =self%darkMatterProfileDMO_ , & & virialDensityContrast_=self%virialDensityContrast_ & & ) !![ @@ -530,7 +519,6 @@ double precision function li2020VelocityTangentialMagnitudeMean(self,node,host) & velocityHost , & & cosmologyParameters_ =self%cosmologyParameters_ , & & cosmologyFunctions_ =self%cosmologyFunctions_ , & - & darkMatterProfileDMO_ =self%darkMatterProfileDMO_ , & & virialDensityContrast_=self%virialDensityContrast_ & & ) massSatellite=Dark_Matter_Profile_Mass_Definition( & @@ -538,7 +526,6 @@ double precision function li2020VelocityTangentialMagnitudeMean(self,node,host) & self%virialDensityContrastDefinition_%densityContrast( basic%mass(), basic%timeLastIsolated()), & & cosmologyParameters_ =self%cosmologyParameters_ , & & cosmologyFunctions_ =self%cosmologyFunctions_ , & - & darkMatterProfileDMO_ =self%darkMatterProfileDMO_ , & & virialDensityContrast_=self%virialDensityContrast_ & & ) !![ @@ -630,7 +617,6 @@ double precision function li2020AngularMomentumMagnitudeMean(self,node,host) & velocityHost , & & cosmologyParameters_ =self%cosmologyParameters_ , & & cosmologyFunctions_ =self%cosmologyFunctions_ , & - & darkMatterProfileDMO_ =self%darkMatterProfileDMO_ , & & virialDensityContrast_=self%virialDensityContrast_ & & ) if (massHost > 0.0d0) then @@ -689,7 +675,6 @@ double precision function li2020VelocityTotalRootMeanSquared(self,node,host) & velocityHost , & & cosmologyParameters_ =self%cosmologyParameters_ , & & cosmologyFunctions_ =self%cosmologyFunctions_ , & - & darkMatterProfileDMO_ =self%darkMatterProfileDMO_ , & & virialDensityContrast_=self%virialDensityContrast_ & & ) massSatellite=Dark_Matter_Profile_Mass_Definition( & @@ -697,7 +682,6 @@ double precision function li2020VelocityTotalRootMeanSquared(self,node,host) & self%virialDensityContrastDefinition_%densityContrast( basic%mass(), basic%timeLastIsolated()), & & cosmologyParameters_ =self%cosmologyParameters_ , & & cosmologyFunctions_ =self%cosmologyFunctions_ , & - & darkMatterProfileDMO_ =self%darkMatterProfileDMO_ , & & virialDensityContrast_=self%virialDensityContrast_ & & ) !![ @@ -732,7 +716,6 @@ double precision function li2020EnergyMean(self,node,host) & velocityHost , & & cosmologyParameters_ =self%cosmologyParameters_ , & & cosmologyFunctions_ =self%cosmologyFunctions_ , & - & darkMatterProfileDMO_ =self%darkMatterProfileDMO_ , & & virialDensityContrast_=self%virialDensityContrast_ & & ) li2020EnergyMean = +0.5d0 & diff --git a/source/satellites.merging.virial_orbits.Wetzel2010.F90 b/source/satellites.merging.virial_orbits.Wetzel2010.F90 index 49f1f1ee76..8eafed5c31 100644 --- a/source/satellites.merging.virial_orbits.Wetzel2010.F90 +++ b/source/satellites.merging.virial_orbits.Wetzel2010.F90 @@ -65,7 +65,6 @@ use this value rather than $10$ since the GSL $_2F_1$ hypergeometric function fa class (cosmologyParametersClass ), pointer :: cosmologyParameters_ => null() class (cosmologyFunctionsClass ), pointer :: cosmologyFunctions_ => null() class (criticalOverdensityClass ), pointer :: criticalOverdensity_ => null() - class (darkMatterProfileDMOClass ), pointer :: darkMatterProfileDMO_ => null() class (virialDensityContrastClass ), pointer :: virialDensityContrast_ => null() type (virialDensityContrastFriendsOfFriends), pointer :: virialDensityContrastDefinition_ => null() contains @@ -119,7 +118,6 @@ function wetzel2010ConstructorParameters(parameters) result(self) class(cosmologyParametersClass ), pointer :: cosmologyParameters_ class(cosmologyFunctionsClass ), pointer :: cosmologyFunctions_ class(criticalOverdensityClass ), pointer :: criticalOverdensity_ - class(darkMatterProfileDMOClass ), pointer :: darkMatterProfileDMO_ class(virialDensityContrastClass), pointer :: virialDensityContrast_ !![ @@ -127,10 +125,9 @@ function wetzel2010ConstructorParameters(parameters) result(self) - !!] - self=virialOrbitWetzel2010(darkMatterHaloScale_,cosmologyFunctions_,criticalOverdensity_,cosmologyParameters_,darkMatterProfileDMO_,virialDensityContrast_) + self=virialOrbitWetzel2010(darkMatterHaloScale_,cosmologyFunctions_,criticalOverdensity_,cosmologyParameters_,virialDensityContrast_) !![ @@ -138,13 +135,12 @@ function wetzel2010ConstructorParameters(parameters) result(self) - !!] return end function wetzel2010ConstructorParameters - function wetzel2010ConstructorInternal(darkMatterHaloScale_,cosmologyFunctions_,criticalOverdensity_,cosmologyParameters_,darkMatterProfileDMO_,virialDensityContrast_) result(self) + function wetzel2010ConstructorInternal(darkMatterHaloScale_,cosmologyFunctions_,criticalOverdensity_,cosmologyParameters_,virialDensityContrast_) result(self) !!{ Internal constructor for the {\normalfont \ttfamily wetzel2010} virial orbits class. !!} @@ -155,14 +151,13 @@ function wetzel2010ConstructorInternal(darkMatterHaloScale_,cosmologyFunctions_, class (cosmologyFunctionsClass ), intent(in ), target :: cosmologyFunctions_ class (cosmologyParametersClass ), intent(in ), target :: cosmologyParameters_ class (virialDensityContrastClass), intent(in ), target :: virialDensityContrast_ - class (darkMatterProfileDMOClass ), intent(in ), target :: darkMatterProfileDMO_ class (criticalOverdensityClass ), intent(in ), target :: criticalOverdensity_ double precision , parameter :: toleranceAbsolute =0.0d0, toleranceRelative =1.0d-2 integer :: iRadius double precision :: x , xGamma2 , & & probabilityCumulative , probabilityCumulativeNormalization !![ - + !!] ! Initialize root finder. @@ -219,7 +214,6 @@ subroutine wetzel2010Destructor(self) - !!] @@ -268,7 +262,6 @@ function wetzel2010Orbit(self,node,host,acceptUnboundOrbits) & velocityHost , & & cosmologyParameters_ =self%cosmologyParameters_ , & & cosmologyFunctions_ =self%cosmologyFunctions_ , & - & darkMatterProfileDMO_ =self%darkMatterProfileDMO_ , & & virialDensityContrast_=self%virialDensityContrast_ & & ) massSatellite=Dark_Matter_Profile_Mass_Definition( & @@ -276,7 +269,6 @@ function wetzel2010Orbit(self,node,host,acceptUnboundOrbits) & self%virialDensityContrastDefinition_%densityContrast( basic%mass(), basic%timeLastIsolated()), & & cosmologyParameters_ =self%cosmologyParameters_ , & & cosmologyFunctions_ =self%cosmologyFunctions_ , & - & darkMatterProfileDMO_ =self%darkMatterProfileDMO_ , & & virialDensityContrast_=self%virialDensityContrast_ & & ) !![ @@ -427,7 +419,6 @@ double precision function wetzel2010AngularMomentumMagnitudeMean(self,node,host) & velocityHost , & & cosmologyParameters_ =self%cosmologyParameters_ , & & cosmologyFunctions_ =self%cosmologyFunctions_ , & - & darkMatterProfileDMO_ =self%darkMatterProfileDMO_ , & & virialDensityContrast_=self%virialDensityContrast_ & & ) wetzel2010AngularMomentumMagnitudeMean = +self%velocityTangentialMagnitudeMean(node,host) & @@ -494,7 +485,6 @@ double precision function wetzel2010EnergyMean(self,node,host) & velocityHost , & & cosmologyParameters_ =self%cosmologyParameters_ , & & cosmologyFunctions_ =self%cosmologyFunctions_ , & - & darkMatterProfileDMO_ =self%darkMatterProfileDMO_ , & & virialDensityContrast_=self%virialDensityContrast_ & & ) wetzel2010EnergyMean = +0.5d0 & diff --git a/source/satellites.merging.virial_orbits.fixed.F90 b/source/satellites.merging.virial_orbits.fixed.F90 index 1c2078864f..aa9d6b7dc1 100644 --- a/source/satellites.merging.virial_orbits.fixed.F90 +++ b/source/satellites.merging.virial_orbits.fixed.F90 @@ -21,11 +21,10 @@ An implementation of virial orbits which assumes fixed orbital parameters. !!} - use :: Cosmology_Functions , only : cosmologyFunctionsClass - use :: Cosmology_Parameters , only : cosmologyParametersClass - use :: Dark_Matter_Profiles_DMO, only : darkMatterProfileDMOClass - use :: Dark_Matter_Halo_Scales , only : darkMatterHaloScaleClass - use :: Virial_Density_Contrast , only : virialDensityContrastClass + use :: Cosmology_Functions , only : cosmologyFunctionsClass + use :: Cosmology_Parameters , only : cosmologyParametersClass + use :: Dark_Matter_Halo_Scales, only : darkMatterHaloScaleClass + use :: Virial_Density_Contrast, only : virialDensityContrastClass !![ @@ -43,7 +42,6 @@ double precision :: velocityRadial , velocityTangential class (cosmologyParametersClass ), pointer :: cosmologyParameters_ => null() class (cosmologyFunctionsClass ), pointer :: cosmologyFunctions_ => null() - class (darkMatterProfileDMOClass ), pointer :: darkMatterProfileDMO_ => null() class (virialDensityContrastClass), pointer :: virialDensityContrast_ => null(), virialDensityContrastDefinition_ => null() class (darkMatterHaloScaleClass ), pointer :: darkMatterHaloScale_ => null() contains @@ -78,7 +76,6 @@ function fixedConstructorParameters(parameters) result(self) type (inputParameters ), intent(inout) :: parameters class (cosmologyParametersClass ), pointer :: cosmologyParameters_ class (cosmologyFunctionsClass ), pointer :: cosmologyFunctions_ - class (darkMatterProfileDMOClass ), pointer :: darkMatterProfileDMO_ class (virialDensityContrastClass), pointer :: virialDensityContrast_, virialDensityContrastDefinition_ class (darkMatterHaloScaleClass ), pointer :: darkMatterHaloScale_ double precision :: velocityRadial , velocityTangential @@ -98,25 +95,23 @@ function fixedConstructorParameters(parameters) result(self) - !!] - self=virialOrbitFixed(velocityRadial,velocityTangential,virialDensityContrastDefinition_,darkMatterHaloScale_,cosmologyParameters_,cosmologyFunctions_,darkMatterProfileDMO_,virialDensityContrast_) + self=virialOrbitFixed(velocityRadial,velocityTangential,virialDensityContrastDefinition_,darkMatterHaloScale_,cosmologyParameters_,cosmologyFunctions_,virialDensityContrast_) !![ - !!] return end function fixedConstructorParameters - function fixedConstructorInternal(velocityRadial,velocityTangential,virialDensityContrastDefinition_,darkMatterHaloScale_,cosmologyParameters_,cosmologyFunctions_,darkMatterProfileDMO_,virialDensityContrast_) result(self) + function fixedConstructorInternal(velocityRadial,velocityTangential,virialDensityContrastDefinition_,darkMatterHaloScale_,cosmologyParameters_,cosmologyFunctions_,virialDensityContrast_) result(self) !!{ Internal constructor for the {\normalfont \ttfamily fixed} virial orbits class. !!} @@ -128,10 +123,9 @@ function fixedConstructorInternal(velocityRadial,velocityTangential,virialDensit class (cosmologyParametersClass ), intent(in ), target :: cosmologyParameters_ class (cosmologyFunctionsClass ), intent(in ), target :: cosmologyFunctions_ class (virialDensityContrastClass), intent(in ), target :: virialDensityContrast_ - class (darkMatterProfileDMOClass ), intent(in ), target :: darkMatterProfileDMO_ double precision , intent(in ) :: velocityRadial , velocityTangential !![ - + !!] return @@ -147,7 +141,6 @@ subroutine fixedDestructor(self) !![ - @@ -189,7 +182,6 @@ function fixedOrbit(self,node,host,acceptUnboundOrbits) & velocityHost , & & cosmologyParameters_ =self%cosmologyParameters_ , & & cosmologyFunctions_ =self%cosmologyFunctions_ , & - & darkMatterProfileDMO_ =self%darkMatterProfileDMO_ , & & virialDensityContrast_=self%virialDensityContrast_ & & ) massSatellite=Dark_Matter_Profile_Mass_Definition( & @@ -197,7 +189,6 @@ function fixedOrbit(self,node,host,acceptUnboundOrbits) & self%virialDensityContrastDefinition_%densityContrast( basic%mass(), basic%timeLastIsolated()), & & cosmologyParameters_ =self%cosmologyParameters_ , & & cosmologyFunctions_ =self%cosmologyFunctions_ , & - & darkMatterProfileDMO_ =self%darkMatterProfileDMO_ , & & virialDensityContrast_=self%virialDensityContrast_ & & ) ! Set basic properties of the orbit - do not allow the satellite mass to exceed the host mass. @@ -267,7 +258,6 @@ double precision function fixedVelocityTangentialMagnitudeMean(self,node,host) & velocityHost , & & cosmologyParameters_ =self%cosmologyParameters_ , & & cosmologyFunctions_ =self%cosmologyFunctions_ , & - & darkMatterProfileDMO_ =self%darkMatterProfileDMO_ , & & virialDensityContrast_=self%virialDensityContrast_ & & ) fixedVelocityTangentialMagnitudeMean = +self%velocityTangential & @@ -313,7 +303,6 @@ double precision function fixedAngularMomentumMagnitudeMean(self,node,host) & velocityHost , & & cosmologyParameters_ =self%cosmologyParameters_ , & & cosmologyFunctions_ =self%cosmologyFunctions_ , & - & darkMatterProfileDMO_ =self%darkMatterProfileDMO_ , & & virialDensityContrast_=self%virialDensityContrast_ & & ) fixedAngularMomentumMagnitudeMean = +self%velocityTangentialMagnitudeMean(node,host) & @@ -364,7 +353,6 @@ double precision function fixedVelocityTotalRootMeanSquared(self,node,host) & velocityHost , & & cosmologyParameters_ =self%cosmologyParameters_ , & & cosmologyFunctions_ =self%cosmologyFunctions_ , & - & darkMatterProfileDMO_ =self%darkMatterProfileDMO_ , & & virialDensityContrast_=self%virialDensityContrast_ & & ) fixedVelocityTotalRootMeanSquared = +sqrt( & @@ -398,7 +386,6 @@ double precision function fixedEnergyMean(self,node,host) & velocityHost , & & cosmologyParameters_ =self%cosmologyParameters_ , & & cosmologyFunctions_ =self%cosmologyFunctions_ , & - & darkMatterProfileDMO_ =self%darkMatterProfileDMO_ , & & virialDensityContrast_=self%virialDensityContrast_ & & ) fixedEnergyMean = +0.5d0 & diff --git a/source/satellites.merging.virial_orbits.loss_cone.F90 b/source/satellites.merging.virial_orbits.loss_cone.F90 index 108d168163..3a3f4952b4 100644 --- a/source/satellites.merging.virial_orbits.loss_cone.F90 +++ b/source/satellites.merging.virial_orbits.loss_cone.F90 @@ -26,7 +26,6 @@ use :: Cosmological_Velocity_Field , only : cosmologicalVelocityFieldClass use :: Cosmological_Density_Field , only : cosmologicalMassVarianceClass , cosmologicalMassVariancePeakBackgroundSplit, criticalOverdensityClass, criticalOverdensityPeakBackgroundSplit, & & haloEnvironmentNormal - use :: Dark_Matter_Profiles_DMO , only : darkMatterProfileDMOClass use :: Halo_Mass_Functions , only : haloMassFunctionShethTormen use :: Dark_Matter_Halo_Biases , only : darkMatterHaloBiasClass use :: Dark_Matter_Halo_Scales , only : darkMatterHaloScaleClass @@ -54,7 +53,6 @@ class (cosmologicalVelocityFieldClass ), pointer :: cosmologicalVelocityField_ => null() class (darkMatterHaloBiasClass ), pointer :: darkMatterHaloBias_ => null() class (darkMatterHaloScaleClass ), pointer :: darkMatterHaloScale_ => null() - class (darkMatterProfileDMOClass ), pointer :: darkMatterProfileDMO_ => null() class (cosmologicalMassVarianceClass ), pointer :: cosmologicalMassVariance_ => null() class (criticalOverdensityClass ), pointer :: criticalOverdensity_ => null() class (linearGrowthClass ), pointer :: linearGrowth_ => null() @@ -149,7 +147,6 @@ function lossConeConstructorParameters(parameters) result(self) class (criticalOverdensityClass ), pointer :: criticalOverdensity_ class (darkMatterHaloBiasClass ), pointer :: darkMatterHaloBias_ class (darkMatterHaloScaleClass ), pointer :: darkMatterHaloScale_ - class (darkMatterProfileDMOClass ), pointer :: darkMatterProfileDMO_ class (linearGrowthClass ), pointer :: linearGrowth_ class (virialDensityContrastClass ), pointer :: virialDensityContrast_ class (correlationFunctionTwoPointClass ), pointer :: correlationFunctionTwoPoint_ @@ -219,12 +216,11 @@ The normalization parameter $A$ of the \cite{sheth_ellipsoidal_2001 - !!] - self=virialOrbitLossCone(velocityMinimum,velocityMaximum,countVelocitiesPerUnit,countMassesPerDecade,includeInFlightGrowth,haloMassFunctionA,haloMassFunctionP,haloMassFunctionNormalization,velocityDispersionMultiplier,cosmologyFunctions_,cosmologyParameters_,cosmologicalVelocityField_,linearGrowth_,darkMatterHaloBias_,darkMatterHaloScale_,virialDensityContrast_,correlationFunctionTwoPoint_,cosmologicalMassVariance_,criticalOverdensity_,mergerTreeBranchingProbability_,darkMatterProfileDMO_) + self=virialOrbitLossCone(velocityMinimum,velocityMaximum,countVelocitiesPerUnit,countMassesPerDecade,includeInFlightGrowth,haloMassFunctionA,haloMassFunctionP,haloMassFunctionNormalization,velocityDispersionMultiplier,cosmologyFunctions_,cosmologyParameters_,cosmologicalVelocityField_,linearGrowth_,darkMatterHaloBias_,darkMatterHaloScale_,virialDensityContrast_,correlationFunctionTwoPoint_,cosmologicalMassVariance_,criticalOverdensity_,mergerTreeBranchingProbability_) !![ @@ -235,7 +231,6 @@ The normalization parameter $A$ of the \cite{sheth_ellipsoidal_2001 - @@ -243,7 +238,7 @@ The normalization parameter $A$ of the \cite{sheth_ellipsoidal_2001 return end function lossConeConstructorParameters - function lossConeConstructorInternal(velocityMinimum,velocityMaximum,countVelocitiesPerUnit,countMassesPerDecade,includeInFlightGrowth,haloMassFunctionA,haloMassFunctionP,haloMassFunctionNormalization,velocityDispersionMultiplier,cosmologyFunctions_,cosmologyParameters_,cosmologicalVelocityField_,linearGrowth_,darkMatterHaloBias_,darkMatterHaloScale_,virialDensityContrast_,correlationFunctionTwoPoint_,cosmologicalMassVariance_,criticalOverdensity_,mergerTreeBranchingProbability_,darkMatterProfileDMO_) result(self) + function lossConeConstructorInternal(velocityMinimum,velocityMaximum,countVelocitiesPerUnit,countMassesPerDecade,includeInFlightGrowth,haloMassFunctionA,haloMassFunctionP,haloMassFunctionNormalization,velocityDispersionMultiplier,cosmologyFunctions_,cosmologyParameters_,cosmologicalVelocityField_,linearGrowth_,darkMatterHaloBias_,darkMatterHaloScale_,virialDensityContrast_,correlationFunctionTwoPoint_,cosmologicalMassVariance_,criticalOverdensity_,mergerTreeBranchingProbability_) result(self) !!{ Internal constructor for the {\normalfont \ttfamily lossCone} virial orbits class. !!} @@ -259,7 +254,6 @@ function lossConeConstructorInternal(velocityMinimum,velocityMaximum,countVeloci class (criticalOverdensityClass ), intent(in ), target :: criticalOverdensity_ class (darkMatterHaloBiasClass ), intent(in ), target :: darkMatterHaloBias_ class (darkMatterHaloScaleClass ), intent(in ), target :: darkMatterHaloScale_ - class (darkMatterProfileDMOClass ), intent(in ), target :: darkMatterProfileDMO_ class (linearGrowthClass ), intent(in ), target :: linearGrowth_ class (virialDensityContrastClass ), intent(in ), target :: virialDensityContrast_ class (correlationFunctionTwoPointClass ), intent(in ), target :: correlationFunctionTwoPoint_ @@ -271,7 +265,7 @@ function lossConeConstructorInternal(velocityMinimum,velocityMaximum,countVeloci logical , intent(in ) :: includeInFlightGrowth integer :: countVelocities !![ - + !!] ! Set an initial mass range, along with an unphysical initial time (so that retabulation will be forced on the first call). @@ -314,7 +308,6 @@ subroutine lossConeDestructor(self) - !!] return end subroutine lossConeDestructor @@ -493,7 +486,6 @@ subroutine lossConeInterpolants(self,node,host,massSatellite,massHost,velocityHo & self%virialDensityContrast_%densityContrast(basicSatellite%mass(),basicSatellite%timeLastIsolated()), & & cosmologyParameters_ =self%cosmologyParameters_ , & & cosmologyFunctions_ =self%cosmologyFunctions_ , & - & darkMatterProfileDMO_ =self%darkMatterProfileDMO_ , & & virialDensityContrast_=self%virialDensityContrast_ & & ) massHost = Dark_Matter_Profile_Mass_Definition( & @@ -503,7 +495,6 @@ subroutine lossConeInterpolants(self,node,host,massSatellite,massHost,velocityHo & velocity =velocityHost , & & cosmologyParameters_ =self%cosmologyParameters_ , & & cosmologyFunctions_ =self%cosmologyFunctions_ , & - & darkMatterProfileDMO_ =self%darkMatterProfileDMO_ , & & virialDensityContrast_=self%virialDensityContrast_ & & ) massSatellite = min(massSatellite,massHost) @@ -588,7 +579,7 @@ double precision function lossConeAngularMomentumMagnitudeMean(self,node,host) basic => node%basic() basicHost => host%basic() - massHost = Dark_Matter_Profile_Mass_Definition(host,self%virialDensityContrast_%densityContrast(basicHost%mass(),basicHost%timeLastIsolated()),radiusHost,velocityHost,self%cosmologyParameters_,self%cosmologyFunctions_,self%darkMatterProfileDMO_,self%virialDensityContrast_) + massHost = Dark_Matter_Profile_Mass_Definition(host,self%virialDensityContrast_%densityContrast(basicHost%mass(),basicHost%timeLastIsolated()),radiusHost,velocityHost,self%cosmologyParameters_,self%cosmologyFunctions_,self%virialDensityContrast_) lossConeAngularMomentumMagnitudeMean = +self%velocityTangentialMagnitudeMean(node,host) & & *radiusHost & & /( & ! Account for reduced mass. @@ -661,7 +652,7 @@ double precision function lossConeEnergyMean(self,node,host) basic => node%basic() basicHost => host%basic() - massHost = Dark_Matter_Profile_Mass_Definition(host,self%virialDensityContrast_%densityContrast(basicHost%mass(),basicHost%timeLastIsolated()),radiusHost,velocityHost,self%cosmologyParameters_,self%cosmologyFunctions_,self%darkMatterProfileDMO_,self%virialDensityContrast_) + massHost = Dark_Matter_Profile_Mass_Definition(host,self%virialDensityContrast_%densityContrast(basicHost%mass(),basicHost%timeLastIsolated()),radiusHost,velocityHost,self%cosmologyParameters_,self%cosmologyFunctions_,self%virialDensityContrast_) lossConeEnergyMean = +0.5d0 & & *self%velocityTotalRootMeanSquared(node,host)**2 & & /( & ! Account for reduced mass. @@ -746,7 +737,6 @@ subroutine lossConeTabulate(self,nodeSatelliteTarget,nodeHostTarget) & self%virialDensityContrast_%densityContrast(basicSatellite%mass(),basicSatellite%timeLastIsolated()), & & cosmologyParameters_ =self%cosmologyParameters_ , & & cosmologyFunctions_ =self%cosmologyFunctions_ , & - & darkMatterProfileDMO_ =self%darkMatterProfileDMO_ , & & virialDensityContrast_=self%virialDensityContrast_ & & ) massHost = Dark_Matter_Profile_Mass_Definition( & @@ -754,7 +744,6 @@ subroutine lossConeTabulate(self,nodeSatelliteTarget,nodeHostTarget) & self%virialDensityContrast_%densityContrast(basicHost %mass(),basicHost %timeLastIsolated()), & & cosmologyParameters_ =self%cosmologyParameters_ , & & cosmologyFunctions_ =self%cosmologyFunctions_ , & - & darkMatterProfileDMO_ =self%darkMatterProfileDMO_ , & & virialDensityContrast_=self%virialDensityContrast_ & & ) if ( & diff --git a/source/satellites.merging.virial_orbits.mass_reduced.F90 b/source/satellites.merging.virial_orbits.mass_reduced.F90 index b4ab5aa75b..b9f882e7fe 100644 --- a/source/satellites.merging.virial_orbits.mass_reduced.F90 +++ b/source/satellites.merging.virial_orbits.mass_reduced.F90 @@ -22,11 +22,9 @@ the primary halo below its ``\gls{dmou}'' value. !!} - use :: Galactic_Structure , only : galacticStructureClass - use :: Cosmology_Parameters , only : cosmologyParametersClass - use :: Cosmology_Functions , only : cosmologyFunctionsClass - use :: Dark_Matter_Profiles_DMO, only : darkMatterProfileDMOClass - use :: Virial_Density_Contrast , only : virialDensityContrastClass + use :: Cosmology_Parameters , only : cosmologyParametersClass + use :: Cosmology_Functions , only : cosmologyFunctionsClass + use :: Virial_Density_Contrast, only : virialDensityContrastClass !![ @@ -43,8 +41,6 @@ !!} private class(virialOrbitClass ), pointer :: virialOrbit_ => null() - class(galacticStructureClass ), pointer :: galacticStructure_ => null() - class(darkMatterProfileDMOClass ), pointer :: darkMatterProfileDMO_ => null() class(virialDensityContrastClass), pointer :: virialDensityContrast_ => null() class(cosmologyParametersClass ), pointer :: cosmologyParameters_ => null() class(cosmologyFunctionsClass ), pointer :: cosmologyFunctions_ => null() @@ -75,47 +71,39 @@ function massReducedConstructorParameters(parameters) result(self) type (virialOrbitMassReduced ) :: self type (inputParameters ), intent(inout) :: parameters class(virialOrbitClass ), pointer :: virialOrbit_ - class(galacticStructureClass ), pointer :: galacticStructure_ class(cosmologyFunctionsClass ), pointer :: cosmologyFunctions_ class(cosmologyParametersClass ), pointer :: cosmologyParameters_ - class(darkMatterProfileDMOClass ), pointer :: darkMatterProfileDMO_ class(virialDensityContrastClass), pointer :: virialDensityContrast_ !![ - - !!] - self=virialOrbitMassReduced(virialOrbit_,galacticStructure_,cosmologyFunctions_,cosmologyParameters_,darkMatterProfileDMO_,virialDensityContrast_) + self=virialOrbitMassReduced(virialOrbit_,cosmologyFunctions_,cosmologyParameters_,virialDensityContrast_) !![ - - !!] return end function massReducedConstructorParameters - function massReducedConstructorInternal(virialOrbit_,galacticStructure_,cosmologyFunctions_,cosmologyParameters_,darkMatterProfileDMO_,virialDensityContrast_) result(self) + function massReducedConstructorInternal(virialOrbit_,cosmologyFunctions_,cosmologyParameters_,virialDensityContrast_) result(self) !!{ Internal constructor for the {\normalfont \ttfamily massReduced} virial orbits class. !!} implicit none type (virialOrbitMassReduced ) :: self class(virialOrbitClass ), intent(in ), target :: virialOrbit_ - class(galacticStructureClass ), intent(in ), target :: galacticStructure_ class(cosmologyParametersClass ), intent(in ), target :: cosmologyParameters_ class(cosmologyFunctionsClass ), intent(in ), target :: cosmologyFunctions_ class(virialDensityContrastClass), intent(in ), target :: virialDensityContrast_ - class(darkMatterProfileDMOClass ), intent(in ), target :: darkMatterProfileDMO_ !![ - + !!] return @@ -130,11 +118,9 @@ subroutine massReducedDestructor(self) !![ - - !!] return end subroutine massReducedDestructor @@ -147,6 +133,7 @@ function massReducedOrbit(self,node,host,acceptUnboundOrbits) result(orbit) use :: Error , only : Error_Report use :: Galacticus_Nodes , only : nodeComponentBasic use :: Kepler_Orbits , only : keplerOrbitPhi , keplerOrbitRadius, keplerOrbitTheta, keplerOrbitVelocityTangential + use :: Mass_Distributions , only : massDistributionClass use :: Numerical_Constants_Astronomical , only : gravitationalConstantGalacticus use :: Galactic_Structure_Options , only : componentTypeAll , massTypeAll use :: Virial_Density_Contrast , only : virialDensityContrastClass @@ -158,6 +145,7 @@ function massReducedOrbit(self,node,host,acceptUnboundOrbits) result(orbit) integer , parameter :: iterationMaximum =1000 class (nodeComponentBasic ), pointer :: basicHost class (virialDensityContrastClass), pointer :: densityContrastDefinition_ + class (massDistributionClass ), pointer :: massDistribution_ double precision :: velocityHost , massHost , & & radiusHost , massHostDMO , & & massSatellite , velocityRadialSquared @@ -176,14 +164,17 @@ function massReducedOrbit(self,node,host,acceptUnboundOrbits) result(orbit) & velocityHost , & & cosmologyParameters_ =self%cosmologyParameters_ , & & cosmologyFunctions_ =self%cosmologyFunctions_ , & - & darkMatterProfileDMO_ =self%darkMatterProfileDMO_ , & & virialDensityContrast_=self%virialDensityContrast_ & & ) !![ !!] ! Find the mass of the host including baryons. - massHost=self%galacticStructure_%massEnclosed(host,radiusHost,massType=massTypeAll,componentType=componentTypeAll) + massDistribution_ => host %massDistribution (massType=massTypeAll,componentType=componentTypeAll) + massHost = massDistribution_%massEnclosedBySphere( radiusHost ) + !![ + + !!] ! Iterate until an acceptable orbit is found. acceptOrbit=.false. iteration =0 @@ -246,6 +237,7 @@ double precision function massReducedVelocityTotalRootMeanSquared(self,node,host !!} use :: Dark_Matter_Profile_Mass_Definitions, only : Dark_Matter_Profile_Mass_Definition use :: Galacticus_Nodes , only : nodeComponentBasic + use :: Mass_Distributions , only : massDistributionClass use :: Numerical_Constants_Astronomical , only : gravitationalConstantGalacticus use :: Galactic_Structure_Options , only : componentTypeAll , massTypeAll use :: Virial_Density_Contrast , only : virialDensityContrastClass @@ -254,6 +246,7 @@ double precision function massReducedVelocityTotalRootMeanSquared(self,node,host type (treeNode ), intent(inout) :: node , host class (nodeComponentBasic ), pointer :: basicHost class (virialDensityContrastClass), pointer :: densityContrastDefinition_ + class (massDistributionClass ), pointer :: massDistribution_ double precision :: massHost , radiusHost , & & velocityHost , massHostDMO !$GLC attributes unused :: node @@ -269,13 +262,16 @@ double precision function massReducedVelocityTotalRootMeanSquared(self,node,host & velocityHost , & & cosmologyParameters_ =self%cosmologyParameters_ , & & cosmologyFunctions_ =self%cosmologyFunctions_ , & - & darkMatterProfileDMO_ =self%darkMatterProfileDMO_ , & & virialDensityContrast_=self%virialDensityContrast_ & & ) !![ !!] - massHost = +self%galacticStructure_%massEnclosed(host,radiusHost,massType=massTypeAll,componentType=componentTypeAll) + massDistribution_ => host %massDistribution (massType=massTypeAll,componentType=componentTypeAll) + massHost = massDistribution_%massEnclosedBySphere( radiusHost ) + !![ + + !!] velocityRootMeanSquared = +sqrt( & & +self%virialOrbit_%velocityTotalRootMeanSquared(node,host)**2 & & +2.0d0 & @@ -297,12 +293,14 @@ double precision function massReducedEnergyMean(self,node,host) result(energyMea use :: Galacticus_Nodes , only : nodeComponentBasic use :: Numerical_Constants_Astronomical , only : gravitationalConstantGalacticus use :: Galactic_Structure_Options , only : componentTypeAll , massTypeAll + use :: Mass_Distributions , only : massDistributionClass use :: Virial_Density_Contrast , only : virialDensityContrastClass implicit none class (virialOrbitMassReduced ), intent(inout) :: self type (treeNode ), intent(inout) :: node , host class (nodeComponentBasic ), pointer :: basicHost class (virialDensityContrastClass), pointer :: densityContrastDefinition_ + class (massDistributionClass ), pointer :: massDistribution_ double precision :: massHost , radiusHost , & & velocityHost , massHostDMO @@ -317,19 +315,22 @@ double precision function massReducedEnergyMean(self,node,host) result(energyMea & velocityHost , & & cosmologyParameters_ =self%cosmologyParameters_ , & & cosmologyFunctions_ =self%cosmologyFunctions_ , & - & darkMatterProfileDMO_ =self%darkMatterProfileDMO_ , & & virialDensityContrast_=self%virialDensityContrast_ & & ) !![ !!] - massHost = +self%galacticStructure_%massEnclosed(host,radiusHost,massType=massTypeAll,componentType=componentTypeAll) - energyMean = +self%virialOrbit_%energyMean(node,host) & - & +gravitationalConstantGalacticus & - & *( & - & +massHost & - & -massHostDMO & - & ) & - & /radiusHost + massDistribution_ => host %massDistribution (massType=massTypeAll,componentType=componentTypeAll) + massHost = massDistribution_%massEnclosedBySphere( radiusHost ) + !![ + + !!] + energyMean=+self%virialOrbit_%energyMean(node,host) & + & +gravitationalConstantGalacticus & + & *( & + & +massHost & + & -massHostDMO & + & ) & + & /radiusHost return end function massReducedEnergyMean diff --git a/source/satellites.merging.virial_orbits.spin_correlated.F90 b/source/satellites.merging.virial_orbits.spin_correlated.F90 index fd7b6a2cbc..a8aea9f50d 100644 --- a/source/satellites.merging.virial_orbits.spin_correlated.F90 +++ b/source/satellites.merging.virial_orbits.spin_correlated.F90 @@ -22,7 +22,7 @@ host halo. !!} - use :: Dark_Matter_Profiles_DMO, only : darkMatterProfileDMO, darkMatterProfileDMOClass + use :: Dark_Matter_Halo_Scales, only : darkMatterHaloScaleClass !![ @@ -35,8 +35,8 @@ !!} private double precision :: alpha - class (virialOrbitClass ), pointer :: virialOrbit_ => null() - class (darkMatterProfileDMOClass), pointer :: darkMatterProfileDMO_ => null() + class (virialOrbitClass ), pointer :: virialOrbit_ => null() + class (darkMatterHaloScaleClass ), pointer :: darkMatterHaloScale_ => null() contains final :: spinCorrelatedDestructor procedure :: orbit => spinCorrelatedOrbit @@ -69,7 +69,7 @@ function spinCorrelatedConstructorParameters(parameters) result(self) type (virialOrbitSpinCorrelated) :: self type (inputParameters ), intent(inout) :: parameters class (virialOrbitClass ), pointer :: virialOrbit_ - class (darkMatterProfileDMOClass), pointer :: darkMatterProfileDMO_ + class (darkMatterHaloScaleClass ), pointer :: darkMatterHaloScale_ double precision :: alpha !![ @@ -80,18 +80,18 @@ function spinCorrelatedConstructorParameters(parameters) result(self) The parameter $\alpha$ which expresses the strength of the correlation between satellite orbital angular momentum and the spin of the host halo. - + !!] - self=virialOrbitSpinCorrelated(alpha,virialOrbit_,darkMatterProfileDMO_) + self=virialOrbitSpinCorrelated(alpha,virialOrbit_,darkMatterHaloScale_) !![ - - + + !!] return end function spinCorrelatedConstructorParameters - function spinCorrelatedConstructorInternal(alpha,virialOrbit_,darkMatterProfileDMO_) result(self) + function spinCorrelatedConstructorInternal(alpha,virialOrbit_,darkMatterHaloScale_) result(self) !!{ Internal constructor for the {\normalfont \ttfamily spinCorrelated} virial orbits class. !!} @@ -101,9 +101,9 @@ function spinCorrelatedConstructorInternal(alpha,virialOrbit_,darkMatterProfileD type (virialOrbitSpinCorrelated) :: self double precision , intent(in ) :: alpha class (virialOrbitClass ), intent(in ), target :: virialOrbit_ - class (darkMatterProfileDMOClass), intent(in ), target :: darkMatterProfileDMO_ + class (darkMatterHaloScaleClass ), intent(in ), target :: darkMatterHaloScale_ !![ - + !!] if (.not.defaultSpinComponent%angularMomentumVectorIsGettable()) & @@ -127,8 +127,8 @@ subroutine spinCorrelatedDestructor(self) type(virialOrbitSpinCorrelated), intent(inout) :: self !![ - - + + !!] return end subroutine spinCorrelatedDestructor @@ -174,7 +174,7 @@ function spinCorrelatedOrbit(self,node,host,acceptUnboundOrbits) ! Compute the cosine of the angle between the angular momentum of this orbit and the spin of the host halo. spinHost => host %spin () spinVector = +spinHost%angularMomentumVector() & - & /Dark_Matter_Halo_Angular_Momentum_Scale(host,self%darkMatterProfileDMO_) + & /Dark_Matter_Halo_Angular_Momentum_Scale(host,self%darkMatterHaloScale_) coordinates = spinCorrelatedOrbit%position () position = coordinates coordinates = spinCorrelatedOrbit%velocity () @@ -234,10 +234,10 @@ function spinCorrelatedVelocityTangentialVectorMean(self,node,host) class (nodeComponentSpin ), pointer :: spinHost spinHost => host %spin ( ) - spinCorrelatedVelocityTangentialVectorMean = +self %alpha & - & *self %velocityTangentialMagnitudeMean(node,host) & - & *spinHost%angularMomentumVector ( ) & - & /Dark_Matter_Halo_Angular_Momentum_Scale(host,self%darkMatterProfileDMO_) & + spinCorrelatedVelocityTangentialVectorMean = +self %alpha & + & *self %velocityTangentialMagnitudeMean(node,host) & + & *spinHost%angularMomentumVector ( ) & + & /Dark_Matter_Halo_Angular_Momentum_Scale(host,self%darkMatterHaloScale_) & & /3.0d0 return end function spinCorrelatedVelocityTangentialVectorMean @@ -267,10 +267,10 @@ function spinCorrelatedAngularMomentumVectorMean(self,node,host) class (nodeComponentSpin ), pointer :: spinHost spinHost => host %spin ( ) - spinCorrelatedAngularMomentumVectorMean = +self %alpha & - & *self %angularMomentumMagnitudeMean(node,host) & - & *spinHost%angularMomentumVector ( ) & - & /Dark_Matter_Halo_Angular_Momentum_Scale(host,self%darkMatterProfileDMO_) & + spinCorrelatedAngularMomentumVectorMean = +self %alpha & + & *self %angularMomentumMagnitudeMean(node,host) & + & *spinHost%angularMomentumVector ( ) & + & /Dark_Matter_Halo_Angular_Momentum_Scale(host,self%darkMatterHaloScale_) & & /3.0d0 return end function spinCorrelatedAngularMomentumVectorMean diff --git a/source/satellites.orbits.F90 b/source/satellites.orbits.F90 index 0808e66e81..aa05d37c1f 100644 --- a/source/satellites.orbits.F90 +++ b/source/satellites.orbits.F90 @@ -25,22 +25,22 @@ module Satellite_Orbits !!{ Implements calculations related to satellite orbits. !!} - use :: Dark_Matter_Profiles_DMO, only : darkMatterProfileDMOClass - use :: Galactic_Structure , only : galacticStructureClass - use :: Galacticus_Nodes , only : treeNode - use :: Kind_Numbers , only : kind_int8 + use :: Galacticus_Nodes , only : treeNode + use :: Kind_Numbers , only : kind_int8 + use :: Mass_Distributions, only : massDistributionClass implicit none private public :: Satellite_Orbit_Equivalent_Circular_Orbit_Radius, Satellite_Orbit_Extremum_Phase_Space_Coordinates ! Orbital energy and angular momentum - used for finding radius of equivalent circular orbit. - double precision :: orbitalAngularMomentumInternal , orbitalEnergyInternal + double precision :: orbitalAngularMomentumInternal , orbitalEnergyInternal !$omp threadprivate(orbitalEnergyInternal,orbitalAngularMomentumInternal) - ! Node used in root finding calculations. + + ! Objects used in root finding calculations. type (treeNode ), pointer :: activeNode - class (darkMatterProfileDMOClass), pointer :: darkMatterProfileDMO__ - class (galacticStructureClass ), pointer :: galacticStructure__ - !$omp threadprivate(activeNode,darkMatterProfileDMO__,galacticStructure__) + class (massDistributionClass ), pointer :: massDistribution__ + double precision :: radiusVirial__ , massVirial__ + !$omp threadprivate(activeNode,radiusVirial__,massVirial__,massDistribution__) ! Enumeration used to indicate type of extremum. integer , parameter, public :: extremumPericenter =-1 @@ -66,10 +66,11 @@ module Satellite_Orbits contains - double precision function Satellite_Orbit_Equivalent_Circular_Orbit_Radius(nodeHost,orbit,darkMatterHaloScale_,darkMatterProfileDMO_,galacticStructure_,errorCode) + double precision function Satellite_Orbit_Equivalent_Circular_Orbit_Radius(nodeHost,orbit,darkMatterHaloScale_,errorCode) !!{ Solves for the equivalent circular orbit radius for {\normalfont \ttfamily orbit} in {\normalfont \ttfamily nodeHost}. !!} + use :: Galacticus_Nodes , only : nodeComponentBasic use :: Dark_Matter_Halo_Scales, only : darkMatterHaloScaleClass use :: Kepler_Orbits , only : keplerOrbit use :: Root_Finder , only : rangeExpandMultiplicative, rangeExpandSignExpectNegative, rangeExpandSignExpectPositive, rootFinder @@ -78,22 +79,28 @@ double precision function Satellite_Orbit_Equivalent_Circular_Orbit_Radius(nodeH type (keplerOrbit ), intent(inout) :: orbit integer , intent( out), optional :: errorCode class (darkMatterHaloScaleClass ), intent(inout) :: darkMatterHaloScale_ - class (darkMatterProfileDMOClass), intent(inout), target :: darkMatterProfileDMO_ - class (galacticStructureClass ), intent(inout), target :: galacticStructure_ - double precision , parameter :: toleranceAbsolute =0.0d0, toleranceRelative=1.0d-6 + class (nodeComponentBasic ) , pointer :: basicHost + double precision , parameter :: toleranceAbsolute =0.0d0, toleranceRelative=1.0d-6, & + & factorRadiusLarge =1.0d6 type (rootFinder ) :: finder type (keplerOrbit ) :: orbitCurrent + double precision :: potential - ! Convert the orbit to the potential of the current halo in which the satellite finds itself. - orbitCurrent=Satellite_Orbit_Convert_To_Current_Potential(orbit,nodeHost,galacticStructure_) ! Assign the active node. - activeNode => nodeHost - darkMatterProfileDMO__ => darkMatterProfileDMO_ - galacticStructure__ => galacticStructure_ + activeNode => nodeHost + ! Get the mass distribution. + massDistribution__ => nodeHost%massDistribution() + ! Convert the orbit to the potential of the current halo in which the satellite finds itself. + orbitCurrent=Satellite_Orbit_Convert_To_Current_Potential(orbit,nodeHost,darkMatterHaloScale_) + ! Get virial properties. + basicHost => nodeHost %basic ( ) + radiusVirial__ = darkMatterHaloScale_ %radiusVirial(nodeHost) + massVirial__ = basicHost %mass ( ) ! Store the orbital energy. orbitalEnergyInternal=orbit%energy() ! Test for conditions that an equivalent circular orbit exists. - if (orbitalEnergyInternal >= 0.0d0) then + potential=Satellite_Orbit_Potential(factorRadiusLarge*radiusVirial__,radiusVirial__,massVirial__) + if (orbitalEnergyInternal >= potential) then ! Orbit is unbound, return unphysical value. Satellite_Orbit_Equivalent_Circular_Orbit_Radius=-1.0d0 if (present(errorCode)) errorCode=errorCodeOrbitUnbound @@ -103,23 +110,26 @@ double precision function Satellite_Orbit_Equivalent_Circular_Orbit_Radius(nodeH Satellite_Orbit_Equivalent_Circular_Orbit_Radius=-1.0d0 if (present(errorCode)) errorCode=errorCodeNoEquivalentOrbit else - finder=rootFinder( & - & rootFunction =Equivalent_Circular_Orbit_Solver, & - & toleranceAbsolute =toleranceAbsolute , & - & toleranceRelative =toleranceRelative , & - & rangeExpandUpward =2.0d0 , & - & rangeExpandDownward =0.5d0 , & - & rangeExpandDownwardSignExpect=rangeExpandSignExpectNegative , & - & rangeExpandUpwardSignExpect =rangeExpandSignExpectPositive , & - & rangeExpandType =rangeExpandMultiplicative & - & ) - Satellite_Orbit_Equivalent_Circular_Orbit_Radius=finder%find(rootGuess=darkMatterHaloScale_%radiusVirial(nodeHost)) + finder =rootFinder( & + & rootFunction =Equivalent_Circular_Orbit_Solver, & + & toleranceAbsolute =toleranceAbsolute , & + & toleranceRelative =toleranceRelative , & + & rangeExpandUpward =2.0d0 , & + & rangeExpandDownward =0.5d0 , & + & rangeExpandDownwardSignExpect=rangeExpandSignExpectNegative , & + & rangeExpandUpwardSignExpect =rangeExpandSignExpectPositive , & + & rangeExpandType =rangeExpandMultiplicative & + & ) + Satellite_Orbit_Equivalent_Circular_Orbit_Radius=finder%find(rootGuess=radiusVirial__) if (present(errorCode)) errorCode=errorCodeSuccess end if + !![ + + !!] return end function Satellite_Orbit_Equivalent_Circular_Orbit_Radius - double precision function Equivalent_Circular_Orbit_Solver(radius) + double precision function Equivalent_Circular_Orbit_Solver(radius) result(radiusCircular) !!{ Root function used in finding equivalent circular orbits. !!} @@ -132,26 +142,27 @@ Root function used in finding equivalent circular orbits. type (enumerationStructureErrorCodeType) :: status ! Get potential. - potential=galacticStructure__%potential(activeNode,radius,status=status) + potential=Satellite_Orbit_Potential(radius,radiusVirial__,massVirial__,status) select case (status%ID) case (structureErrorCodeSuccess %ID) - Equivalent_Circular_Orbit_Solver=potential+0.5d0*darkMatterProfileDMO__%circularVelocity(activeNode,radius)**2-orbitalEnergyInternal + radiusCircular=+potential+0.5d0*massDistribution__%rotationCurve(radius)**2-orbitalEnergyInternal case (structureErrorCodeInfinite%ID) ! The gravitational potential is negative infinity at this radius (most likely zero radius). Since all we care about in ! this root-finding function is the sign of the function, return a large negative value. - Equivalent_Circular_Orbit_Solver=-potentialInfinite + radiusCircular=-potentialInfinite case default - Equivalent_Circular_Orbit_Solver=0.0d0 + radiusCircular=+0.0d0 call Error_Report('dark matter potential evaluation failed'//{introspection:location}) end select return end function Equivalent_Circular_Orbit_Solver - subroutine Satellite_Orbit_Extremum_Phase_Space_Coordinates(nodeHost,orbit,extremumType,radius,velocity,galacticStructure_) + subroutine Satellite_Orbit_Extremum_Phase_Space_Coordinates(nodeHost,orbit,extremumType,radius,velocity,darkMatterHaloScale_) !!{ Solves for the pericentric radius and velocity of {\normalfont \ttfamily orbit} in {\normalfont \ttfamily nodeHost}. !!} use :: Galactic_Structure_Options , only : structureErrorCodeInfinite, structureErrorCodeSuccess ,enumerationStructureErrorCodeType, radiusLarge + use :: Dark_Matter_Halo_Scales , only : darkMatterHaloScaleClass use :: Error , only : Error_Report use :: Galacticus_Nodes , only : nodeComponentBasic , treeNode use :: Kepler_Orbits , only : keplerOrbit @@ -163,7 +174,7 @@ subroutine Satellite_Orbit_Extremum_Phase_Space_Coordinates(nodeHost,orbit,extre type (keplerOrbit ), intent(inout) :: orbit integer , intent(in ) :: extremumType double precision , intent( out) :: radius , velocity - class (galacticStructureClass ), intent(inout), target :: galacticStructure_ + class (darkMatterHaloScaleClass ), intent(inout) :: darkMatterHaloScale_ class (nodeComponentBasic ), pointer :: basicHost double precision , parameter :: toleranceAbsolute=0.0d0 , toleranceRelative=1.0d-6 type (rootFinder ), save :: finder @@ -173,8 +184,10 @@ subroutine Satellite_Orbit_Extremum_Phase_Space_Coordinates(nodeHost,orbit,extre type (enumerationStructureErrorCodeType) :: status double precision :: potential , energyKinetic + ! Get the mass distribution. + massDistribution__ => nodeHost%massDistribution() ! Convert the orbit to the potential of the current halo in which the satellite finds itself. - orbitCurrent=Satellite_Orbit_Convert_To_Current_Potential(orbit,nodeHost,galacticStructure_) + orbitCurrent=Satellite_Orbit_Convert_To_Current_Potential(orbit,nodeHost,darkMatterHaloScale_) ! Extract the orbital energy and angular momentum. orbitalEnergyInternal =orbitCurrent%energy () orbitalAngularMomentumInternal=orbitCurrent%angularMomentum() @@ -197,7 +210,8 @@ subroutine Satellite_Orbit_Extremum_Phase_Space_Coordinates(nodeHost,orbit,extre & ) then ! Set a pointer to the host node. activeNode => nodeHost - galacticStructure__ => galacticStructure_ + radiusVirial__ = darkMatterHaloScale_%radiusVirial(nodeHost) + massVirial__ = basicHost %mass ( ) ! Record previous orbital properties. lastUniqueID =nodeHost %uniqueID() timePrevious =basicHost%time () @@ -252,7 +266,7 @@ subroutine Satellite_Orbit_Extremum_Phase_Space_Coordinates(nodeHost,orbit,extre velocity=orbitalAngularMomentumInternal/radius else ! Orbit is radial - use energy to find velocity. - potential=galacticStructure_%potential(activeNode,radius,status=status) + potential=Satellite_Orbit_Potential(radius,darkMatterHaloScale_%radiusVirial(activeNode),basicHost%mass(),status) select case (status%ID) case (structureErrorCodeSuccess %ID) energyKinetic=max(orbitalEnergyInternal-potential,0.0d0) @@ -288,6 +302,9 @@ subroutine Satellite_Orbit_Extremum_Phase_Space_Coordinates(nodeHost,orbit,extre velocity= apocenterVelocity end select end if + !![ + + !!] return end subroutine Satellite_Orbit_Extremum_Phase_Space_Coordinates @@ -299,12 +316,12 @@ Root function used in finding orbital extremum radius. double precision, intent(in ) :: radius double precision :: potential - potential=galacticStructure__%potential(activeNode,radius) + potential=Satellite_Orbit_Potential(radius,radiusVirial__,massvirial__) Extremum_Solver=potential+0.5d0*(orbitalAngularMomentumInternal/radius)**2-orbitalEnergyInternal return end function Extremum_Solver - function Satellite_Orbit_Convert_To_Current_Potential(orbit,currentHost,galacticStructure_) result(orbitCurrent) + function Satellite_Orbit_Convert_To_Current_Potential(orbit,currentHost,darkMatterHaloScale_) result(orbitCurrent) !!{ Takes a virial orbit and adjusts the energy to account for the change in the definition of potential between the original halo in which the orbit was defined and the current halo. Since the potential at the virial radius of halos is always @@ -315,26 +332,51 @@ function Satellite_Orbit_Convert_To_Current_Potential(orbit,currentHost,galactic where subscript $0$ refers to the original halo in which the orbit was defined and $\Phi(r)$ is the potential of the current halo. !!} + use :: Dark_Matter_Halo_Scales , only : darkMatterHaloScaleClass use :: Kepler_Orbits , only : keplerOrbit use :: Numerical_Constants_Astronomical, only : gravitationalConstantGalacticus implicit none - type (keplerOrbit ) :: orbitCurrent - type (keplerOrbit ), intent(inout) :: orbit - type (treeNode ), intent(inout) :: currentHost - class (galacticStructureClass), intent(inout), target :: galacticStructure_ - double precision :: potentialHost , radiusVirialOriginal, & - & velocityVirialOriginal + type (keplerOrbit ) :: orbitCurrent + type (keplerOrbit ), intent(inout) :: orbit + type (treeNode ), intent(inout) :: currentHost + class (darkMatterHaloScaleClass), intent(inout) :: darkMatterHaloScale_ + double precision :: potentialHost , radiusVirialOriginal, & + & velocityVirialOriginal, radiusVirial ! Compute the properties of the initial orbit, and the current potential. - radiusVirialOriginal =gravitationalConstantGalacticus*orbit%massHost()/orbit%velocityScale()**2 - velocityVirialOriginal= orbit%velocityScale() - potentialHost =galacticStructure_%potential(currentHost,radiusVirialOriginal) + radiusVirialOriginal =gravitationalConstantGalacticus*orbit%massHost()/orbit%velocityScale()**2 + velocityVirialOriginal = orbit%velocityScale() + radiusVirial =darkMatterHaloScale_%radiusVirial (currentHost) + potentialHost =Satellite_Orbit_Potential(radiusVirialOriginal,radiusVirial,orbit%massHost()) ! Create a new orbit with an adjusted energy. orbitCurrent=orbit call orbitCurrent%energySet(orbit%energy()+velocityVirialOriginal**2+potentialHost) return end function Satellite_Orbit_Convert_To_Current_Potential + double precision function Satellite_Orbit_Potential(radius,radiusVirial,massVirial,status) result(potential) + !!{ + Evaluate the gravitational potential under the convention that the potential at the virial radius is always + $\Phi(r_\mathrm{vir}) = - V_\mathrm{vir}^2$, as is assumed for {\normalfont \ttfamily keplerOrbit} objects. + !!} + use :: Galactic_Structure_Options , only : enumerationStructureErrorCodeType + use :: Coordinates , only : coordinateCartesian , assignment(=) + use :: Numerical_Constants_Astronomical, only : gravitationalConstantGalacticus + implicit none + double precision , intent(in ) :: radius , radiusVirial , & + & massVirial + type (enumerationStructureErrorCodeType), intent( out), optional :: status + type (coordinateCartesian ) :: coordinates, coordinatesVirial + + coordinates =[radius ,0.0d0,0.0d0] + coordinatesVirial=[radiusVirial,0.0d0,0.0d0] + potential =+massDistribution__%potentialDifference(coordinates,coordinatesVirial,status) & + & -gravitationalConstantGalacticus & + & * massVirial & + & /radiusVirial + return + end function Satellite_Orbit_Potential + subroutine Satellite_Orbit_Reset(node) !!{ Reset the satellite orbit calculations. diff --git a/source/satellites.orphans.distributions.trace_dark_matter.F90 b/source/satellites.orphans.distributions.trace_dark_matter.F90 index 028c6e3576..d501a587fc 100644 --- a/source/satellites.orphans.distributions.trace_dark_matter.F90 +++ b/source/satellites.orphans.distributions.trace_dark_matter.F90 @@ -23,7 +23,6 @@ !!} use :: Dark_Matter_Halo_Scales, only : darkMatterHaloScaleClass - use :: Galactic_Structure , only : galacticStructureClass !![ @@ -35,7 +34,6 @@ An orphan satellite distribution which assumes an isotropic, random distribution with orphans tracing the radial distribution of dark matter. !!} private - class(galacticStructureClass ), pointer :: galacticStructure_ => null() class(darkMatterHaloScaleClass), pointer :: darkMatterHaloScale_ => null() contains final :: traceDarkMatterDestructor @@ -64,32 +62,28 @@ function traceDarkMatterConstructorParameters(parameters) result(self) type (satelliteOrphanDistributionTraceDarkMatter) :: self type (inputParameters ), intent(inout) :: parameters class(darkMatterHaloScaleClass ), pointer :: darkMatterHaloScale_ - class(galacticStructureClass ), pointer :: galacticStructure_ ! Check and read parameters. !![ - !!] - self=satelliteOrphanDistributionTraceDarkMatter(darkMatterHaloScale_,galacticStructure_) + self=satelliteOrphanDistributionTraceDarkMatter(darkMatterHaloScale_) !![ - !!] return end function traceDarkMatterConstructorParameters - function traceDarkMatterConstructorInternal(darkMatterHaloScale_,galacticStructure_) result(self) + function traceDarkMatterConstructorInternal(darkMatterHaloScale_) result(self) !!{ Internal constructor for the {\normalfont \ttfamily traceDarkMatter} orphan satellite distribution class. !!} implicit none type (satelliteOrphanDistributionTraceDarkMatter) :: self class(darkMatterHaloScaleClass ), intent(in ), target :: darkMatterHaloScale_ - class(galacticStructureClass ), intent(in ), target :: galacticStructure_ !![ - + !!] call self%initialize() @@ -105,7 +99,6 @@ subroutine traceDarkMatterDestructor(self) !![ - !!] return end subroutine traceDarkMatterDestructor @@ -128,21 +121,23 @@ double precision function traceDarkMatterInverseCMFRadial(self,node,fraction) !!{ Return the radial coordinate within which the given {\normalfont \ttfamily fraction} of orphan satellites are found. !!} - use :: Galactic_Structure_Options, only : componentTypeAll, massTypeDark + use :: Galactic_Structure_Options, only : componentTypeAll , massTypeDark + use :: Mass_Distributions , only : massDistributionClass implicit none class (satelliteOrphanDistributionTraceDarkMatter), intent(inout) :: self type (treeNode ), intent(inout) :: node double precision , intent(in ) :: fraction type (treeNode ), pointer :: nodeHost - !$GLC attributes unused :: self - - nodeHost => node%parent - traceDarkMatterInverseCMFRadial = self%galacticStructure_%radiusEnclosingMass( & - & nodeHost , & - & massFractional=fraction , & - & componentType =componentTypeAll, & - & massType =massTypeDark & - & ) + class (massDistributionClass ), pointer :: massDistribution_ + double precision :: massEnclosed + + nodeHost => node %parent + massDistribution_ => nodeHost %massDistribution (componentType=componentTypeAll ,massType=massTypeDark) + massEnclosed = massDistribution_%massEnclosedBySphere(radius =+ self%extent (nodeHost) ) + traceDarkMatterInverseCMFRadial = massDistribution_%radiusEnclosingMass (mass =+fraction* massEnclosed ) + !![ + + !!] return end function traceDarkMatterInverseCMFRadial diff --git a/source/satellites.tidal_fields.spherical_symmetry.F90 b/source/satellites.tidal_fields.spherical_symmetry.F90 index 0a176ed281..b70df73ba8 100644 --- a/source/satellites.tidal_fields.spherical_symmetry.F90 +++ b/source/satellites.tidal_fields.spherical_symmetry.F90 @@ -21,7 +21,7 @@ Contains a module which implements a model of the tidal field acting on a satellite assuming spherical symmetry in the host. !!} - use :: Galactic_Structure, only : galacticStructureClass + use :: Dark_Matter_Halo_Scales, only : darkMatterHaloScaleClass !![ @@ -43,7 +43,7 @@ Implementation of a satellite tidal friction class which assumes spherical symmetry. !!} private - class (galacticStructureClass ), pointer :: galacticStructure_ => null() + class (darkMatterHaloScaleClass), pointer :: darkMatterHaloScale_ => null() double precision :: factorBoost contains final :: sphericalSymmetryDestructor @@ -68,7 +68,7 @@ function sphericalSymmetryConstructorParameters(parameters) result(self) implicit none type (satelliteTidalFieldSphericalSymmetry) :: self type (inputParameters ), intent(inout) :: parameters - class (galacticStructureClass ), pointer :: galacticStructure_ + class (darkMatterHaloScaleClass ), pointer :: darkMatterHaloScale_ double precision :: factorBoost !![ @@ -78,26 +78,26 @@ function sphericalSymmetryConstructorParameters(parameters) result(self) The factor by which to boost satellite tidal fields in the {\normalfont \ttfamily sphericalSymmetry} tidal field class. parameters - + !!] - self=satelliteTidalFieldSphericalSymmetry(factorBoost,galacticStructure_) + self=satelliteTidalFieldSphericalSymmetry(factorBoost,darkMatterHaloScale_) !![ - + !!] return end function sphericalSymmetryConstructorParameters - function sphericalSymmetryConstructorInternal(factorBoost,galacticStructure_) result(self) + function sphericalSymmetryConstructorInternal(factorBoost,darkMatterHaloScale_) result(self) !!{ Internal constructor for the {\normalfont \ttfamily sphericalSymmetry} satellite tidal field class. !!} implicit none type (satelliteTidalFieldSphericalSymmetry) :: self - class (galacticStructureClass ), intent(in ), target :: galacticStructure_ + class (darkMatterHaloScaleClass ), intent(in ), target :: darkMatterHaloScale_ double precision , intent(in ) :: factorBoost !![ - + !!] return @@ -111,7 +111,7 @@ subroutine sphericalSymmetryDestructor(self) type(satelliteTidalFieldSphericalSymmetry), intent(inout) :: self !![ - + !!] return end subroutine sphericalSymmetryDestructor @@ -120,9 +120,10 @@ double precision function sphericalSymmetryTidalTensorRadial(self,node) !!{ Return the radial part of the tidal tensor for satellite halos assuming spherical symmetry of the host. !!} - use :: Galactic_Structure_Options , only : coordinateSystemCylindrical + use :: Coordinates , only : coordinateCylindrical , assignment(=) use :: Galacticus_Nodes , only : nodeComponentSatellite , treeNode use :: Kepler_Orbits , only : keplerOrbit + use :: Mass_Distributions , only : massDistributionClass use :: Numerical_Constants_Math , only : Pi use :: Numerical_Constants_Astronomical, only : gravitationalConstantGalacticus use :: Satellite_Orbits , only : Satellite_Orbit_Extremum_Phase_Space_Coordinates, extremumPericenter @@ -131,9 +132,11 @@ double precision function sphericalSymmetryTidalTensorRadial(self,node) type (treeNode ), intent(inout) :: node type (treeNode ), pointer :: nodeHost class (nodeComponentSatellite ), pointer :: satellite + class (massDistributionClass ), pointer :: massDistribution_ type (keplerOrbit ) :: orbit - double precision :: densityHost , enclosedMassHost, & - & radiusOrbital, velocityOrbital + double precision :: densityHost , enclosedMassHost, & + & radiusOrbital , velocityOrbital + type (coordinateCylindrical ) :: coordinatesOrbital ! For isolated halos, always return zero tidal field. if (node%isSatellite()) then @@ -144,17 +147,15 @@ double precision function sphericalSymmetryTidalTensorRadial(self,node) ! Get the orbit for this node. orbit = satellite%virialOrbit() ! Get the orbital radius and velocity at pericenter. - call Satellite_Orbit_Extremum_Phase_Space_Coordinates(nodeHost,orbit,extremumPericenter,radiusOrbital,velocityOrbital,self%galacticStructure_) + call Satellite_Orbit_Extremum_Phase_Space_Coordinates(nodeHost,orbit,extremumPericenter,radiusOrbital,velocityOrbital,self%darkMatterHaloScale_) ! Find the mass and density of the host halo at pericenter. - densityHost =self%galacticStructure_%density ( & - & nodeHost , & - & [radiusOrbital,0.0d0,0.0d0] , & - & coordinateSystem=coordinateSystemCylindrical & - & ) - enclosedMassHost=self%galacticStructure_%massEnclosed( & - & nodeHost , & - & radiusOrbital & - & ) + coordinatesOrbital = [radiusOrbital,0.0d0,0.0d0] + massDistribution_ => nodeHost %massDistribution ( ) + densityHost = massDistribution_%density (coordinatesOrbital) + enclosedMassHost = massDistribution_%massEnclosedBySphere( radiusOrbital) + !![ + + !!] ! Compute the tidal field. sphericalSymmetryTidalTensorRadial=+ gravitationalConstantGalacticus*enclosedMassHost/ radiusOrbital **3 & & -4.0d0*Pi*gravitationalConstantGalacticus*densityHost & diff --git a/source/satellites.tidal_heating.rate.Gnedin1999.F90 b/source/satellites.tidal_heating.rate.Gnedin1999.F90 index b1bafd8743..57891fca53 100644 --- a/source/satellites.tidal_heating.rate.Gnedin1999.F90 +++ b/source/satellites.tidal_heating.rate.Gnedin1999.F90 @@ -25,7 +25,6 @@ use :: Cosmology_Parameters , only : cosmologyParametersClass use :: Dark_Matter_Halo_Scales, only : darkMatterHaloScaleClass - use :: Galactic_Structure , only : galacticStructureClass !![ @@ -52,7 +51,6 @@ private class (cosmologyParametersClass), pointer :: cosmologyParameters_ => null() class (darkMatterHaloScaleClass), pointer :: darkMatterHaloScale_ => null() - class (galacticStructureClass ), pointer :: galacticStructure_ => null() double precision :: epsilon , gamma contains final :: gnedin1999Destructor @@ -79,7 +77,6 @@ function gnedin1999ConstructorParameters(parameters) result(self) type (inputParameters ), intent(inout) :: parameters class (cosmologyParametersClass ), pointer :: cosmologyParameters_ class (darkMatterHaloScaleClass ), pointer :: darkMatterHaloScale_ - class (galacticStructureClass ), pointer :: galacticStructure_ double precision :: epsilon , gamma !![ @@ -97,19 +94,17 @@ function gnedin1999ConstructorParameters(parameters) result(self) - !!] - self=satelliteTidalHeatingRateGnedin1999(epsilon,gamma,cosmologyParameters_,darkMatterHaloScale_,galacticStructure_) + self=satelliteTidalHeatingRateGnedin1999(epsilon,gamma,cosmologyParameters_,darkMatterHaloScale_) !![ - !!] return end function gnedin1999ConstructorParameters - function gnedin1999ConstructorInternal(epsilon,gamma,cosmologyParameters_,darkMatterHaloScale_,galacticStructure_) result(self) + function gnedin1999ConstructorInternal(epsilon,gamma,cosmologyParameters_,darkMatterHaloScale_) result(self) !!{ Internal constructor for the {\normalfont \ttfamily gnedin1999} satellite tidal heating rate class. !!} @@ -117,10 +112,9 @@ function gnedin1999ConstructorInternal(epsilon,gamma,cosmologyParameters_,darkMa type (satelliteTidalHeatingRateGnedin1999) :: self class (cosmologyParametersClass ), intent(in ), target :: cosmologyParameters_ class (darkMatterHaloScaleClass ), intent(in ), target :: darkMatterHaloScale_ - class (galacticStructureClass ), intent(in ), target :: galacticStructure_ double precision , intent(in) :: epsilon , gamma !![ - + !!] return @@ -136,7 +130,6 @@ subroutine gnedin1999Destructor(self) !![ - !!] return end subroutine gnedin1999Destructor @@ -151,15 +144,19 @@ double precision function gnedin1999HeatingRate(self,node) use :: Numerical_Constants_Math , only : Pi use :: Numerical_Constants_Astronomical, only : gravitationalConstantGalacticus use :: Numerical_Constants_Prefixes , only : kilo + use :: Mass_Distributions , only : massDistributionClass use :: Tensors , only : assignment(=) , max , operator(*) , tensorRank2Dimension3Symmetric use :: Vectors , only : Vector_Magnitude + use :: Coordinates , only : coordinateCartesian , assignment(=) implicit none class (satelliteTidalHeatingRateGnedin1999), intent(inout) :: self type (treeNode ), intent(inout) :: node class (nodeComponentSatellite ), pointer :: satellite type (treeNode ), pointer :: nodeHost class (nodeComponentBasic ), pointer :: basic - double precision , dimension(3) :: position , velocity + class (massDistributionClass ), pointer :: massDistribution_ , massDistributionHost_ + double precision , dimension(3) :: velocity + type (coordinateCartesian ) :: position double precision :: massSatellite , velocityCircularSatellite, & & radius , speed , & & timescaleShock , heatingRateNormalized , & @@ -174,7 +171,7 @@ double precision function gnedin1999HeatingRate(self,node) position = satellite%position ( ) velocity = satellite%velocity ( ) tidalTensorPathIntegrated = satellite%tidalTensorPathIntegrated( ) - radius = Vector_Magnitude (position) + radius = position %rSpherical ( ) speed = Vector_Magnitude (velocity) ! Find the universal dark matter fraction. fractionDarkMatter = +( & @@ -183,32 +180,25 @@ double precision function gnedin1999HeatingRate(self,node) & ) & & / self%cosmologyParameters_%OmegaMatter() ! Find the gravitational tidal tensor. - tidalTensor = self%galacticStructure_%tidalTensor(nodeHost,position) + massDistributionHost_ => nodeHost%massDistribution() + tidalTensor = massDistributionHost_%tidalTensor(position) + !![ + + !!] ! Find the orbital frequency at the half mass radius of the satellite. + massDistribution_ => node%massDistribution(componentTypeAll,massTypeDark) basic => node%basic() - massHalfSatellite = +0.50d0 & - & *min( & - & + fractionDarkMatter & - & * massSatellite , & - & +self%galacticStructure_%massEnclosed ( & - & node , & - & radius =self%darkMatterHaloScale_%radiusVirial(node), & - & componentType=componentTypeAll , & - & massType =massTypeDark & - & ) & + massHalfSatellite = +0.50d0 & + & *min( & + & + fractionDarkMatter & + & * massSatellite , & + & +massDistribution_%massEnclosedBySphere(radius=self%darkMatterHaloScale_%radiusVirial (node)) & & ) - radiusHalfMassSatellite = self%galacticStructure_%radiusEnclosingMass( & - & node , & - & mass =massHalfSatellite, & - & componentType =componentTypeAll , & - & massType =massTypeDark & - & ) - velocityCircularSatellite= self%galacticStructure_%velocityRotation ( & - & node , & - & radiusHalfMassSatellite , & - & componentType =componentTypeAll , & - & massType =massTypeDark & - & ) + radiusHalfMassSatellite = massDistribution_%radiusEnclosingMass (mass = massHalfSatellite ) + velocityCircularSatellite= massDistribution_%rotationCurve (radius= radiusHalfMassSatellite ) + !![ + + !!] if (radiusHalfMassSatellite > 0.0d0) then ! Compute the orbital frequency. orbitalFrequencySatellite = +velocityCircularSatellite & diff --git a/source/satellites.tidal_stripping.radius.King1962.F90 b/source/satellites.tidal_stripping.radius.King1962.F90 index e55beebb4e..1c312be46a 100644 --- a/source/satellites.tidal_stripping.radius.King1962.F90 +++ b/source/satellites.tidal_stripping.radius.King1962.F90 @@ -25,9 +25,7 @@ use :: Cosmology_Parameters , only : cosmologyParametersClass use :: Dark_Matter_Halo_Scales, only : darkMatterHaloScaleClass - use :: Galactic_Structure , only : galacticStructureClass use :: Kind_Numbers , only : kind_int8 - use :: Root_Finder , only : rootFinder !![ @@ -48,24 +46,14 @@ Implementation of a satellite tidal radius class which follows the method of \cite{king_structure_1962}. !!} private - class (cosmologyParametersClass), pointer :: cosmologyParameters_ => null() - class (darkMatterHaloScaleClass), pointer :: darkMatterHaloScale_ => null() - class (galacticStructureClass ), pointer :: galacticStructure_ => null() - double precision :: radiusTidalPrevious , expandMultiplier , & - & fractionDarkMatter , efficiencyCentrifugal - integer (kind_int8 ) :: lastUniqueID - type (rootFinder ) :: finder + class (cosmologyParametersClass), pointer :: cosmologyParameters_ => null() + class (darkMatterHaloScaleClass), pointer :: darkMatterHaloScale_ => null() + double precision :: efficiencyCentrifugal , expandMultiplier, & + & fractionDarkMatter logical :: applyPreInfall contains - !![ - - - - !!] - final :: king1962Destructor - procedure :: autoHook => king1962AutoHook - procedure :: calculationReset => king1962CalculationReset - procedure :: radius => king1962Radius + final :: king1962Destructor + procedure :: radius => king1962Radius end type satelliteTidalStrippingRadiusKing1962 interface satelliteTidalStrippingRadiusKing1962 @@ -76,12 +64,6 @@ module procedure king1962ConstructorInternal end interface satelliteTidalStrippingRadiusKing1962 - ! Module-scope objects used for root finding. - class (satelliteTidalStrippingRadiusKing1962), pointer :: self_ - type (treeNode ), pointer :: node_ - double precision :: tidalPull - !$omp threadprivate(node_,tidalPull,self_) - contains function king1962ConstructorParameters(parameters) result(self) @@ -94,7 +76,6 @@ function king1962ConstructorParameters(parameters) result(self) type (inputParameters ), intent(inout) :: parameters class (cosmologyParametersClass ), pointer :: cosmologyParameters_ class (darkMatterHaloScaleClass ), pointer :: darkMatterHaloScale_ - class (galacticStructureClass ), pointer :: galacticStructure_ double precision :: efficiencyCentrifugal logical :: applyPreInfall @@ -113,19 +94,17 @@ function king1962ConstructorParameters(parameters) result(self) - !!] - self=satelliteTidalStrippingRadiusKing1962(efficiencyCentrifugal,applyPreInfall,cosmologyParameters_,darkMatterHaloScale_,galacticStructure_) + self=satelliteTidalStrippingRadiusKing1962(efficiencyCentrifugal,applyPreInfall,cosmologyParameters_,darkMatterHaloScale_) !![ - !!] return end function king1962ConstructorParameters - function king1962ConstructorInternal(efficiencyCentrifugal,applyPreInfall,cosmologyParameters_,darkMatterHaloScale_,galacticStructure_) result(self) + function king1962ConstructorInternal(efficiencyCentrifugal,applyPreInfall,cosmologyParameters_,darkMatterHaloScale_) result(self) !!{ Internal constructor for the {\normalfont \ttfamily king1962} satellite tidal stripping class. !!} @@ -133,12 +112,11 @@ function king1962ConstructorInternal(efficiencyCentrifugal,applyPreInfall,cosmol type (satelliteTidalStrippingRadiusKing1962) :: self class (cosmologyParametersClass ), intent(in ), target :: cosmologyParameters_ class (darkMatterHaloScaleClass ), intent(in ), target :: darkMatterHaloScale_ - class (galacticStructureClass ), intent(in ), target :: galacticStructure_ double precision , intent(in ) :: efficiencyCentrifugal logical , intent(in ) :: applyPreInfall double precision , parameter :: toleranceAbsolute =0.0d0, toleranceRelative=1.0d-3 !![ - + !!] self%fractionDarkMatter=+( & @@ -146,41 +124,20 @@ function king1962ConstructorInternal(efficiencyCentrifugal,applyPreInfall,cosmol & -self%cosmologyParameters_%OmegaBaryon() & & ) & & / self%cosmologyParameters_%OmegaMatter() - self%expandMultiplier=2.0d0 - self%finder =rootFinder( & - & rootFunction =king1962TidalRadiusSolver, & - & toleranceAbsolute=toleranceAbsolute , & - & toleranceRelative=toleranceRelative & - & ) return end function king1962ConstructorInternal - subroutine king1962AutoHook(self) - !!{ - Attach to the calculation reset event. - !!} - use :: Events_Hooks, only : calculationResetEvent, openMPThreadBindingAllLevels - implicit none - class(satelliteTidalStrippingRadiusKing1962), intent(inout) :: self - - call calculationResetEvent%attach(self,king1962CalculationReset,openMPThreadBindingAllLevels,label='satelliteTidalStrippingRadiusKing1962') - return - end subroutine king1962AutoHook - subroutine king1962Destructor(self) !!{ Destructor for the {\normalfont \ttfamily king1962} satellite tidal stripping class. !!} - use :: Events_Hooks, only : calculationResetEvent implicit none type(satelliteTidalStrippingRadiusKing1962), intent(inout) :: self !![ - !!] - if (calculationResetEvent%isAttached(self,king1962CalculationReset)) call calculationResetEvent%detach(self,king1962CalculationReset) return end subroutine king1962Destructor @@ -211,14 +168,14 @@ double precision function king1962Radius(self,node) largest positive eigenvalue, not the largest absolute eigenvalue as we're interested in stretching tidal fields, not compressive ones.) !!} - use :: Error , only : Error_Report , errorStatusSuccess - use :: Galactic_Structure_Options , only : coordinateSystemCartesian, massTypeDark + use :: Coordinates , only : assignment(=) , coordinateCartesian + use :: Galactic_Structure_Options , only : massTypeDark use :: Galacticus_Nodes , only : nodeComponentSatellite , nodeComponentBasic , treeNode use :: Linear_Algebra , only : assignment(=) , matrix , vector + use :: Mass_Distributions , only : massDistributionClass use :: Numerical_Constants_Astronomical, only : gigaYear , gravitationalConstantGalacticus, megaParsec use :: Numerical_Constants_Math , only : Pi use :: Numerical_Constants_Prefixes , only : kilo - use :: Root_Finder , only : rangeExpandMultiplicative, rangeExpandSignExpectNegative , rangeExpandSignExpectPositive use :: Tensors , only : assignment(=) , tensorRank2Dimension3Symmetric use :: Vectors , only : Vector_Magnitude , Vector_Product implicit none @@ -227,18 +184,21 @@ double precision function king1962Radius(self,node) type (treeNode ), pointer :: nodeHost class (nodeComponentBasic ), pointer :: basic , basicHost class (nodeComponentSatellite ), pointer :: satellite - double precision , dimension(3 ) :: position , velocity , & + class (massDistributionClass ), pointer :: massDistribution_ , massDistributionDark + double precision , dimension(3 ) :: position , velocity , & & tidalTensorEigenValueComponents double precision , dimension(3,3) :: tidalTensorComponents double precision , parameter :: radiusZero =0.0d+0 double precision , parameter :: radiusTidalTinyFraction =1.0d-6 - integer :: status - double precision :: massSatellite , frequencyAngular , & - & radius , tidalFieldRadial , & - & radiusLimitDownward + double precision :: massSatellite , frequencyAngular , & + & radius , tidalFieldRadial , & + & radiusGuess , densityTidal , & + & tidalPull , tidalTensorEigenValueMaximum, & + & radiusDownwardLimit type (tensorRank2Dimension3Symmetric ) :: tidalTensor type (matrix ) :: tidalTensorMatrix , tidalTensorEigenVectors type (vector ) :: tidalTensorEigenValues + type (coordinateCartesian ) :: coordinates ! Find the host node. if (node%isOnMainBranch().or.(.not.self%applyPreInfall.and..not.node%isSatellite())) then @@ -285,115 +245,90 @@ double precision function king1962Radius(self,node) ! ! -2GM(r)r⁻³ - 4πGρ(r) if (associated(nodeHost)) then - tidalTensor = self%galacticStructure_%tidalTensor(nodeHost,position) - tidalTensorComponents = tidalTensor - tidalTensorMatrix = tidalTensorComponents - call tidalTensorMatrix%eigenSystem(tidalTensorEigenVectors,tidalTensorEigenValues) - tidalTensorEigenValueComponents = tidalTensorEigenValues - tidalFieldRadial =-maxval(tidalTensorEigenValueComponents) & - & *( & - & +kilo & - & *gigaYear & - & /megaParsec & - & )**2 + massDistribution_ => nodeHost%massDistribution() + if (massDistribution_%isSphericallySymmetric()) then + ! For spherically-symmetric mass distributions we can avoid the expense of solving for the eigenvalues. We simply + ! evaluate the tidal tensor at [r,0,0] (since the distribution is spherically-symmetric we can evaluate at any + ! position on the sphere), and take the 0,0 element of the tensor which will be the largest (and only) positive + ! eigenvector. + coordinates =[radius,0.0d0,0.0d0] + tidalTensor =massDistribution_%tidalTensor(coordinates) + tidalTensorEigenValueMaximum =tidalTensor%element(0,0) + else + coordinates =position + tidalTensor =massDistribution_%tidalTensor(coordinates) + tidalTensorComponents =tidalTensor + tidalTensorMatrix =tidalTensorComponents + call tidalTensorMatrix%eigenSystem(tidalTensorEigenVectors,tidalTensorEigenValues) + tidalTensorEigenValueComponents=tidalTensorEigenValues + tidalTensorEigenValueMaximum =maxval(tidalTensorEigenValueComponents) + end if + tidalFieldRadial=-tidalTensorEigenValueMaximum & + & *( & + & +kilo & + & *gigaYear & + & /megaParsec & + & )**2 + !![ + + !!] else tidalFieldRadial =+0.0d0 end if ! If the tidal force is stretching (not compressing), compute the tidal radius. - tidalPull=self%efficiencyCentrifugal*frequencyAngular**2-tidalFieldRadial - if ( & - & tidalPull > 0.0d0 & - & .and. & - & massSatellite > 0.0d0 & - & .and. & - & self%galacticStructure_%massEnclosed(node,radiusZero) >= 0.0d0 & + massDistribution_ => node%massDistribution() + tidalPull = self%efficiencyCentrifugal*frequencyAngular**2-tidalFieldRadial + if ( & + & tidalPull > 0.0d0 & + & .and. & + & massSatellite > 0.0d0 & + & .and. & + & massDistribution_%massEnclosedBySphere(radiusZero) >= 0.0d0 & & ) then - ! Check if node differs from previous one for which we performed calculations. - if (node%uniqueID() /= self%lastUniqueID) call self%calculationReset(node,node%uniqueID()) - ! Initial estimate of the tidal radius. - if (self%radiusTidalPrevious <= 0.0d0) then - self%radiusTidalPrevious=+sqrt( & - & +gravitationalConstantGalacticus & - & *massSatellite & - & /self%darkMatterHaloScale_%radiusVirial(node) & - & /tidalPull & - & *(kilo*gigaYear/megaParsec)**2 & - & ) - self%expandMultiplier =+2.0d0 - end if - ! Find the tidal radius in the dark matter profile. - radiusLimitDownward=+radiusTidalTinyFraction & - & *self%radiusTidalPrevious - call self%finder%rangeExpand( & - & rangeExpandUpward =+1.0d0*self%expandMultiplier , & - & rangeExpandDownward =+1.0d0/self%expandMultiplier , & - & rangeExpandDownwardSignExpect= rangeExpandSignExpectNegative, & - & rangeExpandUpwardSignExpect = rangeExpandSignExpectPositive, & - & rangeDownwardLimit = radiusLimitDownward , & - & rangeExpandType = rangeExpandMultiplicative & - & ) - self_ => self - node_ => node - ! Find the tidal radius, using the previous result as an initial guess. - self%radiusTidalPrevious=self%finder%find(rootGuess=self%radiusTidalPrevious,status=status) - if (status == errorStatusSuccess) then - self%expandMultiplier =1.2d0 - else if (king1962TidalRadiusSolver(radiusLimitDownward) > 0.0d0) then - ! Complete stripping. - self%radiusTidalPrevious=0.0d0 + ! Find the tidal density. + densityTidal=+tidalPull & + & /(kilo*gigaYear/megaParsec) **2 & + & /gravitationalConstantGalacticus & + & *3.0d0 & + & /4.0d0 & + & /Pi + ! Solve for the radius enclosing this density. + radiusGuess = +sqrt( & + & +gravitationalConstantGalacticus & + & *massSatellite & + & /self%darkMatterHaloScale_%radiusVirial(node) & + & /tidalPull & + & *(kilo*gigaYear/megaParsec)**2 & + & ) + radiusDownwardLimit=radiusTidalTinyFraction*radiusGuess + if ( & + & + 3.0 & + & /4.0d0 & + & /Pi & + & *massDistribution_%massEnclosedBySphere(radiusDownwardLimit) & + & / radiusDownwardLimit **3 & + & > & + & densityTidal & + & ) then + king1962Radius = massDistribution_%radiusEnclosingDensity(densityTidal,radiusGuess) else - ! Find the tidal radius, using the previous result as an initial guess. - call Error_Report('unable to find tidal radius'//{introspection:location}) + king1962Radius = 0.0d0 end if - king1962Radius=self%radiusTidalPrevious else ! If the bound mass of the satellite exceeds the original mass (which can happen during failed ODE steps), simply return ! the virial radius. Otherwise, solve for the radius enclosing the current bound mass. - if (massSatellite > self%galacticStructure_%massEnclosed(node,radius=self%darkMatterHaloScale_%radiusVirial(node),massType=massTypeDark)) then - king1962Radius=self%darkMatterHaloScale_%radiusVirial (node ) + massDistributionDark => node%massDistribution(massType=massTypeDark) + if (massSatellite > massDistributionDark%massEnclosedBySphere(self%darkMatterHaloScale_%radiusVirial(node))) then + king1962Radius=self %darkMatterHaloScale_%radiusVirial (node ) else - king1962Radius=self%galacticStructure_ %radiusEnclosingMass(node,massSatellite*self%fractionDarkMatter,massType=massTypeDark) + king1962Radius=massDistributionDark %radiusEnclosingMass(massSatellite*self%fractionDarkMatter) end if + !![ + + !!] end if + !![ + + !!] return end function king1962Radius - - double precision function king1962TidalRadiusSolver(radius) - !!{ - Root function used to find the tidal radius within a subhalo. - !!} - use :: Numerical_Constants_Astronomical, only : gigaYear, gravitationalConstantGalacticus, megaParsec - use :: Numerical_Constants_Prefixes , only : kilo - implicit none - double precision, intent(in ) :: radius - double precision :: enclosedMass - - ! Get the satellite component. - enclosedMass =+self_%galacticStructure_%massEnclosed(node_,radius) - king1962TidalRadiusSolver=+tidalPull & - & -gravitationalConstantGalacticus & - & *enclosedMass & - & /radius **3 & - & *( & - & +kilo & - & *gigaYear & - & /megaParsec & - & ) **2 - return - end function king1962TidalRadiusSolver - - subroutine king1962CalculationReset(self,node,uniqueID) - !!{ - Reset the stored tidal radii. - !!} - use :: Kind_Numbers, only : kind_int8 - implicit none - class (satelliteTidalStrippingRadiusKing1962), intent(inout) :: self - type (treeNode ), intent(inout) :: node - integer(kind_int8 ), intent(in ) :: uniqueID - !$GLC attributes unused :: node - - self%radiusTidalPrevious=-1.0d0 - self%lastUniqueID =uniqueID - return - end subroutine king1962CalculationReset diff --git a/source/satellites.tidal_stripping.rate.Zentner2005.F90 b/source/satellites.tidal_stripping.rate.Zentner2005.F90 index adc2eeb404..f12a46e880 100644 --- a/source/satellites.tidal_stripping.rate.Zentner2005.F90 +++ b/source/satellites.tidal_stripping.rate.Zentner2005.F90 @@ -24,7 +24,6 @@ !!} use :: Satellite_Tidal_Stripping_Radii, only : satelliteTidalStrippingRadiusClass - use :: Galactic_Structure , only : galacticStructureClass !![ @@ -61,7 +60,6 @@ !!} private class (satelliteTidalStrippingRadiusClass), pointer :: satelliteTidalStrippingRadius_ => null() - class (galacticStructureClass ), pointer :: galacticStructure_ => null() double precision :: efficiency logical :: useDynamicalTimeScale contains @@ -88,7 +86,6 @@ function zentner2005ConstructorParameters(parameters) result(self) type (satelliteTidalStrippingZentner2005) :: self type (inputParameters ), intent(inout) :: parameters class (satelliteTidalStrippingRadiusClass), pointer :: satelliteTidalStrippingRadius_ - class (galacticStructureClass ), pointer :: galacticStructure_ double precision :: efficiency logical :: useDynamicalTimeScale @@ -106,29 +103,26 @@ function zentner2005ConstructorParameters(parameters) result(self) parameters - !!] - self=satelliteTidalStrippingZentner2005(efficiency,useDynamicalTimeScale,satelliteTidalStrippingRadius_,galacticStructure_) + self=satelliteTidalStrippingZentner2005(efficiency,useDynamicalTimeScale,satelliteTidalStrippingRadius_) !![ - !!] return end function zentner2005ConstructorParameters - function zentner2005ConstructorInternal(efficiency,useDynamicalTimeScale,satelliteTidalStrippingRadius_,galacticStructure_) result(self) + function zentner2005ConstructorInternal(efficiency,useDynamicalTimeScale,satelliteTidalStrippingRadius_) result(self) !!{ Internal constructor for the {\normalfont \ttfamily zentner2005} satellite tidal stripping class. !!} implicit none type (satelliteTidalStrippingZentner2005) :: self class (satelliteTidalStrippingRadiusClass), intent(in ), target :: satelliteTidalStrippingRadius_ - class (galacticStructureClass ), intent(in ), target :: galacticStructure_ double precision , intent(in ) :: efficiency logical , intent(in ) :: useDynamicalTimeScale !![ - + !!] return @@ -143,7 +137,6 @@ subroutine zentner2005Destructor(self) !![ - !!] return end subroutine zentner2005Destructor @@ -153,6 +146,7 @@ double precision function zentner2005MassLossRate(self,node) Return a mass loss rate for satellites due to tidal stripping using the formulation of \cite{zentner_physics_2005}. !!} use :: Galacticus_Nodes , only : nodeComponentSatellite, treeNode + use :: Mass_Distributions , only : massDistributionClass use :: Numerical_Constants_Astronomical, only : gigaYear , megaParsec , gravitationalConstantGalacticus use :: Numerical_Constants_Math , only : Pi use :: Numerical_Constants_Prefixes , only : kilo @@ -161,6 +155,7 @@ double precision function zentner2005MassLossRate(self,node) class (satelliteTidalStrippingZentner2005), intent(inout) :: self type (treeNode ), intent(inout) :: node class (nodeComponentSatellite ), pointer :: satellite + class (massDistributionClass ), pointer :: massDistribution_ double precision , dimension(3 ) :: position , velocity double precision :: massSatellite , frequencyAngular, & & periodOrbital , radius , & @@ -187,14 +182,18 @@ double precision function zentner2005MassLossRate(self,node) & /megaParsec ! Find the orbital period. We use the larger of the angular and radial frequencies to avoid numerical problems for purely ! radial or purely circular orbits. + massDistribution_ => node%massDistribution() periodOrbital = +2.0d0 & & *Pi & & /max( & & frequencyAngular, & & frequencyRadial & & ) - radiusTidal = self%satelliteTidalStrippingRadius_%radius (node ) - massEnclosedTidalRadius=max(0.0d0,self%galacticStructure_ %massEnclosed(node,radiusTidal)) + radiusTidal = self %satelliteTidalStrippingRadius_%radius (node ) + massEnclosedTidalRadius=max(0.0d0,massDistribution_ %massEnclosedBySphere(radiusTidal)) + !![ + + !!] ! Check whether to use the dynamical time scale or the orbital time scale for mass loss rate. if (self%useDynamicalTimeScale .and. massEnclosedTidalRadius > 0.0d0) then timeScaleMassLoss=+2.0d0 & diff --git a/source/star_formation.active_masses.surface_density_threshold.F90 b/source/star_formation.active_masses.surface_density_threshold.F90 index a243f12203..6cc029bc56 100644 --- a/source/star_formation.active_masses.surface_density_threshold.F90 +++ b/source/star_formation.active_masses.surface_density_threshold.F90 @@ -21,7 +21,6 @@ Implementation of an active mass for star formation class in which the mass of the ISM above a surface density threshold is active. !!} use :: Dark_Matter_Profiles_DMO, only : darkMatterProfileDMOClass - use :: Galactic_Structure , only : galacticStructureClass use :: Math_Exponentiation , only : fastExponentiator !![ @@ -35,7 +34,6 @@ !!} private class (darkMatterProfileDMOClass), pointer :: darkMatterProfileDMO_ => null() - class (galacticStructureClass ), pointer :: galacticStructure_ => null() double precision :: surfaceDensityThreshold , surfaceDensityNormalization, & & exponentVelocity type (fastExponentiator ) :: velocityExponentiator @@ -64,7 +62,6 @@ function surfaceDensityThresholdConstructorParameters(parameters) result(self) type (starFormationActiveMassSurfaceDensityThreshold) :: self type (inputParameters ), intent(inout) :: parameters class (darkMatterProfileDMOClass ), pointer :: darkMatterProfileDMO_ - class (galacticStructureClass ), pointer :: galacticStructure_ double precision :: surfaceDensityThreshold, exponentVelocity !![ @@ -81,18 +78,16 @@ function surfaceDensityThresholdConstructorParameters(parameters) result(self) parameters - !!] - self=starFormationActiveMassSurfaceDensityThreshold(surfaceDensityThreshold,exponentVelocity,darkMatterProfileDMO_,galacticStructure_) + self=starFormationActiveMassSurfaceDensityThreshold(surfaceDensityThreshold,exponentVelocity,darkMatterProfileDMO_) !![ - !!] return end function surfaceDensityThresholdConstructorParameters - function surfaceDensityThresholdConstructorInternal(surfaceDensityThreshold,exponentVelocity,darkMatterProfileDMO_,galacticStructure_) result(self) + function surfaceDensityThresholdConstructorInternal(surfaceDensityThreshold,exponentVelocity,darkMatterProfileDMO_) result(self) !!{ Internal constructor for the {\normalfont \ttfamily surfaceDensityThreshold} active mass for star formation class. !!} @@ -100,10 +95,9 @@ function surfaceDensityThresholdConstructorInternal(surfaceDensityThreshold,expo type (starFormationActiveMassSurfaceDensityThreshold) :: self double precision , intent(in ) :: surfaceDensityThreshold , exponentVelocity class (darkMatterProfileDMOClass ), intent(in ), target :: darkMatterProfileDMO_ - class (galacticStructureClass ), intent(in ), target :: galacticStructure_ double precision , parameter :: velocityNormalization =200.0d0 !![ - + !!] ! Initialize exponentiators. @@ -122,7 +116,6 @@ subroutine surfaceDensityThresholdDestructor(self) !![ - !!] return end subroutine surfaceDensityThresholdDestructor @@ -132,30 +125,42 @@ double precision function surfaceDensityThresholdMassActive(self,component) Returns the mass (in $\mathrm{M}_\odot$) of gas actively undergoing star formation in the given {\normalfont \ttfamily component} as the mass of gas in the ISM above a given surface density threshold !!} + use :: Coordinates , only : coordinateCylindrical, assignment(=) use :: Error , only : Error_Report use :: Galacticus_Nodes , only : nodeComponentDisk - use :: Galactic_Structure_Options, only : componentTypeDisk, coordinateSystemCartesian, coordinateSystemCylindrical, massTypeGaseous, & - & weightByMass , weightIndexNull + use :: Galactic_Structure_Options, only : componentTypeDisk , massTypeGaseous + use :: Mass_Distributions , only : massDistributionClass implicit none class (starFormationActiveMassSurfaceDensityThreshold), intent(inout) :: self class (nodeComponent ), intent(inout) :: component + class (massDistributionClass ), pointer :: massDistribution_ double precision :: surfaceDensityThreshold, radiusBounding, & & densitySurfaceCentral + type (coordinateCylindrical ) :: coordinates select type (component) class is (nodeComponentDisk) ! Compute the surface density threshold for this node. - surfaceDensityThreshold=+self%surfaceDensityNormalization & - & *self%velocityExponentiator %exponentiate(self%darkMatterProfileDMO_ %circularVelocityMaximum(component%hostNode)) + massDistribution_ => self%darkMatterProfileDMO_ % get (component %hostNode ) + surfaceDensityThreshold = +self%surfaceDensityNormalization & + & *self%velocityExponentiator %exponentiate(massDistribution_%velocityRotationCurveMaximum()) + !![ + + !!] ! We assume a monotonically decreasing surface density. So, if the central density is below threshold then the active mass ! is zero. - densitySurfaceCentral =self%galacticStructure_%surfaceDensity (component%hostNode,[0.0d0,0.0d0,0.0d0] ,coordinateSystemCylindrical,componentTypeDisk,massTypeGaseous,weightByMass,weightIndexNull) + coordinates=[0.0d0,0.0d0,0.0d0] + massDistribution_ => component%hostNode%massDistribution (componentType=componentTypeDisk ,massType=massTypeGaseous) + densitySurfaceCentral = massDistribution_ %surfaceDensity ( coordinates ) if (densitySurfaceCentral < surfaceDensityThreshold) then - surfaceDensityThresholdMassActive=0.0d0 + surfaceDensityThresholdMassActive =0.0d0 else - radiusBounding =self%galacticStructure_%radiusEnclosingSurfaceDensity(component%hostNode,surfaceDensityThreshold ,componentTypeDisk,massTypeGaseous,weightByMass,weightIndexNull) - surfaceDensityThresholdMassActive=self%galacticStructure_%massEnclosed (component%hostNode,radiusBounding ,componentTypeDisk,massTypeGaseous,weightByMass,weightIndexNull) + radiusBounding = massDistribution_%radiusEnclosingSurfaceDensity( surfaceDensityThreshold ) + surfaceDensityThresholdMassActive = massDistribution_%massEnclosedBySphere ( radiusBounding ) end if + !![ + + !!] class default surfaceDensityThresholdMassActive=0.0d0 call Error_Report('unsupported class'//{introspection:location}) diff --git a/source/star_formation.rate_surface_density.disks.Blitz2006.F90 b/source/star_formation.rate_surface_density.disks.Blitz2006.F90 index 4eb8268568..9f94dabae6 100644 --- a/source/star_formation.rate_surface_density.disks.Blitz2006.F90 +++ b/source/star_formation.rate_surface_density.disks.Blitz2006.F90 @@ -22,7 +22,6 @@ !!} use :: Kind_Numbers , only : kind_int8 - use :: Galactic_Structure , only : galacticStructureClass use :: Root_Finder , only : rootFinder use :: Math_Exponentiation, only : fastExponentiator @@ -61,10 +60,9 @@ Implementation of the \cite{blitz_role_2006} star formation rate surface density law for galactic disks. !!} private - class (galacticStructureClass), pointer :: galacticStructure_ => null() integer (kind_int8 ) :: lastUniqueID logical :: factorsComputed , assumeMonotonicSurfaceDensity , & - & assumeExponentialDisk , useTabulation + & isExponentialDisk , useTabulation double precision :: heightToRadialScaleDisk , pressureCharacteristic , & & pressureExponent , starFormationFrequencyNormalization , & & surfaceDensityCritical , surfaceDensityExponent , & @@ -92,7 +90,6 @@ - @@ -105,7 +102,6 @@ procedure :: unchanged => blitz2006Unchanged procedure :: intervals => blitz2006Intervals procedure :: pressureRatio => blitz2006PressureRatio - procedure :: pressureRatioExponential => blitz2006PressureRatioExponential procedure :: integralFullyMolecular => blitz2006IntegralFullyMolecular procedure :: integralPartiallyMolecular => blitz2006IntegralPartiallyMolecular end type starFormationRateSurfaceDensityDisksBlitz2006 @@ -132,13 +128,11 @@ function blitz2006ConstructorParameters(parameters) result(self) implicit none type (starFormationRateSurfaceDensityDisksBlitz2006) :: self type (inputParameters ), intent(inout) :: parameters - class (galacticStructureClass ), pointer :: galacticStructure_ double precision :: velocityDispersionDiskGas , heightToRadialScaleDisk, & & surfaceDensityCritical , surfaceDensityExponent , & & starFormationFrequencyNormalization, pressureCharacteristic , & & pressureExponent - logical :: assumeMonotonicSurfaceDensity , assumeExponentialDisk , & - & useTabulation + logical :: assumeMonotonicSurfaceDensity , useTabulation !![ @@ -196,29 +190,21 @@ function blitz2006ConstructorParameters(parameters) result(self) If true, assume that the surface density in disks is always monotonically decreasing. parameters - - assumeExponentialDisk - .false. - If true, assume that the surface density in disks follows an exponential profile, $\exp(-r/r_\mathrm{d})$. - parameters - useTabulation .false. If true, then use tabulated solutions to the integrated star formation rate. parameters - !!] - self=starFormationRateSurfaceDensityDisksBlitz2006(velocityDispersionDiskGas,heightToRadialScaleDisk,surfaceDensityCritical,surfaceDensityExponent,starFormationFrequencyNormalization,pressureCharacteristic,pressureExponent,assumeMonotonicSurfaceDensity,assumeExponentialDisk,useTabulation,galacticStructure_) + self=starFormationRateSurfaceDensityDisksBlitz2006(velocityDispersionDiskGas,heightToRadialScaleDisk,surfaceDensityCritical,surfaceDensityExponent,starFormationFrequencyNormalization,pressureCharacteristic,pressureExponent,assumeMonotonicSurfaceDensity,useTabulation) !![ - !!] return end function blitz2006ConstructorParameters - function blitz2006ConstructorInternal(velocityDispersionDiskGas,heightToRadialScaleDisk,surfaceDensityCritical,surfaceDensityExponent,starFormationFrequencyNormalization,pressureCharacteristic,pressureExponent,assumeMonotonicSurfaceDensity,assumeExponentialDisk,useTabulation,galacticStructure_) result(self) + function blitz2006ConstructorInternal(velocityDispersionDiskGas,heightToRadialScaleDisk,surfaceDensityCritical,surfaceDensityExponent,starFormationFrequencyNormalization,pressureCharacteristic,pressureExponent,assumeMonotonicSurfaceDensity,useTabulation) result(self) !!{ Internal constructor for the {\normalfont \ttfamily blitz2006} star formation surface density rate from disks class. !!} @@ -230,18 +216,16 @@ function blitz2006ConstructorInternal(velocityDispersionDiskGas,heightToRadialSc use :: Root_Finder , only : rangeExpandMultiplicative, rangeExpandSignExpectNegative, rangeExpandSignExpectPositive use :: Hashes_Cryptographic , only : Hash_MD5 implicit none - type (starFormationRateSurfaceDensityDisksBlitz2006) :: self - class (galacticStructureClass ), intent(in ), target :: galacticStructure_ - double precision , intent(in ) :: velocityDispersionDiskGas , heightToRadialScaleDisk, & - & surfaceDensityCritical , surfaceDensityExponent , & - & starFormationFrequencyNormalization, pressureCharacteristic , & - & pressureExponent - logical , intent(in ) :: assumeMonotonicSurfaceDensity , assumeExponentialDisk , & - & useTabulation - type (varying_string ) :: descriptorString - character (len=17 ) :: parameterLabel + type (starFormationRateSurfaceDensityDisksBlitz2006) :: self + double precision , intent(in ) :: velocityDispersionDiskGas , heightToRadialScaleDisk, & + & surfaceDensityCritical , surfaceDensityExponent , & + & starFormationFrequencyNormalization, pressureCharacteristic , & + & pressureExponent + logical , intent(in ) :: assumeMonotonicSurfaceDensity , useTabulation + type (varying_string ) :: descriptorString + character (len=17 ) :: parameterLabel !![ - + !!] self%lastUniqueID =-1_kind_int8 @@ -255,29 +239,16 @@ function blitz2006ConstructorInternal(velocityDispersionDiskGas,heightToRadialSc ! Build fast exponentiator. self%pressureRatioExponentiator =fastExponentiator(0.0d0,1.0d0,pressureExponent,1000.0d0,.false.) ! Build root finder. - if (self%assumeExponentialDisk) then - self%finder=rootFinder( & - & rootFunction =blitz2006CriticalDensityExponentialRoot, & - & toleranceAbsolute =0.0d+0 , & - & toleranceRelative =1.0d-4 , & - & rangeExpandUpward =2.0d0 , & - & rangeExpandDownward =0.5d0 , & - & rangeExpandUpwardSignExpect =rangeExpandSignExpectNegative , & - & rangeExpandDownwardSignExpect=rangeExpandSignExpectPositive , & - & rangeExpandType =rangeExpandMultiplicative & - & ) - else - self%finder=rootFinder( & - & rootFunction =blitz2006CriticalDensityRoot , & - & toleranceAbsolute =0.0d+0 , & - & toleranceRelative =1.0d-4 , & - & rangeExpandUpward =2.0d+0 , & - & rangeExpandDownward =0.5d+0 , & - & rangeExpandUpwardSignExpect =rangeExpandSignExpectNegative , & - & rangeExpandDownwardSignExpect=rangeExpandSignExpectPositive , & - & rangeExpandType =rangeExpandMultiplicative & - & ) - end if + self%finder=rootFinder( & + & rootFunction =blitz2006CriticalDensityRoot , & + & toleranceAbsolute =0.0d+0 , & + & toleranceRelative =1.0d-4 , & + & rangeExpandUpward =2.0d+0 , & + & rangeExpandDownward =0.5d+0 , & + & rangeExpandUpwardSignExpect =rangeExpandSignExpectNegative, & + & rangeExpandDownwardSignExpect=rangeExpandSignExpectPositive, & + & rangeExpandType =rangeExpandMultiplicative & + & ) ! Initialize memoized values. self%massGasPrevious =-huge(0.0d0) self%massStellarPrevious =-huge(0.0d0) @@ -328,9 +299,6 @@ subroutine blitz2006Destructor(self) type(starFormationRateSurfaceDensityDisksBlitz2006), intent(inout) :: self if (calculationResetEvent%isAttached(self,blitz2006CalculationReset)) call calculationResetEvent%detach(self,blitz2006CalculationReset) - !![ - - !!] return end subroutine blitz2006Destructor @@ -427,12 +395,16 @@ subroutine blitz2006ComputeFactors(self,node) !!} use :: Abundances_Structure , only : abundances use :: Galacticus_Nodes , only : nodeComponentDisk + use :: Galactic_Structure_Options , only : componentTypeDisk , massTypeGaseous , massTypeStellar + use :: Mass_Distributions , only : massDistributionClass , massDistributionCylindricalScaler, massDistributionExponentialDisk use :: Numerical_Constants_Math , only : Pi use :: Numerical_Constants_Astronomical, only : gravitationalConstantGalacticus implicit none class(starFormationRateSurfaceDensityDisksBlitz2006), intent(inout) :: self type (treeNode ), intent(inout) :: node class(nodeComponentDisk ), pointer :: disk + class(massDistributionClass ), pointer :: massDistributionGaseous, massDistributionStellar, & + & massDistribution_ type (abundances ), save :: abundancesFuel !$omp threadprivate(abundancesFuel) @@ -448,8 +420,48 @@ subroutine blitz2006ComputeFactors(self,node) abundancesFuel=disk%abundancesGas() call abundancesFuel%massToMassFraction(self%massGas) self%hydrogenMassFraction=abundancesFuel%hydrogenMassFraction() + ! Determine if we have an exponential disk. + massDistributionGaseous => node%massDistribution(componentType=componentTypeDisk,massType=massTypeGaseous) + massDistributionStellar => node%massDistribution(componentType=componentTypeDisk,massType=massTypeStellar) + self%isExponentialDisk = .true. + select type (massDistributionGaseous) + class is (massDistributionExponentialDisk) + ! The disk is exponential - no change needed. + class is (massDistributionCylindricalScaler ) + ! Check the unscale distribution. + massDistribution_ => massDistributionGaseous%unscaled() + select type (massDistribution_) + class is (massDistributionExponentialDisk) + ! The disk is exponential - no change needed. + class default + self%isExponentialDisk=.false. + end select + class default + ! Not an exponential distribution. + self%isExponentialDisk=.false. + end select + select type (massDistributionStellar) + class is (massDistributionExponentialDisk) + ! The disk is exponential - no change needed. + class is (massDistributionCylindricalScaler ) + ! Check the unscale distribution. + massDistribution_ => massDistributionStellar%unscaled() + select type (massDistribution_) + class is (massDistributionExponentialDisk) + ! The disk is exponential - no change needed. + class default + self%isExponentialDisk=.false. + end select + class default + ! Not an exponential distribution. + self%isExponentialDisk=.false. + end select + !![ + + + !!] ! Properties required for exponential disks. - if (self%assumeExponentialDisk .and. self%massStellar >= 0.0d0 .and. self%radiusDisk > 0.0d0) then + if (self%isExponentialDisk .and. self%massStellar >= 0.0d0 .and. self%radiusDisk > 0.0d0) then self%pressureRatioCoefficient =+gravitationalConstantGalacticus & & /8.0d0 & & /Pi & @@ -486,6 +498,8 @@ function blitz2006Intervals(self,node,radiusInner,radiusOuter,intervalIsAnalytic !!{ Returns intervals to use for integrating the \cite{krumholz_star_2009} star formation rate over a galactic disk. !!} + use :: Mass_Distributions , only : massDistributionClass + use :: Galactic_Structure_Options , only : componentTypeDisk , massTypeGaseous , massTypeStellar use :: Numerical_Constants_Math , only : Pi use :: Numerical_Constants_Astronomical, only : gravitationalConstantGalacticus implicit none @@ -495,15 +509,25 @@ function blitz2006Intervals(self,node,radiusInner,radiusOuter,intervalIsAnalytic double precision , intent(in ) :: radiusInner , radiusOuter logical , intent(inout), allocatable, dimension( :) :: intervalIsAnalytic double precision , intent(inout), allocatable, dimension( :) :: integralsAnalytic + class (massDistributionClass ), pointer :: massDistributionGaseous , massDistributionStellar double precision , parameter :: factorBoostStellarCoefficientTiny=1.0d-6 - double precision :: coefficientNormalization , coefficientFactorBoost, & - & coefficientFactorBoostStellar , coefficientMolecular , & - & rootValueInner , rootValueOuter , & + double precision :: coefficientNormalization , coefficientFactorBoost , & + & coefficientFactorBoostStellar , coefficientMolecular , & + & rootValueInner , rootValueOuter , & & radiusAnalytic , sqrtTerm - logical :: thresholdCondition + logical :: thresholdCondition , assumeMonotonicSurfaceDensity ! Check if we can assume a monotonic surface density. - if (self%assumeMonotonicSurfaceDensity) then + massDistributionGaseous => node %massDistribution (componentType=componentTypeDisk,massType=massTypeGaseous) + massDistributionStellar => node %massDistribution (componentType=componentTypeDisk,massType=massTypeStellar) + assumeMonotonicSurfaceDensity = massDistributionGaseous%assumeMonotonicDecreasingSurfaceDensity( ) & + & .and. & + & massDistributionStellar%assumeMonotonicDecreasingSurfaceDensity( ) + !![ + + + !!] + if (assumeMonotonicSurfaceDensity) then ! Set the critical radius to a very negative value so that pressure ratio is always computed. self%radiusCritical=-huge(0.0d0) ! Compute factors. @@ -516,20 +540,24 @@ function blitz2006Intervals(self,node,radiusInner,radiusOuter,intervalIsAnalytic self_ => self node_ => node ! Test if the inner radius is below the pressure threshold. - if (self%assumeExponentialDisk) then + if (self%isExponentialDisk) then ! For exponential disks this condition has a simple analytic form. - rootValueInner =-huge(0.0d0) - thresholdCondition=1.0d0/self%pressureRatioCoefficient-self%factorBoostStellarCoefficient >= 1.0d0 + rootValueInner =-huge(0.0d0) + if (self%pressureRatioCoefficient > 0.0d0 .and. -exponent(self%pressureRatioCoefficient) < maxExponent(0.0d0)) then + thresholdCondition=1.0d0/self%pressureRatioCoefficient-self%factorBoostStellarCoefficient >= 1.0d0 + else + thresholdCondition=.true. + end if else ! For generic disks test this numerically. - rootValueInner =blitz2006CriticalDensityRoot(radiusInner) - thresholdCondition=rootValueInner <= 0.0d0 + rootValueInner =blitz2006CriticalDensityRoot(radiusInner) + thresholdCondition =rootValueInner <= 0.0d0 end if if (thresholdCondition) then ! The entire disk is below the pressure threshold so use a single interval. allocate(blitz2006Intervals(2,1)) allocate(intervalIsAnalytic( 1)) - if (self%assumeExponentialDisk.and.self%useTabulation) then + if (self%isExponentialDisk.and.self%useTabulation) then call computeCoefficients() allocate(integralsAnalytic(1)) intervalIsAnalytic=.true. @@ -541,14 +569,14 @@ function blitz2006Intervals(self,node,radiusInner,radiusOuter,intervalIsAnalytic self%radiusCritical=-huge(0.0d0) else ! Compute coefficients needed for analytic solutions. - if (self%assumeExponentialDisk.and.self%useTabulation) call computeCoefficients() + if (self%isExponentialDisk.and.self%useTabulation) call computeCoefficients() ! Test the surface density at the outer radius. rootValueOuter=blitz2006CriticalDensityRoot(radiusOuter) if (rootValueOuter >= 0.0d0) then ! Entire disk is above the pressure threshold so use a single interval. allocate(blitz2006Intervals(2,1)) allocate(intervalIsAnalytic( 1)) - if (self%assumeExponentialDisk.and.self%useTabulation) then + if (self%isExponentialDisk.and.self%useTabulation) then allocate(integralsAnalytic ( 1)) intervalIsAnalytic =.true. integralsAnalytic =self%integralFullyMolecular(coefficientNormalization,coefficientFactorBoost,radiusInner,radiusOuter) @@ -560,7 +588,7 @@ function blitz2006Intervals(self,node,radiusInner,radiusOuter,intervalIsAnalytic else ! The disk transitions the pressure threshold - attempt to locate the radius at which this happens and use two ! intervals split at this point. - if (self%assumeExponentialDisk) then + if (self%isExponentialDisk) then ! For exponential disks we have an analytic solution for the transition radius. if (self%factorBoostStellarCoefficient <= factorBoostStellarCoefficientTiny) then radiusAnalytic=+0.5d0*log(self%pressureRatioCoefficient) @@ -609,7 +637,7 @@ function blitz2006Intervals(self,node,radiusInner,radiusOuter,intervalIsAnalytic self%radiusCriticalPrevious=self%radiusCritical allocate(blitz2006Intervals(2,2)) allocate(intervalIsAnalytic( 2)) - if (self%assumeExponentialDisk.and.self%useTabulation) then + if (self%isExponentialDisk.and.self%useTabulation) then allocate(integralsAnalytic ( 2)) intervalIsAnalytic =.true. integralsAnalytic =[ & @@ -667,8 +695,7 @@ subroutine computeCoefficients() & ) return end subroutine computeCoefficients - - + end function blitz2006Intervals double precision function blitz2006IntegralFullyMolecular(self,coefficientNormalization,coefficientFactorBoost,radiusInner,radiusOuter) @@ -1136,17 +1163,6 @@ Root function used in finding the radius in a disk where the pressure ratio exce return end function blitz2006CriticalDensityRoot - double precision function blitz2006CriticalDensityExponentialRoot(radius) - !!{ - Root function used in finding the radius in a disk where the pressure ratio exceeds the critical ratio. - !!} - implicit none - double precision, intent(in ) :: radius - - blitz2006CriticalDensityExponentialRoot=self_%pressureRatioExponential(node_,radius)-1.0d0 - return - end function blitz2006CriticalDensityExponentialRoot - double precision function blitz2006PressureRatio(self,node,radius,surfaceDensityGas) result(pressureRatio) !!{ Root function used in finding the radius in a disk where the pressure ratio exceeds the critical ratio. @@ -1154,16 +1170,25 @@ Root function used in finding the radius in a disk where the pressure ratio exce use :: Numerical_Constants_Math , only : Pi use :: Numerical_Constants_Astronomical, only : gravitationalConstantGalacticus use :: Galactic_Structure_Options , only : componentTypeDisk , coordinateSystemCylindrical, massTypeGaseous, massTypeStellar + use :: Mass_Distributions , only : massDistributionClass + use :: Coordinates, only : coordinateCylindrical, assignment(=) implicit none class (starFormationRateSurfaceDensityDisksBlitz2006), intent(inout) :: self type (treeNode ), intent(inout) :: node double precision , intent(in ) :: radius double precision , intent( out), optional :: surfaceDensityGas + class (massDistributionClass ), pointer :: massDistribution_ + type (coordinateCylindrical ) :: coordinates double precision :: surfaceDensityGas_, surfaceDensityStellar, & & factorBoostStellar ! Get gas surface density. - surfaceDensityGas_=self%galacticStructure_%surfaceDensity(node,[radius,0.0d0,0.0d0],coordinateSystem=coordinateSystemCylindrical,componentType=componentTypeDisk,massType=massTypeGaseous) + coordinates = [radius,0.0d0,0.0d0] + massDistribution_ => node %massDistribution(componentType=componentTypeDisk,massType=massTypeGaseous) + surfaceDensityGas_ = massDistribution_%surfaceDensity ( coordinates ) + !![ + + !!] if (present(surfaceDensityGas)) surfaceDensityGas=surfaceDensityGas_ ! If the radius is less than the critical radius the pressure radius is above 1 by definition, so simply pin it to that value. if (radius <= self%radiusCritical) then @@ -1179,7 +1204,11 @@ Root function used in finding the radius in a disk where the pressure ratio exce & /self%pressureCharacteristic if (pressureRatio > 0.0d0 .and. pressureRatio < 1.0d0) then ! Compute the stellar boost factor. - surfaceDensityStellar=+self%galacticStructure_%surfaceDensity(node,[radius,0.0d0,0.0d0],coordinateSystem=coordinateSystemCylindrical,componentType=componentTypeDisk,massType=massTypeStellar) + massDistribution_ => node %massDistribution(componentType=componentTypeDisk,massType=massTypeStellar) + surfaceDensityStellar = +massDistribution_%surfaceDensity ( coordinates ) + !![ + + !!] factorBoostStellar =+1.0d0 & & +self%velocityDispersionDiskGas & & /surfaceDensityGas_ & @@ -1196,24 +1225,3 @@ Root function used in finding the radius in a disk where the pressure ratio exce end if return end function blitz2006PressureRatio - - double precision function blitz2006PressureRatioExponential(self,node,radius) result(pressureRatio) - !!{ - Root function used in finding the radius in a disk where the pressure ratio exceeds the critical ratio. - !!} - implicit none - class (starFormationRateSurfaceDensityDisksBlitz2006), intent(inout) :: self - type (treeNode ), intent(inout) :: node - double precision , intent(in ) :: radius - double precision :: factorBoostStellar - - ! Compute the pressure ratio that Blitz & Rosolowsky (2006) use to compute the molecular fraction. - pressureRatio =+self%pressureRatioCoefficient & - & *exp(-2.0d0*radius/self%radiusDisk) - factorBoostStellar=+1.0d0 & - & +self%factorBoostStellarCoefficient & - & *exp(+0.5d0*radius/self%radiusDisk) - pressureRatio =+pressureRatio & - & *factorBoostStellar - return - end function blitz2006PressureRatioExponential diff --git a/source/star_formation.rate_surface_density.disks.Kennicutt-Schmidt.F90 b/source/star_formation.rate_surface_density.disks.Kennicutt-Schmidt.F90 index b46428a068..880f3c2c4f 100644 --- a/source/star_formation.rate_surface_density.disks.Kennicutt-Schmidt.F90 +++ b/source/star_formation.rate_surface_density.disks.Kennicutt-Schmidt.F90 @@ -21,8 +21,7 @@ Implementation of a the Kennicutt-Schmidt star formation rate surface density for galactic disks. !!} - use :: Kind_Numbers , only : kind_int8 - use :: Galactic_Structure, only : galacticStructureClass + use :: Kind_Numbers, only : kind_int8 !![ @@ -57,14 +56,13 @@ Implementation of a Kennicutt-Schmidt star formation rate surface density for galactic disks. !!} private - class (galacticStructureClass), pointer :: galacticStructure_ => null() - double precision :: normalization , exponent , & - & exponentTruncated , velocityDispersionDiskGas, & - & toomreParameterCritical - logical :: truncate - integer (kind_int8 ) :: lastUniqueID - logical :: factorsComputed - double precision :: surfaceDensityCriticalFactor , hydrogenMassFraction + double precision :: normalization , exponent , & + & exponentTruncated , velocityDispersionDiskGas, & + & toomreParameterCritical + logical :: truncate + integer (kind_int8) :: lastUniqueID + logical :: factorsComputed + double precision :: surfaceDensityCriticalFactor, hydrogenMassFraction contains !![ @@ -95,7 +93,6 @@ function kennicuttSchmidtConstructorParameters(parameters) result(self) implicit none type (starFormationRateSurfaceDensityDisksKennicuttSchmidt) :: self type (inputParameters ), intent(inout) :: parameters - class (galacticStructureClass ), pointer :: galacticStructure_ double precision :: normalization , exponent , & & exponentTruncated , velocityDispersionDiskGas, & & toomreParameterCritical @@ -142,30 +139,27 @@ function kennicuttSchmidtConstructorParameters(parameters) result(self) The critical Toomre parameter for star formation in disks. parameters - !!] - self=starFormationRateSurfaceDensityDisksKennicuttSchmidt(normalization,exponent,truncate,exponentTruncated,velocityDispersionDiskGas,toomreParameterCritical,galacticStructure_) + self=starFormationRateSurfaceDensityDisksKennicuttSchmidt(normalization,exponent,truncate,exponentTruncated,velocityDispersionDiskGas,toomreParameterCritical) !![ - !!] return end function kennicuttSchmidtConstructorParameters - function kennicuttSchmidtConstructorInternal(normalization,exponent,truncate,exponentTruncated,velocityDispersionDiskGas,toomreParameterCritical,galacticStructure_) result(self) + function kennicuttSchmidtConstructorInternal(normalization,exponent,truncate,exponentTruncated,velocityDispersionDiskGas,toomreParameterCritical) result(self) !!{ Internal constructor for the {\normalfont \ttfamily kennicuttSchmidt} star formation surface density rate from disks class. !!} use :: Numerical_Constants_Prefixes, only : mega implicit none - type (starFormationRateSurfaceDensityDisksKennicuttSchmidt) :: self - double precision , intent(in ) :: normalization , exponent , & - & exponentTruncated , velocityDispersionDiskGas, & - & toomreParameterCritical - logical , intent(in ) :: truncate - class (galacticStructureClass ), intent(in ), target :: galacticStructure_ + type (starFormationRateSurfaceDensityDisksKennicuttSchmidt) :: self + double precision , intent(in ) :: normalization , exponent , & + & exponentTruncated , velocityDispersionDiskGas, & + & toomreParameterCritical + logical , intent(in ) :: truncate !![ - + !!] self%lastUniqueID =-1_kind_int8 @@ -197,9 +191,6 @@ subroutine kennicuttSchmidtDestructor(self) type(starFormationRateSurfaceDensityDisksKennicuttSchmidt), intent(inout) :: self if (calculationResetEvent%isAttached(self,kennicuttSchmidtCalculationReset)) call calculationResetEvent%detach(self,kennicuttSchmidtCalculationReset) - !![ - - !!] return end subroutine kennicuttSchmidtDestructor @@ -236,8 +227,10 @@ double precision function kennicuttSchmidtRate(self,node,radius) assumed to have a flat rotation curve such that $\kappa = \sqrt{2} V/R$. !!} use :: Abundances_Structure , only : abundances - use :: Galactic_Structure_Options , only : componentTypeDisk , coordinateSystemCylindrical, massTypeGaseous - use :: Galacticus_Nodes , only : nodeComponentDisk , treeNode + use :: Coordinates , only : coordinateCylindrical , assignment(=) + use :: Galactic_Structure_Options , only : componentTypeDisk , massTypeGaseous + use :: Galacticus_Nodes , only : nodeComponentDisk + use :: Mass_Distributions , only : massDistributionClass use :: Numerical_Constants_Math , only : Pi use :: Numerical_Constants_Astronomical, only : gravitationalConstantGalacticus implicit none @@ -245,10 +238,12 @@ double precision function kennicuttSchmidtRate(self,node,radius) type (treeNode ), intent(inout) :: node double precision , intent(in ) :: radius class (nodeComponentDisk ), pointer :: disk + class (massDistributionClass ), pointer :: massDistribution_ type (abundances ), save :: abundancesFuel !$omp threadprivate(abundancesFuel) double precision :: surfaceDensityCritical, massGas, & & surfaceDensityGas + type (coordinateCylindrical ) :: coordinates ! Check if node differs from previous one for which we performed calculations. if (node%uniqueID() /= self%lastUniqueID) call self%calculationReset(node,node%uniqueID()) @@ -272,7 +267,12 @@ double precision function kennicuttSchmidtRate(self,node,radius) self%factorsComputed=.true. end if ! Get gas surface density. - surfaceDensityGas=self%galacticStructure_%surfaceDensity(node,[radius,0.0d0,0.0d0],coordinateSystem=coordinateSystemCylindrical,componentType=componentTypeDisk,massType=massTypeGaseous) + coordinates = [radius,0.0d0,0.0d0] + massDistribution_ => node %massDistribution(componentType=componentTypeDisk,massType=massTypeGaseous) + surfaceDensityGas = massDistribution_%surfaceDensity ( coordinates ) + !![ + + !!] ! Compute the star formation rate surface density. kennicuttSchmidtRate=+self%normalization & & *( & diff --git a/source/star_formation.rate_surface_density.disks.Krumholz2009.F90 b/source/star_formation.rate_surface_density.disks.Krumholz2009.F90 index 7d897ad6bc..778c03e4ed 100644 --- a/source/star_formation.rate_surface_density.disks.Krumholz2009.F90 +++ b/source/star_formation.rate_surface_density.disks.Krumholz2009.F90 @@ -26,7 +26,6 @@ use :: Math_Exponentiation , only : fastExponentiator use :: Tables , only : table1DLinearLinear use :: Root_Finder , only : rootFinder - use :: Galactic_Structure , only : galacticStructureClass !![ @@ -69,22 +68,21 @@ Implementation of the \cite{krumholz_star_2009} star formation rate surface density law for galactic disks. !!} private - class (galacticStructureClass) , pointer :: galacticStructure_ => null() - integer (kind_int8 ) :: lastUniqueID - logical :: factorsComputed - double precision :: massGasPrevious , radiusPrevious , & - & radiusCriticalPrevious , radiusMaximumPrevious - type (abundances ) :: abundancesFuelPrevious - double precision :: chi , radiusDisk , & - & massGas , hydrogenMassFraction , & - & metallicityRelativeToSolar , sNormalization , & - & sigmaMolecularComplexNormalization , clumpingFactorMolecularComplex, & - & frequencyStarFormation - logical :: assumeMonotonicSurfaceDensity , molecularFractionFast - type (rootFinder ) :: finderCritical , finderMolecules - type (fastExponentiator ) :: surfaceDensityExponentiator - type (table1DLinearLinear ) :: molecularFractionTable - procedure (double precision ), nopass, pointer :: molecularFractionFunction + integer (kind_int8 ) :: lastUniqueID + logical :: factorsComputed + double precision :: massGasPrevious , radiusPrevious , & + & radiusCriticalPrevious , radiusMaximumPrevious + type (abundances ) :: abundancesFuelPrevious + double precision :: chi , radiusDisk , & + & massGas , hydrogenMassFraction , & + & metallicityRelativeToSolar , sNormalization , & + & sigmaMolecularComplexNormalization, clumpingFactorMolecularComplex, & + & frequencyStarFormation + logical :: assumeMonotonicSurfaceDensity , molecularFractionFast + type (rootFinder ) :: finderCritical , finderMolecules + type (fastExponentiator ) :: surfaceDensityExponentiator + type (table1DLinearLinear) :: molecularFractionTable + procedure (double precision ), nopass, pointer :: molecularFractionFunction contains !![ @@ -131,9 +129,8 @@ function krumholz2009ConstructorParameters(parameters) result(self) implicit none type (starFormationRateSurfaceDensityDisksKrumholz2009) :: self type (inputParameters ), intent(inout) :: parameters - class (galacticStructureClass ), pointer :: galacticStructure_ => null() - double precision :: frequencyStarFormation , clumpingFactorMolecularComplex - logical :: molecularFractionFast , assumeMonotonicSurfaceDensity + double precision :: frequencyStarFormation, clumpingFactorMolecularComplex + logical :: molecularFractionFast , assumeMonotonicSurfaceDensity !![ @@ -161,17 +158,15 @@ function krumholz2009ConstructorParameters(parameters) result(self) If true, assume that the surface density in disks is always monotonically decreasing. parameters - !!] - self=starFormationRateSurfaceDensityDisksKrumholz2009(frequencyStarFormation,clumpingFactorMolecularComplex,molecularFractionFast,assumeMonotonicSurfaceDensity,galacticStructure_) + self=starFormationRateSurfaceDensityDisksKrumholz2009(frequencyStarFormation,clumpingFactorMolecularComplex,molecularFractionFast,assumeMonotonicSurfaceDensity) !![ - !!] return end function krumholz2009ConstructorParameters - function krumholz2009ConstructorInternal(frequencyStarFormation,clumpingFactorMolecularComplex,molecularFractionFast,assumeMonotonicSurfaceDensity,galacticStructure_) result(self) + function krumholz2009ConstructorInternal(frequencyStarFormation,clumpingFactorMolecularComplex,molecularFractionFast,assumeMonotonicSurfaceDensity) result(self) !!{ Internal constructor for the {\normalfont \ttfamily krumholz2009} star formation surface density rate from disks class. !!} @@ -179,14 +174,13 @@ function krumholz2009ConstructorInternal(frequencyStarFormation,clumpingFactorMo use :: Table_Labels , only : extrapolationTypeFix use :: Root_Finder , only : rangeExpandMultiplicative, rangeExpandSignExpectNegative, rangeExpandSignExpectPositive implicit none - type (starFormationRateSurfaceDensityDisksKrumholz2009) :: self - double precision , intent(in ) :: frequencyStarFormation , clumpingFactorMolecularComplex - logical , intent(in ) :: molecularFractionFast , assumeMonotonicSurfaceDensity - class (galacticStructureClass ), intent(in ), target :: galacticStructure_ - integer , parameter :: sCount =1000 - integer :: i + type (starFormationRateSurfaceDensityDisksKrumholz2009) :: self + double precision , intent(in ) :: frequencyStarFormation , clumpingFactorMolecularComplex + logical , intent(in ) :: molecularFractionFast , assumeMonotonicSurfaceDensity + integer , parameter :: sCount =1000 + integer :: i !![ - + !!] self%lastUniqueID =-1_kind_int8 @@ -257,9 +251,6 @@ subroutine krumholz2009Destructor(self) if (calculationResetEvent%isAttached(self,krumholz2009CalculationReset)) & & call calculationResetEvent%detach (self,krumholz2009CalculationReset) call self %molecularFractionTable%destroy( ) - !![ - - !!] return end subroutine krumholz2009Destructor @@ -398,22 +389,25 @@ subroutine krumholz2009SurfaceDensityFactors(self,node,radius,surfaceDensityGas, !!{ Compute surface density and related quantities needed for the \cite{krumholz_star_2009} star formation rate model. !!} - use :: Galactic_Structure_Options, only : componentTypeDisk, coordinateSystemCylindrical, massTypeGaseous + use :: Coordinates , only : coordinateCylindrical, assignment(=) + use :: Galactic_Structure_Options, only : componentTypeDisk , massTypeGaseous + use :: Mass_Distributions , only : massDistributionClass implicit none class (starFormationRateSurfaceDensityDisksKrumholz2009), intent(inout) :: self type (treeNode ), intent(inout) :: node double precision , intent(in ) :: radius double precision , intent( out) :: surfaceDensityGas , surfaceDensityGasDimensionless + class (massDistributionClass ), pointer :: massDistribution_ double precision , parameter :: surfaceDensityTransition=85.0d12 ! M☉/Mpc² + type (coordinateCylindrical ) :: coordinates ! Get gas surface density. - surfaceDensityGas=self%galacticStructure_%surfaceDensity( & - & node , & - & [radius ,0.0d0,0.0d0], & - & coordinateSystem= coordinateSystemCylindrical , & - & componentType = componentTypeDisk , & - & massType = massTypeGaseous & - & ) + coordinates = [radius,0.0d0,0.0d0] + massDistribution_ => node %massDistribution(componentType=componentTypeDisk,massType=massTypeGaseous) + surfaceDensityGas = massDistribution_%surfaceDensity ( coordinates ) + !![ + + !!] ! Compute the cloud density factor. surfaceDensityGasDimensionless=+self%hydrogenMassFraction & & * surfaceDensityGas & diff --git a/source/star_formation.rate_surface_density.disks.extended_Schmidt.F90 b/source/star_formation.rate_surface_density.disks.extended_Schmidt.F90 index 8f249f273d..beb9169ccb 100644 --- a/source/star_formation.rate_surface_density.disks.extended_Schmidt.F90 +++ b/source/star_formation.rate_surface_density.disks.extended_Schmidt.F90 @@ -22,8 +22,6 @@ !!{ Implementation of the extended Schmidt star formation rate surface density law of \cite{shi_extended_2011} for galactic disks. !!} - - use :: Galactic_Structure, only : galacticStructureClass !![ @@ -43,11 +41,10 @@ Implementation of the extended Schmidt star formation rate surface density law of \cite{shi_extended_2011} for galactic disks. !!} private - class (galacticStructureClass), pointer :: galacticStructure_ => null() - integer (kind_int8 ) :: lastUniqueID - logical :: factorsComputed - double precision :: normalization , exponentGas , & - & exponentStars , hydrogenMassFraction + integer (kind_int8) :: lastUniqueID + logical :: factorsComputed + double precision :: normalization , exponentGas , & + & exponentStars , hydrogenMassFraction contains !![ @@ -78,8 +75,7 @@ function extendedSchmidtConstructorParameters(parameters) result(self) implicit none type (starFormationRateSurfaceDensityDisksExtendedSchmidt) :: self type (inputParameters ), intent(inout) :: parameters - class (galacticStructureClass ), pointer :: galacticStructure_ - double precision :: normalization , exponentGas, & + double precision :: normalization, exponentGas, & & exponentStars !![ @@ -104,28 +100,25 @@ function extendedSchmidtConstructorParameters(parameters) result(self) The exponent of stellar surface density in the extended Schmidt star formation law. parameters - !!] - self=starFormationRateSurfaceDensityDisksExtendedSchmidt(normalization,exponentGas,exponentStars,galacticStructure_) + self=starFormationRateSurfaceDensityDisksExtendedSchmidt(normalization,exponentGas,exponentStars) !![ - !!] return end function extendedSchmidtConstructorParameters - function extendedSchmidtConstructorInternal(normalization,exponentGas,exponentStars,galacticStructure_) result(self) + function extendedSchmidtConstructorInternal(normalization,exponentGas,exponentStars) result(self) !!{ Internal constructor for the {\normalfont \ttfamily extendedSchmidt} star formation surface density rate from disks class. !!} use :: Numerical_Constants_Prefixes, only : giga, mega implicit none - type (starFormationRateSurfaceDensityDisksExtendedSchmidt) :: self - double precision , intent(in ) :: normalization , exponentGas, & - & exponentStars - class (galacticStructureClass ), intent(in ), target :: galacticStructure_ + type (starFormationRateSurfaceDensityDisksExtendedSchmidt) :: self + double precision , intent(in ) :: normalization, exponentGas, & + & exponentStars !![ - + !!] self%lastUniqueID =-1_kind_int8 @@ -159,9 +152,6 @@ subroutine extendedSchmidtDestructor(self) type(starFormationRateSurfaceDensityDisksExtendedSchmidt), intent(inout) :: self if (calculationResetEvent%isAttached(self,extendedSchmidtCalculationReset)) call calculationResetEvent%detach(self,extendedSchmidtCalculationReset) - !![ - - !!] return end subroutine extendedSchmidtDestructor @@ -189,21 +179,25 @@ double precision function extendedSchmidtRate(self,node,radius) \dot{\Sigma}_\star = A \left(x_\mathrm{H} {\Sigma_\mathrm{gas}\over M_\odot \hbox{pc}^{-2}}\right) ^{N_1} \left({\Sigma_{\star}\over M_\odot \hbox{pc}^{-2}}\right)^{N_2}, \end{equation} - where $A=${\normalfont \ttfamily [normalization]} and $N_1=${\normalfont \ttfamily - [exponentGas]}. $N_2=${\normalfont \ttfamily [exponentStars]}. + where $A=${\normalfont \ttfamily [normalization]}, $N_1=${\normalfont \ttfamily + [exponentGas]}, and $N_2=${\normalfont \ttfamily [exponentStars]}. !!} use :: Abundances_Structure , only : abundances - use :: Galactic_Structure_Options, only : componentTypeDisk, coordinateSystemCylindrical, massTypeGaseous, massTypeStellar - use :: Galacticus_Nodes , only : nodeComponentDisk, treeNode + use :: Coordinates , only : coordinateCylindrical, assignment(=) + use :: Galactic_Structure_Options, only : componentTypeDisk , massTypeGaseous, massTypeStellar + use :: Galacticus_Nodes , only : nodeComponentDisk + use :: Mass_Distributions , only : massDistributionClass implicit none class (starFormationRateSurfaceDensityDisksExtendedSchmidt), intent(inout) :: self type (treeNode ), intent(inout) :: node double precision , intent(in ) :: radius class (nodeComponentDisk ), pointer :: disk + class (massDistributionClass ), pointer :: massDistributionGaseous, massDistributionStellar type (abundances ), save :: abundancesFuel !$omp threadprivate(abundancesFuel) - double precision :: massGas , surfaceDensityGas, & + double precision :: massGas , surfaceDensityGas, & & surfaceDensityStellar + type (coordinateCylindrical ) :: coordinates ! Check if node differs from previous one for which we performed calculations. if (node%uniqueID() /= self%lastUniqueID) call self%calculationReset(node,node%uniqueID()) @@ -225,8 +219,15 @@ double precision function extendedSchmidtRate(self,node,radius) return end if ! Get stellar and gas surface densities. - surfaceDensityGas =self%galacticStructure_%surfaceDensity(node,[radius,0.0d0,0.0d0],coordinateSystem=coordinateSystemCylindrical,componentType=componentTypeDisk,massType=massTypeGaseous) - surfaceDensityStellar=self%galacticStructure_%surfaceDensity(node,[radius,0.0d0,0.0d0],coordinateSystem=coordinateSystemCylindrical,componentType=componentTypeDisk,massType=massTypeStellar) + coordinates = [radius,0.0d0,0.0d0] + massDistributionGaseous => node %massDistribution(componentType=componentTypeDisk,massType=massTypeGaseous) + massDistributionStellar => node %massDistribution(componentType=componentTypeDisk,massType=massTypeStellar) + surfaceDensityGas = massDistributionGaseous%surfaceDensity ( coordinates ) + surfaceDensityStellar = massDistributionStellar%surfaceDensity ( coordinates ) + !![ + + + !!] ! Compute the star formation rate surface density. extendedSchmidtRate=+ self%normalization & ! Normalization of the star formation rate. & *((self%hydrogenMassFraction*surfaceDensityGas )**self%exponentGas ) & diff --git a/source/star_formation.timescales.velocity_maximum_scaling.F90 b/source/star_formation.timescales.velocity_maximum_scaling.F90 index dc79bed4b5..aed72ed6b0 100644 --- a/source/star_formation.timescales.velocity_maximum_scaling.F90 +++ b/source/star_formation.timescales.velocity_maximum_scaling.F90 @@ -78,11 +78,11 @@ function velocityMaxScalingConstructorParameters(parameters) result(self) use :: Input_Parameters, only : inputParameter, inputParameters implicit none type (starFormationTimescaleVelocityMaxScaling) :: self - type (inputParameters ), intent(inout) :: parameters - class (cosmologyFunctionsClass ), pointer :: cosmologyFunctions_ - class (darkMatterProfileDMOClass ), pointer :: darkMatterProfileDMO_ - double precision :: timescale , exponentVelocity, & - & exponentRedshift + type (inputParameters ), intent(inout) :: parameters + class (cosmologyFunctionsClass ), pointer :: cosmologyFunctions_ + class (darkMatterProfileDMOClass ), pointer :: darkMatterProfileDMO_ + double precision :: timescale , exponentVelocity, & + & exponentRedshift ! Get parameters of for the timescale calculation. !![ @@ -195,11 +195,13 @@ double precision function velocityMaxScalingTimescale(self,component) !!{ Returns the timescale (in Gyr) for star formation in the {\normalfont \ttfamily component} in the velocity maximum scaling timescale model. !!} - use :: Galacticus_Nodes, only : nodeComponentBasic - implicit none + use :: Galacticus_Nodes , only : nodeComponentBasic + use :: Mass_Distributions, only : massDistributionClass + implicit none class (starFormationTimescaleVelocityMaxScaling), intent(inout) :: self class (nodeComponent ), intent(inout) :: component class (nodeComponentBasic ), pointer :: basic + class (massDistributionClass ), pointer :: massDistribution_ double precision :: expansionFactor, velocityMaximum ! Check if node differs from previous one for which we performed calculations. @@ -207,9 +209,13 @@ double precision function velocityMaxScalingTimescale(self,component) ! Compute the timescale if necessary. if (.not.self%timescaleComputed) then ! Get virial velocity and expansion factor. - basic => component%hostNode%basic ( ) - velocityMaximum = self %darkMatterProfileDMO_%circularVelocityMaximum(component%hostNode ) - expansionFactor = self %cosmologyFunctions_ %expansionFactor (basic %time ()) + massDistribution_ => self %darkMatterProfileDMO_%get (component%hostNode ) + basic => component%hostNode%basic ( ) + velocityMaximum = massDistribution_ %velocityRotationCurveMaximum( ) + expansionFactor = self %cosmologyFunctions_ %expansionFactor (basic %time ()) + !![ + + !!] ! Compute the velocity factor. if (velocityMaximum /= self%velocityMaximumPrevious) then self%velocityMaximumPrevious=velocityMaximum diff --git a/source/statistics.Nbody.halos.mass_errors.SO_halo_finder.F90 b/source/statistics.Nbody.halos.mass_errors.SO_halo_finder.F90 index bd21b29d7a..f36a97dcc7 100644 --- a/source/statistics.Nbody.halos.mass_errors.SO_halo_finder.F90 +++ b/source/statistics.Nbody.halos.mass_errors.SO_halo_finder.F90 @@ -120,13 +120,17 @@ double precision function soHaloFinderErrorFractional(self,node) !!{ Return the fractional error on the mass of an N-body halo in the power-law error model. !!} - use :: Galacticus_Nodes , only : nodeComponentBasic, treeNode + use :: Coordinates , only : coordinateSpherical , assignment(=) + use :: Galacticus_Nodes , only : nodeComponentBasic , treeNode + use :: Mass_Distributions , only : massDistributionClass use :: Numerical_Constants_Math, only : Pi implicit none class (nbodyHaloMassErrorSOHaloFinder), intent(inout) :: self type (treeNode ), intent(inout) :: node class (nodeComponentBasic ), pointer :: basic + class (massDistributionClass ), pointer :: massDistribution_ double precision , parameter :: errorConstant =0.014d0 + type (coordinateSpherical ) :: coordinates double precision :: radiusHalo , densityOuterRadius, & & densityRatioInternalToSurface , particleCount , & & errorFractionalFixedSphere @@ -141,9 +145,14 @@ double precision function soHaloFinderErrorFractional(self,node) errorFractionalFixedSphere = +1.0d0 & & /sqrt(particleCount) ! Get the outer radius of the halo. - radiusHalo = +self%darkMatterHaloScale_ %radiusVirial(node ) + radiusHalo = +self %darkMatterHaloScale_ %radiusVirial(node ) ! Get the density at the edge of the halo. - densityOuterRadius = +self%darkMatterProfileDMO_%density (node,radiusHalo) + massDistribution_ => self %darkMatterProfileDMO_%get (node ) + coordinates = [radiusHalo,0.0d0,0.0d0] + densityOuterRadius = +massDistribution_ %density (coordinates) + !![ + + !!] ! Find the ratio of the mean interior density in the halo to the density at the halo outer radius. densityRatioInternalToSurface = +3.0d0 & & *basic%mass() & diff --git a/source/stellar_feedback.outflows.Creasey2012.F90 b/source/stellar_feedback.outflows.Creasey2012.F90 index eb1446068a..9c6cf9d0ae 100644 --- a/source/stellar_feedback.outflows.Creasey2012.F90 +++ b/source/stellar_feedback.outflows.Creasey2012.F90 @@ -22,7 +22,6 @@ !!} use :: Star_Formation_Rate_Surface_Density_Disks, only : starFormationRateSurfaceDensityDisksClass - use :: Galactic_Structure , only : galacticStructureClass !![ @@ -46,7 +45,6 @@ !!} private class (starFormationRateSurfaceDensityDisksClass), pointer :: starFormationRateSurfaceDensityDisks_ => null() - class (galacticStructureClass ), pointer :: galacticStructure_ => null() double precision :: nu , mu, & & beta0 contains @@ -73,7 +71,6 @@ function creasey2012ConstructorParameters(parameters) result(self) type (stellarFeedbackOutflowsCreasey2012 ) :: self type (inputParameters ), intent(inout) :: parameters class (starFormationRateSurfaceDensityDisksClass), pointer :: starFormationRateSurfaceDensityDisks_ - class (galacticStructureClass ), pointer :: galacticStructure_ double precision :: mu , nu, & & beta0 @@ -100,29 +97,26 @@ function creasey2012ConstructorParameters(parameters) result(self) The parameter $\beta_0$ appearing in the \cite{creasey_how_2012} model for supernovae feedback. - !!] - self=stellarFeedbackOutflowsCreasey2012(mu,nu,beta0,starFormationRateSurfaceDensityDisks_,galacticStructure_) + self=stellarFeedbackOutflowsCreasey2012(mu,nu,beta0,starFormationRateSurfaceDensityDisks_) !![ - !!] return end function creasey2012ConstructorParameters - function creasey2012ConstructorInternal(mu,nu,beta0,starFormationRateSurfaceDensityDisks_,galacticStructure_) result(self) + function creasey2012ConstructorInternal(mu,nu,beta0,starFormationRateSurfaceDensityDisks_) result(self) !!{ Internal constructor for the {\normalfont \ttfamily creasey2012} stellar feedback class. !!} implicit none type (stellarFeedbackOutflowsCreasey2012 ) :: self class (starFormationRateSurfaceDensityDisksClass), intent(in ), target :: starFormationRateSurfaceDensityDisks_ - class (galacticStructureClass ), intent(in ), target :: galacticStructure_ double precision , intent(in ) :: mu , nu, & & beta0 !![ - + !!] return @@ -137,7 +131,6 @@ subroutine creasey2012Destructor(self) !![ - !!] return end subroutine creasey2012Destructor @@ -153,15 +146,18 @@ subroutine creasey2012OutflowRate(self,component,rateStarFormation,rateEnergyInp fraction, $\dot{\Sigma}_\star(r)$ is the surface density of star formation rate, $\beta_0=${\normalfont \ttfamily [beta0]}, $\mu=${\normalfont \ttfamily [mu]}, and $\nu=${\normalfont \ttfamily [nu]}. !!} - use :: Galacticus_Nodes , only : nodeComponentDisk , nodeComponentSpheroid - use :: Numerical_Constants_Math, only : Pi - use :: Numerical_Integration , only : integrator - use :: Stellar_Feedback , only : feedbackEnergyInputAtInfinityCanonical + use :: Galactic_Structure_Options, only : componentTypeDisk , coordinateSystemCylindrical, massTypeGaseous, massTypeStellar + use :: Galacticus_Nodes , only : nodeComponentDisk , nodeComponentSpheroid + use :: Mass_Distributions , only : massDistributionClass + use :: Numerical_Constants_Math , only : Pi + use :: Numerical_Integration , only : integrator + use :: Stellar_Feedback , only : feedbackEnergyInputAtInfinityCanonical implicit none class (stellarFeedbackOutflowsCreasey2012), intent(inout) :: self class (nodeComponent ), intent(inout) :: component double precision , intent(in ) :: rateEnergyInput , rateStarFormation double precision , intent( out) :: rateOutflowEjective , rateOutflowExpulsive + class (massDistributionClass ), pointer :: massDistributionGaseous , massDistributionStellar double precision , parameter :: radiusInnerDimensionless=0.0d0, radiusOuterDimensionless=10.0d0 double precision :: radiusScale , massGas , & & radiusInner , radiusOuter , & @@ -190,15 +186,21 @@ subroutine creasey2012OutflowRate(self,component,rateStarFormation,rateEnergyInp radiusInner=radiusScale*radiusInnerDimensionless radiusOuter=radiusScale*radiusOuterDimensionless ! Compute the outflow rate. - integrator_ =integrator(outflowRateIntegrand,toleranceRelative=1.0d-3) - rateOutflowEjective =+2.0d0 & - & *Pi & - & *self%beta0 & - & *integrator_%integrate(radiusInner,radiusOuter) & - & /rateStarFormation & - & *rateEnergyInput & - & /feedbackEnergyInputAtInfinityCanonical - rateOutflowExpulsive=+0.0d0 + massDistributionGaseous => component%hostNode%massDistribution(componentType=componentTypeDisk,massType=massTypeGaseous) + massDistributionStellar => component%hostNode%massDistribution(componentType=componentTypeDisk,massType=massTypeStellar) + integrator_ = integrator(outflowRateIntegrand,toleranceRelative=1.0d-3) + rateOutflowEjective = +2.0d0 & + & *Pi & + & *self%beta0 & + & *integrator_%integrate(radiusInner,radiusOuter) & + & /rateStarFormation & + & *rateEnergyInput & + & /feedbackEnergyInputAtInfinityCanonical + rateOutflowExpulsive = +0.0d0 + !![ + + + !!] return contains @@ -207,29 +209,19 @@ double precision function outflowRateIntegrand(radius) !!{ Integrand function for the ``Creasey et al. (2012)'' supernovae feedback calculation. !!} - use :: Galactic_Structure_Options , only : componentTypeDisk, coordinateSystemCylindrical, massTypeGaseous, massTypeStellar + use :: Coordinates , only : coordinateCylindrical, assignment(=) use :: Numerical_Constants_Prefixes, only : mega implicit none - double precision, intent(in ) :: radius - double precision :: fractionGas , densitySurfaceRateStarFormation, & - & densitySurfaceGas, densitySurfaceStellar + double precision , intent(in ) :: radius + double precision :: fractionGas , densitySurfaceRateStarFormation, & + & densitySurfaceGas, densitySurfaceStellar + type (coordinateCylindrical) :: coordinates + coordinates=[radius,0.0d0,0.0d0] ! Get gas surface density. - densitySurfaceGas =self%galacticStructure_%surfaceDensity( & - & component%hostNode , & - & [radius ,0.0d0,0.0d0], & - & coordinateSystem= coordinateSystemCylindrical , & - & componentType = componentTypeDisk , & - & massType = massTypeGaseous & - & ) + densitySurfaceGas =massDistributionGaseous%surfaceDensity(coordinates) ! Get stellar surface density. - densitySurfaceStellar=self%galacticStructure_%surfaceDensity( & - & component%hostNode , & - & [radius ,0.0d0,0.0d0], & - & coordinateSystem= coordinateSystemCylindrical , & - & componentType = componentTypeDisk , & - & massType = massTypeStellar & - & ) + densitySurfaceStellar=massDistributionStellar%surfaceDensity(coordinates) ! Compute the gas fraction. fractionGas=+ densitySurfaceGas & & /( & diff --git a/source/stellar_feedback.outflows.velocity_maximum_scaling.F90 b/source/stellar_feedback.outflows.velocity_maximum_scaling.F90 index e473c1e7af..1369a4d8ea 100644 --- a/source/stellar_feedback.outflows.velocity_maximum_scaling.F90 +++ b/source/stellar_feedback.outflows.velocity_maximum_scaling.F90 @@ -151,21 +151,27 @@ subroutine vlctyMxSclngOutflowRate(self,component,rateStarFormation,rateEnergyIn !!{ Returns the outflow rate (in $M_\odot$ Gyr$^{-1}$) for star formation in the given {\normalfont \ttfamily component}. !!} - use :: Galacticus_Nodes, only : nodeComponentBasic + use :: Galacticus_Nodes , only : nodeComponentBasic + use :: Mass_Distributions, only : massDistributionClass implicit none class (stellarFeedbackOutflowsVlctyMxSclng), intent(inout) :: self class (nodeComponent ), intent(inout) :: component double precision , intent(in ) :: rateEnergyInput , rateStarFormation double precision , intent( out) :: rateOutflowEjective, rateOutflowExpulsive class (nodeComponentBasic ), pointer :: basic + class (massDistributionClass ), pointer :: massDistribution_ double precision :: expansionFactor , velocityMaximum !$GLC attributes unused :: rateStarFormation ! Get the basic component. basic => component%hostNode%basic() ! Get virial velocity and expansion factor. - velocityMaximum=self%darkMatterProfileDMO_%circularVelocityMaximum(component%hostNode ) - expansionFactor=self%cosmologyFunctions_ %expansionFactor (basic %time ()) + massDistribution_ => self %darkMatterProfileDMO_%get (component%hostNode ) + velocityMaximum = massDistribution_ %velocityRotationCurveMaximum( ) + expansionFactor = self %cosmologyFunctions_ %expansionFactor (basic %time ()) + !![ + + !!] ! Compute the velocity factor. if (velocityMaximum /= self%velocityPrevious) then self%velocityPrevious = velocityMaximum diff --git a/source/stellar_populations.spectra.postprocess.age_window.F90 b/source/stellar_populations.spectra.postprocess.age_window.F90 index a48f9deeb1..7d6ee201ec 100644 --- a/source/stellar_populations.spectra.postprocess.age_window.F90 +++ b/source/stellar_populations.spectra.postprocess.age_window.F90 @@ -19,11 +19,13 @@ !% An implementation of a spectrum postprocessor that keeps only populations in a specified age window. - !# - !# - !# A stellar population postprocessor class which keeps only emission from populations with ages between {\normalfont \ttfamily [ageMinimum]} and {\normalfont \ttfamily [ageMaximum]}. - !# - !# + !![ + + + A stellar population postprocessor class which keeps only emission from populations with ages between {\normalfont \ttfamily [ageMinimum]} and {\normalfont \ttfamily [ageMaximum]}. + + + !!] type, extends(stellarPopulationSpectraPostprocessorClass) :: stellarPopulationSpectraPostprocessorAgeWindow !% An ageWindow spectrum postprocessor. private @@ -49,18 +51,20 @@ function ageWindowConstructorParameters(parameters) result(self) type (inputParameters ), intent(inout) :: parameters double precision :: ageMinimum, ageMaximum - !# - !# ageMinimum - !# 0.0d0 - !# The minimum age of stellar populations to retain. - !# parameters - !# - !# - !# ageMaximum - !# huge(0.0d0) - !# The maximum age of stellar populations to retain. - !# parameters - !# + !![ + + ageMinimum + 0.0d0 + The minimum age of stellar populations to retain. + parameters + + + ageMaximum + huge(0.0d0) + The maximum age of stellar populations to retain. + parameters + + !!] self=stellarPopulationSpectraPostprocessorAgeWindow(ageMinimum,ageMaximum) return end function ageWindowConstructorParameters @@ -70,8 +74,10 @@ function ageWindowConstructorInternal(ageMinimum,ageMaximum) result(self) implicit none type (stellarPopulationSpectraPostprocessorAgeWindow) :: self double precision , intent(in ) :: ageMinimum, ageMaximum - !# - + !![ + + !!] + return end function ageWindowConstructorInternal diff --git a/source/stellar_populations.standard.F90 b/source/stellar_populations.standard.F90 index 1cb354fa51..19a9fd3f0c 100644 --- a/source/stellar_populations.standard.F90 +++ b/source/stellar_populations.standard.F90 @@ -539,6 +539,7 @@ double precision function standardInterpolate(self,abundances_,ageMinimum,ageMax call file %writeDataset(property%property,char(property%label)) call file %close ( ) !$ call hdf5Access%unset ( ) + call displayIndent('Storing to file: '//fileName,verbosityLevelWorking) end if call File_Unlock(lock) ! Build interpolators. diff --git a/source/structure_formation.accretion_flow.DiemerKravtsov2014.F90 b/source/structure_formation.accretion_flow.DiemerKravtsov2014.F90 deleted file mode 100644 index af263a089b..0000000000 --- a/source/structure_formation.accretion_flow.DiemerKravtsov2014.F90 +++ /dev/null @@ -1,238 +0,0 @@ -!! Copyright 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, -!! 2019, 2020, 2021, 2022, 2023, 2024 -!! Andrew Benson -!! -!! This file is part of Galacticus. -!! -!! Galacticus is free software: you can redistribute it and/or modify -!! it under the terms of the GNU General Public License as published by -!! the Free Software Foundation, either version 3 of the License, or -!! (at your option) any later version. -!! -!! Galacticus is distributed in the hope that it will be useful, -!! but WITHOUT ANY WARRANTY; without even the implied warranty of -!! MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -!! GNU General Public License for more details. -!! -!! You should have received a copy of the GNU General Public License -!! along with Galacticus. If not, see . - - !!{ - An accretion flow class which models the accretion flow using the fitting function of \cite{diemer_dependence_2014}. - !!} - - use :: Cosmology_Functions , only : cosmologyFunctionsClass - use :: Cosmological_Density_Field, only : cosmologicalMassVarianceClass, criticalOverdensityClass - use :: Dark_Matter_Profiles_DMO , only : darkMatterProfileDMOClass - - !![ - - - An accretion flow class which models the accretion flow using the fitting function of - \cite{diemer_dependence_2014}. Specifically, the density profile of the accretion flow is modeled using their equation~(4), - along with fits to the redshift and $\nu$ dependencies of the fitting parameters $b_\mathrm{e}$ and $s_\mathrm{e}$ chosen to - match the results of their figure~18. - - - !!] - type, extends(accretionFlowsClass) :: accretionFlowsDiemerKravtsov2014 - !!{ - An accretion flow class which models the accretion flow using the fitting function of \cite{diemer_dependence_2014}. - !!} - private - class (cosmologyFunctionsClass ), pointer :: cosmologyFunctions_ => null() - class (cosmologicalMassVarianceClass), pointer :: cosmologicalMassVariance_ => null() - class (criticalOverdensityClass ), pointer :: criticalOverdensity_ => null() - class (darkMatterProfileDMOClass ), pointer :: darkMatterProfileDMO_ => null() - double precision :: b0 , s0 , & - & bz , sz , & - & bnu , snu - contains - final :: diemerKravtsov2014Destructor - procedure :: density => diemerKravtsov2014Density - procedure :: velocity => diemerKravtsov2014Velocity - end type accretionFlowsDiemerKravtsov2014 - - interface accretionFlowsDiemerKravtsov2014 - !!{ - Constructors for the {\normalfont \ttfamily diemerKravtsov2014} accretion flows class. - !!} - module procedure diemerKravtsov2014ConstructorParameters - module procedure diemerKravtsov2014ConstructorInternal - end interface accretionFlowsDiemerKravtsov2014 - -contains - - function diemerKravtsov2014ConstructorParameters(parameters) result(self) - !!{ - Constructor for the {\normalfont \ttfamily diemerKravtsov2014} accretion flow class that takes a parameter set as input. - !!} - use :: Input_Parameters, only : inputParameter, inputParameters - implicit none - type (accretionFlowsDiemerKravtsov2014) :: self - type (inputParameters ), intent(inout) :: parameters - class (cosmologyFunctionsClass ), pointer :: cosmologyFunctions_ - class (cosmologicalMassVarianceClass ), pointer :: cosmologicalMassVariance_ - class (criticalOverdensityClass ), pointer :: criticalOverdensity_ - class (darkMatterProfileDMOClass ), pointer :: darkMatterProfileDMO_ - double precision :: b0 , s0 , & - & bz , sz , & - & bnu , snu - - !![ - - b0 - parameters - +1.1250d0 - Derived by Andrew Benson by constructing simple functional forms which fit the plots in figure 18 of \cite{diemer_dependence_2014}. - The parameter $b_0$ in the fitting function $b(\nu,z)=b_0 (1+z)^{b_z} \nu^{b_\nu}$ for the parameter $b(\nu,z)$ appearing in equation (4) of \cite{diemer_dependence_2014}. - - - bz - parameters - +0.625d0 - Derived by Andrew Benson by constructing simple functional forms which fit the plots in figure 18 of \cite{diemer_dependence_2014}. - The parameter $b_z$ in the fitting function $b(\nu,z)=b_0 (1+z)^{b_z} \nu^{b_\nu}$ for the parameter $b(\nu,z)$ appearing in equation (4) of \cite{diemer_dependence_2014}. - - - bnu - parameters - -0.2250d0 - Derived by Andrew Benson by constructing simple functional forms which fit the plots in figure 18 of \cite{diemer_dependence_2014}. - The parameter $b_\nu$ in the fitting function $b(\nu,z)=b_0 (1+z)^{b_z} \nu^{b_\nu}$ for the parameter $b(\nu,z)$ appearing in equation (4) of \cite{diemer_dependence_2014}. - - - s0 - parameters - +1.3925d0 - Derived by Andrew Benson by constructing simple functional forms which fit the plots in figure 18 of \cite{diemer_dependence_2014}. - The parameter $s_0$ in the fitting function $s(\nu,z)=s_0 (1+z)^{s_z} \nu^{s_\nu}$ for the parameter $s(\nu,z)$ appearing in equation (4) of \cite{diemer_dependence_2014}. - - - sz - parameters - -0.199d0 - Derived by Andrew Benson by constructing simple functional forms which fit the plots in figure 18 of \cite{diemer_dependence_2014}. - The parameter $s_z$ in the fitting function $s(\nu,z)=s_0 (1+z)^{s_z} \nu^{s_\nu}$ for the parameter $s(\nu,z)$ appearing in equation (4) of \cite{diemer_dependence_2014}. - - - snu - parameters - +0.0875d0 - Derived by Andrew Benson by constructing simple functional forms which fit the plots in figure 18 of \cite{diemer_dependence_2014}. - The parameter $s_\nu$ in the fitting function $s(\nu,z)=s_0 (1+z)^{s_z} \nu^{s_\nu}$ for the parameter $s(\nu,z)$ appearing in equation (4) of \cite{diemer_dependence_2014}. - - - - - - !!] - self=accretionFlowsDiemerKravtsov2014(b0,bz,bnu,s0,sz,snu,cosmologyFunctions_,cosmologicalMassVariance_,criticalOverdensity_,darkMatterProfileDMO_) - !![ - - - - - - !!] - return - end function diemerKravtsov2014ConstructorParameters - - function diemerKravtsov2014ConstructorInternal(b0,bz,bnu,s0,sz,snu,cosmologyFunctions_,cosmologicalMassVariance_,criticalOverdensity_,darkMatterProfileDMO_) result(self) - !!{ - Internal constructor for the {\normalfont \ttfamily diemerKravtsov2014} accretion flows class. - !!} - implicit none - type (accretionFlowsDiemerKravtsov2014) :: self - class (cosmologyFunctionsClass ), intent(in ), target :: cosmologyFunctions_ - class (cosmologicalMassVarianceClass ), intent(in ), target :: cosmologicalMassVariance_ - class (criticalOverdensityClass ), intent(in ), target :: criticalOverdensity_ - class (darkMatterProfileDMOClass ), intent(in ), target :: darkMatterProfileDMO_ - double precision , intent(in ) :: b0 , s0 , & - & bz , sz , & - & bnu , snu - !![ - - !!] - - return - end function diemerKravtsov2014ConstructorInternal - - subroutine diemerKravtsov2014Destructor(self) - !!{ - Destructor for the {\normalfont \ttfamily diemerKravtsov2014} accretion flows class. - !!} - implicit none - type(accretionFlowsDiemerKravtsov2014), intent(inout) :: self - - !![ - - - - - !!] - return - end subroutine diemerKravtsov2014Destructor - - double precision function diemerKravtsov2014Density(self,node,radius) - !!{ - Compute the density of the accretion flow at the given radius. - !!} - use :: Galacticus_Nodes, only : nodeComponentBasic - implicit none - class (accretionFlowsDiemerKravtsov2014), intent(inout) :: self - type (treeNode ), intent(inout) :: node - double precision , intent(in ) :: radius - class (nodeComponentBasic ), pointer :: basic - double precision :: time , mass , & - & radius200Mean, densityMean, & - & nu , redshift , & - & b , s - - ! Extract basic quantities for the halo. - basic => node %basic() - time = basic%time () - mass = basic%mass () - ! Evaluate the control parameters. - redshift=+self%cosmologyFunctions_ %redshiftFromExpansionFactor( & - & self%cosmologyFunctions_%expansionFactor (time=time ) & - & ) - nu =+self%criticalOverdensity_ %value (time=time,mass=mass,node=node) & - & /self%cosmologicalMassVariance_%rootVariance (time=time,mass=mass ) - ! Evaluate the parameters of the fitting function. These fits were derived by Andrew Benson by constructing simple functional - ! forms which fit the plots in figure 18 of Diemer & Kravtsov (2014). There is no guarantee that these fits will perform - ! sensibly outside the range of that plot (and, of course, they are only approximate even within the range of that plot). - b=+self%b0*(1.0+redshift)**self%bz*nu**self%bnu - s=+self%s0*(1.0+redshift)**self%sz*nu**self%snu - ! Find the radius enclosing 200 times the mean density. - densityMean =self%cosmologyFunctions_ %matterDensityEpochal (time ) - radius200Mean=self%darkMatterProfileDMO_%radiusEnclosingDensity(node,+200.0d0*densityMean) - ! Evaluate equation (4) from Diemer & Kravtsov (2014). - diemerKravtsov2014Density=+densityMean & - & *( & - & +1.0d0 & - & +b & - & /( & - & +radius & - & /5.0d0 & - & /radius200Mean & - & )**s & - & ) - return - end function diemerKravtsov2014Density - - double precision function diemerKravtsov2014Velocity(self,node,radius) - !!{ - Compute the velocity of the accretion flow at the given radius. - !!} - use :: Error, only : Error_Report - implicit none - class (accretionFlowsDiemerKravtsov2014), intent(inout) :: self - type (treeNode ), intent(inout) :: node - double precision , intent(in ) :: radius - !$GLC attributes unused :: self, node, radius - - diemerKravtsov2014Velocity=0.0d0 - call Error_Report('velocity is unsupported'//{introspection:location}) - return - end function diemerKravtsov2014Velocity diff --git a/source/structure_formation.accretion_flow.F90 b/source/structure_formation.accretion_flow.F90 deleted file mode 100644 index b403d4fdae..0000000000 --- a/source/structure_formation.accretion_flow.F90 +++ /dev/null @@ -1,54 +0,0 @@ -!! Copyright 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, -!! 2019, 2020, 2021, 2022, 2023, 2024 -!! Andrew Benson -!! -!! This file is part of Galacticus. -!! -!! Galacticus is free software: you can redistribute it and/or modify -!! it under the terms of the GNU General Public License as published by -!! the Free Software Foundation, either version 3 of the License, or -!! (at your option) any later version. -!! -!! Galacticus is distributed in the hope that it will be useful, -!! but WITHOUT ANY WARRANTY; without even the implied warranty of -!! MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -!! GNU General Public License for more details. -!! -!! You should have received a copy of the GNU General Public License -!! along with Galacticus. If not, see . - -!!{ -Contains a module which provides a class implementing models of accretion flows onto dark matter halos. -!!} - -module Spherical_Collapse_Accretion_Flows - !!{ - Provides a class implementing models of accretion flows onto dark matter halos. - !!} - use :: Galacticus_Nodes, only : treeNode - private - - !![ - - accretionFlows - Accretion Flows onto Dark Matter Halos - Class providing models of accretion flows onto dark matter halos. - shi2016 - - Compute the density of the accretion flow at the given radius. - double precision - yes - type (treeNode), intent(inout) :: node - double precision , intent(in ) :: radius - - - Compute the velocity of the accretion flow at the given radius. - double precision - yes - type (treeNode), intent(inout) :: node - double precision , intent(in ) :: radius - - - !!] - -end module Spherical_Collapse_Accretion_Flows diff --git a/source/structure_formation.accretion_flow.Lam2013.F90 b/source/structure_formation.accretion_flow.Lam2013.F90 deleted file mode 100644 index eb53262c41..0000000000 --- a/source/structure_formation.accretion_flow.Lam2013.F90 +++ /dev/null @@ -1,221 +0,0 @@ -!! Copyright 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, -!! 2019, 2020, 2021, 2022, 2023, 2024 -!! Andrew Benson -!! -!! This file is part of Galacticus. -!! -!! Galacticus is free software: you can redistribute it and/or modify -!! it under the terms of the GNU General Public License as published by -!! the Free Software Foundation, either version 3 of the License, or -!! (at your option) any later version. -!! -!! Galacticus is distributed in the hope that it will be useful, -!! but WITHOUT ANY WARRANTY; without even the implied warranty of -!! MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -!! GNU General Public License for more details. -!! -!! You should have received a copy of the GNU General Public License -!! along with Galacticus. If not, see . - - !!{ - An accretion flow class which models the accretion flow using the model of \cite{lam_modeling_2013}. - !!} - - use :: Cosmology_Functions , only : cosmologyFunctionsClass - use :: Cosmological_Density_Field , only : criticalOverdensityClass - use :: Dark_Matter_Halo_Scales , only : darkMatterHaloScaleClass - use :: Correlation_Functions_Two_Point, only : correlationFunctionTwoPointClass - use :: Dark_Matter_Halo_Biases , only : darkMatterHaloBiasClass - use :: Linear_Growth , only : linearGrowthClass - - - !![ - - - An accretion flow class using the model of \cite{lam_modeling_2013}. - - - !!] - type, extends(accretionFlowsClass) :: accretionFlowsLam2013 - !!{ - An accretion flow class which models the accretion flow using the model of \cite{lam_modeling_2013}. - !!} - private - class (cosmologyFunctionsClass ), pointer :: cosmologyFunctions_ => null() - class (criticalOverdensityClass ), pointer :: criticalOverdensity_ => null() - class (darkMatterHaloBiasClass ), pointer :: darkMatterHaloBias_ => null() - class (darkMatterHaloScaleClass ), pointer :: darkMatterHaloScale_ => null() - class (correlationFunctionTwoPointClass), pointer :: correlationFunctionTwoPoint_ => null() - class (linearGrowthClass ), pointer :: linearGrowth_ => null() - double precision :: scaleFactorVelocity - contains - final :: lam2013Destructor - procedure :: density => lam2013Density - procedure :: velocity => lam2013Velocity - end type accretionFlowsLam2013 - - interface accretionFlowsLam2013 - !!{ - Constructors for the {\normalfont \ttfamily lam2013} accretion flows class. - !!} - module procedure lam2013ConstructorParameters - module procedure lam2013ConstructorInternal - end interface accretionFlowsLam2013 - -contains - - function lam2013ConstructorParameters(parameters) result(self) - !!{ - Constructor for the {\normalfont \ttfamily lam2013} accretion flow class that takes a parameter set as input. - !!} - use :: Input_Parameters, only : inputParameter, inputParameters - implicit none - type (accretionFlowsLam2013) :: self - type (inputParameters ), intent(inout) :: parameters - class (cosmologyFunctionsClass ), pointer :: cosmologyFunctions_ - class (criticalOverdensityClass ), pointer :: criticalOverdensity_ - class (darkMatterHaloBiasClass ), pointer :: darkMatterHaloBias_ - class (darkMatterHaloScaleClass ), pointer :: darkMatterHaloScale_ - class (correlationFunctionTwoPointClass), pointer :: correlationFunctionTwoPoint_ - class (linearGrowthClass ), pointer :: linearGrowth_ - double precision :: scaleFactorVelocity - - !![ - - scaleFactorVelocity - parameters - 1.0d0 - A scale factor to be applied to inflow velocities. - - - - - - - - !!] - self=accretionFlowsLam2013(scaleFactorVelocity,cosmologyFunctions_,criticalOverdensity_,darkMatterHaloBias_,darkMatterHaloScale_,correlationFunctionTwoPoint_,linearGrowth_) - !![ - - - - - - - - !!] - return - end function lam2013ConstructorParameters - - function lam2013ConstructorInternal(scaleFactorVelocity,cosmologyFunctions_,criticalOverdensity_,darkMatterHaloBias_,darkMatterHaloScale_,correlationFunctionTwoPoint_,linearGrowth_) result(self) - !!{ - Internal constructor for the {\normalfont \ttfamily lam2013} accretion flows class. - !!} - implicit none - type (accretionFlowsLam2013 ) :: self - class (cosmologyFunctionsClass ), intent(in ), target :: cosmologyFunctions_ - class (criticalOverdensityClass ), intent(in ), target :: criticalOverdensity_ - class (darkMatterHaloBiasClass ), intent(in ), target :: darkMatterHaloBias_ - class (darkMatterHaloScaleClass ), intent(in ), target :: darkMatterHaloScale_ - class (correlationFunctionTwoPointClass), intent(in ), target :: correlationFunctionTwoPoint_ - class (linearGrowthClass ), intent(in ), target :: linearGrowth_ - double precision , intent(in ) :: scaleFactorVelocity - !![ - - !!] - - return - end function lam2013ConstructorInternal - - subroutine lam2013Destructor(self) - !!{ - Destructor for the {\normalfont \ttfamily lam2013} accretion flows class. - !!} - implicit none - type(accretionFlowsLam2013), intent(inout) :: self - - !![ - - - - - - - !!] - return - end subroutine lam2013Destructor - - double precision function lam2013Density(self,node,radius) - !!{ - Compute the density of the accretion flow at the given radius. - !!} - use :: Error, only : Error_Report - implicit none - class (accretionFlowsLam2013), intent(inout) :: self - type (treeNode ), intent(inout) :: node - double precision , intent(in ) :: radius - !$GLC attributes unused :: self, node, radius - - lam2013Density=0.0d0 - call Error_Report('density is unsupported'//{introspection:location}) - return - end function lam2013Density - - double precision function lam2013Velocity(self,node,radius) - !!{ - Compute the mean radial velocity of the accretion flow at the given radius. - !!} - use :: Galacticus_Nodes , only : nodeComponentBasic - use :: Numerical_Constants_Math, only : Pi - implicit none - class (accretionFlowsLam2013), intent(inout) :: self - type (treeNode ), intent(inout) :: node - double precision , intent(in ) :: radius - class (nodeComponentBasic ), pointer :: basic - double precision :: time , overdensityCritical, & - & radiusVirial , massShell , & - & densityContrastNonLinear - - basic => node %basic ( ) - time = basic %time ( ) - overdensityCritical = self %criticalOverdensity_%value (time) - radiusVirial = self %darkMatterHaloScale_%radiusVirial (node) - ! Evaluate the mass in the shell outside the halo virial radius using equation (B4) of Lam et al. (2013). - if (radius > radiusVirial) then - massShell = +self %cosmologyFunctions_ %matterDensityEpochal(time) & - & *4.0d0 & - & *Pi & - & /3.0d0 & - & *( & - & +radius **3*(1.0d0+self%darkMatterHaloBias_%bias(node,radius )*self%correlationFunctionTwoPoint_%correlationVolumeAveraged(radius ,time)) & - & -radiusVirial**3*(1.0d0+self%darkMatterHaloBias_%bias(node,radiusVirial)*self%correlationFunctionTwoPoint_%correlationVolumeAveraged(radiusVirial,time)) & - & ) - else - massShell=0.0d0 - end if - ! Compute the nonlinear density contrast using equation (B1) of Lam et al. (2013). - densityContrastNonlinear = +( & - & +basic%mass () & - & + massShell & - & ) & - & /self%cosmologyFunctions_%matterDensityEpochal (time) & - & /( & - & +4.0d0 & - & *Pi & - & /3.0d0 & - & *radius**3 & - & ) - ! Evaluate the inflow velocity in the spherical collapse model using equation (B2) of Lam et al. (2013). - lam2013Velocity = -self%scaleFactorVelocity & - & *self%cosmologyFunctions_%hubbleParameterEpochal (time) & - & *radius & - & *self%cosmologyFunctions_%expansionFactor (time) & - & *self%linearGrowth_ %logarithmicDerivativeExpansionFactor(time) & - & /3.0d0 & - & * overdensityCritical & - & *( & - & +densityContrastNonLinear**(1.0d0/overdensityCritical) & - & -1.0d0 & - & ) - return - end function lam2013Velocity diff --git a/source/structure_formation.accretion_flow.Shi2016.F90 b/source/structure_formation.accretion_flow.Shi2016.F90 deleted file mode 100644 index 34bc387bc8..0000000000 --- a/source/structure_formation.accretion_flow.Shi2016.F90 +++ /dev/null @@ -1,805 +0,0 @@ -!! Copyright 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, -!! 2019, 2020, 2021, 2022, 2023, 2024 -!! Andrew Benson -!! -!! This file is part of Galacticus. -!! -!! Galacticus is free software: you can redistribute it and/or modify -!! it under the terms of the GNU General Public License as published by -!! the Free Software Foundation, either version 3 of the License, or -!! (at your option) any later version. -!! -!! Galacticus is distributed in the hope that it will be useful, -!! but WITHOUT ANY WARRANTY; without even the implied warranty of -!! MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -!! GNU General Public License for more details. -!! -!! You should have received a copy of the GNU General Public License -!! along with Galacticus. If not, see . - - !!{ - An accretion flow class using the framework of \cite{shi_outer_2016}. - !!} - - use :: Cosmology_Functions , only : cosmologyFunctionsClass - use :: Cosmology_Parameters , only : cosmologyParametersClass - use :: Dark_Matter_Halo_Mass_Accretion_Histories, only : darkMatterHaloMassAccretionHistoryClass - use :: Dark_Matter_Halo_Scales , only : darkMatterHaloScaleClass - use :: Numerical_Interpolation , only : gsl_interp_cspline , interpolator - use :: Spherical_Collapse_Solvers , only : sphericalCollapseSolverClass - - ! Note: Throughout this class the following acronyms are used: - ! * HRTA - "half radius turnaround" - i.e. half of the turnaround radius for a given shell (more precisely, we use the - ! ratio of the virial radius to turnaround radius determined by spherical collapse models - this is previously - ! 1/2 for an Einstein-de Sitter universe, but differs by a small amount for other cosmologies). - - ! Note: Throughout this class different three separate unit systems are used, identified by the variable name suffix: - ! * Scaled - these correspond to the scaled, self-similar variables used in Appendix A of Shi (2016) - i.e. column 3 of Table A1, "y" for radius, etc. - ! * Original - these correspond to the original variables used in Appendix A of Shi (2016) - i.e. column 2 of Table A1, "R" for radius, etc. - ! * ScaleHRTA - these correspond to the HRTA unit system - that is, quantities are scaled to Rₕᵣₜₐ(a) and Mₕᵣₜₐ(a). - ! * Physical - these correspond to physical units (Mpc, M☉, km/s). - - !![ - - An accretion flow class using the framework of \cite{shi_outer_2016}. - - !!] - type, extends(accretionFlowsClass) :: accretionFlowsShi2016 - !!{ - An accretion flow class using the framework of \cite{shi_outer_2016}. - !!} - private - class (darkMatterHaloScaleClass ), pointer :: darkMatterHaloScale_ => null() - class (darkMatterHaloMassAccretionHistoryClass), pointer :: darkMatterHaloMassAccretionHistory_ => null() - class (cosmologyFunctionsClass ), pointer :: cosmologyFunctions_ => null() - class (cosmologyParametersClass ), pointer :: cosmologyParameters_ => null() - class (sphericalCollapseSolverClass ), pointer :: sphericalCollapseSolver_ => null() - type (interpolator ), allocatable :: interpolatorDensityPhysical , interpolatorVelocityPhysical , & - & interpolatorScaleFactorHalfRadiusTurnaroundScaled , interpolatorRadiusScaled , & - & interpolatorRadiusTurnaroundOriginal , interpolatorRadiusHRTAOriginal , & - & interpolatorRadiusComovingInitialOriginal , interpolatorMassTurnaroundScaled , & - & interpolatorMassHRTAOriginal , interpolatorMassMultiStreamScaleHRTA - double precision , allocatable, dimension(:) :: radiusScaled , overdensityScaled , & - & expansionFactorHRTAScaled , radiusGrowthRateScaled , & - & radiusComovingInitialOriginal , massEnclosedInitialOriginal , & - & timeTurnaroundScaled , timeHRTAScaled , & - & radiusTurnaroundScaled , radiusOrderedOriginal , & - & massShellOrderedOriginal , densityOrderedOriginal , & - & massEnclosedOrderedOriginal - double precision :: radiusMaximumPhysical , timePreviousPhysical , & - & massPreviousPhysical , growthIndexPrevious , & - & expansionFactorScaled , radiusMultistreamMinimumScaledHRTA , & - & radiusMultistreamMaximumScaledHRTA , cosmologicalConstantScaled , & - & radiusTurnaroundNowOriginal , radiusHRTANowOriginal , & - & timeNowScaled , radiusSplashbackTurnaround , & - & ratioRadiusSplashbackHRTA , radiusSplashbackOriginal , & - & radiusSplashbackScaled , radiusMinimumPhysical , & - & scaleFactorVelocity - contains - !![ - - - - !!] - final :: shi2016Destructor - procedure :: density => shi2016Density - procedure :: velocity => shi2016Velocity - procedure :: solve => shi2016Solve - end type accretionFlowsShi2016 - - interface accretionFlowsShi2016 - !!{ - Constructors for the {\normalfont \ttfamily shi2016} accretion flows class. - !!} - module procedure shi2016ConstructorParameters - module procedure shi2016ConstructorInternal - end interface accretionFlowsShi2016 - - ! Sub-module scope variables used in root finding. - double precision :: timeTurnaroundScaled__, radiusTurnaroundScaled__, ratioRadiusTurnaroundVirial - !$omp threadprivate(timeTurnaroundScaled__, radiusTurnaroundScaled__, ratioRadiusTurnaroundVirial) - - ! Sub-module scope variables used in ODE solving. - double precision :: radiusComovingInitialOriginal , massEnclosedInitialOriginal - class (accretionFlowsShi2016), pointer :: self_ - logical :: noShellCrossing =.false. - !$omp threadprivate(self_,massEnclosedInitialOriginal,radiusComovingInitialOriginal,noShellCrossing) - -contains - - function shi2016ConstructorParameters(parameters) result(self) - !!{ - Constructor for the {\normalfont \ttfamily shi2016} accretion flow class that takes a parameter set as input. - !!} - use :: Input_Parameters, only : inputParameter, inputParameters - implicit none - type (accretionFlowsShi2016 ) :: self - type (inputParameters ), intent(inout) :: parameters - class (cosmologyFunctionsClass ), pointer :: cosmologyFunctions_ - class (cosmologyParametersClass ), pointer :: cosmologyParameters_ - class (darkMatterHaloMassAccretionHistoryClass), pointer :: darkMatterHaloMassAccretionHistory_ - class (darkMatterHaloScaleClass ), pointer :: darkMatterHaloScale_ - class (sphericalCollapseSolverClass ), pointer :: sphericalCollapseSolver_ - double precision :: scaleFactorVelocity - - !![ - - scaleFactorVelocity - parameters - 1.0d0 - A scale factor to be applied to inflow velocities. - - - - - - - !!] - self=accretionFlowsShi2016(scaleFactorVelocity,cosmologyParameters_,cosmologyFunctions_,darkMatterHaloMassAccretionHistory_,darkMatterHaloScale_,sphericalCollapseSolver_) - !![ - - - - - - !!] - return - end function shi2016ConstructorParameters - - function shi2016ConstructorInternal(scaleFactorVelocity,cosmologyParameters_,cosmologyFunctions_,darkMatterHaloMassAccretionHistory_,darkMatterHaloScale_,sphericalCollapseSolver_) result(self) - !!{ - Internal constructor for the {\normalfont \ttfamily shi2016} accretion flows class. - !!} - use :: Numerical_Comparison, only : Values_Agree - implicit none - type (accretionFlowsShi2016 ) :: self - class (cosmologyFunctionsClass ), intent(in ), target :: cosmologyFunctions_ - class (cosmologyParametersClass ), intent(in ), target :: cosmologyParameters_ - class (darkMatterHaloMassAccretionHistoryClass), intent(in ), target :: darkMatterHaloMassAccretionHistory_ - class (darkMatterHaloScaleClass ), intent(in ), target :: darkMatterHaloScale_ - class (sphericalCollapseSolverClass ), intent(in ), target :: sphericalCollapseSolver_ - double precision , intent(in ) :: scaleFactorVelocity - !![ - - !!] - - ! Validate cosmology. - if (.not.Values_Agree(self%cosmologyParameters_%OmegaCurvature (),+0.0d0,absTol=1.0d-3)) & - & call Error_Report('this class is applicable only for flat Ω+Λ=1 universes'//{introspection:location}) - if (.not.Values_Agree(self%cosmologyFunctions_ %equationOfStateDarkEnergy(),-1.0d0,absTol=1.0d-3)) & - & call Error_Report('this class is applicable only for flat Ω+Λ=1 universes'//{introspection:location}) - if (.not.Values_Agree(self%cosmologyFunctions_ %exponentDarkEnergy (),+0.0d0,absTol=1.0d-3)) & - & call Error_Report('this class is applicable only for flat Ω+Λ=1 universes'//{introspection:location}) - ! Set previous state to unphysical values. - self%timePreviousPhysical =-huge(0.0d0) - self%massPreviousPhysical =-huge(0.0d0) - self%growthIndexPrevious=-huge(0.0d0) - return - end function shi2016ConstructorInternal - - subroutine shi2016Destructor(self) - !!{ - Destructor for the {\normalfont \ttfamily shi2016} accretion flows class. - !!} - implicit none - type(accretionFlowsShi2016), intent(inout) :: self - - !![ - - - - - - !!] - return - end subroutine shi2016Destructor - - double precision function shi2016Density(self,node,radius) - !!{ - Compute the density of the accretion flow at the given radius. - !!} - use :: Error , only : Error_Report - use :: Galacticus_Nodes, only : nodeComponentBasic - implicit none - class (accretionFlowsShi2016), intent(inout) :: self - type (treeNode ), intent(inout) :: node - double precision , intent(in ) :: radius - class (nodeComponentBasic ), pointer :: basic - - call self%solve(node) - if (radius > self%radiusMaximumPhysical) then - ! Beyond the maximum radius for the flow just return the mean matter density. - basic => node%basic ( ) - shi2016Density = self%cosmologyFunctions_ %matterDensityEpochal(basic%time()) - else if (radius < self%radiusMinimumPhysical) then - shi2016Density = 0.0d0 - call Error_Report('radius is less than minimum tabulated for accretion flow'//{introspection:location}) - else - shi2016Density = self%interpolatorDensityPhysical%interpolate ( radius) - end if - return - end function shi2016Density - - double precision function shi2016Velocity(self,node,radius) - !!{ - Compute the velocity of the accretion flow at the given radius. - !!} - use :: Error , only : Error_Report - use :: Galacticus_Nodes, only : nodeComponentBasic - implicit none - class (accretionFlowsShi2016), intent(inout) :: self - type (treeNode ), intent(inout) :: node - double precision , intent(in ) :: radius - class (nodeComponentBasic ), pointer :: basic - - call self%solve(node) - if (radius > self%radiusMaximumPhysical) then - ! Beyond the maximum radius for the flow just return the mean matter velocity. - basic => node%basic() - shi2016Velocity = +self%cosmologyFunctions_ %hubbleParameterEpochal(basic%time()) & - & *radius - else if (radius < self%radiusMinimumPhysical) then - shi2016Velocity = 0.0d0 - call Error_Report('radius is less than minimum tabulated for accretion flow'//{introspection:location}) - else - shi2016Velocity = self%interpolatorVelocityPhysical%interpolate ( radius) - end if - shi2016Velocity=+shi2016Velocity & - & *self%scaleFactorVelocity - return - end function shi2016Velocity - - subroutine shi2016Solve(self,node) - !!{ - Solve the accretion flow. - !!} - use :: Array_Utilities , only : Array_Reverse - use :: Display , only : displayCounter , displayCounterClear , displayIndent , displayUnindent, & - & verbosityLevelWorking - use :: Elliptic_Integrals , only : Elliptic_Integral_K , Elliptic_Integral_Pi - use :: Error , only : Error_Report - use :: Galacticus_Nodes , only : nodeComponentBasic - use :: ISO_Varying_String , only : var_str - use :: Numerical_Comparison , only : Values_Differ - use :: Numerical_Constants_Astronomical, only : gigaYear , megaParsec - use :: Numerical_Constants_Math , only : Pi - use :: Numerical_Constants_Prefixes , only : kilo - use :: Numerical_Ranges , only : Make_Range , rangeTypeLogarithmic - use :: Root_Finder , only : rangeExpandMultiplicative, rangeExpandSignExpectNegative, rangeExpandSignExpectPositive, rootFinder - use :: Sorting , only : sortIndex - use :: String_Handling , only : operator(//) - use :: Tables , only : table1D - implicit none - class (accretionFlowsShi2016), intent(inout), target :: self - type (treeNode ), intent(inout) :: node - double precision , allocatable , dimension(:) :: radiusOriginal , densityOriginal , & - & velocityOriginal , radiusSingleStreamAnalyticPhysical , & - & velocitySingleStreamAnalyticPhysical , densitySingleStreamAnalyticPhysical , & - & expansionFactorHRTAScaled , radiusPhysical , & - & velocityPhysical , densityPhysical - integer (c_size_t ), allocatable , dimension(:) :: order - class (nodeComponentBasic ), pointer :: basic - type (interpolator ), allocatable :: interpolatorMassMultiStreamNewScaleHRTA - class (table1D ), allocatable :: ratioRadiusTurnaroundVirialTable - integer , parameter :: countRadii =1000 - integer , parameter :: countCompare = 10 - integer , parameter :: iterationMaximum = 30 - double precision , parameter :: expansionFactorRelativeInitial =1.0d-6 - double precision , parameter :: radiusMultiStreamFractionalSmall =1.0d-6 - double precision , parameter :: multistreamToleranceRelative =5.0d-2 - double precision :: expansionFactorOriginal , timeInitialScaled , & - & radiusInitialScaled , radiusGrowthRateInitialScaled , & - & overdensityMinimumScaled , overdensityMaximumScaled , & - & bigA , bigB , & - & bigC , expansionFactorScaledInitial , & - & massVirialOriginal , radiusVirialOriginal , & - & growthIndex , radiusScaleHRTA , & - & radiusMultistreamMinimumNewScaledHRTA , radiusMultistreamMaximumNewScaledHRTA, & - & radiusCompareMinimumScaledHRTA , radiusCompareMaximumScaledHRTA , & - & massMultiStream , massMultiStreamNew , & - & changeRelative , changeRelativeMaximum , & - & radiusSplashbackPhysical , h - integer :: i , j , & - & iTurnaround , iFirstZero , & - & iVirial , iteration - type (rootFinder ) :: finder - logical :: firstZeroFound , multistreamConverged - character (len=12 ) :: label - - ! Extract basic properties. - basic => node%basic() - ! The growth index specifies the profile of the initial mass perturbation, δMᵢ/Mᵢ ∝ M^{-1/s}, or, equivalently, the growth rate, - ! M(t) ∝ aˢ. - growthIndex=+self%darkMatterHaloMassAccretionHistory_%massAccretionRate(node, basic%time()) & - & /self%cosmologyFunctions_ %expansionRate ( self%cosmologyFunctions_%expansionFactor(basic%time())) & - & / basic%mass() - ! Determine if we need to updated the scaled solution. - if ( & - & basic%time() /= self%timePreviousPhysical & - & .or. & - & growthIndex /= self%growthIndexPrevious & - & ) then - ! Time has changed - we must recompute the scale-free solution. - self%timePreviousPhysical=basic%time() - self%growthIndexPrevious =growthIndex - self%massPreviousPhysical=-huge(0.0d0) - if (allocated(self%overdensityScaled )) deallocate(self%overdensityScaled ) - if (allocated(self%radiusScaled )) deallocate(self%radiusScaled ) - if (allocated(self%radiusGrowthRateScaled )) deallocate(self%radiusGrowthRateScaled ) - if (allocated(self%expansionFactorHRTAScaled )) deallocate(self%expansionFactorHRTAScaled ) - if (allocated(self%radiusComovingInitialOriginal)) deallocate(self%radiusComovingInitialOriginal) - if (allocated(self%massEnclosedInitialOriginal )) deallocate(self%massEnclosedInitialOriginal ) - if (allocated(self%radiusTurnaroundScaled )) deallocate(self%radiusTurnaroundScaled ) - if (allocated(self%timeTurnaroundScaled )) deallocate(self%timeTurnaroundScaled ) - if (allocated(self%radiusOrderedOriginal )) deallocate(self%radiusOrderedOriginal ) - if (allocated(self%massShellOrderedOriginal )) deallocate(self%massShellOrderedOriginal ) - if (allocated(self%massEnclosedOrderedOriginal )) deallocate(self%massEnclosedOrderedOriginal ) - if (allocated(self%densityOrderedOriginal )) deallocate(self%densityOrderedOriginal ) - if (allocated(self%timeHRTAScaled )) deallocate(self%timeHRTAScaled ) - allocate(self%overdensityScaled (countRadii)) - allocate(self%radiusScaled (countRadii)) - allocate(self%radiusGrowthRateScaled (countRadii)) - allocate(self%expansionFactorHRTAScaled (countRadii)) - allocate(self%radiusComovingInitialOriginal(countRadii)) - allocate(self%massEnclosedInitialOriginal (countRadii)) - allocate(self%radiusTurnaroundScaled (countRadii)) - allocate(self%timeTurnaroundScaled (countRadii)) - allocate(self%radiusOrderedOriginal (countRadii)) - allocate(self%massShellOrderedOriginal (countRadii)) - allocate(self%massEnclosedOrderedOriginal (countRadii)) - allocate(self%densityOrderedOriginal (countRadii)) - allocate(self%timeHRTAScaled (countRadii)) - allocate( order (countRadii)) - allocate( expansionFactorHRTAScaled (countRadii)) - allocate( radiusOriginal (countRadii)) - ! Create a module-scope pointer to self for use in ODE solver functions. - self_ => self - ! Find the ratio of turnaround to virial radius. - call self%sphericalCollapseSolver_%radiusTurnaround(time=basic%time(),tableStore=.false.,radiusTurnaround_=ratioRadiusTurnaroundVirialTable) - ratioRadiusTurnaroundVirial=ratioRadiusTurnaroundVirialTable%interpolate(basic%time()) - ! Compute the scaled cosmological constant parameter ("w" in the notation of Shi 2016, Table A1). - self%cosmologicalConstantScaled=+1.0d0/self%cosmologyFunctions_ %OmegaMatterEpochal(basic%time()) & - & -1.0d0 - ! Compute expansion factor, and scaled expansion factor ("y" in the notation of Shi 2016, Table A1). - expansionFactorOriginal =+self%cosmologyFunctions_ %expansionFactor (basic%time()) - self%expansionFactorScaled =+self%cosmologicalConstantScaled **(1.0d0/3.0d0) & - & *expansionFactorOriginal - ! Find the scaled time at the current epoch. - self%timeNowScaled =+sqrt(1.0d0-self %cosmologyFunctions_%OmegaMatterEpochal(basic%time() )) & ! Equation A5 from Shi (2016). - & * self %cosmologyFunctions_%expansionRate (expansionFactorOriginal) & - & * basic%time ( ) - ! Choose an initial epoch and (scaled) radius for the ODE. This is chosen to be an expansion factor much smaller than the - ! present day such that the perturbations will be small. - expansionFactorScaledInitial =+ expansionFactorRelativeInitial & - & *self%expansionFactorScaled - radiusInitialScaled =+ expansionFactorScaledInitial - ! Choose a value of the scaled overdensity, "β" in the notation of Shi (2016), identifying a mass shell. We avoid β=1 because such a shell never collapses. - overdensityMinimumScaled =+1.001d0 - ! Choose a maximum overdensity. We use β=10 here as it's more than sufficient to allow most of the accretion stream to be captured, - overdensityMaximumScaled =+1.000d1 - ! Build array of overdensities. - self%overdensityScaled =Make_Range(overdensityMaximumScaled,overdensityMinimumScaled,countRadii,rangeTypeLogarithmic) - ! Build a root finder which will be used for finding the time at half the turnaround radius. - finder=rootFinder( & - & rootFunction =halfRadiusTurnAroundRoot, & - & toleranceAbsolute=0.0d+0 , & - & toleranceRelative=1.0d-3 & - & ) - ! Find turnaround radius and mass as a function of time, along with epoch at which half the turnaround radius is - ! reached. This is all in scale-free units. - do i=1,countRadii - ! Find the epoch of turnaround. - self%radiusTurnaroundScaled(i)=+2.0d0**(2.0d0/3.0d0) & ! Equation A9 from Shi (2016). - & * sqrt( self%overdensityScaled(i) ) & - & *sin((1.0d0/3.0d0)*asin(1.0d0/self%overdensityScaled(i)**1.5d0)) - bigA =+1.0d0 & ! Text after equation of A12 from Shi (2016). - & /self%radiusTurnaroundScaled(i)**3 - bigB =+2.0d0 & ! Text after equation of A12 from Shi (2016). - & / ( +3.0d0+sqrt(1.0d0+4.0d0*bigA) ) - bigC =+ sqrt(1.0d0+4.0d0*bigA) & ! Text after equation of A12 from Shi (2016). - & / (bigA-0.5d0+sqrt(1.0d0+4.0d0*bigA)/2.0d0) - self%timeTurnaroundScaled (i)=+ ( +1.0d0+sqrt(1.0d0+4.0d0*bigA) ) & ! Equation A12 of Shi (2016). - & /sqrt(bigA-0.5d0+sqrt(1.0d0+4.0d0*bigA)/2.0d0) & - & *( & - & +Elliptic_Integral_Pi(bigC,bigB) & - & -Elliptic_Integral_K (bigC ) & - & ) - ! Find the epoch corresponding to reaching half of the turnaround radius. - radiusTurnaroundScaled__=self%radiusTurnaroundScaled(i) - timeTurnaroundScaled__ =self%timeTurnaroundScaled (i) - call finder%rangeExpand( & - & rangeExpandUpward =1.1d0 , & - & rangeExpandDownward =0.5d0 , & - & rangeExpandType =rangeExpandMultiplicative , & - & rangeExpandUpwardSignExpect =rangeExpandSignExpectNegative, & - & rangeExpandDownwardSignExpect=rangeExpandSignExpectPositive, & - & rangeDownwardLimit =timeTurnaroundScaled__ & - & ) - self%timeHRTAScaled (i)=finder%find(rootGuess=timeTurnaroundScaled__) - self%expansionFactorHRTAScaled(i)=expansionFactorFromTimeScaled(self%timeHRTAScaled(i)) - end do - ! Find enclosed masses. Note that this is found using the self-similarity assumption that the mass of a shell should scale - ! as the expansion factor at "half-radius-turnaround" to the power of the growth index, i.e. M ∝ uₕᵣₜₐˢ. In regions - ! where the cosmological constant is negligible (high overdensities, which collapse at early times), this will also give - ! the expected scaling with overdensity, M ∝ β⁻ˢ. But, at lower overdensities, which collapse when the - ! cosmological constant is non-negligible, the scaling with overdensity will change. What we do here seems to be consistent - ! with what Shi (2016) assumes, and has the nice feature that it ensures the mass-epoch relation is the simple scale-free - ! expectation at all times. - expansionFactorHRTAScaled=expansionFactorFromTimeScaled(self%timeHRTAScaled) - self%massEnclosedInitialOriginal =(expansionFactorHRTAScaled/self%expansionFactorScaled)**growthIndex - self%radiusComovingInitialOriginal = self%massEnclosedInitialOriginal **(1.0d0/3.0d0) - ! Build a variety of interpolators for different radii and masses as functions of scaled time. - if (allocated(self%interpolatorRadiusTurnaroundOriginal )) deallocate(self%interpolatorRadiusTurnaroundOriginal ) - if (allocated(self%interpolatorRadiusHRTAOriginal )) deallocate(self%interpolatorRadiusHRTAOriginal ) - if (allocated(self%interpolatorRadiusComovingInitialOriginal )) deallocate(self%interpolatorRadiusComovingInitialOriginal ) - if (allocated(self%interpolatorMassTurnaroundScaled )) deallocate(self%interpolatorMassTurnaroundScaled ) - if (allocated(self%interpolatorMassHRTAOriginal )) deallocate(self%interpolatorMassHRTAOriginal ) - if (allocated(self%interpolatorScaleFactorHalfRadiusTurnaroundScaled)) deallocate(self%interpolatorScaleFactorHalfRadiusTurnaroundScaled) - allocate(self%interpolatorRadiusTurnaroundOriginal ) - allocate(self%interpolatorRadiusHRTAOriginal ) - allocate(self%interpolatorRadiusComovingInitialOriginal ) - allocate(self%interpolatorMassTurnaroundScaled ) - allocate(self%interpolatorMassHRTAOriginal ) - allocate(self%interpolatorScaleFactorHalfRadiusTurnaroundScaled) - self%interpolatorRadiusTurnaroundOriginal =interpolator( self%timeTurnaroundScaled , self%radiusTurnaroundScaled *self%radiusComovingInitialOriginal/self%cosmologicalConstantScaled**(1.0d0/3.0d0) ) - self%interpolatorRadiusComovingInitialOriginal =interpolator( self%timeTurnaroundScaled , self%radiusComovingInitialOriginal ) - self%interpolatorMassTurnaroundScaled =interpolator( self%timeTurnaroundScaled , self%massEnclosedInitialOriginal ) - self%interpolatorRadiusHRTAOriginal =interpolator( self%timeHRTAScaled , self%radiusTurnaroundScaled *self%radiusComovingInitialOriginal/self%cosmologicalConstantScaled**(1.0d0/3.0d0)/ratioRadiusTurnaroundVirial ) - self%interpolatorMassHRTAOriginal =interpolator( self%timeHRTAScaled , self%massEnclosedInitialOriginal ) - self%interpolatorScaleFactorHalfRadiusTurnaroundScaled=interpolator(Array_Reverse(self%overdensityScaled ),Array_Reverse(self%expansionFactorHRTAScaled )) - ! Compute present epoch turnaround and half-turnaround radii. - self%radiusTurnaroundNowOriginal=self%interpolatorRadiusTurnaroundOriginal%interpolate(self%timeNowScaled) - self%radiusHRTANowOriginal =self%interpolatorRadiusHRTAOriginal %interpolate(self%timeNowScaled) - ! Make an estimate of the splashback radius (in units of the turnaround radius, using eqn. 2 of Shi 2016 which is for an - ! Einstein-de Sitter universe). An approximate value is acceptable here as this is used only on the first iteration. It's - ! not clear if this is precisely what Shi (2016) chose, but it should not matter. - if (growthIndex <= 1.5d0) then - self%radiusSplashbackTurnaround=+1.0d0/3.0d0**(2.0d0/3.0d0+2.0d0*growthIndex/9.0d0) - else - self%radiusSplashbackTurnaround=+1.0d0/(1.0d0+4.0d0*(4.0d0*growthIndex/9.0d0+1.0d0/3.0d0)/sqrt(Pi)) - end if - ! Find the splashback radius in units of the present day half-turnaround radius. - self%ratioRadiusSplashbackHRTA=self%radiusSplashbackTurnaround*self%radiusTurnaroundNowOriginal/self%radiusHRTANowOriginal - ! For the first iteration, adopt a mass profile solution in the multi-stream region that has the form f(x)=x (Shi 2016, - ! section 2.1). - if (allocated(self%interpolatorMassMultiStreamScaleHRTA)) deallocate(self%interpolatorMassMultiStreamScaleHRTA) - allocate(self%interpolatorMassMultiStreamScaleHRTA) - self%radiusMultistreamMinimumScaledHRTA=radiusMultiStreamFractionalSmall*self%ratioRadiusSplashbackHRTA - self%radiusMultistreamMaximumScaledHRTA= self%ratioRadiusSplashbackHRTA - self%interpolatorMassMultiStreamScaleHRTA=interpolator([self%radiusMultistreamMinimumScaledHRTA,self%radiusMultistreamMaximumScaledHRTA],[self%radiusMultistreamMinimumScaledHRTA,self%radiusMultistreamMaximumScaledHRTA]) - ! Begin iterating to find a solution. - iteration =0 - multistreamConverged=.false. - do while (iteration < iterationMaximum .and. .not.multistreamConverged) - iteration=iteration+1 - call displayIndent(var_str('multistream mass profile iteration ')//iteration,verbosity=verbosityLevelWorking) - ! Iterate over all overdensity shells solving for their radial position and velocity at the present epoch. - do i=1,countRadii - call displayCounter(int(100.0d0*dble(i-1)/dble(countRadii)),isNew=i==1,verbosity=verbosityLevelWorking) - ! Solve the dynamical ODEs to get the scaled radius at the final time. - radiusComovingInitialOriginal=self%radiusComovingInitialOriginal(i) - massEnclosedInitialOriginal =self%massEnclosedInitialOriginal (i) - radiusGrowthRateInitialScaled=+sqrt( & ! Equation A8 from Shi (2016). - & +1.0d0/ radiusInitialScaled & - & + radiusInitialScaled **2 & - & -3.0d0*self%overdensityScaled (i) & - & /2.0d0 **(2.0d0/3.0d0) & - & ) - timeInitialScaled =+2.0d0 & ! Equation A6 from Shi (2016). - & /3.0d0 & - & *asinh( expansionFactorScaledInitial **1.5d0) - call radiusScaledSolver(timeInitialScaled,self%timeNowScaled,radiusInitialScaled,radiusGrowthRateInitialScaled,self%radiusScaled(i),self%radiusGrowthRateScaled(i)) - end do - call displayCounterClear(verbosity=verbosityLevelWorking) - ! Build an interpolator for the scaled radius as a function of overdensity. - if (allocated(self%interpolatorRadiusScaled)) deallocate(self%interpolatorRadiusScaled) - allocate(self%interpolatorRadiusScaled) - self%interpolatorRadiusScaled=interpolator(Array_Reverse(self%overdensityScaled),Array_Reverse(self%radiusScaled)) - ! Construct the mass and density profile by ordering the shells in radius. Also find the splashback radius. - radiusOriginal =abs(self%radiusScaled)*self%radiusComovingInitialOriginal/self%cosmologicalConstantScaled**(1.0d0/3.0d0) - order =sortIndex(radiusOriginal) - self%radiusSplashBackOriginal=-huge(0.0d0) - firstZeroFound =.false. - do i=1,countRadii - ! Order radii. - self%radiusOrderedOriginal(i)=radiusOriginal(order(i)) - ! Compute mass and density in this shell. - if (order(i) == 1) then - self%massShellOrderedOriginal(i)=self%massEnclosedInitialOriginal(order(i)) - self%densityOrderedOriginal (i)=self%massShellOrderedOriginal(i)*3.0d0/4.0d0/Pi/ self%radiusOrderedOriginal(i)**3 - else - self%massShellOrderedOriginal(i)=self%massEnclosedInitialOriginal(order(i))-self%massEnclosedInitialOriginal(order(i)-1) - self%densityOrderedOriginal (i)=self%massShellOrderedOriginal(i)*3.0d0/4.0d0/Pi/(self%radiusOrderedOriginal(i)**3-self%radiusOrderedOriginal(i-1)**3) - end if - ! Find the mass enclosed by this shell. - self%massEnclosedOrderedOriginal(i)=sum(self%massShellOrderedOriginal(1:i)) - end do - ! Find the splashback radius. - do i=countRadii,1,-1 - firstZeroFound=firstZeroFound.or.self%radiusScaled(i) <= 0.0d0 - if (firstZeroFound .and. radiusOriginal(i) > self%radiusSplashBackOriginal) then - self%radiusSplashbackOriginal= radiusOriginal(i) - self%radiusSplashbackScaled =abs(self%radiusScaled (i)) - end if - end do - deallocate(radiusOriginal) - ! Find the splashback radius in units of the turnaround radius, and the half-turnaround radius. - self%radiusSplashbackTurnaround=self%radiusSplashBackOriginal/self%radiusTurnaroundNowOriginal - self%ratioRadiusSplashbackHRTA =self%radiusSplashBackOriginal/self%radiusHRTANowOriginal - ! Build a new interpolator for the mass profile in the multistream region, and compare it to the previous one. - allocate(interpolatorMassMultiStreamNewScaleHRTA) - interpolatorMassMultiStreamNewScaleHRTA=interpolator(self%radiusOrderedOriginal/self%radiusHRTANowOriginal,self%massEnclosedOrderedOriginal) - radiusMultistreamMinimumNewScaledHRTA =self%radiusOrderedOriginal( 1)/self%radiusHRTANowOriginal - radiusMultistreamMaximumNewScaledHRTA =self%radiusOrderedOriginal(countRadii)/self%radiusHRTANowOriginal - radiusCompareMinimumScaledHRTA =max(radiusMultistreamMinimumNewScaledHRTA,self%radiusMultistreamMinimumScaledHRTA) - radiusCompareMaximumScaledHRTA =min(radiusMultistreamMaximumNewScaledHRTA,self%radiusMultistreamMaximumScaledHRTA) - changeRelativeMaximum =-huge(0.0d0) - do j=1,countCompare - radiusScaleHRTA =+ radiusCompareMinimumScaledHRTA & - & +(radiusCompareMaximumScaledHRTA-radiusCompareMinimumScaledHRTA) & - & *dble( j) & - & /dble(countCompare) - massMultiStream =self%interpolatorMassMultiStreamScaleHRTA %interpolate(radiusScaleHRTA) - massMultiStreamNew = interpolatorMassMultiStreamNewScaleHRTA%interpolate(radiusScaleHRTA) - changeRelative =+abs(massMultiStream-massMultiStreamNew) & - & / (massMultiStream+massMultiStreamNew) & - & /0.5d0 - changeRelativeMaximum=max(changeRelative,changeRelativeMaximum) - end do - multistreamConverged=changeRelativeMaximum <= multistreamToleranceRelative - deallocate(interpolatorMassMultiStreamNewScaleHRTA) - ! Replace the interpolator for the mass profile in the multistream region with the updated one. - deallocate(self%interpolatorMassMultiStreamScaleHRTA) - allocate (self%interpolatorMassMultiStreamScaleHRTA) - self%interpolatorMassMultiStreamScaleHRTA=interpolator(self%radiusOrderedOriginal/self%radiusHRTANowOriginal,self%massEnclosedOrderedOriginal) - self%radiusMultistreamMinimumScaledHRTA =self%radiusOrderedOriginal( 1)/self%radiusHRTANowOriginal - self%radiusMultistreamMaximumScaledHRTA =self%radiusOrderedOriginal(countRadii)/self%radiusHRTANowOriginal - write (label,'(e8.2)') changeRelativeMaximum - call displayUnindent(var_str('done [fractional change = ')//trim(adjustl(label))//']',verbosity=verbosityLevelWorking) - end do - if (.not.multistreamConverged) call Error_Report('failed to reach convergence in the multistream region'//{introspection:location}) - end if - ! Determine if the dimensionful solution needs to be updated. - if (basic%mass() == self%massPreviousPhysical .and. growthIndex == self%growthIndexPrevious) return - self%massPreviousPhysical=basic%mass() - self%growthIndexPrevious =growthIndex - ! Compute properties along the stream. - allocate(radiusOriginal (countRadii)) - allocate(densityOriginal (countRadii)) - allocate(velocityOriginal(countRadii)) - !! Find the radii and velocities in the stream. - radiusOriginal =self%radiusScaled *self%radiusComovingInitialOriginal/self%cosmologicalConstantScaled**(1.0d0/3.0d0) - velocityOriginal=self%radiusGrowthRateScaled*self%radiusComovingInitialOriginal/self%cosmologicalConstantScaled**(1.0d0/3.0d0) - !! Compute densities. - do i=1,countRadii - if (i == 1) then - densityOriginal(i)= self%massEnclosedInitialOriginal(i) *3.0d0/4.0d0/Pi/abs(radiusOriginal(i))**3 - else - densityOriginal(i)=(self%massEnclosedInitialOriginal(i)-self%massEnclosedInitialOriginal(i-1))*3.0d0/4.0d0/Pi/abs(radiusOriginal(i) **3-radiusOriginal(i-1)**3) - end if - end do - ! Find the shells at their turnaround radius and "half" of their turnaround radius (this defines the virial mass/radius), and - ! also the shell which is about to make its first passage through zero radius. - iVirial =-1 - iTurnaround=-1 - iFirstZero =-1 - do i=countRadii,1,-1 - if (iVirial < 0 .and. self%radiusGrowthRateScaled(i) < 0.0d0 .and. self%radiusScaled(i) <= self%radiusTurnaroundScaled(i)/ratioRadiusTurnaroundVirial) & - & iVirial =i - if (iTurnaround < 0 .and. self%radiusGrowthRateScaled(i) <= 0.0d0 ) & - & iTurnaround=i - if (iFirstZero < 0 .and. self%radiusGrowthRateScaled(i) < 0.0d0 .and. self%radiusScaled(i) < 0.0d0 ) & - & iFirstZero =i+1 - end do - ! Interpolate to get a more precise virial radius. - h =+(1.0d0 /ratioRadiusTurnaroundVirial -self%radiusScaled(iVirial)/self%radiusTurnaroundScaled(iVirial)) & - & /(self%radiusScaled(iVirial+1)/self%radiusTurnaroundScaled(iVirial+1)-self%radiusScaled(iVirial)/self%radiusTurnaroundScaled(iVirial)) - radiusVirialOriginal=+( & - & +self%radiusScaled(iVirial )*self%radiusComovingInitialOriginal(iVirial )*(1.0d0-h) & - & +self%radiusScaled(iVirial+1)*self%radiusComovingInitialOriginal(iVirial+1)* h & - & ) & - & /self%cosmologicalConstantScaled**(1.0d0/3.0d0) - massVirialOriginal =+self%interpolatorMassHRTAOriginal %interpolate( self%timeNowScaled ) & - & *self%interpolatorMassMultiStreamScaleHRTA%interpolate(radiusVirialOriginal/self%radiusHRTANowOriginal) - ! Scale radii, velocities, and densities to the virial radius/mass. - allocate(radiusPhysical (countRadii)) - allocate(velocityPhysical(countRadii)) - allocate(densityPhysical (countRadii)) - radiusPhysical =+radiusOriginal & - & * self_%darkMatterHaloScale_%radiusVirial(node)/radiusVirialOriginal - velocityPhysical=+velocityOriginal & - & * self_%darkMatterHaloScale_%radiusVirial(node)/radiusVirialOriginal & - & * self_%timeNowScaled /basic%time () & - & *megaParsec & - & /gigaYear & - & /kilo - densityPhysical =+densityOriginal & - & * basic%mass ( )/massVirialOriginal & - & /(self_%darkMatterHaloScale_%radiusVirial(node)/radiusVirialOriginal)**3 - ! Build interpolators into the infall stream. - if (allocated(self%interpolatorDensityPhysical )) deallocate(self%interpolatorDensityPhysical ) - if (allocated(self%interpolatorVelocityPhysical)) deallocate(self%interpolatorVelocityPhysical) - allocate(self%interpolatorDensityPhysical ) - allocate(self%interpolatorVelocityPhysical) - self%interpolatorDensityPhysical =interpolator(radiusPhysical(iFirstZero:countRadii),densityPhysical (iFirstZero:countRadii),interpolationType=gsl_interp_cspline) - self%interpolatorVelocityPhysical=interpolator(radiusPhysical(iFirstZero:countRadii),velocityPhysical(iFirstZero:countRadii),interpolationType=gsl_interp_cspline) - self%radiusMinimumPhysical =radiusPhysical(iFirstZero) - self%radiusMaximumPhysical =radiusPhysical(countRadii) - ! Compute the analytic solution in the single stream regime. - allocate(radiusSingleStreamAnalyticPhysical (countRadii-iVirial-1)) - allocate(densitySingleStreamAnalyticPhysical (countRadii-iVirial-1)) - allocate(velocitySingleStreamAnalyticPhysical(countRadii-iVirial-1)) - do i=iVirial+1,countRadii-1 - radiusSingleStreamAnalyticPhysical (i-iVirial)=+( & - & +3.0d0 & - & *basic%mass() & - & /4.0d0 & - & /Pi & - & /self%cosmologyFunctions_%matterDensityEpochal(basic%time()) & - & )**(1.0d0/3.0d0) & - & *( & - & +self%expansionFactorHRTAScaled(i) & - & /self%expansionFactorScaled & - & )**(growthIndex/3.0d0) & - & *self%radiusScaled(i) & - & /self%expansionFactorScaled - densitySingleStreamAnalyticPhysical (i-iVirial)=+self%cosmologyFunctions_%matterDensityEpochal(basic%time()) & - & *( & - & +self%expansionFactorScaled & - & /self%radiusScaled (i) & - & )**3 & - & /( & - & +1.0d0 & - & +3.0d0 & - & /growthIndex & - & /(self%interpolatorScaleFactorHalfRadiusTurnaroundScaled%derivative(self%overdensityScaled(i))/self%expansionFactorHRTAScaled(i)) & - & *(self%interpolatorRadiusScaled %derivative(self%overdensityScaled(i))/self%radiusScaled (i)) & - & ) - velocitySingleStreamAnalyticPhysical(i-iVirial)=+self%radiusGrowthRateScaled (i ) & - & * radiusSingleStreamAnalyticPhysical(i-iVirial) & - & /self%radiusScaled (i ) & - & *sqrt(1.0d0-self%cosmologyFunctions_%OmegaMatterEpochal (basic%time())) & - & * self%cosmologyFunctions_%HubbleParameterEpochal(basic%time()) - end do - ! Check that the numerical solution matches the analytic solution in the single stream region. - radiusSplashbackPhysical=+self %radiusSplashBackOriginal & - & *self%darkMatterHaloScale_%radiusVirial (node) & - & / radiusVirialOriginal - do i=iVirial+1,countRadii-1 - if ( & - & radiusPhysical(i) > radiusSplashBackPhysical & - & .and. & - & Values_Differ( & - & radiusSingleStreamAnalyticPhysical(i-iVirial)/radiusSingleStreamAnalyticPhysical(iTurnaround-iVirial), & - & radiusPhysical (i )/radiusPhysical (iTurnaround ), & - & relTol=1.0d-6 & - & ) & - & ) & - & call Error_Report('numerical and analytic solutions disagree in single stream region'//{introspection:location}) - end do - return - end subroutine shi2016Solve - - double precision function halfRadiusTurnAroundRoot(timeFinalScaled) - !!{ - Root function used in finding the epoch at which a shell reaches a radius equal to half of its turnaround radius, $y^*$. - !!} - implicit none - double precision, intent(in ) :: timeFinalScaled - double precision :: radiusScaled , radiusGrowthRateScaled - - ! Integrate the dynamical equations governing the evolution of the shell radius starting from the turnaround time, I⋆, at - ! which the radius is y⋆, and the rate of change of radius is zero (by definition). - noShellCrossing=.true. - call radiusScaledSolver(timeTurnaroundScaled__,timeFinalScaled,radiusTurnaroundScaled__,0.0d0,radiusScaled,radiusGrowthRateScaled) - noShellCrossing=.false. - halfRadiusTurnAroundRoot=+radiusScaled & - & -radiusTurnaroundScaled__ & - & /ratioRadiusTurnaroundVirial - return - end function halfRadiusTurnAroundRoot - - subroutine radiusScaledSolver(timeInitialScaled,timeFinalScaled,radiusInitialScaled,radiusGrowthRateInitialScaled,radiusScaled,radiusGrowthRateScaled) - !!{ - Compute the scaled radius (and its growth rate) as a function of the initial state and final time. - !!} - use :: Interface_GSL , only : GSL_Success - use :: Numerical_ODE_Solvers, only : odeSolver - implicit none - double precision , intent(in ) :: timeInitialScaled , timeFinalScaled , & - & radiusInitialScaled , radiusGrowthRateInitialScaled - double precision , intent( out) :: radiusScaled , radiusGrowthRateScaled - double precision , dimension(2) :: odeVariables - double precision , parameter :: odeToleranceAbsolute=0.0d0, odeToleranceRelative =1.0d-9 - type (odeSolver) :: solver - double precision :: timeScaled - - solver =odeSolver(2_c_size_t,dynamicalODEs,toleranceAbsolute=odeToleranceAbsolute,toleranceRelative=odeToleranceRelative) - odeVariables=[radiusInitialScaled,radiusGrowthRateInitialScaled] - timeScaled = timeInitialScaled - call solver%solve(timeScaled,timeFinalScaled,odeVariables) - radiusScaled =odeVariables(1) - radiusGrowthRateScaled=odeVariables(2) - return - end subroutine radiusScaledSolver - - integer function dynamicalODES(timeScaled,odeVariables,odeVariablesGrowthRate) - !!{ - The dynamical equation describing the motion of a shell of matter in scaled variables \citep[][eqn.~A7]{shi_outer_2016}. - !!} - use :: Interface_GSL, only : GSL_Success - implicit none - double precision , intent(in ) :: timeScaled - double precision, dimension(:), intent(in ) :: odeVariables - double precision, dimension(:), intent( out) :: odeVariablesGrowthRate - double precision :: radiusScaled , radiusGrowthRateScaled, radiusHRTA, radiusSplashback, radius, massHRTA, massEnclosedRatio - !$GLC attributes unused :: timeScaled - - ! Extract ODE variables into named variables for clarity. - radiusScaled =odeVariables(1) - radiusGrowthRateScaled=odeVariables(2) - ! Determine the ratio of the mass enclosed within the current radius to the mass enclosed within the shell at the initial - ! time. - if (noShellCrossing .or. timeScaled < self_%timeHRTAScaled(1)) then - ! If shell crossing is being ignored, or if the current time is less than the earliest time for which we have the - ! half-turnaround radius tabulated, simply assume a mass ratio of unity. - massEnclosedRatio=1.0d0 - else - ! Our solution for the mass in the multistream regime is the self-similar solution expressed in units of the radius of the - ! shell at its half-turnaround radius, and the mass within that shell (assuming no shell crossing). Compute that radius and - ! mass at the present epoch. - radiusHRTA=self_%interpolatorRadiusHRTAOriginal%interpolate(timeScaled) - massHRTA =self_%interpolatorMassHRTAOriginal %interpolate(timeScaled) - ! Find the current splashback radius by scaling the ratio of splashback to half-turnaround radius to the current epoch. - radiusSplashback=+self_%ratioRadiusSplashbackHRTA & - & * radiusHRTA - ! Find the radius of the current shell in unscaled units. - radius=abs(radiusScaled)*radiusComovingInitialOriginal/self_%cosmologicalConstantScaled**(1.0d0/3.0d0) - ! Determine where in the stream our shell is. - if (radius < radiusSplashback) then - ! The shell is within the splashback radius. - if (radius > self_%radiusMultistreamMinimumScaledHRTA*radiusHRTA) then - ! Shell is outside the minimum radius that we have tabulated for the multistream region. Therefore, simply - ! interpolate to get the mass ratio. - massEnclosedRatio=+massHRTA & - & /massEnclosedInitialOriginal & - & *self_%interpolatorMassMultiStreamScaleHRTA%interpolate(radius/radiusHRTA ) - else - ! Shell is inside the minimum radius that we have tabulated for the multistream region. In this region we assume that - ! the mass enclosed by the innermost tabulated point is distributed with uniform density. The mass enclosed therefore - ! grows as the cube of radius. - massEnclosedRatio=+massHRTA & - & /massEnclosedInitialOriginal & - & *self_%interpolatorMassMultiStreamScaleHRTA%interpolate(self_%radiusMultistreamMinimumScaledHRTA) & - & *(radius/radiusHRTA/self_%radiusMultistreamMinimumScaledHRTA)**3 - end if - else - ! Mass is outside the splashback radius, so no shell crossing has occurred. The mass ratio is therefore unity. - massEnclosedRatio=1.0d0 - end if - end if - ! Set ODE rates of change. - !! Radius rate of change is just the velocity. - odeVariablesGrowthRate(1)=+radiusGrowthRateScaled - !! Velocity rate of change is given by equation (A7) of Shi (2016). - if (radiusScaled == 0.0d0) then - odeVariablesGrowthRate(2)=+0.0d0 - else - odeVariablesGrowthRate(2)=-0.5d0*massEnclosedRatio*sign(1.0d0,radiusScaled)/radiusScaled**2 & - & + radiusScaled - end if - dynamicalODES=GSL_Success - return - end function dynamicalODES - - elemental double precision function expansionFactorFromTimeScaled(timeScaled) result(expansionFactor) - !!{ - Compute the scaled expansion factor from the scaled time using equation~(A6) of \cite{shi_outer_2016}. - !!} - implicit none - double precision, intent(in ) :: timeScaled - - expansionFactor=sinh(1.5d0*timeScaled)**(2.0d0/3.0d0) - return - end function expansionFactorFromTimeScaled - diff --git a/source/structure_formation.accretion_flow.correlation_function.F90 b/source/structure_formation.accretion_flow.correlation_function.F90 deleted file mode 100644 index 83a5b798d6..0000000000 --- a/source/structure_formation.accretion_flow.correlation_function.F90 +++ /dev/null @@ -1,152 +0,0 @@ -!! Copyright 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, -!! 2019, 2020, 2021, 2022, 2023, 2024 -!! Andrew Benson -!! -!! This file is part of Galacticus. -!! -!! Galacticus is free software: you can redistribute it and/or modify -!! it under the terms of the GNU General Public License as published by -!! the Free Software Foundation, either version 3 of the License, or -!! (at your option) any later version. -!! -!! Galacticus is distributed in the hope that it will be useful, -!! but WITHOUT ANY WARRANTY; without even the implied warranty of -!! MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -!! GNU General Public License for more details. -!! -!! You should have received a copy of the GNU General Public License -!! along with Galacticus. If not, see . - - !!{ - An accretion flow class which models the accretion flow using the 2-halo correlation function. - !!} - - use :: Correlation_Functions_Two_Point, only : correlationFunctionTwoPointClass - use :: Cosmology_Functions , only : cosmologyFunctionsClass - use :: Dark_Matter_Halo_Biases , only : darkMatterHaloBiasClass - - !![ - - An accretion flow class which models the accretion flow using the 2-halo correlation function. - - !!] - type, extends(accretionFlowsClass) :: accretionFlowsCorrelationFunction - !!{ - An accretion flow class which models the accretion flow using the 2-halo correlation function. - !!} - private - class(correlationFunctionTwoPointClass), pointer :: correlationFunctionTwoPoint_ => null() - class(cosmologyFunctionsClass ), pointer :: cosmologyFunctions_ => null() - class(darkMatterHaloBiasClass ), pointer :: darkMatterHaloBias_ => null() - contains - final :: correlationFunctionDestructor - procedure :: density => correlationFunctionDensity - procedure :: velocity => correlationFunctionVelocity - end type accretionFlowsCorrelationFunction - - interface accretionFlowsCorrelationFunction - !!{ - Constructors for the {\normalfont \ttfamily correlationFunction} accretion flows class. - !!} - module procedure correlationFunctionConstructorParameters - module procedure correlationFunctionConstructorInternal - end interface accretionFlowsCorrelationFunction - -contains - - function correlationFunctionConstructorParameters(parameters) result(self) - !!{ - Constructor for the {\normalfont \ttfamily correlationFunction} accretion flow class that takes a parameter set as input. - !!} - use :: Input_Parameters, only : inputParameter, inputParameters - implicit none - type (accretionFlowsCorrelationFunction) :: self - type (inputParameters ), intent(inout) :: parameters - class(correlationFunctionTwoPointClass ), pointer :: correlationFunctionTwoPoint_ - class(cosmologyFunctionsClass ), pointer :: cosmologyFunctions_ - class(darkMatterHaloBiasClass ), pointer :: darkMatterHaloBias_ - - !![ - - - - !!] - self=accretionFlowsCorrelationFunction(cosmologyFunctions_,correlationFunctionTwoPoint_,darkMatterHaloBias_) - !![ - - - - - !!] - return - end function correlationFunctionConstructorParameters - - function correlationFunctionConstructorInternal(cosmologyFunctions_,correlationFunctionTwoPoint_,darkMatterHaloBias_) result(self) - !!{ - Internal constructor for the {\normalfont \ttfamily correlationFunction} accretion flows class. - !!} - implicit none - type (accretionFlowsCorrelationFunction) :: self - class(correlationFunctionTwoPointClass ), intent(in ), target :: correlationFunctionTwoPoint_ - class(cosmologyFunctionsClass ), intent(in ), target :: cosmologyFunctions_ - class(darkMatterHaloBiasClass ), intent(in ), target :: darkMatterHaloBias_ - !![ - - !!] - - return - end function correlationFunctionConstructorInternal - - subroutine correlationFunctionDestructor(self) - !!{ - Destructor for the {\normalfont \ttfamily correlationFunction} accretion flows class. - !!} - implicit none - type(accretionFlowsCorrelationFunction), intent(inout) :: self - - !![ - - - - !!] - return - end subroutine correlationFunctionDestructor - - double precision function correlationFunctionDensity(self,node,radius) - !!{ - Compute the density of the accretion flow at the given radius. - !!} - use :: Galacticus_Nodes, only : nodeComponentBasic - implicit none - class (accretionFlowsCorrelationFunction), intent(inout) :: self - type (treeNode ), intent(inout) :: node - double precision , intent(in ) :: radius - class (nodeComponentBasic ), pointer :: basic - double precision :: time - - basic => node %basic() - time = basic%time () - correlationFunctionDensity = +( & - & +1.0d0 & - & +self%darkMatterHaloBias_ %bias (node,radius ) & - & *self%correlationFunctionTwoPoint_%correlation ( radius,time) & - & ) & - & * self%cosmologyFunctions_ %matterDensityEpochal( time) - return - end function correlationFunctionDensity - - double precision function correlationFunctionVelocity(self,node,radius) - !!{ - Compute the velocity of the accretion flow at the given radius. - !!} - use :: Error, only : Error_Report - implicit none - class (accretionFlowsCorrelationFunction), intent(inout) :: self - type (treeNode ), intent(inout) :: node - double precision , intent(in ) :: radius - !$GLC attributes unused :: self, node, radius - - correlationFunctionVelocity=0.0d0 - call Error_Report('velocity is currently unsupported'//{introspection:location}) - return - end function correlationFunctionVelocity diff --git a/source/structure_formation.accretion_flow.utilities.F90 b/source/structure_formation.accretion_flow.utilities.F90 deleted file mode 100644 index 88e4b55932..0000000000 --- a/source/structure_formation.accretion_flow.utilities.F90 +++ /dev/null @@ -1,222 +0,0 @@ -!! Copyright 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, -!! 2019, 2020, 2021, 2022, 2023, 2024 -!! Andrew Benson -!! -!! This file is part of Galacticus. -!! -!! Galacticus is free software: you can redistribute it and/or modify -!! it under the terms of the GNU General Public License as published by -!! the Free Software Foundation, either version 3 of the License, or -!! (at your option) any later version. -!! -!! Galacticus is distributed in the hope that it will be useful, -!! but WITHOUT ANY WARRANTY; without even the implied warranty of -!! MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -!! GNU General Public License for more details. -!! -!! You should have received a copy of the GNU General Public License -!! along with Galacticus. If not, see . - -!!{ -Contains a module of globally-accessible functions supporting the {\normalfont \ttfamily accretionFlows} class. -!!} - -module Accretion_Flows_Utilities - !!{ - Provides globally-accessible functions supporting the {\normalfont \ttfamily accretionFlows} class. - !!} - private - public :: accretionFlowsConstruct , accretionFlowsDensity , accretionFlowsDestruct, accretionFlowsDeepCopy, & - & accretionFlowsDeepCopyReset, accretionFlowsDeepCopyFinalize - - ! Module-scope pointer to our accretionFlow object. This is used for reference counting so that debugging information is consistent - ! between the increments and decrements. - class(*), pointer :: accretionFlows__ - !$omp threadprivate(accretionFlows__) - -contains - - !![ - - accretionFlowsConstruct - void - Input_Parameters, only : inputParameters - type (inputParameters), intent(inout), target :: parameters - class(* ), intent( out), pointer :: accretionFlows_ - - !!] - subroutine accretionFlowsConstruct(parameters,accretionFlows_) - !!{ - Build a {\normalfont \ttfamily accretionFlowsClass} object from a given parameter set. This is a globally-callable function - to allow us to subvert the class/module hierarchy. - !!} - use :: Error , only : Error_Report - use :: Input_Parameters , only : inputParameter, inputParameters - use :: Spherical_Collapse_Accretion_Flows, only : accretionFlows, accretionFlowsClass - implicit none - type (inputParameters), intent(inout), target :: parameters - class(* ), intent( out), pointer :: accretionFlows_ - type (inputParameters) , pointer :: parametersCurrent - - parametersCurrent => parameters - do while (.not.parametersCurrent%isPresent('accretionFlows').and.associated(parametersCurrent%parent)) - parametersCurrent => parametersCurrent%parent - end do - if (.not.parametersCurrent%isPresent('accretionFlows')) parametersCurrent => parameters - accretionFlows__ => accretionFlows(parametersCurrent) - select type (accretionFlows__) - class is (accretionFlowsClass) - !![ - - !!] - call accretionFlows__%autoHook() - class default - call Error_Report('accretionFlow must be of the "accretionFlowsClass" class'//{introspection:location}) - end select - accretionFlows_ => accretionFlows__ - return - end subroutine accretionFlowsConstruct - - !![ - - accretionFlowsDensity - double precision - Galacticus_Nodes, only : treeNode - class (* ), intent(inout) :: accretionFlows_ - type (treeNode), intent(inout) :: node - double precision , intent(in ) :: radius - - !!] - double precision function accretionFlowsDensity(accretionFlows_,node,radius) - !!{ - Perform the accretionFlow for a {\normalfont \ttfamily accretionFlowsClass} object passed to us as an unlimited polymorphic object. - !!} - use :: Error , only : Error_Report - use :: Galacticus_Nodes , only : treeNode - use :: Spherical_Collapse_Accretion_Flows, only : accretionFlowsClass - implicit none - class (* ), intent(inout) :: accretionFlows_ - type (treeNode), intent(inout) :: node - double precision , intent(in ) :: radius - - select type (accretionFlows_) - class is (accretionFlowsClass) - accretionFlowsDensity=accretionFlows_%density(node,radius) - class default - accretionFlowsDensity=0.0d0 - call Error_Report('accretionFlow must be of the "accretionFlowsClass" class'//{introspection:location}) - end select - return - end function accretionFlowsDensity - - !![ - - accretionFlowsDestruct - void - class(*), intent(inout), pointer :: accretionFlows_ - - !!] - subroutine accretionFlowsDestruct(accretionFlows_) - !!{ - Destruct a {\normalfont \ttfamily accretionFlowsClass} object passed to us as an unlimited polymorphic object. - !!} - use :: Error , only : Error_Report - use :: Spherical_Collapse_Accretion_Flows, only : accretionFlowsClass - use :: Function_Classes - use :: iso_varying_string - implicit none - class(*), intent(inout), pointer :: accretionFlows_ - - accretionFlows__ => accretionFlows_ - select type (accretionFlows__) - class is (accretionFlowsClass) - !![ - - !!] - class default - call Error_Report('accretionFlow must be of the "accretionFlowsClass" class'//{introspection:location}) - end select - return - end subroutine accretionFlowsDestruct - - !![ - - accretionFlowsDeepCopyReset - void - class(*), intent(inout) :: self - - !!] - subroutine accretionFlowsDeepCopyReset(self) - !!{ - Perform a deep copy of galactic structure objects. - !!} - use :: Error , only : Error_Report - use :: Spherical_Collapse_Accretion_Flows, only : accretionFlowsClass - implicit none - class(*), intent(inout) :: self - - select type (self) - class is (accretionFlowsClass) - call self%deepCopyReset() - class default - call Error_Report("unexpected class"//{introspection:location}) - end select - return - end subroutine accretionFlowsDeepCopyReset - - !![ - - accretionFlowsDeepCopyFinalize - void - class(*), intent(inout) :: self - - !!] - subroutine accretionFlowsDeepCopyFinalize(self) - !!{ - Finalize a deep copy of galactic structure objects. - !!} - use :: Error , only : Error_Report - use :: Spherical_Collapse_Accretion_Flows, only : accretionFlowsClass - implicit none - class(*), intent(inout) :: self - - select type (self) - class is (accretionFlowsClass) - call self%deepCopyFinalize() - class default - call Error_Report("unexpected class"//{introspection:location}) - end select - return - end subroutine accretionFlowsDeepCopyFinalize - - !![ - - accretionFlowsDeepCopy - void - class(*), intent(inout) :: self, destination - - !!] - subroutine accretionFlowsDeepCopy(self,destination) - !!{ - Perform a deep copy of galactic structure objects. - !!} - use :: Error , only : Error_Report - use :: Spherical_Collapse_Accretion_Flows, only : accretionFlowsClass - implicit none - class(*), intent(inout) :: self, destination - - select type (self) - class is (accretionFlowsClass) - select type (destination) - class is (accretionFlowsClass) - call self%deepCopy(destination) - class default - call Error_Report("unexpected class"//{introspection:location}) - end select - class default - call Error_Report("unexpected class"//{introspection:location}) - end select - return - end subroutine accretionFlowsDeepCopy - -end module Accretion_Flows_Utilities diff --git a/source/structure_formation.halo_mass_function.friends_of_friends_bias.F90 b/source/structure_formation.halo_mass_function.friends_of_friends_bias.F90 index 65a9b5569f..cbfb86586d 100644 --- a/source/structure_formation.halo_mass_function.friends_of_friends_bias.F90 +++ b/source/structure_formation.halo_mass_function.friends_of_friends_bias.F90 @@ -164,8 +164,10 @@ double precision function fofBiasDifferential(self,time,mass,node) !!{ Return the differential halo mass function at the given time and mass. !!} + use :: Coordinates , only : coordinateSpherical , assignment(=) use :: Error , only : Error_Report - use :: Galacticus_Nodes , only : nodeComponentBasic, treeNode + use :: Galacticus_Nodes , only : nodeComponentBasic , treeNode + use :: Mass_Distributions , only : massDistributionClass use :: Numerical_Constants_Math, only : Pi implicit none class (haloMassFunctionFofBias), intent(inout), target :: self @@ -180,6 +182,8 @@ Return the differential halo mass function at the given time and mass. integer , parameter :: iterationCountMaximum =1000 type (treeNode ), pointer :: nodeWork class (nodeComponentBasic ), pointer :: basic + class (massDistributionClass ), pointer :: massDistribution_ + type (coordinateSpherical ) :: coordinates integer :: iterationCount double precision :: fractionalAccuracyParameter , & & densityProfileLogarithmicSlope , & @@ -234,7 +238,12 @@ Return the differential halo mass function at the given time and mass. & *gradientRadiusHaloMass & & /linkingLength ! Compute negative a logarithmic slope of the density profile at the outer edge of the halo. - densityProfileLogarithmicSlope=+self%darkMatterProfileDMO_%densityLogSlope(nodeWork,radiusHalo) + coordinates = [radiusHalo,0.0d0,0.0d0] + massDistribution_ => self %darkMatterProfileDMO_%get (nodeWork ) + densityProfileLogarithmicSlope = +massDistribution_ %densityGradientRadial(coordinates,logarithmic=.true.) + !![ + + !!] ! Compute absolute value of the rate of change of logarithmic mass at halo outer edge as the ! percolation critical probability changes. numberDensityCritical =+numberDensityPercolation & diff --git a/source/structure_formation.transfer_function.CAMB.F90 b/source/structure_formation.transfer_function.CAMB.F90 index 3e5de46537..04ae960b4e 100644 --- a/source/structure_formation.transfer_function.CAMB.F90 +++ b/source/structure_formation.transfer_function.CAMB.F90 @@ -146,11 +146,13 @@ Internal constructor for the \href{http://camb.info}{\normalfont \scshape CAMB} self%transferFunctionReferenceAvailable = .false. self%transferFunctionReference => null() ! Set the epoch time for this transfer function. - self%time=self%cosmologyFunctions_%cosmicTime(self%cosmologyFunctions_%expansionFactorFromRedshift(redshift)) + self%time = self%cosmologyFunctions_%cosmicTime(self%cosmologyFunctions_%expansionFactorFromRedshift(redshift)) ! Set maximum wavenumber. - self%wavenumberMaximum=+wavenumberMaximumLimit & - & *self%cosmologyParameters_%hubbleConstant(units=hubbleUnitsLittleH) - self%wavenumberMaximumReached=.false. + self%wavenumberMaximum = +wavenumberMaximumLimit & + & *self%cosmologyParameters_%hubbleConstant(units=hubbleUnitsLittleH) + self%wavenumberMaximumReached = .false. + ! Set an empty file name - this will be generated when we run CAMB, + self%fileName = "" return end function cambConstructorInternal diff --git a/source/structure_formation.virial_density_contrast.percolation.utilities.F90 b/source/structure_formation.virial_density_contrast.percolation.utilities.F90 index 52935d475b..1a2963b0af 100644 --- a/source/structure_formation.virial_density_contrast.percolation.utilities.F90 +++ b/source/structure_formation.virial_density_contrast.percolation.utilities.F90 @@ -485,11 +485,15 @@ double precision function haloRadiusRootFunction(haloRadiusTrial) Root function used to find the radius of a halo giving the correct bounding density. !!} use :: Calculations_Resets , only : Calculations_Reset + use :: Coordinates , only : coordinateSpherical , assignment(=) + use :: Mass_Distributions , only : massDistributionClass use :: Numerical_Constants_Math, only : Pi implicit none - double precision, intent(in ) :: haloRadiusTrial - double precision :: scaleRadius , densityHaloRadius - + double precision , intent(in ) :: haloRadiusTrial + class (massDistributionClass), pointer :: massDistribution_ + double precision :: scaleRadius , densityHaloRadius + type (coordinateSpherical ) :: coordinates + ! Construct the current density contrast. state(stateCount)%densityContrast=+3.0d0 & & *state(stateCount)%massHalo & @@ -505,7 +509,12 @@ Root function used to find the radius of a halo giving the correct bounding dens end if call Calculations_Reset(state(stateCount)%workNode) ! Compute density at the halo radius. - densityHaloRadius=state(stateCount)%darkMatterProfileDMO_%density(state(stateCount)%workNode,haloRadiusTrial) + coordinates = [haloRadiusTrial,0.0d0,0.0d0] + massDistribution_ => state (stateCount)%darkMatterProfileDMO_%get (state(stateCount)%workNode ) + densityHaloRadius = massDistribution_ %density( coordinates) + !![ + + !!] ! Find difference from target density. haloRadiusRootFunction=state(stateCount)%boundingDensity-densityHaloRadius return diff --git a/source/tasks.evolve_forests.F90 b/source/tasks.evolve_forests.F90 index d8cb21f2ac..1b96f83092 100644 --- a/source/tasks.evolve_forests.F90 +++ b/source/tasks.evolve_forests.F90 @@ -508,7 +508,7 @@ subroutine evolveForestsPerform(self,status) - + !!] !$omp end critical(evolveForestsDeepCopy) ! Call routines to perform initialization which must occur for all threads if run in parallel. diff --git a/source/tasks.halo_mass_function.F90 b/source/tasks.halo_mass_function.F90 index e9394da699..57ff4c787d 100644 --- a/source/tasks.halo_mass_function.F90 +++ b/source/tasks.halo_mass_function.F90 @@ -56,7 +56,6 @@ Implementation of a task which computes and outputs the halo mass function and r class (cosmologyParametersClass ), pointer :: cosmologyParameters_ => null() class (cosmologyFunctionsClass ), pointer :: cosmologyFunctions_ => null() class (virialDensityContrastClass ), pointer :: virialDensityContrast_ => null() - class (darkMatterProfileDMOClass ), pointer :: darkMatterProfileDMO_ => null() class (criticalOverdensityClass ), pointer :: criticalOverdensity_ => null() class (linearGrowthClass ), pointer :: linearGrowth_ => null() class (haloMassFunctionClass ), pointer :: haloMassFunction_ => null() @@ -115,7 +114,6 @@ function haloMassFunctionConstructorParameters(parameters) result(self) class (cosmologyParametersClass ), pointer :: cosmologyParameters_ class (cosmologyFunctionsClass ), pointer :: cosmologyFunctions_ class (virialDensityContrastClass ), pointer :: virialDensityContrast_ - class (darkMatterProfileDMOClass ), pointer :: darkMatterProfileDMO_ class (criticalOverdensityClass ), pointer :: criticalOverdensity_ class (linearGrowthClass ), pointer :: linearGrowth_ class (haloMassFunctionClass ), pointer :: haloMassFunction_ @@ -203,7 +201,6 @@ The HDF5 output group within which to write mass function data. - @@ -276,7 +273,6 @@ The HDF5 output group within which to write mass function data.The HDF5 output group within which to write mass function data. - @@ -351,7 +346,6 @@ function haloMassFunctionConstructorInternal( & cosmologyParameters_ , & & cosmologyFunctions_ , & & virialDensityContrast_ , & - & darkMatterProfileDMO_ , & & criticalOverdensity_ , & & linearGrowth_ , & & haloMassFunction_ , & @@ -379,7 +373,6 @@ function haloMassFunctionConstructorInternal( class (cosmologyParametersClass ), intent(in ), target :: cosmologyParameters_ class (cosmologyFunctionsClass ), intent(in ), target :: cosmologyFunctions_ class (virialDensityContrastClass ), intent(in ), target :: virialDensityContrast_ - class (darkMatterProfileDMOClass ), intent(in ), target :: darkMatterProfileDMO_ class (criticalOverdensityClass ), intent(in ), target :: criticalOverdensity_ class (linearGrowthClass ), intent(in ), target :: linearGrowth_ class (haloMassFunctionClass ), intent(in ), target :: haloMassFunction_ @@ -405,7 +398,7 @@ function haloMassFunctionConstructorInternal( type (inputParameters ), intent(in ), target :: parameters integer :: i !![ - + !!] self%parameters=inputParameters(parameters) @@ -433,7 +426,6 @@ subroutine haloMassFunctionDestructor(self) - @@ -480,8 +472,10 @@ subroutine haloMassFunctionPerform(self,status) use :: Error , only : errorStatusSuccess use :: Output_HDF5 , only : outputFile use :: Galacticus_Nodes , only : mergerTree , nodeComponentBasic , nodeComponentDarkMatterProfile, treeNode + use :: Galactic_Structure_Options , only : componentTypeDarkMatterOnly , massTypeDark use :: IO_HDF5 , only : hdf5Object use, intrinsic :: ISO_C_Binding , only : c_size_t + use :: Mass_Distributions , only : massDistributionClass use :: Node_Components , only : Node_Components_Thread_Initialize , Node_Components_Thread_Uninitialize use :: Numerical_Constants_Astronomical , only : gigaYear , massSolar , megaParsec use :: Numerical_Constants_Math , only : Pi @@ -518,7 +512,6 @@ subroutine haloMassFunctionPerform(self,status) class (nodeComponentDarkMatterProfile ), pointer :: darkMatterProfileHalo class (cosmologyParametersClass ), pointer :: cosmologyParameters_ => null() class (cosmologyFunctionsClass ), pointer :: cosmologyFunctions_ => null() - class (darkMatterProfileDMOClass ), pointer :: darkMatterProfileDMO_ => null() class (criticalOverdensityClass ), pointer :: criticalOverdensity_ => null() class (haloMassFunctionClass ), pointer :: haloMassFunction_ => null() class (haloEnvironmentClass ), pointer :: haloEnvironment_ => null() @@ -530,11 +523,15 @@ subroutine haloMassFunctionPerform(self,status) class (darkMatterProfileShapeClass ), pointer :: darkMatterProfileShape_ => null() class (darkMatterHaloMassAccretionHistoryClass), pointer :: darkMatterHaloMassAccretionHistory_ => null() class (virialDensityContrastClass ), pointer :: virialDensityContrast_ => null() - !$omp threadprivate(haloEnvironment_,cosmologyFunctions_,cosmologyParameters_,cosmologicalMassVariance_,haloMassFunction_,darkMatterHaloScale_,darkMatterProfileDMO_,unevolvedSubhaloMassFunction_,darkMatterHaloBias_,darkMatterProfileScaleRadius_,darkMatterProfileShape_,darkMatterHaloMassAccretionHistory_,virialDensityContrast_,criticalOverdensity_) + !$omp threadprivate(haloEnvironment_,cosmologyFunctions_,cosmologyParameters_,cosmologicalMassVariance_,haloMassFunction_,darkMatterHaloScale_,unevolvedSubhaloMassFunction_,darkMatterHaloBias_,darkMatterProfileScaleRadius_,darkMatterProfileShape_,darkMatterHaloMassAccretionHistory_,virialDensityContrast_,criticalOverdensity_) + class (massDistributionClass ), pointer , save :: massDistribution_ => null() + !$omp threadprivate(massDistribution_) type (virialDensityContrastList ), allocatable , dimension(: ) :: virialDensityContrasts type (mergerTree ), allocatable , target , save :: tree !$omp threadprivate(tree) type (integrator ), allocatable :: integrator_ + type (inputParameters ), allocatable , save :: parameters + !$omp threadprivate(parameters) integer (c_size_t ) :: iOutput , outputCount , & & iMass , massCount , & & iAlternate @@ -678,7 +675,6 @@ subroutine haloMassFunctionPerform(self,status) allocate(criticalOverdensity_ ,mold=self%criticalOverdensity_ ) allocate(haloMassFunction_ ,mold=self%haloMassFunction_ ) allocate(darkMatterHaloScale_ ,mold=self%darkMatterHaloScale_ ) - allocate(darkMatterProfileDMO_ ,mold=self%darkMatterProfileDMO_ ) allocate(unevolvedSubhaloMassFunction_ ,mold=self%unevolvedSubhaloMassFunction_ ) allocate(darkMatterHaloBias_ ,mold=self%darkMatterHaloBias_ ) allocate(darkMatterProfileScaleRadius_ ,mold=self%darkMatterProfileScaleRadius_ ) @@ -690,7 +686,7 @@ subroutine haloMassFunctionPerform(self,status) end do !$omp critical(taskHaloMassFunctionDeepCopy) !![ - + @@ -699,13 +695,12 @@ subroutine haloMassFunctionPerform(self,status) - - + !!] do iAlternate=1,size(self%virialDensityContrasts) !![ @@ -715,6 +710,11 @@ subroutine haloMassFunctionPerform(self,status) !!] end do !$omp end critical(taskHaloMassFunctionDeepCopy) + ! Call routines to perform initialization which must occur for all threads if run in parallel. + allocate(parameters) + parameters=inputParameters(self%parameters) + call parameters%parametersGroupCopy(self%parameters) + call Node_Components_Thread_Initialize(parameters) ! Build an integrator. allocate(integrator_) integrator_=integrator(subhaloMassFunctionIntegrand,toleranceRelative=1.0d-3,integrationRule=GSL_Integ_Gauss15) @@ -759,6 +759,8 @@ subroutine haloMassFunctionPerform(self,status) if (scaleIsSettable) call darkMatterProfileHalo%scaleSet(darkMatterProfileScaleRadius_%radius(tree%nodeBase)) ! Set the node shape parameter. if (shapeIsSettable) call darkMatterProfileHalo%shapeSet(darkMatterProfileShape_ %shape (tree%nodeBase)) + ! Get the mass distribution. + massDistribution_ => tree%nodeBase%massDistribution(componentTypeDarkMatterOnly,massTypeDark) ! Compute bin interval. massHaloBinMinimum=massHalo(iMass)*exp(-0.5*massHaloLogarithmicInterval) massHaloBinMaximum=massHalo(iMass)*exp(+0.5*massHaloLogarithmicInterval) @@ -793,13 +795,13 @@ subroutine haloMassFunctionPerform(self,status) velocityVirial (iMass,iOutput)=darkMatterHaloScale_ %velocityVirial ( node=tree%nodeBase) temperatureVirial (iMass,iOutput)=darkMatterHaloScale_ %temperatureVirial ( node=tree%nodeBase) radiusVirial (iMass,iOutput)=darkMatterHaloScale_ %radiusVirial ( node=tree%nodeBase) - velocityMaximum (iMass,iOutput)=darkMatterProfileDMO_ %circularVelocityMaximum ( node=tree%nodeBase) + velocityMaximum (iMass,iOutput)=massDistribution_ %velocityRotationCurveMaximum ( ) darkMatterProfileRadiusScale (iMass,iOutput)=darkMatterProfileHalo %scale ( ) if (self%includeMassAccretionRate) & & massAccretionRate (iMass,iOutput)=darkMatterHaloMassAccretionHistory_%massAccretionRate ( time=outputTimes(iOutput),node=tree%nodeBase) ! Compute alternate mass definitions for halos. do iAlternate=1,size(self%virialDensityContrasts) - massAlternate(iAlternate,iMass,iOutput)=Dark_Matter_Profile_Mass_Definition(tree%nodeBase,virialDensityContrasts(iAlternate)%virialDensityContrast_%densityContrast(mass=massHalo(iMass),time=outputTimes(iOutput)),radius=radiusAlternate(iAlternate,iMass,iOutput),cosmologyParameters_=cosmologyParameters_,cosmologyFunctions_=cosmologyFunctions_,darkMatterProfileDMO_=darkMatterProfileDMO_,virialDensityContrast_=virialDensityContrast_) + massAlternate(iAlternate,iMass,iOutput)=Dark_Matter_Profile_Mass_Definition(tree%nodeBase,virialDensityContrasts(iAlternate)%virialDensityContrast_%densityContrast(mass=massHalo(iMass),time=outputTimes(iOutput)),radius=radiusAlternate(iAlternate,iMass,iOutput),cosmologyParameters_=cosmologyParameters_,cosmologyFunctions_=cosmologyFunctions_,virialDensityContrast_=virialDensityContrast_) end do ! Integrate the unevolved subhalo mass function over the halo mass function to get the total subhalo mass function. if (self%includeUnevolvedSubhaloMassFunction) & @@ -807,6 +809,9 @@ subroutine haloMassFunctionPerform(self,status) & log(massHalo(1)/subhaloMassMaximum ), & & log( haloMassEffectiveInfinity) & & ) + !![ + + !!] end do !$omp end do !$omp single @@ -828,7 +833,6 @@ subroutine haloMassFunctionPerform(self,status) - @@ -841,6 +845,7 @@ subroutine haloMassFunctionPerform(self,status) !!] end do deallocate(virialDensityContrasts) + call Node_Components_Thread_Uninitialize() !$omp end parallel ! Open the group for output time information. if (self%outputGroup == ".") then diff --git a/source/tasks.halo_model_generate.F90 b/source/tasks.halo_model_generate.F90 index 560e164373..3fff1aec03 100644 --- a/source/tasks.halo_model_generate.F90 +++ b/source/tasks.halo_model_generate.F90 @@ -22,7 +22,6 @@ use :: Cosmology_Parameters , only : cosmologyParameters , cosmologyParametersClass use :: Dark_Matter_Profile_Scales, only : darkMatterProfileScaleRadius, darkMatterProfileScaleRadiusClass use :: Dark_Matter_Profiles_DMO , only : darkMatterProfileDMO , darkMatterProfileDMOClass - use :: Galactic_Structure , only : galacticStructureClass use :: Numerical_Random_Numbers , only : randomNumberGeneratorClass !![ @@ -40,7 +39,6 @@ class (darkMatterProfileDMOClass ), pointer :: darkMatterProfileDMO_ => null() class (conditionalMassFunctionClass ), pointer :: conditionalMassFunction_ => null() class (darkMatterProfileScaleRadiusClass), pointer :: darkMatterProfileScaleRadius_ => null() - class (galacticStructureClass ), pointer :: galacticStructure_ => null() class (randomNumberGeneratorClass ), pointer :: randomNumberGenerator_ => null() double precision :: massMinimum , massMaximum type (varying_string ) :: galaxyCatalogFileName , haloCatalogFileName @@ -79,7 +77,6 @@ function haloModelGenerateConstructorParameters(parameters) result(self) class (conditionalMassFunctionClass ), pointer :: conditionalMassFunction_ class (darkMatterProfileScaleRadiusClass), pointer :: darkMatterProfileScaleRadius_ class (randomNumberGeneratorClass ), pointer :: randomNumberGenerator_ - class (galacticStructureClass ), pointer :: galacticStructure_ type (inputParameters ), pointer :: parametersRoot double precision :: massMinimum , massMaximum type (varying_string ) :: galaxyCatalogFileName , haloCatalogFileName @@ -126,9 +123,8 @@ function haloModelGenerateConstructorParameters(parameters) result(self) - !!] - self=taskHaloModelGenerate(galaxyCatalogFileName,haloCatalogFileName,massMinimum,massMaximum,cosmologyParameters_,cosmologyFunctions_,darkMatterProfileDMO_,conditionalMassFunction_,darkMatterProfileScaleRadius_,randomNumberGenerator_,galacticStructure_,parametersRoot) + self=taskHaloModelGenerate(galaxyCatalogFileName,haloCatalogFileName,massMinimum,massMaximum,cosmologyParameters_,cosmologyFunctions_,darkMatterProfileDMO_,conditionalMassFunction_,darkMatterProfileScaleRadius_,randomNumberGenerator_,parametersRoot) !![ @@ -137,12 +133,11 @@ function haloModelGenerateConstructorParameters(parameters) result(self) - !!] return end function haloModelGenerateConstructorParameters - function haloModelGenerateConstructorInternal(galaxyCatalogFileName,haloCatalogFileName,massMinimum,massMaximum,cosmologyParameters_,cosmologyFunctions_,darkMatterProfileDMO_,conditionalMassFunction_,darkMatterProfileScaleRadius_,randomNumberGenerator_,galacticStructure_,parameters) result(self) + function haloModelGenerateConstructorInternal(galaxyCatalogFileName,haloCatalogFileName,massMinimum,massMaximum,cosmologyParameters_,cosmologyFunctions_,darkMatterProfileDMO_,conditionalMassFunction_,darkMatterProfileScaleRadius_,randomNumberGenerator_,parameters) result(self) !!{ Constructor for the {\normalfont \ttfamily haloModelGenerate} task class which takes a parameter set as input. !!} @@ -156,10 +151,9 @@ function haloModelGenerateConstructorInternal(galaxyCatalogFileName,haloCatalogF class (conditionalMassFunctionClass ), intent(in ), target :: conditionalMassFunction_ class (darkMatterProfileScaleRadiusClass), intent(in ), target :: darkMatterProfileScaleRadius_ class (randomNumberGeneratorClass ), intent(in ), target :: randomNumberGenerator_ - class (galacticStructureClass ), intent(in ), target :: galacticStructure_ type (inputParameters ), intent(in ), target :: parameters !![ - + !!] self%parameters=inputParameters(parameters) @@ -182,7 +176,6 @@ subroutine haloModelGenerateDestructor(self) - !!] if(self%nodeComponentsInitialized) call Node_Components_Uninitialize() return @@ -196,14 +189,15 @@ subroutine haloModelGeneratePerform(self,status) use :: Display , only : displayIndent , displayMessage , displayUnindent use :: Galactic_Structure_Options, only : massTypeDark use :: Calculations_Resets , only : Calculations_Reset - use :: Error , only : errorStatusSuccess + use :: Error , only : errorStatusSuccess use :: Galacticus_Nodes , only : nodeComponentBasic , nodeComponentDarkMatterProfile , treeNode use :: IO_IRATE , only : irate use :: ISO_Varying_String , only : varying_string + use :: Mass_Distributions , only : massDistributionClass use :: Node_Components , only : Node_Components_Thread_Initialize, Node_Components_Thread_Uninitialize use :: Numerical_Constants_Math , only : Pi use :: Root_Finder , only : rangeExpandMultiplicative , rootFinder - use :: String_Handling , only : operator(//) + use :: String_Handling , only : operator(//) implicit none class (taskHaloModelGenerate ), intent(inout), target :: self integer , intent( out), optional :: status @@ -215,6 +209,7 @@ subroutine haloModelGeneratePerform(self,status) type (treeNode ), pointer :: node class (nodeComponentBasic ), pointer :: basic class (nodeComponentDarkMatterProfile), pointer :: profile + class (massDistributionClass ), pointer :: massDistribution_ integer :: iHalo , galaxyCount , & & numberSatelliteActual, iSatellite , & & iAxis @@ -292,15 +287,16 @@ subroutine haloModelGeneratePerform(self,status) call Calculations_Reset( node ) call profile%scaleSet (self%darkMatterProfileScaleRadius_%radius(node )) call Calculations_Reset( node ) + massDistribution_ => self%darkMatterProfileDMO_%get(node) do iSatellite=1,numberSatelliteActual ! Sample satellite galaxy mass. - xSatellite =self%randomNumberGenerator_%uniformSample()*numberSatelliteMean - massGalaxy_ =finderSatellite%find(rootGuess=self%massMinimum) + xSatellite = self%randomNumberGenerator_%uniformSample()*numberSatelliteMean + massGalaxy_ = finderSatellite%find(rootGuess=self%massMinimum) ! Sample galaxy radial position. - xSatellite =self%randomNumberGenerator_%uniformSample ( ) - radiusSatellite =self%galacticStructure_ %radiusEnclosingMass(node,massFractional=xSatellite ,massType=massTypeDark) + xSatellite = self %randomNumberGenerator_%uniformSample ( ) + radiusSatellite = massDistribution_ %radiusEnclosingMass(massFractional=xSatellite ) ! Get circular velocity at this radius. - velocitySatelliteCircular=self%darkMatterProfileDMO_ %circularVelocity (node, radiusSatellite ) + velocitySatelliteCircular = massDistribution_%rotationCurve(radiusSatellite) ! Convert radial position to comoving coordinates. radiusSatellite =radiusSatellite*(1.0d0+redshift) ! Sample galaxy angular position. @@ -335,6 +331,9 @@ subroutine haloModelGeneratePerform(self,status) ! Store the satellite. call galaxyAdd(massGalaxy_,positionSatellite,velocitySatellite) end do + !![ + + !!] end if end do message="Created " diff --git a/source/tasks.halo_spin_distribution.F90 b/source/tasks.halo_spin_distribution.F90 index e09d1fcd4f..164770efb7 100644 --- a/source/tasks.halo_spin_distribution.F90 +++ b/source/tasks.halo_spin_distribution.F90 @@ -17,10 +17,10 @@ !! You should have received a copy of the GNU General Public License !! along with Galacticus. If not, see . - use :: Cosmology_Functions , only : cosmologyFunctions , cosmologyFunctionsClass - use :: Dark_Matter_Profiles_DMO, only : darkMatterProfileDMOClass - use :: Halo_Spin_Distributions , only : haloSpinDistribution , haloSpinDistributionClass - use :: Output_Times , only : outputTimes , outputTimesClass + use :: Cosmology_Functions , only : cosmologyFunctions , cosmologyFunctionsClass + use :: Dark_Matter_Halo_Scales , only : darkMatterHaloScaleClass + use :: Halo_Spin_Distributions , only : haloSpinDistribution , haloSpinDistributionClass + use :: Output_Times , only : outputTimes , outputTimesClass !![ @@ -35,7 +35,7 @@ class (haloSpinDistributionClass), pointer :: haloSpinDistribution_ => null() class (outputTimesClass ), pointer :: outputTimes_ => null() class (cosmologyFunctionsClass ), pointer :: cosmologyFunctions_ => null() - class (darkMatterProfileDMOClass), pointer :: darkMatterProfileDMO_ => null() + class (darkMatterHaloScaleClass ), pointer :: darkMatterHaloScale_ => null() double precision :: spinMinimum , spinMaximum , & & spinPointsPerDecade , haloMassMinimum type (varying_string ) :: outputGroup @@ -70,7 +70,7 @@ function haloSpinDistributionConstructorParameters(parameters) result(self) class (haloSpinDistributionClass), pointer :: haloSpinDistribution_ class (outputTimesClass ), pointer :: outputTimes_ class (cosmologyFunctionsClass ), pointer :: cosmologyFunctions_ - class (darkMatterProfileDMOClass), pointer :: darkMatterProfileDMO_ + class (darkMatterHaloScaleClass ), pointer :: darkMatterHaloScale_ type (inputParameters ), pointer :: parametersRoot type (varying_string ) :: outputGroup double precision :: spinMinimum , spinMaximum , & @@ -128,20 +128,20 @@ Maximum spin for which the distribution function should be calculat - + !!] - self=taskHaloSpinDistribution(spinMinimum,spinMaximum,spinPointsPerDecade,haloMassMinimum,outputGroup,haloSpinDistribution_,outputTimes_,cosmologyFunctions_,darkMatterProfileDMO_,parametersRoot) + self=taskHaloSpinDistribution(spinMinimum,spinMaximum,spinPointsPerDecade,haloMassMinimum,outputGroup,darkMatterHaloScale_,haloSpinDistribution_,outputTimes_,cosmologyFunctions_,parametersRoot) !![ - + !!] return end function haloSpinDistributionConstructorParameters - function haloSpinDistributionConstructorInternal(spinMinimum,spinMaximum,spinPointsPerDecade,haloMassMinimum,outputGroup,haloSpinDistribution_,outputTimes_,cosmologyFunctions_,darkMatterProfileDMO_,parameters) result(self) + function haloSpinDistributionConstructorInternal(spinMinimum,spinMaximum,spinPointsPerDecade,haloMassMinimum,outputGroup,darkMatterHaloScale_,haloSpinDistribution_,outputTimes_,cosmologyFunctions_,parameters) result(self) !!{ Constructor for the {\normalfont \ttfamily haloSpinDistribution} task class which takes a parameter set as input. !!} @@ -150,13 +150,13 @@ function haloSpinDistributionConstructorInternal(spinMinimum,spinMaximum,spinPoi class (haloSpinDistributionClass), intent(in ), target :: haloSpinDistribution_ class (outputTimesClass ), intent(in ), target :: outputTimes_ class (cosmologyFunctionsClass ), intent(in ), target :: cosmologyFunctions_ - class (darkMatterProfileDMOClass), intent(in ), target :: darkMatterProfileDMO_ + class (darkMatterHaloScaleClass ), intent(in ), target :: darkMatterHaloScale_ type (varying_string ), intent(in ) :: outputGroup double precision , intent(in ) :: spinMinimum , spinMaximum , & & spinPointsPerDecade , haloMassMinimum type (inputParameters ), intent(in ), target :: parameters !![ - + !!] self%parameters=inputParameters(parameters) @@ -176,7 +176,7 @@ subroutine haloSpinDistributionDestructor(self) - + !!] if (self%nodeComponentsInitialized) call Node_Components_Uninitialize() return @@ -236,7 +236,7 @@ subroutine haloSpinDistributionPerform(self,status) ! Iterate over spins. do iSpin=1,spinCount spin(iSpin)=exp(log(self%spinMinimum)+log(self%spinMaximum/self%spinMinimum)*dble(iSpin-1)/dble(spinCount-1)) - call nodeSpin%angularMomentumSet(spin(iSpin)*Dark_Matter_Halo_Angular_Momentum_Scale(node,self%darkMatterProfileDMO_)) + call nodeSpin%angularMomentumSet(spin(iSpin)*Dark_Matter_Halo_Angular_Momentum_Scale(node,self%darkMatterHaloScale_)) ! Evaluate the distribution. if (self%haloMassMinimum <= 0.0d0) then ! No minimum halo mass specified - simply evaluate the spin distribution. diff --git a/source/tasks.merging_halo_orbit_distribution.F90 b/source/tasks.merging_halo_orbit_distribution.F90 index 2763ae5021..0546c84161 100644 --- a/source/tasks.merging_halo_orbit_distribution.F90 +++ b/source/tasks.merging_halo_orbit_distribution.F90 @@ -17,14 +17,14 @@ !! You should have received a copy of the GNU General Public License !! along with Galacticus. If not, see . - use :: Dark_Matter_Halo_Scales , only : darkMatterHaloScaleClass - use :: Virial_Orbits , only : virialOrbitClass - use :: Numerical_Random_Numbers , only : randomNumberGeneratorClass - use :: Spherical_Collapse_Accretion_Flows, only : accretionFlowsClass - use :: Merger_Tree_Branching , only : mergerTreeBranchingProbabilityClass - use :: Cosmological_Density_Field , only : criticalOverdensityClass , cosmologicalMassVarianceClass - use :: Halo_Mass_Functions , only : haloMassFunctionClass - use :: Cosmology_Functions , only : cosmologyFunctionsClass + use :: Dark_Matter_Halo_Scales , only : darkMatterHaloScaleClass + use :: Dark_Matter_Profiles_DMO , only : darkMatterProfileDMOClass + use :: Virial_Orbits , only : virialOrbitClass + use :: Numerical_Random_Numbers , only : randomNumberGeneratorClass + use :: Merger_Tree_Branching , only : mergerTreeBranchingProbabilityClass + use :: Cosmological_Density_Field, only : criticalOverdensityClass , cosmologicalMassVarianceClass + use :: Halo_Mass_Functions , only : haloMassFunctionClass + use :: Cosmology_Functions , only : cosmologyFunctionsClass !![ @@ -39,7 +39,7 @@ class (darkMatterHaloScaleClass ), pointer :: darkMatterHaloScale_ => null() class (virialOrbitClass ), pointer :: virialOrbit_ => null() class (randomNumberGeneratorClass ), pointer :: randomNumberGenerator_ => null() - class (accretionFlowsClass ), pointer :: accretionFlows_ => null() + class (darkMatterProfileDMOClass ), pointer :: darkMatterProfileDMO_ => null() class (mergerTreeBranchingProbabilityClass), pointer :: mergerTreeBranchingProbability_ => null() class (criticalOverdensityClass ), pointer :: criticalOverdensity_ => null() class (cosmologicalMassVarianceClass ), pointer :: cosmologicalMassVariance_ => null() @@ -78,8 +78,8 @@ function mergingHaloOrbitDistributionConstructorParameters(parameters) result(se class (cosmologyFunctionsClass ), pointer :: cosmologyFunctions_ class (virialOrbitClass ), pointer :: virialOrbit_ class (darkMatterHaloScaleClass ), pointer :: darkMatterHaloScale_ + class (darkMatterProfileDMOClass ), pointer :: darkMatterProfileDMO_ class (randomNumberGeneratorClass ), pointer :: randomNumberGenerator_ - class (accretionFlowsClass ), pointer :: accretionFlows_ class (mergerTreeBranchingProbabilityClass), pointer :: mergerTreeBranchingProbability_ class (criticalOverdensityClass ), pointer :: criticalOverdensity_ class (cosmologicalMassVarianceClass ), pointer :: cosmologicalMassVariance_ @@ -156,24 +156,24 @@ function mergingHaloOrbitDistributionConstructorParameters(parameters) result(se real 0..1 - - - - - - - - - + + + + + + + + + !!] time=cosmologyFunctions_%cosmicTime(cosmologyFunctions_%expansionFactorFromRedshift(redshift)) - self=taskMergingHaloOrbitDistribution(time,velocityMinimum,velocityMaximum,countVelocitiesPerUnit,massMinimum,massMaximum,countMassesPerDecade,virialOrbit_,cosmologyFunctions_,darkMatterHaloScale_,accretionFlows_,mergerTreeBranchingProbability_,criticalOverdensity_,cosmologicalMassVariance_,haloMassFunction_,randomNumberGenerator_) + self=taskMergingHaloOrbitDistribution(time,velocityMinimum,velocityMaximum,countVelocitiesPerUnit,massMinimum,massMaximum,countMassesPerDecade,virialOrbit_,cosmologyFunctions_,darkMatterHaloScale_,darkMatterProfileDMO_,mergerTreeBranchingProbability_,criticalOverdensity_,cosmologicalMassVariance_,haloMassFunction_,randomNumberGenerator_) !![ - + @@ -183,7 +183,7 @@ function mergingHaloOrbitDistributionConstructorParameters(parameters) result(se return end function mergingHaloOrbitDistributionConstructorParameters - function mergingHaloOrbitDistributionConstructorInternal(time,velocityMinimum,velocityMaximum,countVelocitiesPerUnit,massMinimum,massMaximum,countMassesPerDecade,virialOrbit_,cosmologyFunctions_,darkMatterHaloScale_,accretionFlows_,mergerTreeBranchingProbability_,criticalOverdensity_,cosmologicalMassVariance_,haloMassFunction_,randomNumberGenerator_) result(self) + function mergingHaloOrbitDistributionConstructorInternal(time,velocityMinimum,velocityMaximum,countVelocitiesPerUnit,massMinimum,massMaximum,countMassesPerDecade,virialOrbit_,cosmologyFunctions_,darkMatterHaloScale_,darkMatterProfileDMO_,mergerTreeBranchingProbability_,criticalOverdensity_,cosmologicalMassVariance_,haloMassFunction_,randomNumberGenerator_) result(self) !!{ Internal constructor for the {\normalfont \ttfamily mergingHaloOrbitDistribution} task class. !!} @@ -192,7 +192,7 @@ function mergingHaloOrbitDistributionConstructorInternal(time,velocityMinimum,ve class (virialOrbitClass ), intent(in ), target :: virialOrbit_ class (cosmologyFunctionsClass ), intent(in ), target :: cosmologyFunctions_ class (randomNumberGeneratorClass ), intent(in ), target :: randomNumberGenerator_ - class (accretionFlowsClass ), intent(in ), target :: accretionFlows_ + class (darkMatterProfileDMOClass ), intent(in ), target :: darkMatterProfileDMO_ class (darkMatterHaloScaleClass ), intent(in ), target :: darkMatterHaloScale_ class (mergerTreeBranchingProbabilityClass), intent(in ), target :: mergerTreeBranchingProbability_ class (criticalOverdensityClass ), intent(in ), target :: criticalOverdensity_ @@ -203,7 +203,7 @@ function mergingHaloOrbitDistributionConstructorInternal(time,velocityMinimum,ve & time integer , intent(in ) :: countMassesPerDecade , countVelocitiesPerUnit !![ - + !!] self%redshift=self%cosmologyFunctions_%redshiftFromExpansionFactor(self%cosmologyFunctions_%expansionFactor(time)) @@ -223,7 +223,7 @@ subroutine mergingHaloOrbitDistributionDestructor(self) - + @@ -248,12 +248,15 @@ subroutine mergingHaloOrbitDistributionPerform(self,status) use :: HDF5_Access , only : hdf5Access use :: IO_HDF5 , only : hdf5Object use :: Numerical_Ranges , only : Make_Range , rangeTypeLogarithmic, rangeTypeLinear + use :: Mass_Distributions , only : massDistributionClass + use :: Coordinates , only : coordinateSpherical , assignment(=) implicit none class (taskMergingHaloOrbitDistribution), intent(inout) , target :: self integer , intent( out) , optional :: status type (treeNode ) , pointer :: nodeHost , nodeSatellite class (nodeComponentBasic ) , pointer :: basicHost , basicSatellite type (mergerTree ) , pointer :: tree + class (massDistributionClass ) , pointer :: massDistribution_ double precision , dimension(: ), allocatable :: mass , velocity , & & separation , density , & & haloMassFunction @@ -272,7 +275,8 @@ subroutine mergingHaloOrbitDistributionPerform(self,status) & velocityWidthBin , overdensityCriticalGrowthRate , & & overdensityCritical , rootVarianceLogarithmicGrowthRate , & & barrierEffectiveGrowthRate - + type (coordinateSpherical ) :: coordinates + call displayIndent('Begin task: merging halo orbit distribution') ! Build range of velocities. countVelocities=int( (self%velocityMaximum-self%velocityMinimum)*dble(self%countVelocitiesPerUnit))+1 @@ -326,9 +330,14 @@ subroutine mergingHaloOrbitDistributionPerform(self,status) call Calculations_Reset ( nodeHost) ! Compute the separation of the merging pairs (i.e. the virial radius of the primary halo), the density of the ! accretion flow at that separation, and the halo mass function. - separation (iHost)=self%darkMatterHaloScale_%radiusVirial(nodeHost ) - density (iHost)=self%accretionFlows_ %density (nodeHost ,separation(iHost)) - haloMassFunction(iHost)=self%haloMassFunction_ %differential(self%time,massHost,nodeHost) + massDistribution_ => self %darkMatterProfileDMO_%get ( nodeHost) + separation (iHost) = self %darkMatterHaloScale_ %radiusVirial( nodeHost) + coordinates = [separation(iHost),0.0d0,0.0d0] + density (iHost) = massDistribution_ %density ( coordinates ) + haloMassFunction (iHost) = self %haloMassFunction_ %differential(self%time,massHost ,nodeHost) + !![ + + !!] ! Iterate over satellite masses. do iSatellite=1,countMasses ! Only consider satellites less (or equally) massive than their host. diff --git a/source/tests.dark_matter_halo_radius_enclosing_mass.F90 b/source/tests.dark_matter_halo_radius_enclosing_mass.F90 index 7168b40fea..0d6391c0d8 100644 --- a/source/tests.dark_matter_halo_radius_enclosing_mass.F90 +++ b/source/tests.dark_matter_halo_radius_enclosing_mass.F90 @@ -27,28 +27,29 @@ program Test_Dark_Matter_Halo_Radius_Enclosing_Mass !!{ Tests the calculation of dark matter halo radius enclosing a given mass. !!} - use :: Cosmology_Parameters , only : cosmologyParametersSimple - use :: Cosmology_Functions , only : cosmologyFunctionsMatterLambda - use :: Dark_Matter_Halo_Scales , only : darkMatterHaloScale , darkMatterHaloScaleClass , darkMatterHaloScaleVirialDensityContrastDefinition - use :: Virial_Density_Contrast , only : virialDensityContrastSphericalCollapseClsnlssMttrCsmlgclCnstnt - use :: Dark_Matter_Profiles_DMO , only : darkMatterProfileDMOBurkert , darkMatterProfileDMOClass , darkMatterProfileDMOHeated , darkMatterProfileDMONFW , & - & darkMatterProfileDMOTruncated , darkMatterProfileDMOTruncatedExponential, darkMatterProfileHeatingTidal - use :: Dark_Matter_Profiles_Generic, only : nonAnalyticSolversFallThrough - use :: Display , only : displayVerbositySet , verbosityLevelStandard - use :: Events_Hooks , only : eventsHooksInitialize - use :: Functions_Global_Utilities , only : Functions_Global_Set - use :: Error , only : Error_Report - use :: Galacticus_Nodes , only : nodeClassHierarchyFinalize , nodeClassHierarchyInitialize , nodeComponentBasic , nodeComponentDarkMatterProfile, & - & nodeComponentSatellite , treeNode - use :: ISO_Varying_String , only : assignment(=) , varying_string - use :: Input_Parameters , only : inputParameters - use :: Node_Components , only : Node_Components_Initialize , Node_Components_Thread_Initialize , Node_Components_Thread_Uninitialize , Node_Components_Uninitialize - use :: Unit_Tests , only : Assert , Unit_Tests_Begin_Group , Unit_Tests_End_Group , Unit_Tests_Finish + use :: Cosmology_Parameters , only : cosmologyParametersSimple + use :: Cosmology_Functions , only : cosmologyFunctionsMatterLambda + use :: Dark_Matter_Halo_Scales , only : darkMatterHaloScale , darkMatterHaloScaleClass , darkMatterHaloScaleVirialDensityContrastDefinition + use :: Virial_Density_Contrast , only : virialDensityContrastSphericalCollapseClsnlssMttrCsmlgclCnstnt + use :: Dark_Matter_Profiles_DMO , only : darkMatterProfileDMOBurkert , darkMatterProfileDMOClass , darkMatterProfileDMOHeated , darkMatterProfileDMONFW , & + & darkMatterProfileDMOTruncated , darkMatterProfileDMOTruncatedExponential, darkMatterProfileHeatingTidal + use :: Mass_Distributions , only : nonAnalyticSolversFallThrough , massDistributionClass + use :: Display , only : displayVerbositySet , verbosityLevelStandard + use :: Events_Hooks , only : eventsHooksInitialize + use :: Functions_Global_Utilities, only : Functions_Global_Set + use :: Error , only : Error_Report + use :: Galacticus_Nodes , only : nodeClassHierarchyFinalize , nodeClassHierarchyInitialize , nodeComponentBasic , nodeComponentDarkMatterProfile, & + & nodeComponentSatellite , treeNode + use :: ISO_Varying_String , only : assignment(=) , varying_string + use :: Input_Parameters , only : inputParameters + use :: Node_Components , only : Node_Components_Initialize , Node_Components_Thread_Initialize , Node_Components_Thread_Uninitialize , Node_Components_Uninitialize + use :: Unit_Tests , only : Assert , Unit_Tests_Begin_Group , Unit_Tests_End_Group , Unit_Tests_Finish implicit none type (treeNode ) :: node class (nodeComponentBasic ), pointer :: basic class (nodeComponentSatellite ), pointer :: satellite class (nodeComponentDarkMatterProfile ), pointer :: dmProfile + class (massDistributionClass ), pointer :: massDistribution_ class (darkMatterProfileDMOClass ), pointer :: darkMatterProfileDMO_ type (darkMatterProfileDMONFW ), target :: darkMatterProfileDMONFW_ type (darkMatterProfileDMOBurkert ), target :: darkMatterProfileDMOBurkert_ @@ -66,8 +67,7 @@ program Test_Dark_Matter_Halo_Radius_Enclosing_Mass double precision , parameter :: radiusFractionalTruncateMinimum = 2.00d+00, radiusFractionalTruncateMaximum=8.0d0 double precision , parameter :: time =13.80d+00 double precision , parameter :: massVirial = 1.00d+10, concentration =8.0d0 - double precision :: radiusFractionalDecay = 0.06d+00, alpha =1.0d0 , & - & beta = 3.00d+00, gamma =1.0d0 + double precision :: radiusFractionalDecay = 0.06d+00 double precision , parameter :: heatingSpecific = 1.00d+06 double precision , parameter :: coefficientSecondOrder = 0.00d+00 double precision , parameter :: correlationVelocityRadius =-1.00d+00 @@ -78,7 +78,8 @@ program Test_Dark_Matter_Halo_Radius_Enclosing_Mass type (varying_string ) :: parameterFile type (inputParameters ) :: parameters integer :: i , j - logical , parameter :: velocityDispersionUseSeriesExpansion =.true. , velocityDispersionApproximate =.true. + logical , parameter :: velocityDispersionUseSeriesExpansion =.true. , velocityDispersionApproximate =.true., & + & tolerateVelocityMaximumFailure =.false. logical :: limitToVirialRadius ! Set verbosity level. @@ -133,15 +134,15 @@ program Test_Dark_Matter_Halo_Radius_Enclosing_Mass !!] - darkMatterProfileDMONFW_ = darkMatterProfileDMONFW (velocityDispersionUseSeriesExpansion, darkMatterHaloScale_ ) - darkMatterProfileDMOBurkert_ = darkMatterProfileDMOBurkert ( darkMatterHaloScale_ ) - darkMatterProfileDMOTruncated_ = darkMatterProfileDMOTruncated (radiusFractionalTruncateMinimum ,radiusFractionalTruncateMaximum, & - & nonAnalyticSolversFallThrough , darkMatterProfileDMONFW_ ,darkMatterHaloScale_ ) - darkMatterProfileDMOTruncatedExponential_ = darkMatterProfileDMOTruncatedExponential(radiusFractionalDecay ,alpha ,beta ,gamma , & - & nonAnalyticSolversFallThrough , darkMatterProfileDMONFW_ ,darkMatterHaloScale_ ) - darkMatterProfileHeatingTidal_ = darkMatterProfileHeatingTidal (coefficientSecondOrder ,coefficientSecondOrder ,coefficientSecondOrder ,correlationVelocityRadius ) - darkMatterProfileDMOHeated_ = darkMatterProfileDMOHeated (nonAnalyticSolversFallThrough ,velocityDispersionApproximate ,toleranceRelativeVelocityDispersion,toleranceRelativeVelocityDispersionMaximum,darkMatterProfileDMONFW_ ,darkMatterHaloScale_, & - & darkMatterProfileHeatingTidal_ ) + darkMatterProfileDMONFW_ = darkMatterProfileDMONFW (velocityDispersionUseSeriesExpansion, darkMatterHaloScale_ ) + darkMatterProfileDMOBurkert_ = darkMatterProfileDMOBurkert ( darkMatterHaloScale_ ) + darkMatterProfileDMOTruncated_ = darkMatterProfileDMOTruncated (radiusFractionalTruncateMinimum ,radiusFractionalTruncateMaximum, & + & nonAnalyticSolversFallThrough , darkMatterProfileDMONFW_ ,darkMatterHaloScale_ ) + darkMatterProfileDMOTruncatedExponential_ = darkMatterProfileDMOTruncatedExponential(radiusFractionalDecay , & + & nonAnalyticSolversFallThrough , darkMatterProfileDMONFW_ ,darkMatterHaloScale_ ) + darkMatterProfileHeatingTidal_ = darkMatterProfileHeatingTidal (coefficientSecondOrder ,coefficientSecondOrder ,coefficientSecondOrder ,correlationVelocityRadius ) + darkMatterProfileDMOHeated_ = darkMatterProfileDMOHeated (nonAnalyticSolversFallThrough ,velocityDispersionApproximate ,tolerateVelocityMaximumFailure,toleranceRelativeVelocityDispersion,toleranceRelativeVelocityDispersionMaximum,darkMatterProfileDMONFW_, & + & darkMatterProfileHeatingTidal_ ) ! Set up the node. basic => node%basic (autoCreate=.true.) satellite => node%satellite (autoCreate=.true.) @@ -181,11 +182,15 @@ program Test_Dark_Matter_Halo_Radius_Enclosing_Mass case default call Error_Report('unknown profile'//{introspection:location}) end select + massDistribution_ => darkMatterProfileDMO_%get(node) do j=1,7 - mass (j)=darkMatterProfileDMO_%enclosedMass (node, radius(j)) - radiusRoot(j)=darkMatterProfileDMO_%radiusEnclosingMass(node, mass (j)) + mass (j)=massDistribution_%massEnclosedBySphere(radius(j)) + radiusRoot(j)=massDistribution_%radiusEnclosingMass (mass (j)) if (limitToVirialRadius .and. radiusOverVirialRadius(j) > 1.0d0) radiusRoot(j)=radius(j) end do + !![ + + !!] call Assert('radius enclosing a given mass',radius,radiusRoot,relTol=toleranceRelative) call Unit_Tests_End_Group() end do diff --git a/source/tests.dark_matter_profiles.F90 b/source/tests.dark_matter_profiles.F90 index 88a07dd4ee..68f70f4d82 100644 --- a/source/tests.dark_matter_profiles.F90 +++ b/source/tests.dark_matter_profiles.F90 @@ -25,26 +25,29 @@ program Test_Dark_Matter_Profiles !!{ Tests dark matter profiles. !!} - use :: Calculations_Resets , only : Calculations_Reset - use :: Cosmology_Parameters , only : cosmologyParametersSimple - use :: Cosmology_Functions , only : cosmologyFunctionsMatterLambda - use :: Dark_Matter_Particles , only : darkMatterParticleSelfInteractingDarkMatter , darkMatterParticleCDM - use :: Dark_Matter_Halo_Scales , only : darkMatterHaloScaleVirialDensityContrastDefinition - use :: Dark_Matter_Profiles_DMO , only : darkMatterProfileDMOBurkert , darkMatterProfileDMONFW , darkMatterProfileDMOFiniteResolution, darkMatterProfileDMOSIDMCoreNFW, & - & darkMatterProfileDMOSIDMIsothermal - use :: Dark_Matter_Profiles , only : darkMatterProfileSIDMIsothermal , darkMatterProfileAdiabaticGnedin2004 - use :: Dark_Matter_Profiles_Generic, only : nonAnalyticSolversNumerical - use :: Galactic_Structure , only : galacticStructureStandard - use :: Virial_Density_Contrast , only : virialDensityContrastSphericalCollapseClsnlssMttrCsmlgclCnstnt, virialDensityContrastFixed , fixedDensityTypeCritical - use :: Events_Hooks , only : eventsHooksInitialize - use :: Functions_Global_Utilities , only : Functions_Global_Set - use :: Display , only : displayVerbositySet , verbosityLevelStandard - use :: Galacticus_Nodes , only : nodeClassHierarchyFinalize , nodeClassHierarchyInitialize , nodeComponentBasic , nodeComponentDarkMatterProfile, & - & treeNode , nodeComponentSpheroid - use :: Input_Parameters , only : inputParameters - use :: ISO_Varying_String , only : varying_string , assignment(=) , var_str - use :: Node_Components , only : Node_Components_Initialize , Node_Components_Thread_Initialize , Node_Components_Thread_Uninitialize , Node_Components_Uninitialize - use :: Unit_Tests , only : Assert , Unit_Tests_Begin_Group , Unit_Tests_End_Group , Unit_Tests_Finish + use :: Calculations_Resets , only : Calculations_Reset + use :: Coordinates , only : coordinateSpherical , assignment(=) + use :: Cosmology_Parameters , only : cosmologyParametersSimple + use :: Cosmology_Functions , only : cosmologyFunctionsMatterLambda + use :: Dark_Matter_Particles , only : darkMatterParticleSelfInteractingDarkMatter , darkMatterParticleCDM + use :: Dark_Matter_Halo_Scales , only : darkMatterHaloScaleVirialDensityContrastDefinition + use :: Dark_Matter_Profiles_DMO , only : darkMatterProfileDMOBurkert , darkMatterProfileDMONFW , darkMatterProfileDMOFiniteResolution, darkMatterProfileDMOSIDMCoreNFW, & + & darkMatterProfileDMOSIDMIsothermal , darkMatterProfileDMOZhao1996 + use :: Dark_Matter_Profiles , only : darkMatterProfileSIDMIsothermal , darkMatterProfileAdiabaticGnedin2004 + use :: Virial_Density_Contrast , only : virialDensityContrastSphericalCollapseClsnlssMttrCsmlgclCnstnt, virialDensityContrastFixed , fixedDensityTypeCritical + use :: Events_Hooks , only : eventsHooksInitialize + use :: Functions_Global_Utilities , only : Functions_Global_Set + use :: Display , only : displayVerbositySet , verbosityLevelStandard + use :: Mass_Distributions , only : massDistributionClass , massDistributionSpherical , kinematicsDistributionClass , nonAnalyticSolversNumerical , & + & massDistributionSphericalSIDM + use :: Galacticus_Nodes , only : nodeClassHierarchyFinalize , nodeClassHierarchyInitialize , nodeComponentBasic , nodeComponentDarkMatterProfile, & + & treeNode , nodeComponentSpheroid + use :: Numerical_Constants_Math , only : Pi + use :: Numerical_Constants_Astronomical, only : gravitationalConstantGalacticus, Mpc_per_km_per_s_To_Gyr + use :: Input_Parameters , only : inputParameters + use :: ISO_Varying_String , only : varying_string , assignment(=) , var_str + use :: Node_Components , only : Node_Components_Initialize , Node_Components_Thread_Initialize , Node_Components_Thread_Uninitialize , Node_Components_Uninitialize + use :: Unit_Tests , only : Assert , Unit_Tests_Begin_Group , Unit_Tests_End_Group , Unit_Tests_Finish implicit none type (treeNode ), pointer :: node , & & nodePippin , & @@ -52,9 +55,11 @@ program Test_Dark_Matter_Profiles class (nodeComponentBasic ), pointer :: basic class (nodeComponentDarkMatterProfile ), pointer :: dmProfile class (nodeComponentSpheroid ), pointer :: spheroid - double precision , parameter :: concentration = 8.0d0 , & - & massVirial = 1.0d0 , & - & massSmall = 1.0d-4 , & + class (massDistributionClass ), pointer :: massDistribution_ + class (kinematicsDistributionClass ), pointer :: kinematicsDistribution_ + double precision , parameter :: concentration = 8.0d+0 , & + & massVirial = 1.0d+0 , & + & massSmall = 1.0d-7 , & ! Mass and concentration of Pippin halos (Jiang et al. 2022). & concentrationPippin =15.8d0 , & & massVirialPippin =10.0d0**9.89d0 , & @@ -65,10 +70,15 @@ program Test_Dark_Matter_Profiles & fractionMassBaryonicJiang =1.0d-2 , & & fractionRadiusHalfMassJiang =2.0d-2 , & & radiusHalfMassDimensionlessHernquist=1.0d0/(sqrt(2.0d0)-1.0d0) - double precision , dimension(7) :: radius =[0.125d0, 0.250d0, 0.500d0, 1.000d0, 2.000d0, 4.000d0, 8.000d0] + double precision , dimension(7) :: radius =[0.125000d0,0.25000d0,0.50000d0,1.00000d0,2.00000d0,4.00000d0,8.00000d0] + double precision , dimension(7) :: timeFreefall double precision , dimension(7) :: mass , & - & radiusEnclosingMass , & & density , & + & radiusRecoveredFromMass , & + & radiusRecoveredFromDensity , & + & radiusRecoveredFromTimeFreefall , & + & radiusRecoveredFromSpecificAngularMomentum , & + & potential , & & fourier , & & radialVelocityDispersion , & & radialVelocityDispersionSeriesExpansion @@ -76,6 +86,7 @@ program Test_Dark_Matter_Profiles type (darkMatterParticleSelfInteractingDarkMatter ), pointer :: darkMatterParticleSelfInteractingDarkMatter_ , & & darkMatterParticleSelfInteractingDarkMatterJiang_ type (darkMatterProfileDMOBurkert ), pointer :: darkMatterProfileDMOBurkert_ + type (darkMatterProfileDMOZhao1996 ), pointer :: darkMatterProfileDMOZhao1996_ type (darkMatterProfileDMONFW ), pointer :: darkMatterProfileDMONFW_ , & & darkMatterProfileDMONFWPippin_ type (darkMatterProfileDMONFW ), pointer :: darkMatterProfileDMONFWSeriesExpansion_ @@ -84,7 +95,6 @@ program Test_Dark_Matter_Profiles type (darkMatterProfileDMOSIDMIsothermal ), pointer :: darkMatterProfileDMOSIDMIsothermal_ type (darkMatterProfileAdiabaticGnedin2004 ), pointer :: darkMatterProfileAdiabaticPippin_ type (darkMatterProfileSIDMIsothermal ), pointer :: darkMatterProfileSIDMIsothermal_ - type (galacticStructureStandard ), pointer :: galacticStructureStandard_ type (cosmologyParametersSimple ), pointer :: cosmologyParameters_ , & & cosmologyParametersPippin_ type (cosmologyFunctionsMatterLambda ), pointer :: cosmologyFunctions_ , & @@ -93,11 +103,23 @@ program Test_Dark_Matter_Profiles & darkMatterHaloScalePippin_ type (virialDensityContrastSphericalCollapseClsnlssMttrCsmlgclCnstnt), pointer :: virialDensityContrast_ type (virialDensityContrastFixed ), pointer :: virialDensityContrastPippin_ + type (coordinateSpherical ) :: coordinates type (inputParameters ) :: parameters integer :: i double precision :: radiusScale , & + & timeScale , & + & radiusVelocityMaximum , & + & velocityMaximum , & + & velocityMaximumIndirect , & + & radiusVirial , & + & energyPotential , & + & energyKinetic , & + & energyPotentialNumerical , & + & energyKineticNumerical , & + & densityNormalization , & & radiusSmall + ! Set verbosity level. call displayVerbositySet(verbosityLevelStandard) ! Initialize event hooks and global functions. @@ -114,6 +136,7 @@ program Test_Dark_Matter_Profiles allocate(virialDensityContrast_ ) allocate(darkMatterHaloScale_ ) allocate(darkMatterProfileDMOBurkert_ ) + allocate(darkMatterProfileDMOZhao1996_ ) allocate(darkMatterProfileDMONFW_ ) allocate(darkMatterProfileDMONFWPippin_ ) allocate(darkMatterProfileDMONFWSeriesExpansion_ ) @@ -122,7 +145,6 @@ program Test_Dark_Matter_Profiles allocate(darkMatterProfileDMOSIDMIsothermal_ ) allocate(darkMatterProfileSIDMIsothermal_ ) allocate(darkMatterProfileAdiabaticPippin_ ) - allocate(galacticStructureStandard_ ) allocate(darkMatterParticleSelfInteractingDarkMatter_ ) allocate(darkMatterParticleSelfInteractingDarkMatterJiang_) allocate(darkMatterParticleCDM_ ) @@ -176,8 +198,15 @@ program Test_Dark_Matter_Profiles call basic%timeSet(cosmologyFunctions_%cosmicTime(1.0d0)) call basic%massSet(massVirial ) ! Compute scale radius. - radiusScale=+darkMatterHaloScale_%radiusVirial(node) & - & /concentration + radiusScale =+darkMatterHaloScale_%radiusVirial(node) & + & /concentration + densityNormalization=+massVirial & + & /radiusScale**3 + timeScale =+1.0d0/sqrt( & + & +gravitationalConstantGalacticus & + & *densityNormalization & + & ) & + & *Mpc_per_km_per_s_To_Gyr call dmProfile%scaleSet(radiusScale) ! Build dark matter profiles. !![ @@ -212,7 +241,6 @@ program Test_Dark_Matter_Profiles & resolutionIsComoving =.false. , & & nonAnalyticSolver =nonAnalyticSolversNumerical, & & darkMatterProfileDMO_ =darkMatterProfileDMONFW_ , & - & darkMatterHaloScale_ =darkMatterHaloScale_ , & & cosmologyFunctions_ =cosmologyFunctions_ & & ) @@ -220,13 +248,862 @@ program Test_Dark_Matter_Profiles !!] ! Begin unit tests. call Unit_Tests_Begin_Group('Dark matter profiles') + ! Test Zhao1996 profile. + call Unit_Tests_Begin_Group('Zhao1996 profile') + !! Special case: NFW: (α,β,γ) = (1,3,1). + call Unit_Tests_Begin_Group('(α,β,γ) = (1,3,1)') + !![ + + + darkMatterProfileDMOZhao1996 ( & + & alpha =1.0d0 , & + & beta =3.0d0 , & + & gamma =1.0d0 , & + & darkMatterHaloScale_=darkMatterHaloScale_ & + & ) + + + !!] + massDistribution_ => darkMatterProfileDMOZhao1996_%get (node) + kinematicsDistribution_ => massDistribution_ %kinematicsDistribution( ) + radiusVirial = darkMatterHaloScale_ %radiusVirial (node) + timeFreefall = [0.864113d0,1.29807d0,2.04324d0,3.44421d0,6.32148d0,1.26727d1,2.74642d1] + select type (massDistribution_) + class is (massDistributionSpherical) + do i=1,7 + coordinates=[radiusScale*radius(i),0.0d0,0.0d0] + mass (i)=massDistribution_ %massEnclosedBySphere ( radiusScale *radius (i) ) + density (i)=massDistribution_ %density ( coordinates )*radiusScale**3 + radiusRecoveredFromMass (i)=massDistribution_ %radiusEnclosingMass ( mass(i) )/radiusScale + radiusRecoveredFromDensity (i)=massDistribution_ %radiusEnclosingDensity ( 3.0d0/4.0d0/Pi*mass(i)/radiusScale**3/radius (i)**3 )/radiusScale + radiusRecoveredFromSpecificAngularMomentum(i)=massDistribution_ %radiusFromSpecificAngularMomentum( sqrt(gravitationalConstantGalacticus*mass(i)*radiusScale *radius (i) ) )/radiusScale + radiusRecoveredFromTimeFreefall (i)=massDistribution_ %radiusFreefall ( timeScale *timeFreefall(i) )/radiusScale + potential (i)=massDistribution_ %potential ( coordinates )*radiusScale/gravitationalConstantGalacticus + fourier (i)=massDistribution_ %fourierTransform (radiusVirial,1.0d0 /radiusScale /radius (i) ) + radialVelocityDispersion (i)=kinematicsDistribution_%velocityDispersion1D ( coordinates ,massDistribution_) + end do + radiusSmall =massDistribution_%radiusEnclosingMass (massSmall ) + radiusVelocityMaximum =massDistribution_%radiusRotationCurveMaximum ( ) + velocityMaximum =massDistribution_%velocityRotationCurveMaximum( ) + velocityMaximumIndirect =massDistribution_%rotationCurve (radiusVelocityMaximum ) + energyPotential =massDistribution_%energyPotential (radiusVirial ) + energyPotentialNumerical=massDistribution_%energyPotentialNumerical (radiusVirial ) + energyKinetic =massDistribution_%energyKinetic (radiusVirial ,massDistribution_) + energyKineticNumerical =massDistribution_%energyKineticNumerical (radiusVirial ,massDistribution_) + end select + !![ + + + + !!] + ! Radial velocity dispersion in units of virial velocity. + radialVelocityDispersion=radialVelocityDispersion/darkMatterHaloScale_%velocityVirial(node) + call Assert( & + & 'enclosed mass' , & + & mass , & + & [ & + & 5.099550982355504d-3 , & + & 1.768930674181593d-2 , & + & 5.513246746363203d-2 , & + & 1.476281525188409d-1 , & + & 3.301489257042704d-1 , & + & 6.186775455118112d-1 , & + & 1.000000000000000d+0 & + & ] , & + & relTol=1.0d-6 & + & ) + call Assert( & + & 'radius enclosing mass', & + & radiusRecoveredFromMass, & + & radius , & + & relTol=1.0d-6 & + & ) + call Assert( & + & 'radius enclosing mass (at small radii)', & + & radiusSmall , & + & 1.6764798688849444d-9 , & + & relTol=1.0d-5 & + & ) + call Assert( & + & 'density' , & + & density , & + & [ & + & 3.844641857939078d-1 , & + & 1.557079952465327d-1 , & + & 5.406527612726829d-2 , & + & 1.520585891079421d-2 , & + & 3.379079757954268d-3 , & + & 6.082343564317684d-4 , & + & 9.386332660984080d-5 & + & ] , & + & relTol=1.0d-6 & + & ) + call Assert( & + & 'radius enclosing density', & + & radiusRecoveredFromDensity, & + & radius , & + & relTol=2.0d-4 & + & ) + call Assert( & + & 'radius from specific angular momentum' , & + & radiusRecoveredFromSpecificAngularMomentum, & + & radius , & + & relTol=2.0d-4 & + & ) + call Assert( & + & 'radius from freefall time' , & + & radiusRecoveredFromTimeFreefall , & + & radius , & + & relTol=2.0d-4 & + & ) + call Assert( & + & 'potential' , & + & +potential & + & -potential(1) , & + & [ & + & 4.412912928902085d-2, & + & 8.210873989889300d-2, & + & 1.445116765163305d-1, & + & 2.345367646465509d-1, & + & 3.444787600350539d-1, & + & 4.567944810866741d-1, & + & 5.544042971829187d-1 & + & ] & + & -4.412912928902085d-2, & + & relTol=1.0d-6 & + & ) + call Assert( & + & 'fourier' , & + & fourier , & + & [ & + & 1.099052711906094d-2 , & + & 3.746284433831412d-2 , & + & 1.127728075593513d-1 , & + & 2.619030036621630d-1 , & + & 5.434560723827092d-1 , & + & 8.448608065763800d-1 , & + & 9.579597044271230d-1 & + & ] , & + & relTol=1.0d-6 & + & ) + call Assert( & + & 'peak rotation velocity', & + & velocityMaximum , & + & velocityMaximumIndirect , & + & relTol=1.0d-6 & + & ) + call Assert( & + & 'potential energy' , & + & energyPotential , & + & energyPotentialNumerical, & + & relTol=1.0d-6 & + & ) + call Assert( & + & 'kinetic energy' , & + & energyKinetic , & + & energyKineticNumerical , & + & relTol=1.0d-6 & + & ) + call Assert( & + & 'radial velocity dispersion', & + & radialVelocityDispersion , & + & [ & + & 6.285734096346791d-1 , & + & 7.048421272327700d-1 , & + & 7.510776824829392d-1 , & + & 7.558757679348769d-1 , & + & 7.172187600402157d-1 , & + & 6.441447075213688d-1 , & + & 5.520559866965048d-1 & + & ] , & + & relTol=1.0d-6 & + & ) + call Unit_Tests_End_Group() + !! Special case: cored NFW: (α,β,γ) = (1,3,0). + call Unit_Tests_Begin_Group('(α,β,γ) = (1,3,0)') + allocate(darkMatterProfileDMOZhao1996_) + !![ + + + darkMatterProfileDMOZhao1996 ( & + & alpha =1.0d0 , & + & beta =3.0d0 , & + & gamma =0.0d0 , & + & darkMatterHaloScale_=darkMatterHaloScale_ & + & ) + + + !!] + massDistribution_ => darkMatterProfileDMOZhao1996_%get (node) + kinematicsDistribution_ => massDistribution_ %kinematicsDistribution( ) + radiusVirial = darkMatterHaloScale_ %radiusVirial (node) + timeFreefall = [2.912172466390932d0,3.227873348829691d0,3.870044363719088d0,5.196579302974257d0,8.00677623636939d0,1.417018715413697d1,2.823093711816523d1] + select type (massDistribution_) + class is (massDistributionSpherical) + do i=1,7 + coordinates=[radiusScale*radius(i),0.0d0,0.0d0] + mass (i)=massDistribution_ %massEnclosedBySphere ( radiusScale *radius (i) ) + density (i)=massDistribution_ %density ( coordinates )*radiusScale**3 + radiusRecoveredFromMass (i)=massDistribution_ %radiusEnclosingMass ( mass(i) )/radiusScale + radiusRecoveredFromDensity (i)=massDistribution_ %radiusEnclosingDensity ( 3.0d0/4.0d0/Pi*mass(i)/radiusScale**3/radius (i)**3 )/radiusScale + radiusRecoveredFromSpecificAngularMomentum(i)=massDistribution_ %radiusFromSpecificAngularMomentum( sqrt(gravitationalConstantGalacticus*mass(i)*radiusScale *radius (i) ) )/radiusScale + radiusRecoveredFromTimeFreefall (i)=massDistribution_ %radiusFreefall ( timeScale *timeFreefall(i) )/radiusScale + potential (i)=massDistribution_ %potential ( coordinates )*radiusScale/gravitationalConstantGalacticus + fourier (i)=massDistribution_ %fourierTransform (radiusVirial,1.0d0 /radiusScale /radius (i) ) + radialVelocityDispersion (i)=kinematicsDistribution_%velocityDispersion1D ( coordinates ,massDistribution_) + end do + radiusVelocityMaximum =massDistribution_%radiusRotationCurveMaximum ( ) + velocityMaximum =massDistribution_%velocityRotationCurveMaximum( ) + velocityMaximumIndirect =massDistribution_%rotationCurve (radiusVelocityMaximum ) + energyPotential =massDistribution_%energyPotential (radiusVirial ) + energyPotentialNumerical=massDistribution_%energyPotentialNumerical (radiusVirial ) + energyKinetic =massDistribution_%energyKinetic (radiusVirial ,massDistribution_) + energyKineticNumerical =massDistribution_%energyKineticNumerical (radiusVirial ,massDistribution_) + end select + !![ + + + + !!] + ! Radial velocity dispersion in units of virial velocity. + radialVelocityDispersion=radialVelocityDispersion/darkMatterHaloScale_%velocityVirial(node) + call Assert( & + & 'enclosed mass' , & + & mass , & + & [ & + & 5.464789985591522d-4 , & + & 3.442068263974007d-3 , & + & 1.815032503316619d-2 , & + & 7.461855208928223d-2 , & + & 2.296390885460238d-1 , & + & 5.359157644285496d-1 , & + & 1.000000000000000d+0 & + & ] , & + & relTol=1.0d-6 & + & ) + call Assert( & + & 'radius enclosing mass', & + & radiusRecoveredFromMass, & + & radius , & + & relTol=1.0d-6 & + & ) + call Assert( & + & 'density' , & + & density , & + & [ & + & 6.119719178912785d-2 , & + & 4.461275281427421d-2 , & + & 2.581756528603831d-2 , & + & 1.089178535504741d-2 , & + & 3.227195660754789d-3 , & + & 6.970742627230344d-4 , & + & 1.195257652131403d-4 & + & ] , & + & relTol=1.0d-6 & + & ) + call Assert( & + & 'radius enclosing density', & + & radiusRecoveredFromDensity, & + & radius , & + & relTol=2.0d-4 & + & ) + call Assert( & + & 'radius from specific angular momentum' , & + & radiusRecoveredFromSpecificAngularMomentum, & + & radius , & + & relTol=2.0d-4 & + & ) + call Assert( & + & 'radius from freefall time' , & + & radiusRecoveredFromTimeFreefall , & + & radius , & + & relTol=2.0d-4 & + & ) + call Assert( & + & 'potential' , & + & +potential & + & -potential(1) , & + & [ & + & 2.387190797876185d-3, & + & 8.130960771876030d-3, & + & 2.453055501081222d-2, & + & 6.225165933429317d-2, & + & 1.285052760355665d-1, & + & 2.164088001372156d-1, & + & 3.075774583263617d-1 & + & ] & + & -2.387190797876185d-3, & + & relTol=1.0d-6 & + & ) + call Assert( & + & 'fourier' , & + & fourier , & + & [ & + & 9.697536800190850d-4 , & + & 8.183597728241470d-3 , & + & 5.062326038988727d-2 , & + & 1.681631870688479d-1 , & + & 4.620457662739311d-1 , & + & 8.150847326475010d-1 , & + & 9.497559618451080d-1 & + & ] , & + & relTol=1.0d-6 & + & ) + call Assert( & + & 'peak rotation velocity', & + & velocityMaximum , & + & velocityMaximumIndirect , & + & relTol=1.0d-6 & + & ) + call Assert( & + & 'potential energy' , & + & energyPotential , & + & energyPotentialNumerical, & + & relTol=1.0d-6 & + & ) + call Assert( & + & 'kinetic energy' , & + & energyKinetic , & + & energyKineticNumerical , & + & relTol=1.0d-6 & + & ) + call Assert( & + & 'radial velocity dispersion', & + & radialVelocityDispersion , & + & [ & + & 5.214290018440262d-1 , & + & 5.654096128890455d-1 , & + & 6.175226284000780d-1 , & + & 6.597522036704852d-1 , & + & 6.699313413583514d-1 , & + & 6.379354590281848d-1 , & + & 5.714635036404890d-1 & + & ] , & + & relTol=1.0d-6 & + & ) + call Unit_Tests_End_Group() + !! Special case: cored NFW: (α,β,γ) = (1,3,½). + call Unit_Tests_Begin_Group('(α,β,γ) = (1,3,½)') + allocate(darkMatterProfileDMOZhao1996_) + !![ + + + darkMatterProfileDMOZhao1996 ( & + & alpha =1.0d0 , & + & beta =3.0d0 , & + & gamma =0.5d0 , & + & darkMatterHaloScale_=darkMatterHaloScale_ & + & ) + + + !!] + massDistribution_ => darkMatterProfileDMOZhao1996_%get (node) + kinematicsDistribution_ => massDistribution_ %kinematicsDistribution( ) + radiusVirial = darkMatterHaloScale_ %radiusVirial (node) + timeFreefall = [1.596420780360787d0,2.059037739059005d0,2.826539348071723d0,4.248077776277237d0,7.134952215026308d0,1.342236886351155d1,2.785866097530184d1] + select type (massDistribution_) + class is (massDistributionSpherical) + do i=1,7 + coordinates=[radiusScale*radius(i),0.0d0,0.0d0] + mass (i)=massDistribution_ %massEnclosedBySphere ( radiusScale *radius (i) ) + density (i)=massDistribution_ %density ( coordinates )*radiusScale**3 + radiusRecoveredFromMass (i)=massDistribution_ %radiusEnclosingMass ( mass(i) )/radiusScale + radiusRecoveredFromDensity (i)=massDistribution_ %radiusEnclosingDensity ( 3.0d0/4.0d0/Pi*mass(i)/radiusScale**3/radius (i)**3 )/radiusScale + radiusRecoveredFromSpecificAngularMomentum(i)=massDistribution_ %radiusFromSpecificAngularMomentum( sqrt(gravitationalConstantGalacticus*mass(i)*radiusScale *radius (i) ) )/radiusScale + radiusRecoveredFromTimeFreefall (i)=massDistribution_ %radiusFreefall ( timeScale *timeFreefall(i) )/radiusScale + potential (i)=massDistribution_ %potential ( coordinates )*radiusScale/gravitationalConstantGalacticus + fourier (i)=massDistribution_ %fourierTransform (radiusVirial,1.0d0 /radiusScale /radius (i) ) + radialVelocityDispersion (i)=kinematicsDistribution_%velocityDispersion1D ( coordinates ,massDistribution_) + end do + radiusVelocityMaximum =massDistribution_%radiusRotationCurveMaximum ( ) + velocityMaximum =massDistribution_%velocityRotationCurveMaximum( ) + velocityMaximumIndirect =massDistribution_%rotationCurve (radiusVelocityMaximum ) + energyPotential =massDistribution_%energyPotential (radiusVirial ) + energyPotentialNumerical=massDistribution_%energyPotentialNumerical (radiusVirial ) + energyKinetic =massDistribution_%energyKinetic (radiusVirial ,massDistribution_) + energyKineticNumerical =massDistribution_%energyKineticNumerical (radiusVirial ,massDistribution_) + end select + !![ + + + + !!] + ! Radial velocity dispersion in units of virial velocity. + radialVelocityDispersion=radialVelocityDispersion/darkMatterHaloScale_%velocityVirial(node) + call Assert( & + & 'enclosed mass' , & + & mass , & + & [ & + & 1.654826011427427d-3 , & + & 7.739711640396846d-3 , & + & 3.140778408165459d-2 , & + & 1.043599712384584d-1 , & + & 2.742860732070792d-1 , & + & 5.747348550741195d-1 , & + & 1.000000000000000d+0 & + & ] , & + & relTol=1.0d-6 & + & ) + call Assert( & + & 'radius enclosing mass', & + & radiusRecoveredFromMass, & + & radius , & + & relTol=1.0d-6 & + & ) + call Assert( & + & 'density' , & + & density , & + & [ & + &1.550807828980169d-1,& + & 8.42653949330162d-2,& + & 3.777297120800491d-2,& + & 1.301125858993861d-2,& + & 3.338690510843061d-3,& + & 6.583233979141888d-4,& + & 1.070885480653384d-4& + & ] , & + & relTol=1.0d-6 & + & ) + call Assert( & + & 'radius enclosing density', & + & radiusRecoveredFromDensity, & + & radius , & + & relTol=2.0d-4 & + & ) + call Assert( & + & 'radius from specific angular momentum' , & + & radiusRecoveredFromSpecificAngularMomentum, & + & radius , & + & relTol=2.0d-4 & + & ) + call Assert( & + & 'radius from freefall time' , & + & radiusRecoveredFromTimeFreefall , & + & radius , & + & relTol=2.0d-4 & + & ) + call Assert( & + & 'potential' , & + & +potential & + & -potential(1) , & + & [ & + &9.59892229601949d-3,& + & 2.419272545370556d-2,& + & 5.585172068801785d-2,& + & 1.136457588954521d-1,& + & 1.98498742003645d-1,& + & 2.975288623538137d-1,& + & 3.917543232803806d-1& + & ] & + & -9.59892229601949d-3, & + & relTol=1.0d-6 & + & ) + call Assert( & + & 'fourier' , & + & fourier , & + & [ & + &3.707401665292136d-3,& + & 1.826316873546129d-2,& + & 7.540534190396314d-2,& + & 2.092215103402299d-1,& + & 4.994927051818294d-1,& + & 8.28909754178526d-1,& + & 9.53572760138475d-1& + & ] , & + & relTol=1.0d-6 & + & ) + call Assert( & + & 'peak rotation velocity', & + & velocityMaximum , & + & velocityMaximumIndirect , & + & relTol=1.0d-6 & + & ) + call Assert( & + & 'potential energy' , & + & energyPotential , & + & energyPotentialNumerical, & + & relTol=1.0d-6 & + & ) + call Assert( & + & 'kinetic energy' , & + & energyKinetic , & + & energyKineticNumerical , & + & relTol=1.0d-6 & + & ) + call Assert( & + & 'radial velocity dispersion', & + & radialVelocityDispersion , & + & [ & + & 5.347095696836751d-1 , & + & 6.083588653620841d-1 , & + & 6.691204678148943d-1 , & + & 7.007128495278605d-1 , & + & 6.909164992407667d-1 , & + & 6.403334146398749d-1 , & + & 5.617664344971543d-1 & + & ] , & + & relTol=1.0d-6 & + & ) + call Unit_Tests_End_Group() + !! Special case: cored NFW: (α,β,γ) = (1,3,1½). + call Unit_Tests_Begin_Group('(α,β,γ) = (1,3,1½)') + allocate(darkMatterProfileDMOZhao1996_) + !![ + + + darkMatterProfileDMOZhao1996 ( & + & alpha =1.0d0 , & + & beta =3.0d0 , & + & gamma =1.5d0 , & + & darkMatterHaloScale_=darkMatterHaloScale_ & + & ) + + + !!] + massDistribution_ => darkMatterProfileDMOZhao1996_%get (node) + kinematicsDistribution_ => massDistribution_ %kinematicsDistribution( ) + radiusVirial = darkMatterHaloScale_ %radiusVirial (node) + timeFreefall = [4.596100900700435d-1,8.05108081560643d-1,1.455969425989372d0,2.760391236512836d0,5.555351793104961d0,1.191020177556312d1,2.703975097809943d1] + select type (massDistribution_) + class is (massDistributionSpherical) + do i=1,7 + coordinates=[radiusScale*radius(i),0.0d0,0.0d0] + mass (i)=massDistribution_ %massEnclosedBySphere ( radiusScale *radius (i) ) + density (i)=massDistribution_ %density ( coordinates )*radiusScale**3 + radiusRecoveredFromMass (i)=massDistribution_ %radiusEnclosingMass ( mass(i) )/radiusScale + radiusRecoveredFromDensity (i)=massDistribution_ %radiusEnclosingDensity ( 3.0d0/4.0d0/Pi*mass(i)/radiusScale**3/radius (i)**3 )/radiusScale + radiusRecoveredFromSpecificAngularMomentum(i)=massDistribution_ %radiusFromSpecificAngularMomentum( sqrt(gravitationalConstantGalacticus*mass(i)*radiusScale *radius (i) ) )/radiusScale + radiusRecoveredFromTimeFreefall (i)=massDistribution_ %radiusFreefall ( timeScale *timeFreefall(i) )/radiusScale + potential (i)=massDistribution_ %potential ( coordinates )*radiusScale/gravitationalConstantGalacticus + fourier (i)=massDistribution_ %fourierTransform (radiusVirial,1.0d0 /radiusScale /radius (i) ) + radialVelocityDispersion (i)=kinematicsDistribution_%velocityDispersion1D ( coordinates ,massDistribution_) + end do + radiusVelocityMaximum =massDistribution_%radiusRotationCurveMaximum ( ) + velocityMaximum =massDistribution_%velocityRotationCurveMaximum( ) + velocityMaximumIndirect =massDistribution_%rotationCurve (radiusVelocityMaximum ) + energyPotential =massDistribution_%energyPotential (radiusVirial ) + energyPotentialNumerical=massDistribution_%energyPotentialNumerical (radiusVirial ) + energyKinetic =massDistribution_%energyKinetic (radiusVirial ,massDistribution_) + energyKineticNumerical =massDistribution_%energyKineticNumerical (radiusVirial ,massDistribution_) + end select + !![ + + + + !!] + ! Radial velocity dispersion in units of virial velocity. + radialVelocityDispersion=radialVelocityDispersion/darkMatterHaloScale_%velocityVirial(node) + call Assert( & + & 'enclosed mass' , & + & mass , & + & [ & + & 1.614787314131082d-2 , & + & 4.146438397463787d-2 , & + & 9.894487896260260d-2 , & + & 2.125365304218604d-1 , & + & 4.021269908070108d-1 , & + & 6.698167367007793d-1 , & + & 1.000000000000000d+0 & + & ] , & + & relTol=1.0d-6 & + & ) + call Assert( & + & 'radius enclosing mass', & + & radiusRecoveredFromMass, & + & radius , & + & relTol=1.0d-6 & + & ) + call Assert( & + & 'density' , & + & density , & + & [ & + & 9.202064069700650d-1 , & + & 2.777819507076467d-1 , & + & 7.471144923386984d-2 , & + & 1.715671205314034d-2 , & + & 3.301810774096491d-3 , & + & 5.425428724758725d-4 , & + & 7.942922246824230d-5 & + & ] , & + & relTol=1.0d-6 & + & ) + call Assert( & + & 'radius enclosing density', & + & radiusRecoveredFromDensity, & + & radius , & + & relTol=2.0d-4 & + & ) + call Assert( & + & 'radius from specific angular momentum' , & + & radiusRecoveredFromSpecificAngularMomentum, & + & radius , & + & relTol=2.0d-4 & + & ) + call Assert( & + & 'radius from freefall time' , & + & radiusRecoveredFromTimeFreefall , & + & radius , & + & relTol=2.0d-4 & + & ) + call Assert( & + & 'potential' , & + & +potential & + & -potential(1) , & + & [ & + & 2.773517522337437d-1, & + & 3.795660488783141d-1, & + & 5.062490622313113d-1, & + & 6.498538783125577d-1, & + & 7.947391738552302d-1, & + & 9.233929853785370d-1, & + & 1.024853878312558d+0 & + & ] & + & -2.773517522337437d-1, & + & relTol=1.0d-6 & + & ) + call Assert( & + & 'fourier' , & + & fourier , & + & [ & + & 3.012898538414511d-2 , & + & 7.490502708109203d-2 , & + & 1.710991422211459d-1 , & + & 3.320442821775421d-1 , & + & 5.969264990169439d-1 , & + & 8.639011197411600d-1 , & + & 9.631744296899940d-1 & + & ] , & + & relTol=1.0d-5 & + & ) + call Assert( & + & 'peak rotation velocity', & + & velocityMaximum , & + & velocityMaximumIndirect , & + & relTol=1.0d-6 & + & ) + call Assert( & + & 'potential energy' , & + & energyPotential , & + & energyPotentialNumerical, & + & relTol=1.0d-6 & + & ) + call Assert( & + & 'kinetic energy' , & + & energyKinetic , & + & energyKineticNumerical , & + & relTol=1.0d-6 & + & ) + call Assert( & + & 'radial velocity dispersion', & + & radialVelocityDispersion , & + & [ & + & 8.363292318000940d-1 , & + & 8.746095866642690d-1 , & + & 8.733596657690690d-1 , & + & 8.296377525840130d-1 , & + & 7.505291252896317d-1 , & + & 6.497937689634748d-1 , & + & 5.421632188128948d-1 & + & ] , & + & relTol=1.0d-6 & + & ) + call Unit_Tests_End_Group() + !! General case: (α,β,γ) = (1,2,1). + call Unit_Tests_Begin_Group('(α,β,γ) = (1,2,1)') + allocate(darkMatterProfileDMOZhao1996_) + !![ + + + darkMatterProfileDMOZhao1996 ( & + & alpha =1.0d0 , & + & beta =2.0d0 , & + & gamma =1.0d0 , & + & darkMatterHaloScale_=darkMatterHaloScale_ & + & ) + + + !!] + massDistribution_ => darkMatterProfileDMOZhao1996_%get (node) + kinematicsDistribution_ => massDistribution_ %kinematicsDistribution( ) + radiusVirial = darkMatterHaloScale_ %radiusVirial (node) + timeFreefall = [1.760884513374176d0,2.56756519338419d0,3.83552259760776d0,5.9420179316033d0,9.64631439102321d0,1.646977873143257d1,2.944555422801916d1] + select type (massDistribution_) + class is (massDistributionSpherical) + do i=1,7 + coordinates=[radiusScale*radius(i),0.0d0,0.0d0] + mass (i)=massDistribution_ %massEnclosedBySphere ( radiusScale *radius (i) ) + density (i)=massDistribution_ %density ( coordinates )*radiusScale**3 + radiusRecoveredFromMass (i)=massDistribution_ %radiusEnclosingMass ( mass(i) )/radiusScale + radiusRecoveredFromDensity (i)=massDistribution_ %radiusEnclosingDensity ( 3.0d0/4.0d0/Pi*mass(i)/radiusScale**3/radius (i)**3 )/radiusScale + radiusRecoveredFromSpecificAngularMomentum(i)=massDistribution_ %radiusFromSpecificAngularMomentum( sqrt(gravitationalConstantGalacticus*mass(i)*radiusScale *radius (i) ) )/radiusScale + radiusRecoveredFromTimeFreefall (i)=massDistribution_ %radiusFreefall ( timeScale *timeFreefall(i) )/radiusScale + potential (i)=massDistribution_ %potential ( coordinates )*radiusScale/gravitationalConstantGalacticus + fourier (i)=massDistribution_ %fourierTransform (radiusVirial,1.0d0 /radiusScale /radius (i) ) + radialVelocityDispersion (i)=kinematicsDistribution_%velocityDispersion1D ( coordinates ,massDistribution_) + end do + radiusVelocityMaximum =massDistribution_%radiusRotationCurveMaximum ( ) + velocityMaximum =massDistribution_%velocityRotationCurveMaximum( ) + velocityMaximumIndirect =massDistribution_%rotationCurve (radiusVelocityMaximum ) + energyPotential =massDistribution_%energyPotential (radiusVirial ) + energyPotentialNumerical=massDistribution_%energyPotentialNumerical (radiusVirial ) + energyKinetic =massDistribution_%energyKinetic (radiusVirial ,massDistribution_) + energyKineticNumerical =massDistribution_%energyKineticNumerical (radiusVirial ,massDistribution_) + end select + !![ + + + + !!] + ! Radial velocity dispersion in units of virial velocity. + radialVelocityDispersion=radialVelocityDispersion/darkMatterHaloScale_%velocityVirial(node) + call Assert( & + & 'enclosed mass' , & + & mass , & + & [ & + & 1.243709056088815d-3 , & + & 4.628207492038648d-3 , & + & 1.629132354883366d-2 , & + & 5.288035415632079d-2 , & + & 1.553373421641236d-1 , & + & 4.119687414110722d-1 , & + & 1.000000000000000d+0 & + & ] , & + & relTol=1.0d-6 & + & ) + call Assert( & + & 'radius enclosing mass', & + & radiusRecoveredFromMass, & + & radius , & + & relTol=1.0d-6 & + & ) + call Assert( & + & 'density' , & + & density , & + & [ & + & 9.751958345559170d-2 , & + & 4.388381255501626d-2 , & + & 1.828492189792344d-2 , & + & 6.856845711721292d-3 , & + & 2.285615237240430d-3 , & + & 6.856845711721292d-4 , & + & 1.904679364367025d-4 & + & ] , & + & relTol=1.0d-6 & + & ) + call Assert( & + & 'radius enclosing density', & + & radiusRecoveredFromDensity, & + & radius , & + & relTol=2.0d-4 & + & ) + call Assert( & + & 'radius from specific angular momentum' , & + & radiusRecoveredFromSpecificAngularMomentum, & + & radius , & + & relTol=2.0d-4 & + & ) + call Assert( & + & 'radius from freefall time' , & + & radiusRecoveredFromTimeFreefall , & + & radius , & + & relTol=2.0d-4 & + & ) + call Assert( & + & 'potential' , & + & +potential & + & -potential(1) , & + & [ & + & 1.034803460994295d-2, & + & 1.994179476929133d-2, & + & 3.729169381246816d-2, & + & 6.657062060529668d-2, & + & 1.116566445896912d-1, & + & 1.743643889079128d-1, & + & 2.536506313435059d-1 & + & ] & + & -1.034803460994295d-2, & + & relTol=1.0d-6 & + & ) + call Assert( & + & 'fourier' , & + & fourier , & + & [ & + & 2.498947569002664d-3 , & + & 8.859191321495360d-3 , & + & 3.901409916856754d-2 , & + & 1.078443248271426d-1 , & + & 3.537730117747184d-1 , & + & 7.703987792232572d-1 , & + & 9.371207041223680d-1 & + & ] , & + & relTol=1.0d-5 & + & ) + call Assert( & + & 'peak rotation velocity', & + & velocityMaximum , & + & velocityMaximumIndirect , & + & relTol=1.0d-6 & + & ) + call Assert( & + & 'potential energy' , & + & energyPotential , & + & energyPotentialNumerical, & + & relTol=1.0d-6 & + & ) + call Assert( & + & 'kinetic energy' , & + & energyKinetic , & + & energyKineticNumerical , & + & relTol=1.0d-6 & + & ) + call Assert( & + & 'radial velocity dispersion', & + & radialVelocityDispersion , & + & [ & + & 3.790874753794872d-1 , & + & 4.539034405088716d-1 , & + & 5.289441877216294d-1 , & + & 5.997499368748768d-1 , & + & 6.624381831647858d-1 , & + & 7.141526014953340d-1 , & + & 7.536975187158942d-1 & + & ] , & + & relTol=1.0d-3 & + & ) + call Unit_Tests_End_Group() + call Unit_Tests_End_Group() ! Test Burkert profile. call Unit_Tests_Begin_Group('Burkert profile') - do i=1,7 - mass (i)=darkMatterProfileDMOBurkert_%enclosedMass(node, radiusScale*radius(i)) - density(i)=darkMatterProfileDMOBurkert_%density (node, radiusScale*radius(i))*radiusScale**3 - fourier(i)=darkMatterProfileDMOBurkert_%kSpace (node,1.0d0/radiusScale/radius(i)) - end do + massDistribution_ => darkMatterProfileDMOBurkert_%get (node) + kinematicsDistribution_ => massDistribution_ %kinematicsDistribution( ) + radiusVirial = darkMatterHaloScale_ %radiusVirial (node) + timeFreefall = [3.378196272398096d0,3.533466912782305d0,3.901024113095533d0,4.840727096354573d0,7.260451446070998d0,1.323150559822296d1,2.774028478732573d1] + select type (massDistribution_) + class is (massDistributionSpherical) + do i=1,7 + coordinates=[radiusScale*radius(i),0.0d0,0.0d0] + mass (i)=massDistribution_ %massEnclosedBySphere ( radiusScale *radius (i) ) + density (i)=massDistribution_ %density ( coordinates )*radiusScale**3 + radiusRecoveredFromMass (i)=massDistribution_ %radiusEnclosingMass ( mass(i) )/radiusScale + radiusRecoveredFromDensity (i)=massDistribution_ %radiusEnclosingDensity ( 3.0d0/4.0d0/Pi*mass(i)/radiusScale**3/radius (i)**3 )/radiusScale + radiusRecoveredFromSpecificAngularMomentum(i)=massDistribution_ %radiusFromSpecificAngularMomentum( sqrt(gravitationalConstantGalacticus*mass(i)*radiusScale *radius (i) ) )/radiusScale + radiusRecoveredFromTimeFreefall (i)=massDistribution_ %radiusFreefall ( timeScale *timeFreefall(i) )/radiusScale + potential (i)=massDistribution_ %potential ( coordinates )*radiusScale/gravitationalConstantGalacticus + fourier (i)=massDistribution_ %fourierTransform (radiusVirial,1.0d0 /radiusScale /radius (i) ) + radialVelocityDispersion (i)=kinematicsDistribution_%velocityDispersion1D ( coordinates ,massDistribution_) + end do + radiusVelocityMaximum =massDistribution_%radiusRotationCurveMaximum ( ) + velocityMaximum =massDistribution_%velocityRotationCurveMaximum( ) + velocityMaximumIndirect =massDistribution_%rotationCurve (radiusVelocityMaximum) + energyPotential =massDistribution_%energyPotential (radiusVirial ) + energyPotentialNumerical=massDistribution_%energyPotentialNumerical (radiusVirial ) + end select + !![ + + + !!] + ! Radial velocity dispersion in units of virial velocity. + radialVelocityDispersion=radialVelocityDispersion/darkMatterHaloScale_%velocityVirial(node) call Assert( & & 'enclosed mass' , & & mass , & @@ -241,6 +1118,12 @@ program Test_Dark_Matter_Profiles & ] , & & relTol=1.0d-6 & & ) + call Assert( & + & 'radius enclosing mass', & + & radiusRecoveredFromMass, & + & radius , & + & relTol=1.0d-6 & + & ) call Assert( & & 'density' , & & density , & @@ -255,6 +1138,46 @@ program Test_Dark_Matter_Profiles & ] , & & relTol=1.0d-6 & & ) + call Assert( & + & 'radius enclosing density', & + & radiusRecoveredFromDensity, & + & radius , & + & relTol=2.0d-4 & + & ) + call Assert( & + & 'radius from specific angular momentum' , & + & radiusRecoveredFromSpecificAngularMomentum, & + & radius , & + & relTol=2.0d-4 & + & ) + call Assert( & + & 'radius from freefall time' , & + & radiusRecoveredFromTimeFreefall , & + & radius , & + & relTol=2.0d-4 & + & ) + call Assert( & + & 'peak rotation velocity', & + & velocityMaximum , & + & velocityMaximumIndirect , & + & relTol=1.0d-6 & + & ) + call Assert( & + & 'potential' , & + & +potential & + & -potential(1) , & + & [ & + & -5.517710030243666d-1, & + & -5.470649572158022d-1, & + & -5.313012274275472d-1, & + & -4.884797937826585d-1, & + & -4.072028257618435d-1, & + & -3.040428693416573d-1, & + & -2.075890931613922d-1 & + & ] & + & +5.517710030243666d-1, & + & relTol=1.0d-6 & + & ) call Assert( & & 'fourier' , & & fourier , & @@ -269,21 +1192,61 @@ program Test_Dark_Matter_Profiles & ] , & & relTol=1.0d-6 & & ) + call Assert( & + & 'potential energy' , & + & energyPotential , & + & energyPotentialNumerical, & + & relTol=1.0d-6 & + & ) + call Assert( & + & 'radial velocity dispersion (analytic)', & + & radialVelocityDispersion , & + & [ & + & 6.395640553962476d-1 , & + & 6.595971570604928d-1 , & + & 6.818328396903716d-1 , & + & 6.957079957624708d-1 , & + & 6.836738566635967d-1 , & + & 6.307711855060676d-1 , & + & 5.466303194157120d-1 & + & ] , & + & relTol=1.0d-6 & + & ) call Unit_Tests_End_Group () ! Test NFW profile. call Unit_Tests_Begin_Group('NFW profile') - do i=1,7 - mass (i)=darkMatterProfileDMONFW_ %enclosedMass (node, radiusScale*radius(i)) - radiusEnclosingMass (i)=darkMatterProfileDMONFW_ %radiusEnclosingMass (node, mass (i)) - density (i)=darkMatterProfileDMONFW_ %density (node, radiusScale*radius(i))*radiusScale**3 - fourier (i)=darkMatterProfileDMONFW_ %kSpace (node,1.0d0/radiusScale/radius(i)) - radialVelocityDispersion (i)=darkMatterProfileDMONFW_ %radialVelocityDispersion(node, radiusScale*radius(i)) - radialVelocityDispersionSeriesExpansion(i)=darkMatterProfileDMONFWSeriesExpansion_%radialVelocityDispersion(node, radiusScale*radius(i)) - end do + massDistribution_ => darkMatterProfileDMONFW_%get (node) + kinematicsDistribution_ => massDistribution_ %kinematicsDistribution( ) + radiusVirial = darkMatterHaloScale_ %radiusVirial (node) + timeFreefall = [0.864113d0,1.29807d0,2.04324d0,3.44421d0,6.32148d0,1.26727d1,2.74642d1] + select type (massDistribution_) + class is (massDistributionSpherical) + do i=1,7 + coordinates=[radiusScale*radius(i),0.0d0,0.0d0] + mass (i)=massDistribution_ %massEnclosedBySphere ( radiusScale *radius (i) ) + density (i)=massDistribution_ %density ( coordinates )*radiusScale**3 + radiusRecoveredFromMass (i)=massDistribution_ %radiusEnclosingMass ( mass(i) )/radiusScale + radiusRecoveredFromDensity (i)=massDistribution_ %radiusEnclosingDensity ( 3.0d0/4.0d0/Pi*mass(i)/radiusScale**3/radius (i)**3 )/radiusScale + radiusRecoveredFromSpecificAngularMomentum(i)=massDistribution_ %radiusFromSpecificAngularMomentum( sqrt(gravitationalConstantGalacticus*mass(i)*radiusScale *radius (i) ) )/radiusScale + radiusRecoveredFromTimeFreefall (i)=massDistribution_ %radiusFreefall ( timeScale *timeFreefall(i) )/radiusScale + potential (i)=massDistribution_ %potential ( coordinates )*radiusScale/gravitationalConstantGalacticus + fourier (i)=massDistribution_ %fourierTransform (radiusVirial,1.0d0 /radiusScale /radius (i) ) + radialVelocityDispersion (i)=kinematicsDistribution_%velocityDispersion1D ( coordinates ,massDistribution_) + end do + energyPotential =massDistribution_%energyPotential (radiusVirial ) + energyPotentialNumerical=massDistribution_%energyPotentialNumerical (radiusVirial ) + energyKinetic =massDistribution_%energyKinetic (radiusVirial ,massDistribution_) + energyKineticNumerical =massDistribution_%energyKineticNumerical (radiusVirial ,massDistribution_) + radiusVelocityMaximum =massDistribution_%radiusRotationCurveMaximum ( ) + velocityMaximum =massDistribution_%velocityRotationCurveMaximum( ) + velocityMaximumIndirect =massDistribution_%rotationCurve (radiusVelocityMaximum ) + end select + !![ + + + !!] ! Radial velocity dispersion in units of virial velocity. - radialVelocityDispersion =radialVelocityDispersion /darkMatterHaloScale_%velocityVirial (node ) - radialVelocityDispersionSeriesExpansion=radialVelocityDispersionSeriesExpansion/darkMatterHaloScale_%velocityVirial (node ) - radiusSmall =darkMatterProfileDMONFW_ %radiusEnclosingMass(node,massSmall) + radialVelocityDispersion=radialVelocityDispersion/darkMatterHaloScale_%velocityVirial(node) call Assert( & & 'enclosed mass' , & & mass , & @@ -300,8 +1263,8 @@ program Test_Dark_Matter_Profiles & ) call Assert( & & 'radius enclosing mass', & - & radiusEnclosingMass , & - & radius*radiusScale , & + & radiusRecoveredFromMass, & + & radius , & & relTol=1.0d-6 & & ) call Assert( & @@ -318,6 +1281,40 @@ program Test_Dark_Matter_Profiles & ] , & & relTol=1.0d-6 & & ) + call Assert( & + & 'radius enclosing density', & + & radiusRecoveredFromDensity, & + & radius , & + & relTol=2.0d-4 & + & ) + call Assert( & + & 'radius from specific angular momentum' , & + & radiusRecoveredFromSpecificAngularMomentum, & + & radius , & + & relTol=2.0d-4 & + & ) + call Assert( & + & 'radius from freefall time' , & + & radiusRecoveredFromTimeFreefall , & + & radius , & + & relTol=2.0d-4 & + & ) + call Assert( & + & 'potential' , & + & +potential & + & -potential(1) , & + & [ & + & 4.412912928902085d-2, & + & 8.210873989889300d-2, & + & 1.445116765163305d-1, & + & 2.345367646465509d-1, & + & 3.444787600350539d-1, & + & 4.567944810866741d-1, & + & 5.544042971829187d-1 & + & ] & + & -4.412912928902085d-2, & + & relTol=1.0d-6 & + & ) call Assert( & & 'fourier' , & & fourier , & @@ -332,6 +1329,24 @@ program Test_Dark_Matter_Profiles & ] , & & relTol=1.0d-6 & & ) + call Assert( & + & 'peak rotation velocity', & + & velocityMaximum , & + & velocityMaximumIndirect , & + & relTol=1.0d-6 & + & ) + call Assert( & + & 'potential energy' , & + & energyPotential , & + & energyPotentialNumerical, & + & relTol=1.0d-6 & + & ) + call Assert( & + & 'kinetic energy' , & + & energyKinetic , & + & energyKineticNumerical , & + & relTol=1.0d-6 & + & ) call Assert( & & 'radial velocity dispersion (analytic )', & & radialVelocityDispersion , & @@ -346,6 +1361,21 @@ program Test_Dark_Matter_Profiles & ] , & & relTol=1.0d-6 & & ) + massDistribution_ => darkMatterProfileDMONFWSeriesExpansion_%get (node) + kinematicsDistribution_ => massDistribution_ %kinematicsDistribution( ) + select type (massDistribution_) + class is (massDistributionSpherical) + do i=1,7 + coordinates =[radiusScale*radius(i),0.0d0,0.0d0] + radialVelocityDispersionSeriesExpansion(i)=kinematicsDistribution_%velocityDispersion1D(coordinates,massDistribution_) + end do + end select + !![ + + + !!] + ! Radial velocity dispersion in units of virial velocity. + radialVelocityDispersionSeriesExpansion=radialVelocityDispersionSeriesExpansion/darkMatterHaloScale_%velocityVirial(node) call Assert( & & 'radial velocity dispersion (series approximation)', & & radialVelocityDispersionSeriesExpansion , & @@ -360,20 +1390,20 @@ program Test_Dark_Matter_Profiles & ] , & & relTol=1.0d-5 & & ) - call Assert( & - & 'radius enclosing mass (at small radii )', & - & radiusSmall , & - & 5.357343922297839d-8 , & - & relTol=1.0d-6 & - & ) call Unit_Tests_End_Group () ! Test finite resolution NFW profile. call Unit_Tests_Begin_Group('Finite resolution NFW profile') + massDistribution_ => darkMatterProfileDMOFiniteResolution_%get (node) + radiusVirial = darkMatterHaloScale_ %radiusVirial(node) do i=1,7 - mass (i)=darkMatterProfileDMOFiniteResolution_%enclosedMass(node, radiusScale*radius(i)) - density(i)=darkMatterProfileDMOFiniteResolution_%density (node, radiusScale*radius(i))*radiusScale**3 - fourier(i)=darkMatterProfileDMOFiniteResolution_%kSpace (node,1.0d0/radiusScale/radius(i)) + coordinates=[radiusScale*radius(i),0.0d0,0.0d0] + mass (i)=massDistribution_%massEnclosedBySphere(radius = radiusScale*radius(i) ) + density(i)=massDistribution_%density (coordinates= coordinates )*radiusScale**3 + fourier(i)=massDistribution_%fourierTransform (wavenumber =1.0d0/radiusScale/radius(i),radiusOuter=radiusVirial) end do + !![ + + !!] call Assert( & & 'enclosed mass' , & & mass , & @@ -453,18 +1483,9 @@ program Test_Dark_Matter_Profiles & ) - - - galacticStructureStandard ( & - & cosmologyFunctions_ =cosmologyFunctionsPippin_ , & - & darkMatterHaloScale_ =darkMatterHaloScalePippin_ , & - & darkMatterProfile_ =darkMatterProfileSIDMIsothermal_ & - & ) - - + offsetting the cross-section by this factor we get the correct interaction radius. --> darkMatterParticleSelfInteractingDarkMatter ( & & crossSectionSelfInteraction =+1.0d0 & @@ -501,8 +1522,7 @@ program Test_Dark_Matter_Profiles & nonAnalyticSolver =nonAnalyticSolversNumerical , & & cosmologyParameters_ =cosmologyParametersPippin_ , & & darkMatterHaloScale_ =darkMatterHaloScalePippin_ , & - & darkMatterProfileDMO_ =darkMatterProfileDMONFWPippin_ , & - & galacticStructure_ =galacticStructureStandard_ & + & darkMatterProfileDMO_ =darkMatterProfileDMONFWPippin_ & & ) @@ -510,7 +1530,6 @@ program Test_Dark_Matter_Profiles darkMatterProfileDMOSIDMIsothermal ( & & darkMatterProfileDMO_ =darkMatterProfileDMONFWPippin_ , & - & darkMatterHaloScale_ =darkMatterHaloScalePippin_ , & & darkMatterParticle_ =darkMatterParticleSelfInteractingDarkMatterJiang_ & & ) @@ -519,9 +1538,7 @@ program Test_Dark_Matter_Profiles darkMatterProfileSIDMIsothermal ( & & darkMatterProfile_ =darkMatterProfileAdiabaticPippin_ , & - & darkMatterHaloScale_ =darkMatterHaloScalePippin_ , & - & darkMatterParticle_ =darkMatterParticleSelfInteractingDarkMatterJiang_, & - & galacticStructure_ =galacticStructureStandard_ & + & darkMatterParticle_ =darkMatterParticleSelfInteractingDarkMatterJiang_ & & ) @@ -537,17 +1554,22 @@ program Test_Dark_Matter_Profiles radiusScale=+darkMatterHaloScalePippin_%radiusVirial(nodePippin) & & /concentrationPippin call dmProfile%scaleSet(radiusScale) + massDistribution_ => darkMatterProfileDMOSIDMCoreNFW_%get(nodePippin) do i=1,7 - mass (i)=darkMatterProfileDMOSIDMCoreNFW_%enclosedMass(nodePippin,radiusScale*radius(i)) - density(i)=darkMatterProfileDMOSIDMCoreNFW_%density (nodePippin,radiusScale*radius(i)) + coordinates =[radiusScale*radius(i),0.0d0,0.0d0] + mass (i)=massDistribution_%massEnclosedBySphere(radiusScale*radius(i)) + density (i)=massDistribution_%density (coordinates ) end do ! Interaction radius estimated from Figure 2 of Jiang et al. (2022). - call Assert( & - & 'interaction radius' , & - & darkMatterProfileDMOSIDMCoreNFW_%radiusInteraction(nodePippin), & - & 2.337664390096387d-3 , & - & relTol=1.0d-2 & - & ) + select type (massDistribution_) + class is (massDistributionSphericalSIDM) + call Assert( & + & 'interaction radius' , & + & massDistribution_%radiusInteraction(), & + & 2.337664390096387d-3 , & + & relTol=1.0d-2 & + & ) + end select ! Mass and density computed using Mathematica. call Assert( & & 'enclosed mass' , & @@ -578,6 +1600,9 @@ program Test_Dark_Matter_Profiles & relTol=1.0d-2 & & ) call Unit_Tests_End_Group () + !![ + + !!] ! Test isothermal self-interacting dark matter profile. call Unit_Tests_Begin_Group('Isothermal self-interacting dark matter profile') !! Set properties to match the example halo generated by Fangzhou Jiang (private communication). @@ -589,26 +1614,36 @@ program Test_Dark_Matter_Profiles radiusScale=+darkMatterHaloScalePippin_%radiusVirial(nodeJiang) & & /concentrationJiang call dmProfile%scaleSet(radiusScale) + massDistribution_ => darkMatterProfileDMOSIDMIsothermal_%get (nodeJiang) + kinematicsDistribution_ => massDistribution_ %kinematicsDistribution( ) !! Target values were provided by Fangzhou Jiang (private communication). - call Assert( & - & 'interaction radius' , & - & darkMatterProfileDMOSIDMIsothermal_%radiusInteraction (nodeJiang ), & - & 6.9732d-3 , & - & relTol=1.0d-2 & - & ) - call Assert( & - & 'central density' , & - & darkMatterProfileDMOSIDMIsothermal_%density (nodeJiang,radius=0.0d0), & - & 4.1168d16 , & - & relTol=1.0d-1 & - & ) - call Assert( & - & 'central velocity dispersion' , & - & darkMatterProfileDMOSIDMIsothermal_%radialVelocityDispersion(nodeJiang,radius=0.0d0), & - & 54.9811d0 , & - & relTol=1.0d-2 & - & ) + select type (massDistribution_) + class is (massDistributionSphericalSIDM) + coordinates=[0.0d0,0.0d0,0.0d0] + call Assert( & + & 'interaction radius' , & + & massDistribution_ %radiusInteraction ( ), & + & 6.9732d-3 , & + & relTol=1.0d-2 & + & ) + call Assert( & + & 'central density' , & + & massDistribution_ %density (coordinates ), & + & 4.1168d16 , & + & relTol=1.0d-1 & + & ) + call Assert( & + & 'central velocity dispersion' , & + & kinematicsDistribution_%velocityDispersion1D(coordinates,massDistribution_), & + & 54.9811d0 , & + & relTol=1.0d-2 & + & ) + end select call Unit_Tests_End_Group () + !![ + + + !!] ! Test isothermal self-interacting dark matter profile with adiabatic contraction. call Unit_Tests_Begin_Group('Isothermal self-interacting dark matter profile (with adiabatic contraction)') !! Set properties to match the example halo generated by Fangzhou Jiang (private communication). @@ -620,52 +1655,72 @@ program Test_Dark_Matter_Profiles radiusScale=+darkMatterHaloScalePippin_%radiusVirial(nodeJiang) & & /concentrationJiang call dmProfile%scaleSet(radiusScale) + massDistribution_ => darkMatterProfileSIDMIsothermal_%get (nodeJiang) + kinematicsDistribution_ => massDistribution_ %kinematicsDistribution( ) !! Target values were provided by Fangzhou Jiang (private communication). - call Assert( & - & 'interaction radius' , & - & darkMatterProfileSIDMIsothermal_%radiusInteraction (nodeJiang ), & - & 6.9732d-3 , & - & relTol=1.0d-2 & - & ) - call Assert( & - & 'central density' , & - & darkMatterProfileSIDMIsothermal_%density (nodeJiang,radius=0.0d0), & - & 4.1168d16 , & - & relTol=1.0d-1 & - & ) - call Assert( & - & 'central velocity dispersion' , & - & darkMatterProfileSIDMIsothermal_%radialVelocityDispersion(nodeJiang,radius=0.0d0), & - & 54.9811d0 , & - & relTol=1.0d-2 & - & ) + select type (massDistribution_) + class is (massDistributionSphericalSIDM) + coordinates=[0.0d0,0.0d0,0.0d0] + call Assert( & + & 'interaction radius' , & + & massDistribution_ %radiusInteraction ( ), & + & 6.9732d-3 , & + & relTol=1.0d-2 & + & ) + call Assert( & + & 'central density' , & + & massDistribution_ %density (coordinates ), & + & 4.1168d16 , & + & relTol=1.0d-1 & + & ) + call Assert( & + & 'central velocity dispersion' , & + & kinematicsDistribution_%velocityDispersion1D(coordinates,massDistribution_), & + & 54.9811d0 , & + & relTol=1.0d-2 & + & ) + end select call Unit_Tests_End_Group () + !![ + + + !!] !! Insert a spheroid. call Unit_Tests_Begin_Group('With baryons case') spheroid => nodeJiang%spheroid (autoCreate=.true.) call spheroid%massStellarSet(fractionMassBaryonicJiang * massVirialJiang ) call spheroid% radiusSet(fractionRadiusHalfMassJiang*darkMatterHaloScalePippin_%radiusVirial (nodeJiang)/radiusHalfMassDimensionlessHernquist) call Calculations_Reset(nodeJiang) + massDistribution_ => darkMatterProfileSIDMIsothermal_%get (nodeJiang) + kinematicsDistribution_ => massDistribution_ %kinematicsDistribution( ) !! Target values were measured from Figure A1 of Jiang et al. (2022). - call Assert( & - & 'interaction radius' , & - & darkMatterProfileSIDMIsothermal_%radiusInteraction (nodeJiang ), & - & 6.9732d-3 , & - & relTol=1.0d-2 & - & ) - call Assert( & - & 'central density' , & - & darkMatterProfileSIDMIsothermal_%density (nodeJiang,radius=0.0d0), & - & 2.534d17 , & - & relTol=2.0d-1 & - & ) - call Assert( & - & 'central velocity dispersion' , & - & darkMatterProfileSIDMIsothermal_%radialVelocityDispersion(nodeJiang,radius=0.0d0), & - & 59.139d0 , & - & relTol=5.0d-2 & - & ) + select type (massDistribution_) + class is (massDistributionSphericalSIDM) + coordinates=[0.0d0,0.0d0,0.0d0] + call Assert( & + & 'interaction radius' , & + & massDistribution_ %radiusInteraction ( ), & + & 6.9732d-3 , & + & relTol=1.0d-2 & + & ) + call Assert( & + & 'central density' , & + & massDistribution_ %density (coordinates ), & + & 2.534d17 , & + & relTol=2.0d-1 & + & ) + call Assert( & + & 'central velocity dispersion' , & + & kinematicsDistribution_%velocityDispersion1D(coordinates,massDistribution_), & + & 59.139d0 , & + & relTol=5.0d-2 & + & ) + end select call Unit_Tests_End_Group() + !![ + + + !!] call Unit_Tests_End_Group() ! End unit tests. call Unit_Tests_End_Group() @@ -688,7 +1743,6 @@ program Test_Dark_Matter_Profiles - diff --git a/source/tests.dark_matter_profiles.Zhao1996.F90 b/source/tests.dark_matter_profiles.Zhao1996.F90 index eab42ea603..6ba929a795 100644 --- a/source/tests.dark_matter_profiles.Zhao1996.F90 +++ b/source/tests.dark_matter_profiles.Zhao1996.F90 @@ -25,24 +25,25 @@ program Test_Dark_Matter_Profiles_Zhao1996 !!{ Test calculations for Zhao1996 dark matter profiles. !!} - use :: Calculations_Resets , only : Calculations_Reset - use :: Cosmology_Functions , only : cosmologyFunctionsMatterLambda - use :: Cosmology_Parameters , only : cosmologyParametersSimple - use :: Dark_Matter_Halo_Scales , only : darkMatterHaloScaleVirialDensityContrastDefinition - use :: Virial_Density_Contrast , only : virialDensityContrastSphericalCollapseClsnlssMttrCsmlgclCnstnt - use :: Dark_Matter_Profiles_DMO , only : darkMatterProfileDMOClass , darkMatterProfileDMOZhao1996 - use :: Dark_Matter_Profiles_Generic, only : nonAnalyticSolversNumerical - use :: Display , only : displayMessage , displayVerbositySet , verbosityLevelStandard - use :: Events_Hooks , only : eventsHooksInitialize - use :: Functions_Global_Utilities , only : Functions_Global_Set - use :: Galacticus_Nodes , only : nodeClassHierarchyFinalize , nodeClassHierarchyInitialize , nodeComponentBasic , nodeComponentDarkMatterProfile, & - & treeNode , nodeComponentSatellite - use :: Input_Parameters , only : inputParameters - use :: Node_Components , only : Node_Components_Initialize , Node_Components_Thread_Initialize , Node_Components_Thread_Uninitialize, Node_Components_Uninitialize - use :: Numerical_Ranges , only : Make_Range , rangeTypeLogarithmic - use :: Numerical_Constants_Math , only : Pi - use :: Unit_Tests , only : Assert , Unit_Tests_Begin_Group , Unit_Tests_End_Group , Unit_Tests_Finish - use :: Error , only : Error_Report + use :: Calculations_Resets , only : Calculations_Reset + use :: Cosmology_Functions , only : cosmologyFunctionsMatterLambda + use :: Cosmology_Parameters , only : cosmologyParametersSimple + use :: Coordinates , only : coordinateSpherical , assignment(=) + use :: Dark_Matter_Halo_Scales , only : darkMatterHaloScaleVirialDensityContrastDefinition + use :: Virial_Density_Contrast , only : virialDensityContrastSphericalCollapseClsnlssMttrCsmlgclCnstnt + use :: Dark_Matter_Profiles_DMO , only : darkMatterProfileDMOClass , darkMatterProfileDMOZhao1996 + use :: Mass_Distributions , only : nonAnalyticSolversNumerical , massDistributionClass , kinematicsDistributionClass , massDistributionSpherical + use :: Display , only : displayMessage , displayVerbositySet , verbosityLevelStandard + use :: Events_Hooks , only : eventsHooksInitialize + use :: Functions_Global_Utilities, only : Functions_Global_Set + use :: Galacticus_Nodes , only : nodeClassHierarchyFinalize , nodeClassHierarchyInitialize , nodeComponentBasic , nodeComponentDarkMatterProfile, & + & treeNode , nodeComponentSatellite + use :: Input_Parameters , only : inputParameters + use :: Node_Components , only : Node_Components_Initialize , Node_Components_Thread_Initialize, Node_Components_Thread_Uninitialize, Node_Components_Uninitialize + use :: Numerical_Ranges , only : Make_Range , rangeTypeLogarithmic + use :: Numerical_Constants_Math , only : Pi + use :: Unit_Tests , only : Assert , Unit_Tests_Begin_Group , Unit_Tests_End_Group , Unit_Tests_Finish + use :: Error , only : Error_Report implicit none type (darkMatterHaloScaleVirialDensityContrastDefinition ) :: darkMatterHaloScale_ type (cosmologyParametersSimple ) :: cosmologyParameters_ @@ -52,6 +53,8 @@ program Test_Dark_Matter_Profiles_Zhao1996 & darkMatterProfileZhao1996CoredNFW_ , darkMatterProfileZhao1996Gamma0_5NFW_, & & darkMatterProfileZhao1996Gamma1_5NFW_ class (darkMatterProfileDMOClass ), pointer :: darkMatterProfileZhao1996_ + class (massDistributionClass ), pointer :: massDistribution_ + class (kinematicsDistributionClass ), pointer :: kinematicsDistribution_ type (treeNode ), pointer :: node_ class (nodeComponentBasic ), pointer :: basic_ class (nodeComponentDarkMatterProfile ), pointer :: darkMatterProfile_ @@ -65,7 +68,8 @@ program Test_Dark_Matter_Profiles_Zhao1996 integer :: i , j double precision :: radiusScale , radiusVirial , & & potentialNumerical , potential - + type (coordinateSpherical ) :: coordinates , coordinatesReference + call displayVerbositySet(verbosityLevelStandard) call Unit_Tests_Begin_Group("Zhao1996 dark matter profiles") parameters=inputParameters('testSuite/parameters/darkMatterProfilesZhao1996.xml') @@ -195,35 +199,47 @@ program Test_Dark_Matter_Profiles_Zhao1996 case default call Error_Report('unknown profile'//{introspection:location}) end select - do j=1,countRadii - mass (j)=+darkMatterProfileZhao1996_%enclosedMass (node_,radius = radii(j)) - massNumerical (j)=+darkMatterProfileZhao1996_%enclosedMassNumerical (node_,radius = radii(j)) - velocityDispersion (j)=+darkMatterProfileZhao1996_%radialVelocityDispersion (node_,radius = radii(j)) - velocityDispersionNumerical(j)=+darkMatterProfileZhao1996_%radialVelocityDispersionNumerical(node_,radius = radii(j)) - end do - call Assert( & - & "Enclosed mass" , & - & mass , & - & massNumerical , & - & relTol=+2.0d-2 & - & ) - call Assert( & - & "Velocity dispersion" , & - & velocityDispersion , & - & velocityDispersionNumerical, & - & relTol=+2.0d-2 & - & ) - !! When comparing to the numerical calculation of potential we take the potential relative to 100 times the virial radius, as - !! that is the radius to which the numerical solution is integrated. - potential =+darkMatterProfileZhao1996_%potential (node_,radius=2.0d+0*radiusScale ) & - & -darkMatterProfileZhao1996_%potential (node_,radius=1.0d+2*radiusVirial) - potentialNumerical=+darkMatterProfileZhao1996_%potentialNumerical(node_,radius=2.0d+0*radiusScale ) - call Assert( & - & "Potential" , & - & potential , & - & potentialNumerical, & - & relTol=+2.0d-2 & - & ) + massDistribution_ => darkMatterProfileZhao1996_%get (node_) + kinematicsDistribution_ => massDistribution_ %kinematicsDistribution( ) + select type (massDistribution_) + class is (massDistributionSpherical) + do j=1,countRadii + coordinates =[radii(j),0.0d0,0.0d0] + mass (j)=+massDistribution_ %massEnclosedBySphere (radius = radii(j) ) + massNumerical (j)=+massDistribution_ %massEnclosedBySphereNumerical(radius = radii(j) ) + velocityDispersion (j)=+kinematicsDistribution_%velocityDispersion1D (coordinates=coordinates ,massDistributionEmbedding=massDistribution_) + velocityDispersionNumerical(j)=+kinematicsDistribution_%velocityDispersion1DNumerical(coordinates=coordinates ,massDistributionEmbedding=massDistribution_) + end do + call Assert( & + & "Enclosed mass" , & + & mass , & + & massNumerical , & + & relTol=+2.0d-2 & + & ) + call Assert( & + & "Velocity dispersion" , & + & velocityDispersion , & + & velocityDispersionNumerical, & + & relTol=+2.0d-2 & + & ) + !! When comparing to the numerical calculation of potential we take the potential relative to 100 times the virial radius, as + !! that is the radius to which the numerical solution is integrated. + coordinates =[2.0d+0*radiusScale ,0.0d0,0.0d0] + coordinatesReference=[1.0d+2*radiusVirial,0.0d0,0.0d0] + potential =+massDistribution_%potential (coordinates=coordinates ) & + & -massDistribution_%potential (coordinates=coordinatesReference) + potentialNumerical =+massDistribution_%potentialNumerical(coordinates=coordinates ) + call Assert( & + & "Potential" , & + & potential , & + & potentialNumerical, & + & relTol=+2.1d-2 & + & ) + end select + !![ + + + !!] call Unit_Tests_End_Group () end do call Unit_Tests_End_Group () diff --git a/source/tests.dark_matter_profiles.adiabaticGnedin2004.F90 b/source/tests.dark_matter_profiles.adiabaticGnedin2004.F90 index e3e6d3a190..7ac25be90b 100644 --- a/source/tests.dark_matter_profiles.adiabaticGnedin2004.F90 +++ b/source/tests.dark_matter_profiles.adiabaticGnedin2004.F90 @@ -25,26 +25,25 @@ program Test_Dark_Matter_Profiles_Gnedin2004 !!{ Tests the implementation of the \cite{gnedin_response_2004} dark matter profile. !!} - use :: Calculations_Resets , only : Calculations_Reset - use :: Cosmology_Parameters , only : cosmologyParametersSimple - use :: Cosmology_Functions , only : cosmologyFunctionsMatterLambda - use :: Dark_Matter_Halo_Scales , only : darkMatterHaloScaleVirialDensityContrastDefinition - use :: Dark_Matter_Profiles_DMO , only : darkMatterProfileDMONFW - use :: Dark_Matter_Profiles , only : darkMatterProfileAdiabaticGnedin2004 - use :: Dark_Matter_Profiles_Generic, only : nonAnalyticSolversNumerical - use :: File_Utilities , only : Count_Lines_in_File - use :: Galactic_Structure , only : galacticStructureStandard - use :: Virial_Density_Contrast , only : virialDensityContrastSphericalCollapseClsnlssMttrCsmlgclCnstnt - use :: Events_Hooks , only : eventsHooksInitialize - use :: Functions_Global_Utilities , only : Functions_Global_Set - use :: Display , only : displayVerbositySet , verbosityLevelStandard - use :: Galacticus_Nodes , only : nodeClassHierarchyFinalize , nodeClassHierarchyInitialize , nodeComponentBasic , nodeComponentDarkMatterProfile, & - & treeNode , nodeComponentSPheroid - use :: Input_Parameters , only : inputParameters - use :: Input_Paths , only : inputPath , pathTypeExec - use :: ISO_Varying_String , only : varying_string , assignment(=) , char, var_str - use :: Node_Components , only : Node_Components_Initialize , Node_Components_Thread_Initialize, Node_Components_Thread_Uninitialize, Node_Components_Uninitialize - use :: Unit_Tests , only : Assert , Unit_Tests_Begin_Group , Unit_Tests_End_Group , Unit_Tests_Finish + use :: Calculations_Resets , only : Calculations_Reset + use :: Cosmology_Parameters , only : cosmologyParametersSimple + use :: Cosmology_Functions , only : cosmologyFunctionsMatterLambda + use :: Dark_Matter_Halo_Scales , only : darkMatterHaloScaleVirialDensityContrastDefinition + use :: Dark_Matter_Profiles_DMO , only : darkMatterProfileDMONFW + use :: Dark_Matter_Profiles , only : darkMatterProfileAdiabaticGnedin2004 + use :: Mass_Distributions , only : nonAnalyticSolversNumerical , massDistributionClass , massDistributionSphericalAdiabaticGnedin2004 + use :: File_Utilities , only : Count_Lines_in_File + use :: Virial_Density_Contrast , only : virialDensityContrastSphericalCollapseClsnlssMttrCsmlgclCnstnt + use :: Events_Hooks , only : eventsHooksInitialize + use :: Functions_Global_Utilities, only : Functions_Global_Set + use :: Display , only : displayVerbositySet , verbosityLevelStandard + use :: Galacticus_Nodes , only : nodeClassHierarchyFinalize , nodeClassHierarchyInitialize , nodeComponentBasic , nodeComponentDarkMatterProfile, & + & treeNode , nodeComponentSPheroid + use :: Input_Parameters , only : inputParameters + use :: Input_Paths , only : inputPath , pathTypeExec + use :: ISO_Varying_String , only : varying_string , assignment(=) , char, var_str + use :: Node_Components , only : Node_Components_Initialize , Node_Components_Thread_Initialize, Node_Components_Thread_Uninitialize , Node_Components_Uninitialize + use :: Unit_Tests , only : Assert , Unit_Tests_Begin_Group , Unit_Tests_End_Group , Unit_Tests_Finish implicit none type (treeNode ), pointer :: node class (nodeComponentBasic ), pointer :: basic @@ -54,11 +53,11 @@ program Test_Dark_Matter_Profiles_Gnedin2004 & fractionBaryons =0.15d0, radiusFractionalBaryons =0.03d0 type (darkMatterProfileDMONFW ), pointer :: darkMatterProfileDMONFW_ type (darkMatterProfileAdiabaticGnedin2004 ), pointer :: darkMatterProfileAdiabaticGnedin2004_ - type (galacticStructureStandard ), pointer :: galacticStructureStandard_ type (cosmologyParametersSimple ), pointer :: cosmologyParameters_ type (cosmologyFunctionsMatterLambda ), pointer :: cosmologyFunctions_ type (darkMatterHaloScaleVirialDensityContrastDefinition ), pointer :: darkMatterHaloScale_ type (virialDensityContrastSphericalCollapseClsnlssMttrCsmlgclCnstnt), pointer :: virialDensityContrast_ + class (massDistributionClass ), pointer :: massDistribution_ type (inputParameters ) :: parameters double precision :: radiusScale , radiusVirial , & & radiusFractionalFinalContra @@ -81,7 +80,6 @@ program Test_Dark_Matter_Profiles_Gnedin2004 allocate(cosmologyParameters_ ) allocate(cosmologyFunctions_ ) allocate(virialDensityContrast_ ) - allocate(galacticStructureStandard_ ) allocate(darkMatterHaloScale_ ) allocate(darkMatterProfileDMONFW_ ) allocate(darkMatterProfileAdiabaticGnedin2004_) @@ -121,15 +119,6 @@ program Test_Dark_Matter_Profiles_Gnedin2004 & ) - - - galacticStructureStandard ( & - & cosmologyFunctions_ =cosmologyFunctions_ , & - & darkMatterHaloScale_ =darkMatterHaloScale_ , & - & darkMatterProfile_ =darkMatterProfileAdiabaticGnedin2004_ & - & ) - - darkMatterProfileDMONFW ( & @@ -148,8 +137,7 @@ program Test_Dark_Matter_Profiles_Gnedin2004 & nonAnalyticSolver =nonAnalyticSolversNumerical , & & cosmologyParameters_ =cosmologyParameters_ , & & darkMatterHaloScale_ =darkMatterHaloScale_ , & - & darkMatterProfileDMO_ =darkMatterProfileDMONFW_ , & - & galacticStructure_ =galacticStructureStandard_ & + & darkMatterProfileDMO_ =darkMatterProfileDMONFW_ & & ) @@ -172,6 +160,8 @@ program Test_Dark_Matter_Profiles_Gnedin2004 ! Construct spheroid. call spheroid%massStellarSet(fractionBaryons *massVirial ) call spheroid%radiusSet (radiusFractionalBaryons*radiusVirial) + ! Get the mass distribution. + massDistribution_ => darkMatterProfileAdiabaticGnedin2004_%get(node) ! Begin unit tests. call Unit_Tests_Begin_Group('Gnedin et al. (2004) dark matter profile') ! Read data from the reference "contra" file. @@ -184,13 +174,19 @@ program Test_Dark_Matter_Profiles_Gnedin2004 do i=1,(countLines-countRadii) read (fileUnit,*) end do - do i=1,countRadii - read (fileUnit,*) radiusFractionalInitialContra(i),radiusFractionalFinalContra - ! Evaluate the initial radius corresponding to this final radius. - radiusFractionalInitial(i)=+darkMatterProfileAdiabaticGnedin2004_%radiusInitial(node,radiusFractionalFinalContra*radiusVirial) & - & / radiusVirial - end do + select type (massDistribution_) + type is (massDistributionSphericalAdiabaticGnedin2004) + do i=1,countRadii + read (fileUnit,*) radiusFractionalInitialContra(i),radiusFractionalFinalContra + ! Evaluate the initial radius corresponding to this final radius. + radiusFractionalInitial(i)=+massDistribution_%radiusInitial(radiusFractionalFinalContra*radiusVirial) & + & / radiusVirial + end do + end select close(fileUnit) + !![ + + !!] ! Use a tolerance of 2% in the assertion, as our solver for the initial radius uses a tolerance of 1%. call Assert('initial radii',radiusFractionalInitialContra,radiusFractionalInitial,relTol=2.0d-2) ! End unit tests. @@ -207,6 +203,5 @@ program Test_Dark_Matter_Profiles_Gnedin2004 - !!] end program Test_Dark_Matter_Profiles_Gnedin2004 diff --git a/source/tests.dark_matter_profiles.finite_resolution.F90 b/source/tests.dark_matter_profiles.finite_resolution.F90 index fc4b3a4418..0a41e80b1b 100644 --- a/source/tests.dark_matter_profiles.finite_resolution.F90 +++ b/source/tests.dark_matter_profiles.finite_resolution.F90 @@ -25,23 +25,24 @@ program Test_Dark_Matter_Profiles_Finite_Resolution !!{ Test calculations for finite resolution dark matter profiles. !!} - use :: Calculations_Resets , only : Calculations_Reset - use :: Cosmology_Functions , only : cosmologyFunctionsMatterLambda - use :: Cosmology_Parameters , only : cosmologyParametersSimple - use :: Dark_Matter_Halo_Scales , only : darkMatterHaloScaleVirialDensityContrastDefinition - use :: Virial_Density_Contrast , only : virialDensityContrastSphericalCollapseClsnlssMttrCsmlgclCnstnt - use :: Dark_Matter_Profiles_DMO , only : darkMatterProfileDMOFiniteResolution , darkMatterProfileDMOFiniteResolutionNFW, darkMatterProfileDMONFW - use :: Dark_Matter_Profiles_Generic, only : nonAnalyticSolversNumerical - use :: Display , only : displayMessage , displayVerbositySet , verbosityLevelStandard - use :: Events_Hooks , only : eventsHooksInitialize - use :: Functions_Global_Utilities , only : Functions_Global_Set - use :: Galacticus_Nodes , only : nodeClassHierarchyFinalize , nodeClassHierarchyInitialize , nodeComponentBasic , nodeComponentDarkMatterProfile, & - & treeNode , nodeComponentSatellite - use :: Input_Parameters , only : inputParameters - use :: Node_Components , only : Node_Components_Initialize , Node_Components_Thread_Initialize , Node_Components_Thread_Uninitialize, Node_Components_Uninitialize - use :: Numerical_Ranges , only : Make_Range , rangeTypeLogarithmic - use :: Numerical_Constants_Math , only : Pi - use :: Unit_Tests , only : Assert , Unit_Tests_Begin_Group , Unit_Tests_End_Group , Unit_Tests_Finish + use :: Calculations_Resets , only : Calculations_Reset + use :: Coordinates , only : coordinateSpherical , assignment(=) + use :: Cosmology_Functions , only : cosmologyFunctionsMatterLambda + use :: Cosmology_Parameters , only : cosmologyParametersSimple + use :: Dark_Matter_Halo_Scales , only : darkMatterHaloScaleVirialDensityContrastDefinition + use :: Virial_Density_Contrast , only : virialDensityContrastSphericalCollapseClsnlssMttrCsmlgclCnstnt + use :: Dark_Matter_Profiles_DMO , only : darkMatterProfileDMOFiniteResolution , darkMatterProfileDMOFiniteResolutionNFW, darkMatterProfileDMONFW + use :: Mass_Distributions , only : nonAnalyticSolversNumerical , massDistributionClass , kinematicsDistributionClass + use :: Display , only : displayMessage , displayVerbositySet , verbosityLevelStandard + use :: Events_Hooks , only : eventsHooksInitialize + use :: Functions_Global_Utilities, only : Functions_Global_Set + use :: Galacticus_Nodes , only : nodeClassHierarchyFinalize , nodeClassHierarchyInitialize , nodeComponentBasic , nodeComponentDarkMatterProfile, & + & treeNode , nodeComponentSatellite + use :: Input_Parameters , only : inputParameters + use :: Node_Components , only : Node_Components_Initialize , Node_Components_Thread_Initialize , Node_Components_Thread_Uninitialize, Node_Components_Uninitialize + use :: Numerical_Ranges , only : Make_Range , rangeTypeLogarithmic + use :: Numerical_Constants_Math , only : Pi + use :: Unit_Tests , only : Assert , Unit_Tests_Begin_Group , Unit_Tests_End_Group , Unit_Tests_Finish implicit none type (darkMatterHaloScaleVirialDensityContrastDefinition ) :: darkMatterHaloScale_ type (cosmologyParametersSimple ) :: cosmologyParameters_ @@ -53,20 +54,25 @@ program Test_Dark_Matter_Profiles_Finite_Resolution type (treeNode ), pointer :: node_ class (nodeComponentBasic ), pointer :: basic_ class (nodeComponentDarkMatterProfile ), pointer :: darkMatterProfile_ + class (massDistributionClass ), pointer :: massDistributionFiniteResolutionNFW_ , massDistributionFiniteResolution_ , & + & massDistribution_ + class (kinematicsDistributionClass ), pointer :: kinematicsDistributionFiniteResolutionNFW_ , kinematicsDistributionFiniteResolution_ type (inputParameters ) :: parameters - double precision , parameter :: concentration =+8.0d0, massVirial =+1.0d12 - integer , parameter :: countRadii =10 - double precision , parameter :: radiiMinimum =1.0d-6, radiiMaximum =+1.0d01 - double precision , dimension(countRadii) :: massNumerical , mass , & - & densityNumerical , density , & - & velocityDispersionNumerical , velocityDispersion , & - & radii , radiusEnclosingDensity , & + double precision , parameter :: concentration =+8.0d0, massVirial =+1.0d12 + integer , parameter :: countRadii =10 + double precision , parameter :: radiiMinimum =1.0d-6, radiiMaximum =+1.0d01 + double precision , dimension(countRadii) :: massNumerical , mass , & + & densityNumerical , density , & + & velocityDispersionNumerical , velocityDispersion , & + & radii , radiusEnclosingDensity , & & radiusEnclosingMass integer :: i - double precision :: radiusScale , radiusVirial , & - & potentialNumerical , potential , & - & energyNumerical , energy - + double precision :: radiusScale , radiusVirial , & + & potentialNumerical , potential , & + & energyNumerical , energy + type (coordinateSpherical ) :: coordinates , coordinatesScale , & + & coordinatesVirial + call displayVerbositySet(verbosityLevelStandard) call Unit_Tests_Begin_Group("Finite resolution dark matter profiles") parameters=inputParameters('testSuite/parameters/darkMatterProfilesFiniteResolution.xml') @@ -130,7 +136,6 @@ program Test_Dark_Matter_Profiles_Finite_Resolution & resolutionIsComoving =.false. , & & nonAnalyticSolver =nonAnalyticSolversNumerical, & & darkMatterProfileDMO_ =darkMatterProfileNFW_ , & - & darkMatterHaloScale_ =darkMatterHaloScale_ , & & cosmologyFunctions_ =cosmologyFunctions_ & & ) @@ -156,18 +161,24 @@ program Test_Dark_Matter_Profiles_Finite_Resolution & /concentration call darkMatterProfile_%scaleSet(radiusScale) call Calculations_Reset(node_) + massDistribution_ => darkMatterProfileNFW_ %get (node_) + massDistributionFiniteResolutionNFW_ => darkMatterProfileFiniteResolutionNFW_%get (node_) + massDistributionFiniteResolution_ => darkMatterProfileFiniteResolution_ %get (node_) + kinematicsDistributionFiniteResolutionNFW_ => massDistributionFiniteResolutionNFW_ %kinematicsDistribution( ) + kinematicsDistributionFiniteResolution_ => massDistributionFiniteResolution_ %kinematicsDistribution( ) ! Begin tests. call Unit_Tests_Begin_Group("Finite resolution NFW profile") radii=Make_Range(radiiMinimum,radiiMaximum,countRadii,rangeTypeLogarithmic)*radiusScale do i=1,countRadii - mass (i)=+darkMatterProfileFiniteResolutionNFW_%enclosedMass (node_,radius = radii(i) ) - massNumerical (i)=+darkMatterProfileFiniteResolution_ %enclosedMass (node_,radius = radii(i) ) - density (i)=+darkMatterProfileFiniteResolutionNFW_%density (node_,radius = radii(i) ) - densityNumerical (i)=+darkMatterProfileFiniteResolution_ %density (node_,radius = radii(i) ) - velocityDispersion (i)=+darkMatterProfileFiniteResolutionNFW_%radialVelocityDispersion(node_,radius = radii(i) ) - velocityDispersionNumerical(i)=+darkMatterProfileFiniteResolution_ %radialVelocityDispersion(node_,radius = radii(i) ) - radiusEnclosingDensity (i)=+darkMatterProfileFiniteResolutionNFW_%radiusEnclosingDensity (node_,density=3.0d0*mass(i)/4.0d0/Pi/radii(i)**3) - radiusEnclosingMass (i)=+darkMatterProfileFiniteResolutionNFW_%radiusEnclosingMass (node_,mass = mass(i) ) + coordinates =[radii(i),0.0d0,0.0d0] + mass (i)=+massDistributionFiniteResolutionNFW_ %massEnclosedBySphere (radius = radii(i) ) + massNumerical (i)=+massDistributionFiniteResolution_ %massEnclosedBySphere (radius = radii(i) ) + density (i)=+massDistributionFiniteResolutionNFW_ %density (coordinates= coordinates ) + densityNumerical (i)=+massDistributionFiniteResolution_ %density (coordinates= coordinates ) + velocityDispersion (i)=+kinematicsDistributionFiniteResolutionNFW_%velocityDispersion1D (coordinates= coordinates,massDistributionEmbedding=massDistributionFiniteResolutionNFW_) + velocityDispersionNumerical(i)=+kinematicsDistributionFiniteResolution_ %velocityDispersion1D (coordinates= coordinates,massDistributionEmbedding=massDistributionFiniteResolution_ ) + radiusEnclosingDensity (i)=+massDistributionFiniteResolutionNFW_ %radiusEnclosingDensity (density =3.0d0*mass(i)/4.0d0/Pi/radii(i)**3 ) + radiusEnclosingMass (i)=+massDistributionFiniteResolutionNFW_ %radiusEnclosingMass (mass = mass(i) ) end do call Assert( & & "Density" , & @@ -202,24 +213,34 @@ program Test_Dark_Matter_Profiles_Finite_Resolution & ) !! When comparing to the numerical calculation of potential we take the potential relative to 100 times the virial radius, as !! that is the radius to which the numerical solution is integrated. - potential =+darkMatterProfileFiniteResolutionNFW_%potential(node_,radius=2.0d+0*radiusScale ) & - & -darkMatterProfileFiniteResolutionNFW_%potential(node_,radius=1.0d+2*radiusVirial) - potentialNumerical=+darkMatterProfileFiniteResolution_ %potential(node_,radius=2.0d+0*radiusScale ) + coordinatesScale =[2.0d+0*radiusScale ,0.0d0,0.0d0] + coordinatesVirial =[1.0d+2*radiusVirial,0.0d0,0.0d0] + potential =+massDistributionFiniteResolutionNFW_%potential(coordinates=coordinatesScale ) & + & -massDistributionFiniteResolutionNFW_%potential(coordinates=coordinatesVirial) + potentialNumerical=+massDistributionFiniteResolution_ %potential(coordinates=coordinatesScale ) & + & +massDistributionFiniteResolution_ %potential(coordinates=coordinatesVirial) call Assert( & & "Potential" , & & potential , & & potentialNumerical, & - & relTol=+2.0d-2 & + & relTol=+4.0d-2 & & ) !! Total energy. - energy =+darkMatterProfileFiniteResolutionNFW_%energy(node_) - energyNumerical=+darkMatterProfileFiniteResolution_ %energy(node_) + energy =+massDistributionFiniteResolutionNFW_%energy(radiusVirial,massDistributionFiniteResolutionNFW_) + energyNumerical=+massDistributionFiniteResolution_ %energy(radiusVirial,massDistributionFiniteResolution_ ) call Assert( & & "Energy" , & & energy , & & energyNumerical, & & relTol=+3.0d-3 & & ) + !![ + + + + + + !!] call Unit_Tests_End_Group () call Unit_Tests_Finish () call Node_Components_Thread_Uninitialize() diff --git a/source/tests.dark_matter_profiles.generic.F90 b/source/tests.dark_matter_profiles.generic.F90 index 376636344d..c7096a67a9 100644 --- a/source/tests.dark_matter_profiles.generic.F90 +++ b/source/tests.dark_matter_profiles.generic.F90 @@ -25,23 +25,24 @@ program Test_Dark_Matter_Profiles_Generic !!{ Tests calculations for generic dark matter profiles. !!} - use :: Cosmology_Functions , only : cosmologyFunctionsMatterLambda - use :: Cosmology_Parameters , only : cosmologyParametersSimple - use :: Dark_Matter_Halo_Scales , only : darkMatterHaloScaleVirialDensityContrastDefinition - use :: Virial_Density_Contrast , only : virialDensityContrastSphericalCollapseClsnlssMttrCsmlgclCnstnt - use :: Dark_Matter_Profiles , only : darkMatterProfile , darkMatterProfileDarkMatterOnly - use :: Dark_Matter_Profiles_DMO , only : darkMatterProfileDMOBurkert , darkMatterProfileDMOEinasto , darkMatterProfileDMOIsothermal , darkMatterProfileDMONFW , & - & darkMatterProfileDMOTruncated , darkMatterProfileDMOTruncatedExponential, darkMatterProfileDMOZhao1996 - use :: Dark_Matter_Profiles_Generic, only : nonAnalyticSolversNumerical - use :: Display , only : displayMessage , displayVerbositySet , verbosityLevelStandard - use :: Events_Hooks , only : eventsHooksInitialize - use :: Functions_Global_Utilities , only : Functions_Global_Set - use :: Galacticus_Nodes , only : nodeClassHierarchyFinalize , nodeClassHierarchyInitialize , nodeComponentBasic , nodeComponentDarkMatterProfile, & - & treeNode - use :: Input_Parameters , only : inputParameters - use :: Node_Components , only : Node_Components_Initialize , Node_Components_Thread_Initialize , Node_Components_Thread_Uninitialize, Node_Components_Uninitialize - use :: Unit_Tests , only : Assert , Skip , Unit_Tests_Begin_Group , Unit_Tests_End_Group , & - & Unit_Tests_Finish + use :: Coordinates , only : coordinateSpherical , assignment(=) + use :: Cosmology_Functions , only : cosmologyFunctionsMatterLambda + use :: Cosmology_Parameters , only : cosmologyParametersSimple + use :: Dark_Matter_Halo_Scales , only : darkMatterHaloScaleVirialDensityContrastDefinition + use :: Virial_Density_Contrast , only : virialDensityContrastSphericalCollapseClsnlssMttrCsmlgclCnstnt + use :: Dark_Matter_Profiles , only : darkMatterProfile , darkMatterProfileDarkMatterOnly + use :: Dark_Matter_Profiles_DMO , only : darkMatterProfileDMOBurkert , darkMatterProfileDMOEinasto , darkMatterProfileDMOIsothermal , darkMatterProfileDMONFW , & + & darkMatterProfileDMOTruncated , darkMatterProfileDMOTruncatedExponential, darkMatterProfileDMOZhao1996 + use :: Mass_Distributions , only : nonAnalyticSolversNumerical , massDistributionClass , kinematicsDistributionClass , massDistributionSpherical + use :: Display , only : displayMessage , displayVerbositySet , verbosityLevelStandard + use :: Events_Hooks , only : eventsHooksInitialize + use :: Functions_Global_Utilities, only : Functions_Global_Set + use :: Galacticus_Nodes , only : nodeClassHierarchyFinalize , nodeClassHierarchyInitialize , nodeComponentBasic , nodeComponentDarkMatterProfile, & + & treeNode + use :: Input_Parameters , only : inputParameters + use :: Node_Components , only : Node_Components_Initialize , Node_Components_Thread_Initialize , Node_Components_Thread_Uninitialize, Node_Components_Uninitialize + use :: Unit_Tests , only : Assert , Skip , Unit_Tests_Begin_Group , Unit_Tests_End_Group , & + & Unit_Tests_Finish implicit none type (darkMatterHaloScaleVirialDensityContrastDefinition ) :: darkMatterHaloScale_ type (cosmologyParametersSimple ) :: cosmologyParameters_ @@ -61,6 +62,8 @@ program Test_Dark_Matter_Profiles_Generic type (treeNode ), pointer :: node_ class (nodeComponentBasic ), pointer :: basic_ class (nodeComponentDarkMatterProfile ), pointer :: darkMatterProfile_ + class (massDistributionClass ), pointer :: massDistribution_ + class (kinematicsDistributionClass ), pointer :: kinematicsDistribution_ double precision , parameter :: concentration =8.0d+0, massVirial =1.0d12, & & shapeProfile =1.8d-1 type (inputParameters ) :: parameters @@ -77,10 +80,10 @@ program Test_Dark_Matter_Profiles_Generic & radiusEnclosingDensityNumerical , radiusEnclosingDensity , & & radiusEnclosingMassNumerical , radiusEnclosingMass , & & radiusFromSpecificAngularMomentumNumerical , radiusFromSpecificAngularMomentum , & - & densityLogSlopeNumerical , densityLogSlope , & - & density , densityReference + & densityLogSlopeNumerical , densityLogSlope double precision , dimension(9) :: scaleFractional=[0.01d0,0.03d0,0.10d0,0.30d0,1.00d0,3.00d0,10.0d0,30.0d0,100.0d0] - + type (coordinateSpherical ) :: coordinates , coordinatesOuter + call displayVerbositySet(verbosityLevelStandard) call Unit_Tests_Begin_Group("Generic dark matter profiles") parameters=inputParameters('testSuite/parameters/darkMatterProfilesGeneric.xml') @@ -168,312 +171,360 @@ program Test_Dark_Matter_Profiles_Generic & ) darkMatterProfileTruncatedExponential_ = darkMatterProfileDMOTruncatedExponential( & & radiusFractionalDecay = 3.0d0 , & - & alpha = 1.0d0 , & - & beta = 3.0d0 , & - & gamma = 1.0d0 , & & nonAnalyticSolver =nonAnalyticSolversNumerical, & & darkMatterProfileDMO_ =darkMatterProfileNFW_ , & & darkMatterHaloScale_ =darkMatterHaloScale_ & & ) - darkMatterProfileIsothermal__ = darkMatterProfileDarkMatterOnly(cosmologyParameters_,darkMatterHaloScale_,darkMatterProfileIsothermal_ ) - darkMatterProfileNFW__ = darkMatterProfileDarkMatterOnly(cosmologyParameters_,darkMatterHaloScale_,darkMatterProfileNFW_ ) - darkMatterProfileEinasto__ = darkMatterProfileDarkMatterOnly(cosmologyParameters_,darkMatterHaloScale_,darkMatterProfileEinasto_ ) - darkMatterProfileBurkert__ = darkMatterProfileDarkMatterOnly(cosmologyParameters_,darkMatterHaloScale_,darkMatterProfileBurkert_ ) - darkMatterProfileZhao1996__ = darkMatterProfileDarkMatterOnly(cosmologyParameters_,darkMatterHaloScale_,darkMatterProfileZhao1996_ ) - darkMatterProfileTruncated__ = darkMatterProfileDarkMatterOnly(cosmologyParameters_,darkMatterHaloScale_,darkMatterProfileTruncated_ ) - darkMatterProfileTruncatedExponential__ = darkMatterProfileDarkMatterOnly(cosmologyParameters_,darkMatterHaloScale_,darkMatterProfileTruncatedExponential_ ) + darkMatterProfileIsothermal__ = darkMatterProfileDarkMatterOnly(cosmologyParameters_,darkMatterProfileIsothermal_ ) + darkMatterProfileNFW__ = darkMatterProfileDarkMatterOnly(cosmologyParameters_,darkMatterProfileNFW_ ) + darkMatterProfileEinasto__ = darkMatterProfileDarkMatterOnly(cosmologyParameters_,darkMatterProfileEinasto_ ) + darkMatterProfileBurkert__ = darkMatterProfileDarkMatterOnly(cosmologyParameters_,darkMatterProfileBurkert_ ) + darkMatterProfileZhao1996__ = darkMatterProfileDarkMatterOnly(cosmologyParameters_,darkMatterProfileZhao1996_ ) + darkMatterProfileTruncated__ = darkMatterProfileDarkMatterOnly(cosmologyParameters_,darkMatterProfileTruncated_ ) + darkMatterProfileTruncatedExponential__ = darkMatterProfileDarkMatterOnly(cosmologyParameters_,darkMatterProfileTruncatedExponential_) node_ => treeNode ( ) basic_ => node_ %basic (autoCreate=.true.) darkMatterProfile_ => node_ %darkMatterProfile(autoCreate=.true.) call basic_ %timeSet (cosmologyFunctions_%cosmicTime(1.0d0)) call basic_ %timeLastIsolatedSet(cosmologyFunctions_%cosmicTime(1.0d0)) call basic_ %massSet (massVirial ) - radiusVirial =+darkMatterHaloScale_%radiusVirial (node_) - timeDynamical=+darkMatterHaloScale_%timescaleDynamical(node_) - radiusScale =+radiusVirial & - & /concentration + radiusVirial =+darkMatterHaloScale_%radiusVirial (node_) + timeDynamical =+darkMatterHaloScale_%timescaleDynamical(node_) + radiusScale =+radiusVirial & + & /concentration + coordinatesOuter=[radiusVirial,0.0d0,0.0d0] call darkMatterProfile_%scaleSet(radiusScale ) call darkMatterProfile_%shapeSet(shapeProfile) call Unit_Tests_Begin_Group("Isothermal profile" ) - do i=1,size(scaleFractional) - enclosedMass (i)=darkMatterProfileIsothermal__%enclosedMass (node_, scaleFractional(i)*radiusScale ) - enclosedMassNumerical (i)=darkMatterProfileIsothermal__%enclosedMassNumerical (node_, scaleFractional(i)*radiusScale ) - potential (i)=darkMatterProfileIsothermal__%potential (node_, scaleFractional(i)*radiusScale ) - potentialNumerical (i)=darkMatterProfileIsothermal__%potentialNumerical (node_, scaleFractional(i)*radiusScale ) - velocityCircular (i)=darkMatterProfileIsothermal__%circularVelocity (node_, scaleFractional(i)*radiusScale ) - velocityCircularNumerical (i)=darkMatterProfileIsothermal__%circularVelocityNumerical (node_, scaleFractional(i)*radiusScale ) - radialVelocityDispersion (i)=darkMatterProfileIsothermal__%radialVelocityDispersion (node_, scaleFractional(i)*radiusScale ) - radialVelocityDispersionNumerical (i)=darkMatterProfileIsothermal__%radialVelocityDispersionNumerical (node_, scaleFractional(i)*radiusScale ) - kSpace (i)=darkMatterProfileIsothermal__%kSpace (node_, scaleFractional(i)/radiusScale ) - kSpaceNumerical (i)=darkMatterProfileIsothermal__%kSpaceNumerical (node_, scaleFractional(i)/radiusScale ) - freefallRadius (i)=darkMatterProfileIsothermal__%freefallRadius (node_, scaleFractional(i)*timeDynamical) - freefallRadiusNumerical (i)=darkMatterProfileIsothermal__%freefallRadiusNumerical (node_, scaleFractional(i)*timeDynamical) - freefallRadiusIncreaseRate (i)=darkMatterProfileIsothermal__%freefallRadiusIncreaseRate (node_, scaleFractional(i)*timeDynamical) - freefallRadiusIncreaseRateNumerical (i)=darkMatterProfileIsothermal__%freefallRadiusIncreaseRateNumerical (node_, scaleFractional(i)*timeDynamical) - radiusEnclosingDensity (i)=darkMatterProfileIsothermal__%radiusEnclosingDensity (node_,darkMatterProfileIsothermal__%density (node_,scaleFractional(i)*radiusScale) ) - radiusEnclosingDensityNumerical (i)=darkMatterProfileIsothermal__%radiusEnclosingDensityNumerical (node_,darkMatterProfileIsothermal__%density (node_,scaleFractional(i)*radiusScale) ) - radiusEnclosingMass (i)=darkMatterProfileIsothermal__%radiusEnclosingMass (node_,darkMatterProfileIsothermal__%enclosedMass (node_,scaleFractional(i)*radiusScale) ) - radiusEnclosingMassNumerical (i)=darkMatterProfileIsothermal__%radiusEnclosingMassNumerical (node_,darkMatterProfileIsothermal__%enclosedMass (node_,scaleFractional(i)*radiusScale) ) - radiusFromSpecificAngularMomentum (i)=darkMatterProfileIsothermal__%radiusFromSpecificAngularMomentum (node_,darkMatterProfileIsothermal__%circularVelocity(node_,scaleFractional(i)*radiusScale)*scaleFractional(i)*radiusScale ) - radiusFromSpecificAngularMomentumNumerical(i)=darkMatterProfileIsothermal__%radiusFromSpecificAngularMomentumNumerical(node_,darkMatterProfileIsothermal__%circularVelocity(node_,scaleFractional(i)*radiusScale)*scaleFractional(i)*radiusScale ) - densityLogSlope (i)=darkMatterProfileIsothermal__%densityLogSlope (node_, scaleFractional(i)*radiusScale ) - densityLogSlopeNumerical (i)=darkMatterProfileIsothermal__%densityLogSlopeNumerical (node_, scaleFractional(i)*radiusScale ) - end do - potential =potential -potential (1) - potentialNumerical=potentialNumerical-potentialNumerical(1) - call Skip ("Energy , E ","isothermal profile assumes virial equilibrium") - call Skip ("Energy growth rate , ̇E ","isothermal profile assumes virial equilibrium") - call Skip ("Radial moment , ℛ₁ ","1ˢᵗ moment diverges for isothermal profile" ) - call Assert("Radial moment , ℛ₂ ",darkMatterProfileIsothermal__%radialMomentNumerical (node_,2.0d0,0.0d0,radiusVirial),darkMatterProfileIsothermal__%radialMoment (node_,2.0d0,0.0d0,radiusVirial),relTol=1.0d-6 ) - call Assert("Radial moment , ℛ₃ ",darkMatterProfileIsothermal__%radialMomentNumerical (node_,3.0d0,0.0d0,radiusVirial),darkMatterProfileIsothermal__%radialMoment (node_,3.0d0,0.0d0,radiusVirial),relTol=1.0d-6 ) - call Assert("Rotation normalization , A ",darkMatterProfileIsothermal__%rotationNormalizationNumerical (node_ ),darkMatterProfileIsothermal__%rotationNormalization (node_ ),relTol=1.0d-3 ) - call Assert("Peak circular velocity , Vmax",darkMatterProfileIsothermal__%circularVelocityMaximumNumerical(node_ ),darkMatterProfileIsothermal__%circularVelocityMaximum(node_ ),relTol=1.0d-3 ) - call Assert("Enclosed mass , M(r)", enclosedMassNumerical , enclosedMass ,relTol=2.0d-3 ) - call Assert("Potential , Φ(r)", potentialNumerical , potential ,relTol=1.0d-3 ) - call Assert("Circular velocity , V(r)", velocityCircularNumerical , velocityCircular ,relTol=1.0d-3 ) - call Assert("Radial velocity dispersion , σ(r)", radialVelocityDispersionNumerical , radialVelocityDispersion ,relTol=3.0d-3 ) - call Assert("Fourier transform , u(k)", kSpaceNumerical , kSpace ,relTol=1.0d-3,absTol=1.0d-4) - call Assert("Freefall radius , r(t)", freefallRadiusNumerical , freefallRadius ,relTol=1.0d-3 ) - call Assert("Freefall radius increase rate , ̇r(t)", freefallRadiusIncreaseRateNumerical , freefallRadiusIncreaseRate ,relTol=1.0d-3 ) - call Assert("Radius enclosing density , r(ρ)", radiusEnclosingDensityNumerical , radiusEnclosingDensity ,relTol=1.0d-3 ) - call Assert("Radius enclosing mass , r(M)", radiusEnclosingMassNumerical , radiusEnclosingMass ,relTol=1.0d-3 ) - call Assert("Radius-specific angular momentum, r(j)", radiusFromSpecificAngularMomentumNumerical , radiusFromSpecificAngularMomentum ,relTol=1.0d-3 ) - call Assert("Density log gradient , α(r)", densityLogSlopeNumerical , densityLogSlope ,relTol=1.0d-3 ) + massDistribution_ => darkMatterProfileIsothermal__%get (node_) + kinematicsDistribution_ => massDistribution_ %kinematicsDistribution( ) + select type (massDistribution_) + class is (massDistributionSpherical) + do i=1,size(scaleFractional) + coordinates =[scaleFractional(i)*radiusScale,0.0d0,0.0d0] + enclosedMass (i)=massDistribution_ %massEnclosedBySphere ( scaleFractional(i)*radiusScale ) + enclosedMassNumerical (i)=massDistribution_ %massEnclosedBySphereNumerical ( scaleFractional(i)*radiusScale ) + potential (i)=massDistribution_ %potentialDifference ( coordinates ,coordinatesOuter ) + potentialNumerical (i)=massDistribution_ %potentialDifferenceNumerical ( coordinates ,coordinatesOuter ) + velocityCircular (i)=massDistribution_ %rotationCurve ( scaleFractional(i)*radiusScale ) + velocityCircularNumerical (i)=massDistribution_ %rotationCurveNumerical ( scaleFractional(i)*radiusScale ) + radialVelocityDispersion (i)=kinematicsDistribution_%velocityDispersion1D ( coordinates ,massDistribution_ ) + radialVelocityDispersionNumerical (i)=kinematicsDistribution_%velocityDispersion1DNumerical ( coordinates ,massDistribution_ ) + kSpace (i)=massDistribution_ %fourierTransform (radiusVirial, scaleFractional(i)/radiusScale ) + kSpaceNumerical (i)=massDistribution_ %fourierTransformNumerical (radiusVirial, scaleFractional(i)/radiusScale ) + freefallRadius (i)=massDistribution_ %radiusFreeFall ( scaleFractional(i)*timeDynamical ) + freefallRadiusNumerical (i)=massDistribution_ %radiusFreefallNumerical ( scaleFractional(i)*timeDynamical ) + freefallRadiusIncreaseRate (i)=massDistribution_ %radiusFreefallIncreaseRate ( scaleFractional(i)*timeDynamical ) + freefallRadiusIncreaseRateNumerical (i)=massDistribution_ %radiusFreefallIncreaseRateNumerical ( scaleFractional(i)*timeDynamical ) + radiusEnclosingDensity (i)=massDistribution_ %radiusEnclosingDensity ( massDistribution_%density (coordinates ) ) + radiusEnclosingDensityNumerical (i)=massDistribution_ %radiusEnclosingDensityNumerical ( massDistribution_%density (coordinates ) ) + radiusEnclosingMass (i)=massDistribution_ %radiusEnclosingMass ( massDistribution_%massEnclosedBySphere(scaleFractional(i)*radiusScale) ) + radiusEnclosingMassNumerical (i)=massDistribution_ %radiusEnclosingMassNumerical ( massDistribution_%massEnclosedBySphere(scaleFractional(i)*radiusScale) ) + radiusFromSpecificAngularMomentum (i)=massDistribution_ %radiusFromSpecificAngularMomentum ( massDistribution_%rotationCurve (scaleFractional(i)*radiusScale)*scaleFractional(i)*radiusScale ) + radiusFromSpecificAngularMomentumNumerical(i)=massDistribution_ %radiusFromSpecificAngularMomentumNumerical( massDistribution_%rotationCurve (scaleFractional(i)*radiusScale)*scaleFractional(i)*radiusScale ) + densityLogSlope (i)=massDistribution_ %densityGradientRadial ( coordinates ,logarithmic=.true.) + densityLogSlopeNumerical (i)=massDistribution_ %densityGradientRadialNumerical ( coordinates ,logarithmic=.true.) + end do + call Skip ("Energy , E ","isothermal profile assumes virial equilibrium") + call Skip ("Radial moment , ℛ₁ " ,"1ˢᵗ moment diverges for isothermal profile" ) + call Assert("Radial moment , ℛ₂ " ,massDistribution_%densityRadialMomentNumerical (2.0d0,0.0d0,radiusVirial),massDistribution_%densityRadialMoment (2.0d0,0.0d0,radiusVirial),relTol=1.0d-6 ) + call Assert("Radial moment , ℛ₃ " ,massDistribution_%densityRadialMomentNumerical (3.0d0,0.0d0,radiusVirial),massDistribution_%densityRadialMoment (3.0d0,0.0d0,radiusVirial),relTol=1.0d-6 ) + call Assert("Radius of peak circular velocity, Rmax",massDistribution_%radiusRotationCurveMaximumNumerical( ),massDistribution_%radiusRotationCurveMaximum( ),relTol=1.0d-3 ) + call Assert("Enclosed mass , M(r)", enclosedMassNumerical , enclosedMass ,relTol=2.0d-3 ) + call Assert("Potential , Φ(r)", potentialNumerical , potential ,relTol=1.0d-3 ) + call Assert("Circular velocity , V(r)", velocityCircularNumerical , velocityCircular ,relTol=1.0d-3 ) + call Assert("Radial velocity dispersion , σ(r)", radialVelocityDispersionNumerical , radialVelocityDispersion ,relTol=3.0d-3 ) + call Assert("Fourier transform , u(k)", kSpaceNumerical , kSpace ,relTol=1.0d-3,absTol=1.0d-4) + call Assert("Freefall radius , r(t)", freefallRadiusNumerical , freefallRadius ,relTol=1.0d-3 ) + call Assert("Freefall radius increase rate , ̇r(t)", freefallRadiusIncreaseRateNumerical , freefallRadiusIncreaseRate ,relTol=5.0d-3 ) + call Assert("Radius enclosing density , r(ρ)", radiusEnclosingDensityNumerical , radiusEnclosingDensity ,relTol=1.0d-3 ) + call Assert("Radius enclosing mass , r(M)", radiusEnclosingMassNumerical , radiusEnclosingMass ,relTol=1.0d-3 ) + call Assert("Radius-specific angular momentum, r(j)", radiusFromSpecificAngularMomentumNumerical , radiusFromSpecificAngularMomentum ,relTol=1.0d-3 ) + call Assert("Density log gradient , α(r)", densityLogSlopeNumerical , densityLogSlope ,relTol=1.0d-3 ) + end select + !![ + + + !!] call Unit_Tests_End_Group ( ) call Unit_Tests_Begin_Group("NFW profile" ) - do i=1,size(scaleFractional) - enclosedMass (i)=darkMatterProfileNFW__ %enclosedMass (node_, scaleFractional(i)*radiusScale ) - enclosedMassNumerical (i)=darkMatterProfileNFW__ %enclosedMassNumerical (node_, scaleFractional(i)*radiusScale ) - potential (i)=darkMatterProfileNFW__ %potential (node_, scaleFractional(i)*radiusScale ) - potentialNumerical (i)=darkMatterProfileNFW__ %potentialNumerical (node_, scaleFractional(i)*radiusScale ) - velocityCircular (i)=darkMatterProfileNFW__ %circularVelocity (node_, scaleFractional(i)*radiusScale ) - velocityCircularNumerical (i)=darkMatterProfileNFW__ %circularVelocityNumerical (node_, scaleFractional(i)*radiusScale ) - radialVelocityDispersion (i)=darkMatterProfileNFW__ %radialVelocityDispersion (node_, scaleFractional(i)*radiusScale ) - radialVelocityDispersionNumerical (i)=darkMatterProfileNFW__ %radialVelocityDispersionNumerical (node_, scaleFractional(i)*radiusScale ) - kSpace (i)=darkMatterProfileNFW__ %kSpace (node_, scaleFractional(i)/radiusScale ) - kSpaceNumerical (i)=darkMatterProfileNFW__ %kSpaceNumerical (node_, scaleFractional(i)/radiusScale ) - freefallRadius (i)=darkMatterProfileNFW__ %freefallRadius (node_, scaleFractional(i)*timeDynamical) - freefallRadiusNumerical (i)=darkMatterProfileNFW__ %freefallRadiusNumerical (node_, scaleFractional(i)*timeDynamical) - freefallRadiusIncreaseRate (i)=darkMatterProfileNFW__ %freefallRadiusIncreaseRate (node_, scaleFractional(i)*timeDynamical) - freefallRadiusIncreaseRateNumerical (i)=darkMatterProfileNFW__ %freefallRadiusIncreaseRateNumerical (node_, scaleFractional(i)*timeDynamical) - radiusEnclosingDensity (i)=darkMatterProfileNFW__ %radiusEnclosingDensity (node_,darkMatterProfileNFW__ %density (node_,scaleFractional(i)*radiusScale) ) - radiusEnclosingDensityNumerical (i)=darkMatterProfileNFW__ %radiusEnclosingDensityNumerical (node_,darkMatterProfileNFW__ %density (node_,scaleFractional(i)*radiusScale) ) - radiusEnclosingMass (i)=darkMatterProfileNFW__ %radiusEnclosingMass (node_,darkMatterProfileNFW__ %enclosedMass (node_,scaleFractional(i)*radiusScale) ) - radiusEnclosingMassNumerical (i)=darkMatterProfileNFW__ %radiusEnclosingMassNumerical (node_,darkMatterProfileNFW__ %enclosedMass (node_,scaleFractional(i)*radiusScale) ) - radiusFromSpecificAngularMomentum (i)=darkMatterProfileNFW__ %radiusFromSpecificAngularMomentum (node_,darkMatterProfileNFW__ %circularVelocity(node_,scaleFractional(i)*radiusScale)*scaleFractional(i)*radiusScale ) - radiusFromSpecificAngularMomentumNumerical(i)=darkMatterProfileNFW__ %radiusFromSpecificAngularMomentumNumerical(node_,darkMatterProfileNFW__ %circularVelocity(node_,scaleFractional(i)*radiusScale)*scaleFractional(i)*radiusScale ) - densityLogSlope (i)=darkMatterProfileNFW__ %densityLogSlope (node_, scaleFractional(i)*radiusScale ) - densityLogSlopeNumerical (i)=darkMatterProfileNFW__ %densityLogSlopeNumerical (node_, scaleFractional(i)*radiusScale ) - end do - potential =potential -potential (1) - potentialNumerical=potentialNumerical-potentialNumerical(1) - call Assert("Energy , E ",darkMatterProfileNFW__ %energyNumerical (node_ ),darkMatterProfileNFW__ %energy (node_ ),relTol=1.0d-3 ) - call Assert("Radial moment , ℛ₁ ",darkMatterProfileNFW__ %radialMomentNumerical (node_,1.0d0,0.0d0,radiusVirial),darkMatterProfileNFW__ %radialMoment (node_,1.0d0,0.0d0,radiusVirial),relTol=1.0d-6 ) - call Assert("Radial moment , ℛ₂ ",darkMatterProfileNFW__ %radialMomentNumerical (node_,2.0d0,0.0d0,radiusVirial),darkMatterProfileNFW__ %radialMoment (node_,2.0d0,0.0d0,radiusVirial),relTol=1.0d-6 ) - call Assert("Radial moment , ℛ₃ ",darkMatterProfileNFW__ %radialMomentNumerical (node_,3.0d0,0.0d0,radiusVirial),darkMatterProfileNFW__ %radialMoment (node_,3.0d0,0.0d0,radiusVirial),relTol=1.0d-6 ) - call Assert("Rotation normalization , A ",darkMatterProfileNFW__ %rotationNormalizationNumerical (node_ ),darkMatterProfileNFW__ %rotationNormalization (node_ ),relTol=1.0d-3 ) - call Assert("Peak circular velocity , Vmax",darkMatterProfileNFW__ %circularVelocityMaximumNumerical(node_ ),darkMatterProfileNFW__ %circularVelocityMaximum(node_ ),relTol=1.0d-3 ) - call Assert("Enclosed mass , M(r)", enclosedMassNumerical , enclosedMass ,relTol=1.0d-2 ) - call Assert("Potential , Φ(r)", potentialNumerical , potential ,relTol=1.0d-3 ) - call Assert("Circular velocity , V(r)", velocityCircularNumerical , velocityCircular ,relTol=1.0d-3 ) - call Assert("Radial velocity dispersion , σ(r)", radialVelocityDispersionNumerical , radialVelocityDispersion ,relTol=3.0d-3 ) - call Assert("Fourier transform , u(k)", kSpaceNumerical , kSpace ,relTol=1.0d-3,absTol=1.0d-4) - call Assert("Freefall radius , r(t)", freefallRadiusNumerical , freefallRadius ,relTol=1.0d-3 ) - call Assert("Freefall radius increase rate , ̇r(t)", freefallRadiusIncreaseRateNumerical , freefallRadiusIncreaseRate ,relTol=1.0d-2 ) - call Assert("Radius enclosing density , r(ρ)", radiusEnclosingDensityNumerical , radiusEnclosingDensity ,relTol=1.0d-3 ) - call Assert("Radius enclosing mass , r(M)", radiusEnclosingMassNumerical , radiusEnclosingMass ,relTol=1.0d-3 ) - call Assert("Radius-specific angular momentum, r(j)", radiusFromSpecificAngularMomentumNumerical , radiusFromSpecificAngularMomentum ,relTol=1.0d-3 ) - call Assert("Density log gradient , α(r)", densityLogSlopeNumerical , densityLogSlope ,relTol=1.0d-3 ) + massDistribution_ => darkMatterProfileNFW__%get (node_) + kinematicsDistribution_ => massDistribution_ %kinematicsDistribution( ) + select type (massDistribution_) + class is (massDistributionSpherical) + do i=1,size(scaleFractional) + coordinates =[scaleFractional(i)*radiusScale,0.0d0,0.0d0] + enclosedMass (i)=massDistribution_ %massEnclosedBySphere ( scaleFractional(i)*radiusScale ) + enclosedMassNumerical (i)=massDistribution_ %massEnclosedBySphereNumerical ( scaleFractional(i)*radiusScale ) + potential (i)=massDistribution_ %potentialDifference ( coordinates ,coordinatesOuter ) + potentialNumerical (i)=massDistribution_ %potentialDifferenceNumerical ( coordinates ,coordinatesOuter ) + velocityCircular (i)=massDistribution_ %rotationCurve ( scaleFractional(i)*radiusScale ) + velocityCircularNumerical (i)=massDistribution_ %rotationCurveNumerical ( scaleFractional(i)*radiusScale ) + radialVelocityDispersion (i)=kinematicsDistribution_%velocityDispersion1D ( coordinates ,massDistribution_ ) + radialVelocityDispersionNumerical (i)=kinematicsDistribution_%velocityDispersion1DNumerical ( coordinates ,massDistribution_ ) + kSpace (i)=massDistribution_ %fourierTransform (radiusVirial, scaleFractional(i)/radiusScale ) + kSpaceNumerical (i)=massDistribution_ %fourierTransformNumerical (radiusVirial, scaleFractional(i)/radiusScale ) + freefallRadius (i)=massDistribution_ %radiusFreeFall ( scaleFractional(i)*timeDynamical ) + freefallRadiusNumerical (i)=massDistribution_ %radiusFreefallNumerical ( scaleFractional(i)*timeDynamical ) + freefallRadiusIncreaseRate (i)=massDistribution_ %radiusFreefallIncreaseRate ( scaleFractional(i)*timeDynamical ) + freefallRadiusIncreaseRateNumerical (i)=massDistribution_ %radiusFreefallIncreaseRateNumerical ( scaleFractional(i)*timeDynamical ) + radiusEnclosingDensity (i)=massDistribution_ %radiusEnclosingDensity ( massDistribution_%density (coordinates ) ) + radiusEnclosingDensityNumerical (i)=massDistribution_ %radiusEnclosingDensityNumerical ( massDistribution_%density (coordinates ) ) + radiusEnclosingMass (i)=massDistribution_ %radiusEnclosingMass ( massDistribution_%massEnclosedBySphere(scaleFractional(i)*radiusScale) ) + radiusEnclosingMassNumerical (i)=massDistribution_ %radiusEnclosingMassNumerical ( massDistribution_%massEnclosedBySphere(scaleFractional(i)*radiusScale) ) + radiusFromSpecificAngularMomentum (i)=massDistribution_ %radiusFromSpecificAngularMomentum ( massDistribution_%rotationCurve (scaleFractional(i)*radiusScale)*scaleFractional(i)*radiusScale ) + radiusFromSpecificAngularMomentumNumerical(i)=massDistribution_ %radiusFromSpecificAngularMomentumNumerical( massDistribution_%rotationCurve (scaleFractional(i)*radiusScale)*scaleFractional(i)*radiusScale ) + densityLogSlope (i)=massDistribution_ %densityGradientRadial ( coordinates ,logarithmic=.true.) + densityLogSlopeNumerical (i)=massDistribution_ %densityGradientRadialNumerical ( coordinates ,logarithmic=.true.) + end do + call Assert("Energy , E ",massDistribution_%energyNumerical ( radiusVirial,massDistribution_),massDistribution_%energy ( radiusVirial,massDistribution_),relTol=1.0d-3 ) + call Assert("Radial moment , ℛ₁ ",massDistribution_%densityRadialMomentNumerical (1.0d0,0.0d0,radiusVirial ),massDistribution_%densityRadialMoment (1.0d0,0.0d0,radiusVirial ),relTol=1.0d-6 ) + call Assert("Radial moment , ℛ₂ ",massDistribution_%densityRadialMomentNumerical (2.0d0,0.0d0,radiusVirial ),massDistribution_%densityRadialMoment (2.0d0,0.0d0,radiusVirial ),relTol=1.0d-6 ) + call Assert("Radial moment , ℛ₃ ",massDistribution_%densityRadialMomentNumerical (3.0d0,0.0d0,radiusVirial ),massDistribution_%densityRadialMoment (3.0d0,0.0d0,radiusVirial ),relTol=1.0d-6 ) + call Assert("Radius of peak circular velocity, Rmax",massDistribution_%radiusRotationCurveMaximumNumerical( ),massDistribution_%radiusRotationCurveMaximum( ),relTol=1.0d-3 ) + call Assert("Enclosed mass , M(r)", enclosedMassNumerical , enclosedMass ,relTol=2.0d-3 ) + call Assert("Potential , Φ(r)", potentialNumerical , potential ,relTol=1.0d-3 ) + call Assert("Circular velocity , V(r)", velocityCircularNumerical , velocityCircular ,relTol=1.0d-3 ) + call Assert("Radial velocity dispersion , σ(r)", radialVelocityDispersionNumerical , radialVelocityDispersion ,relTol=3.0d-3 ) + call Assert("Fourier transform , u(k)", kSpaceNumerical , kSpace ,relTol=1.0d-3,absTol=1.0d-4) + call Assert("Freefall radius , r(t)", freefallRadiusNumerical , freefallRadius ,relTol=1.0d-3 ) + call Assert("Freefall radius increase rate , ̇r(t)", freefallRadiusIncreaseRateNumerical , freefallRadiusIncreaseRate ,relTol=5.0d-3 ) + call Assert("Radius enclosing density , r(ρ)", radiusEnclosingDensityNumerical , radiusEnclosingDensity ,relTol=1.0d-3 ) + call Assert("Radius enclosing mass , r(M)", radiusEnclosingMassNumerical , radiusEnclosingMass ,relTol=1.0d-3 ) + call Assert("Radius-specific angular momentum, r(j)", radiusFromSpecificAngularMomentumNumerical , radiusFromSpecificAngularMomentum ,relTol=1.0d-3 ) + call Assert("Density log gradient , α(r)", densityLogSlopeNumerical , densityLogSlope ,relTol=1.0d-3 ) + end select + !![ + + + !!] call Unit_Tests_End_Group ( ) call Unit_Tests_Begin_Group("Einasto profile" ) - do i=1,size(scaleFractional) - enclosedMass (i)=darkMatterProfileEinasto__ %enclosedMass (node_, scaleFractional(i)*radiusScale ) - enclosedMassNumerical (i)=darkMatterProfileEinasto__ %enclosedMassNumerical (node_, scaleFractional(i)*radiusScale ) - potential (i)=darkMatterProfileEinasto__ %potential (node_, scaleFractional(i)*radiusScale ) - potentialNumerical (i)=darkMatterProfileEinasto__ %potentialNumerical (node_, scaleFractional(i)*radiusScale ) - velocityCircular (i)=darkMatterProfileEinasto__ %circularVelocity (node_, scaleFractional(i)*radiusScale ) - velocityCircularNumerical (i)=darkMatterProfileEinasto__ %circularVelocityNumerical (node_, scaleFractional(i)*radiusScale ) - radialVelocityDispersion (i)=darkMatterProfileEinasto__ %radialVelocityDispersion (node_, scaleFractional(i)*radiusScale ) - radialVelocityDispersionNumerical (i)=darkMatterProfileEinasto__ %radialVelocityDispersionNumerical (node_, scaleFractional(i)*radiusScale ) - kSpace (i)=darkMatterProfileEinasto__ %kSpace (node_, scaleFractional(i)/radiusScale ) - kSpaceNumerical (i)=darkMatterProfileEinasto__ %kSpaceNumerical (node_, scaleFractional(i)/radiusScale ) - freefallRadius (i)=darkMatterProfileEinasto__ %freefallRadius (node_, scaleFractional(i)*timeDynamical) - freefallRadiusNumerical (i)=darkMatterProfileEinasto__ %freefallRadiusNumerical (node_, scaleFractional(i)*timeDynamical) - radiusEnclosingDensity (i)=darkMatterProfileEinasto__ %radiusEnclosingDensity (node_,darkMatterProfileEinasto__ %density (node_,scaleFractional(i)*radiusScale) ) - radiusEnclosingDensityNumerical (i)=darkMatterProfileEinasto__ %radiusEnclosingDensityNumerical (node_,darkMatterProfileEinasto__ %density (node_,scaleFractional(i)*radiusScale) ) - radiusEnclosingMass (i)=darkMatterProfileEinasto__ %radiusEnclosingMass (node_,darkMatterProfileEinasto__ %enclosedMass (node_,scaleFractional(i)*radiusScale) ) - radiusEnclosingMassNumerical (i)=darkMatterProfileEinasto__ %radiusEnclosingMassNumerical (node_,darkMatterProfileEinasto__ %enclosedMass (node_,scaleFractional(i)*radiusScale) ) - radiusFromSpecificAngularMomentum (i)=darkMatterProfileEinasto__ %radiusFromSpecificAngularMomentum (node_,darkMatterProfileEinasto__ %circularVelocity(node_,scaleFractional(i)*radiusScale)*scaleFractional(i)*radiusScale ) - radiusFromSpecificAngularMomentumNumerical(i)=darkMatterProfileEinasto__ %radiusFromSpecificAngularMomentumNumerical(node_,darkMatterProfileEinasto__ %circularVelocity(node_,scaleFractional(i)*radiusScale)*scaleFractional(i)*radiusScale ) - densityLogSlope (i)=darkMatterProfileEinasto__ %densityLogSlope (node_, scaleFractional(i)*radiusScale ) - densityLogSlopeNumerical (i)=darkMatterProfileEinasto__ %densityLogSlopeNumerical (node_, scaleFractional(i)*radiusScale ) - end do - potential =potential -potential (1) - potentialNumerical=potentialNumerical-potentialNumerical(1) - call Assert("Energy , E ",darkMatterProfileEinasto__ %energyNumerical (node_ ),darkMatterProfileEinasto__ %energy (node_ ),relTol=1.0d-3 ) - call Assert("Radial moment , ℛ₁ ",darkMatterProfileEinasto__ %radialMomentNumerical (node_,1.0d0,0.0d0,radiusVirial),darkMatterProfileEinasto__ %radialMoment (node_,1.0d0,0.0d0,radiusVirial),relTol=1.0d-6 ) - call Assert("Radial moment , ℛ₂ ",darkMatterProfileEinasto__ %radialMomentNumerical (node_,2.0d0,0.0d0,radiusVirial),darkMatterProfileEinasto__ %radialMoment (node_,2.0d0,0.0d0,radiusVirial),relTol=1.0d-6 ) - call Assert("Radial moment , ℛ₃ ",darkMatterProfileEinasto__ %radialMomentNumerical (node_,3.0d0,0.0d0,radiusVirial),darkMatterProfileEinasto__ %radialMoment (node_,3.0d0,0.0d0,radiusVirial),relTol=1.0d-6 ) - call Assert("Rotation normalization , A ",darkMatterProfileEinasto__ %rotationNormalizationNumerical (node_ ),darkMatterProfileEinasto__ %rotationNormalization (node_ ),relTol=1.0d-3 ) - call Assert("Peak circular velocity , Vmax",darkMatterProfileEinasto__ %circularVelocityMaximumNumerical(node_ ),darkMatterProfileEinasto__ %circularVelocityMaximum(node_ ),relTol=1.0d-3 ) - call Assert("Enclosed mass , M(r)", enclosedMassNumerical , enclosedMass ,relTol=1.0d-2 ) - call Assert("Potential , Φ(r)", potentialNumerical , potential ,relTol=1.0d-3 ) - call Assert("Circular velocity , V(r)", velocityCircularNumerical , velocityCircular ,relTol=1.0d-3 ) - call Assert("Radial velocity dispersion , σ(r)", radialVelocityDispersionNumerical , radialVelocityDispersion ,relTol=1.0d-2 ) - call Assert("Fourier transform , u(k)", kSpaceNumerical , kSpace ,relTol=1.0d-3,absTol=1.0d-4) - call Assert("Freefall radius , r(t)", freefallRadiusNumerical , freefallRadius ,relTol=1.0d-3 ) - call Assert("Radius enclosing density , r(ρ)", radiusEnclosingDensityNumerical , radiusEnclosingDensity ,relTol=1.0d-3 ) - call Assert("Radius enclosing mass , r(M)", radiusEnclosingMassNumerical , radiusEnclosingMass ,relTol=1.0d-3 ) - call Assert("Radius-specific angular momentum, r(j)", radiusFromSpecificAngularMomentumNumerical , radiusFromSpecificAngularMomentum ,relTol=1.0d-3 ) - call Assert("Density log gradient , α(r)", densityLogSlopeNumerical , densityLogSlope ,relTol=1.0d-3 ) + massDistribution_ => darkMatterProfileEinasto__%get (node_) + kinematicsDistribution_ => massDistribution_ %kinematicsDistribution( ) + select type (massDistribution_) + class is (massDistributionSpherical) + do i=1,size(scaleFractional) + coordinates =[scaleFractional(i)*radiusScale,0.0d0,0.0d0] + enclosedMass (i)=massDistribution_ %massEnclosedBySphere ( scaleFractional(i)*radiusScale ) + enclosedMassNumerical (i)=massDistribution_ %massEnclosedBySphereNumerical ( scaleFractional(i)*radiusScale ) + potential (i)=massDistribution_ %potentialDifference ( coordinates ,coordinatesOuter ) + potentialNumerical (i)=massDistribution_ %potentialDifferenceNumerical ( coordinates ,coordinatesOuter ) + velocityCircular (i)=massDistribution_ %rotationCurve ( scaleFractional(i)*radiusScale ) + velocityCircularNumerical (i)=massDistribution_ %rotationCurveNumerical ( scaleFractional(i)*radiusScale ) + radialVelocityDispersion (i)=kinematicsDistribution_%velocityDispersion1D ( coordinates ,massDistribution_ ) + radialVelocityDispersionNumerical (i)=kinematicsDistribution_%velocityDispersion1DNumerical ( coordinates ,massDistribution_ ) + kSpace (i)=massDistribution_ %fourierTransform (radiusVirial, scaleFractional(i)/radiusScale ) + kSpaceNumerical (i)=massDistribution_ %fourierTransformNumerical (radiusVirial, scaleFractional(i)/radiusScale ) + freefallRadius (i)=massDistribution_ %radiusFreeFall ( scaleFractional(i)*timeDynamical ) + freefallRadiusNumerical (i)=massDistribution_ %radiusFreefallNumerical ( scaleFractional(i)*timeDynamical ) + freefallRadiusIncreaseRate (i)=massDistribution_ %radiusFreefallIncreaseRate ( scaleFractional(i)*timeDynamical ) + freefallRadiusIncreaseRateNumerical (i)=massDistribution_ %radiusFreefallIncreaseRateNumerical ( scaleFractional(i)*timeDynamical ) + radiusEnclosingDensity (i)=massDistribution_ %radiusEnclosingDensity ( massDistribution_%density (coordinates ) ) + radiusEnclosingDensityNumerical (i)=massDistribution_ %radiusEnclosingDensityNumerical ( massDistribution_%density (coordinates ) ) + radiusEnclosingMass (i)=massDistribution_ %radiusEnclosingMass ( massDistribution_%massEnclosedBySphere(scaleFractional(i)*radiusScale) ) + radiusEnclosingMassNumerical (i)=massDistribution_ %radiusEnclosingMassNumerical ( massDistribution_%massEnclosedBySphere(scaleFractional(i)*radiusScale) ) + radiusFromSpecificAngularMomentum (i)=massDistribution_ %radiusFromSpecificAngularMomentum ( massDistribution_%rotationCurve (scaleFractional(i)*radiusScale)*scaleFractional(i)*radiusScale ) + radiusFromSpecificAngularMomentumNumerical(i)=massDistribution_ %radiusFromSpecificAngularMomentumNumerical( massDistribution_%rotationCurve (scaleFractional(i)*radiusScale)*scaleFractional(i)*radiusScale ) + densityLogSlope (i)=massDistribution_ %densityGradientRadial ( coordinates ,logarithmic=.true.) + densityLogSlopeNumerical (i)=massDistribution_ %densityGradientRadialNumerical ( coordinates ,logarithmic=.true.) + end do + call Assert("Energy , E ",massDistribution_%energyNumerical ( radiusVirial,massDistribution_),massDistribution_%energy ( radiusVirial,massDistribution_),relTol=1.0d-3 ) + call Assert("Radial moment , ℛ₁ ",massDistribution_%densityRadialMomentNumerical (1.0d0,0.0d0,radiusVirial ),massDistribution_%densityRadialMoment (1.0d0,0.0d0,radiusVirial ),relTol=1.0d-6 ) + call Assert("Radial moment , ℛ₂ ",massDistribution_%densityRadialMomentNumerical (2.0d0,0.0d0,radiusVirial ),massDistribution_%densityRadialMoment (2.0d0,0.0d0,radiusVirial ),relTol=1.0d-6 ) + call Assert("Radial moment , ℛ₃ ",massDistribution_%densityRadialMomentNumerical (3.0d0,0.0d0,radiusVirial ),massDistribution_%densityRadialMoment (3.0d0,0.0d0,radiusVirial ),relTol=1.0d-6 ) + call Assert("Radius of peak circular velocity, Rmax",massDistribution_%radiusRotationCurveMaximumNumerical( ),massDistribution_%radiusRotationCurveMaximum( ),relTol=1.0d-3 ) + call Assert("Enclosed mass , M(r)", enclosedMassNumerical , enclosedMass ,relTol=2.0d-3 ) + call Assert("Potential , Φ(r)", potentialNumerical , potential ,relTol=1.0d-3 ) + call Assert("Circular velocity , V(r)", velocityCircularNumerical , velocityCircular ,relTol=1.0d-3 ) + call Assert("Radial velocity dispersion , σ(r)", radialVelocityDispersionNumerical , radialVelocityDispersion ,relTol=3.0d-3 ) + call Assert("Fourier transform , u(k)", kSpaceNumerical , kSpace ,relTol=1.0d-3,absTol=1.0d-4) + call Assert("Freefall radius , r(t)", freefallRadiusNumerical , freefallRadius ,relTol=1.0d-3 ) + call Assert("Freefall radius increase rate , ̇r(t)", freefallRadiusIncreaseRateNumerical , freefallRadiusIncreaseRate ,relTol=6.0d-3 ) + call Assert("Radius enclosing density , r(ρ)", radiusEnclosingDensityNumerical , radiusEnclosingDensity ,relTol=1.0d-3 ) + call Assert("Radius enclosing mass , r(M)", radiusEnclosingMassNumerical , radiusEnclosingMass ,relTol=1.0d-3 ) + call Assert("Radius-specific angular momentum, r(j)", radiusFromSpecificAngularMomentumNumerical , radiusFromSpecificAngularMomentum ,relTol=1.0d-3 ) + call Assert("Density log gradient , α(r)", densityLogSlopeNumerical , densityLogSlope ,relTol=1.0d-3 ) + end select + !![ + + + !!] call Unit_Tests_End_Group ( ) call Unit_Tests_Begin_Group("Burkert profile" ) - do i=1,size(scaleFractional) - enclosedMass (i)=darkMatterProfileBurkert__ %enclosedMass (node_, scaleFractional(i)*radiusScale ) - enclosedMassNumerical (i)=darkMatterProfileBurkert__ %enclosedMassNumerical (node_, scaleFractional(i)*radiusScale ) - potential (i)=darkMatterProfileBurkert__ %potential (node_, scaleFractional(i)*radiusScale ) - potentialNumerical (i)=darkMatterProfileBurkert__ %potentialNumerical (node_, scaleFractional(i)*radiusScale ) - velocityCircular (i)=darkMatterProfileBurkert__ %circularVelocity (node_, scaleFractional(i)*radiusScale ) - velocityCircularNumerical (i)=darkMatterProfileBurkert__ %circularVelocityNumerical (node_, scaleFractional(i)*radiusScale ) - radialVelocityDispersion (i)=darkMatterProfileBurkert__ %radialVelocityDispersion (node_, scaleFractional(i)*radiusScale ) - radialVelocityDispersionNumerical (i)=darkMatterProfileBurkert__ %radialVelocityDispersionNumerical (node_, scaleFractional(i)*radiusScale ) - kSpace (i)=darkMatterProfileBurkert__ %kSpace (node_, scaleFractional(i)/radiusScale ) - kSpaceNumerical (i)=darkMatterProfileBurkert__ %kSpaceNumerical (node_, scaleFractional(i)/radiusScale ) - ! Freefall radius is only evaluated for sufficiently large radii, as this profile is cored, which means that the freefall - ! radius-time curve becomes asymptotic and difficult to evaluate numerically. - if (i >= 4) then - freefallRadius (i)=darkMatterProfileBurkert__ %freefallRadius (node_, scaleFractional(i)*timeDynamical) - freefallRadiusNumerical (i)=darkMatterProfileBurkert__ %freefallRadiusNumerical (node_, scaleFractional(i)*timeDynamical) - else - freefallRadius (i)=0.0d0 - freefallRadiusNumerical (i)=0.0d0 - end if - radiusEnclosingDensity (i)=darkMatterProfileBurkert__ %radiusEnclosingDensity (node_,darkMatterProfileBurkert__ %density (node_,scaleFractional(i)*radiusScale) ) - radiusEnclosingDensityNumerical (i)=darkMatterProfileBurkert__ %radiusEnclosingDensityNumerical (node_,darkMatterProfileBurkert__ %density (node_,scaleFractional(i)*radiusScale) ) - radiusEnclosingMass (i)=darkMatterProfileBurkert__ %radiusEnclosingMass (node_,darkMatterProfileBurkert__ %enclosedMass (node_,scaleFractional(i)*radiusScale) ) - radiusEnclosingMassNumerical (i)=darkMatterProfileBurkert__ %radiusEnclosingMassNumerical (node_,darkMatterProfileBurkert__ %enclosedMass (node_,scaleFractional(i)*radiusScale) ) - radiusFromSpecificAngularMomentum (i)=darkMatterProfileBurkert__ %radiusFromSpecificAngularMomentum (node_,darkMatterProfileBurkert__ %circularVelocity(node_,scaleFractional(i)*radiusScale)*scaleFractional(i)*radiusScale ) - radiusFromSpecificAngularMomentumNumerical(i)=darkMatterProfileBurkert__ %radiusFromSpecificAngularMomentumNumerical(node_,darkMatterProfileBurkert__ %circularVelocity(node_,scaleFractional(i)*radiusScale)*scaleFractional(i)*radiusScale ) - densityLogSlope (i)=darkMatterProfileBurkert__ %densityLogSlope (node_, scaleFractional(i)*radiusScale ) - densityLogSlopeNumerical (i)=darkMatterProfileBurkert__ %densityLogSlopeNumerical (node_, scaleFractional(i)*radiusScale ) - end do - potential =potential -potential (1) - potentialNumerical=potentialNumerical-potentialNumerical(1) - call Assert("Energy , E ",darkMatterProfileBurkert__ %energyNumerical (node_ ),darkMatterProfileBurkert__ %energy (node_ ),relTol=1.0d-3 ) - call Assert("Radial moment , ℛ₁ ",darkMatterProfileBurkert__ %radialMomentNumerical (node_,1.0d0,0.0d0,radiusVirial),darkMatterProfileBurkert__ %radialMoment (node_,1.0d0,0.0d0,radiusVirial),relTol=1.0d-6 ) - call Assert("Radial moment , ℛ₂ ",darkMatterProfileBurkert__ %radialMomentNumerical (node_,2.0d0,0.0d0,radiusVirial),darkMatterProfileBurkert__ %radialMoment (node_,2.0d0,0.0d0,radiusVirial),relTol=1.0d-6 ) - call Assert("Radial moment , ℛ₃ ",darkMatterProfileBurkert__ %radialMomentNumerical (node_,3.0d0,0.0d0,radiusVirial),darkMatterProfileBurkert__ %radialMoment (node_,3.0d0,0.0d0,radiusVirial),relTol=1.0d-6 ) - call Assert("Rotation normalization , A ",darkMatterProfileBurkert__ %rotationNormalizationNumerical (node_ ),darkMatterProfileBurkert__ %rotationNormalization (node_ ),relTol=1.0d-3 ) - call Assert("Peak circular velocity , Vmax",darkMatterProfileBurkert__ %circularVelocityMaximumNumerical(node_ ),darkMatterProfileBurkert__ %circularVelocityMaximum(node_ ),relTol=1.0d-3 ) - call Assert("Enclosed mass , M(r)", enclosedMassNumerical , enclosedMass ,relTol=1.0d-2 ) - call Assert("Potential , Φ(r)", potentialNumerical , potential ,relTol=1.0d-3 ) - call Assert("Circular velocity , V(r)", velocityCircularNumerical , velocityCircular ,relTol=1.0d-3 ) - call Assert("Radial velocity dispersion , σ(r)", radialVelocityDispersionNumerical , radialVelocityDispersion ,relTol=3.0d-3 ) - call Assert("Fourier transform , u(k)", kSpaceNumerical , kSpace ,relTol=1.0d-3,absTol=1.0d-4) - call Assert("Freefall radius , r(t)", freefallRadiusNumerical , freefallRadius ,relTol=1.0d-3 ) - call Assert("Radius enclosing density , r(ρ)", radiusEnclosingDensityNumerical , radiusEnclosingDensity ,relTol=1.0d-3 ) - call Assert("Radius enclosing mass , r(M)", radiusEnclosingMassNumerical , radiusEnclosingMass ,relTol=1.0d-3 ) - call Assert("Radius-specific angular momentum, r(j)", radiusFromSpecificAngularMomentumNumerical , radiusFromSpecificAngularMomentum ,relTol=1.0d-3 ) - call Assert("Density log gradient , α(r)", densityLogSlopeNumerical , densityLogSlope ,relTol=1.0d-3 ) + massDistribution_ => darkMatterProfileBurkert__%get (node_) + kinematicsDistribution_ => massDistribution_ %kinematicsDistribution( ) + select type (massDistribution_) + class is (massDistributionSpherical) + do i=size(scaleFractional),1,-1 + coordinates =[scaleFractional(i)*radiusScale,0.0d0,0.0d0] + enclosedMass (i)=massDistribution_ %massEnclosedBySphere ( scaleFractional(i)*radiusScale ) + enclosedMassNumerical (i)=massDistribution_ %massEnclosedBySphereNumerical ( scaleFractional(i)*radiusScale ) + potential (i)=massDistribution_ %potentialDifference ( coordinates ,coordinatesOuter ) + potentialNumerical (i)=massDistribution_ %potentialDifferenceNumerical ( coordinates ,coordinatesOuter ) + velocityCircular (i)=massDistribution_ %rotationCurve ( scaleFractional(i)*radiusScale ) + velocityCircularNumerical (i)=massDistribution_ %rotationCurveNumerical ( scaleFractional(i)*radiusScale ) + radialVelocityDispersion (i)=kinematicsDistribution_%velocityDispersion1D ( coordinates ,massDistribution_ ) + radialVelocityDispersionNumerical (i)=kinematicsDistribution_%velocityDispersion1DNumerical ( coordinates ,massDistribution_ ) + kSpace (i)=massDistribution_ %fourierTransform (radiusVirial, scaleFractional(i)/radiusScale ) + kSpaceNumerical (i)=massDistribution_ %fourierTransformNumerical (radiusVirial, scaleFractional(i)/radiusScale ) + freefallRadius (i)=massDistribution_ %radiusFreeFall ( scaleFractional(i)*timeDynamical ) + freefallRadiusNumerical (i)=massDistribution_ %radiusFreefallNumerical ( scaleFractional(i)*timeDynamical ) + freefallRadiusIncreaseRate (i)=massDistribution_ %radiusFreefallIncreaseRate ( scaleFractional(i)*timeDynamical ) + freefallRadiusIncreaseRateNumerical (i)=massDistribution_ %radiusFreefallIncreaseRateNumerical ( scaleFractional(i)*timeDynamical ) + radiusEnclosingDensity (i)=massDistribution_ %radiusEnclosingDensity ( massDistribution_%density (coordinates ) ) + radiusEnclosingDensityNumerical (i)=massDistribution_ %radiusEnclosingDensityNumerical ( massDistribution_%density (coordinates ) ) + radiusEnclosingMass (i)=massDistribution_ %radiusEnclosingMass ( massDistribution_%massEnclosedBySphere(scaleFractional(i)*radiusScale) ) + radiusEnclosingMassNumerical (i)=massDistribution_ %radiusEnclosingMassNumerical ( massDistribution_%massEnclosedBySphere(scaleFractional(i)*radiusScale) ) + radiusFromSpecificAngularMomentum (i)=massDistribution_ %radiusFromSpecificAngularMomentum ( massDistribution_%rotationCurve (scaleFractional(i)*radiusScale)*scaleFractional(i)*radiusScale ) + radiusFromSpecificAngularMomentumNumerical(i)=massDistribution_ %radiusFromSpecificAngularMomentumNumerical( massDistribution_%rotationCurve (scaleFractional(i)*radiusScale)*scaleFractional(i)*radiusScale ) + densityLogSlope (i)=massDistribution_ %densityGradientRadial ( coordinates ,logarithmic=.true.) + densityLogSlopeNumerical (i)=massDistribution_ %densityGradientRadialNumerical ( coordinates ,logarithmic=.true.) + end do + call Assert("Energy , E ",massDistribution_%energyNumerical ( radiusVirial,massDistribution_),massDistribution_%energy ( radiusVirial,massDistribution_),relTol=1.0d-3 ) + call Assert("Radial moment , ℛ₁ ",massDistribution_%densityRadialMomentNumerical (1.0d0,0.0d0,radiusVirial ),massDistribution_%densityRadialMoment (1.0d0,0.0d0,radiusVirial ),relTol=1.0d-6 ) + call Assert("Radial moment , ℛ₂ ",massDistribution_%densityRadialMomentNumerical (2.0d0,0.0d0,radiusVirial ),massDistribution_%densityRadialMoment (2.0d0,0.0d0,radiusVirial ),relTol=1.0d-6 ) + call Assert("Radial moment , ℛ₃ ",massDistribution_%densityRadialMomentNumerical (3.0d0,0.0d0,radiusVirial ),massDistribution_%densityRadialMoment (3.0d0,0.0d0,radiusVirial ),relTol=1.0d-6 ) + call Assert("Radius of peak circular velocity, Rmax",massDistribution_%radiusRotationCurveMaximumNumerical( ),massDistribution_%radiusRotationCurveMaximum( ),relTol=1.0d-3 ) + call Assert("Enclosed mass , M(r)", enclosedMassNumerical , enclosedMass ,relTol=2.0d-3 ) + call Assert("Potential , Φ(r)", potentialNumerical , potential ,relTol=1.0d-3 ) + call Assert("Circular velocity , V(r)", velocityCircularNumerical , velocityCircular ,relTol=1.0d-3 ) + call Assert("Radial velocity dispersion , σ(r)", radialVelocityDispersionNumerical , radialVelocityDispersion ,relTol=3.0d-3 ) + call Assert("Fourier transform , u(k)", kSpaceNumerical , kSpace ,relTol=1.0d-3,absTol=1.0d-4) + call Assert("Freefall radius , r(t)", freefallRadiusNumerical , freefallRadius ,relTol=1.0d-3 ) + call Assert("Freefall radius increase rate , ̇r(t)", freefallRadiusIncreaseRateNumerical , freefallRadiusIncreaseRate ,relTol=5.0d-3 ) + call Assert("Radius enclosing density , r(ρ)", radiusEnclosingDensityNumerical , radiusEnclosingDensity ,relTol=1.0d-3 ) + call Assert("Radius enclosing mass , r(M)", radiusEnclosingMassNumerical , radiusEnclosingMass ,relTol=1.0d-3 ) + call Assert("Radius-specific angular momentum, r(j)", radiusFromSpecificAngularMomentumNumerical , radiusFromSpecificAngularMomentum ,relTol=1.0d-3 ) + call Assert("Density log gradient , α(r)", densityLogSlopeNumerical , densityLogSlope ,relTol=1.0d-3 ) + end select + !![ + + + !!] call Unit_Tests_End_Group () call Unit_Tests_Begin_Group("Truncated NFW profile") - do i=1,size(scaleFractional) - densityLogSlope (i)=darkMatterProfileTruncated__ %densityLogSlope (node_, scaleFractional(i)*radiusScale ) - densityLogSlopeNumerical (i)=darkMatterProfileTruncated__ %densityLogSlopeNumerical (node_, scaleFractional(i)*radiusScale ) - radialVelocityDispersion (i)=darkMatterProfileTruncated__ %radialVelocityDispersion (node_, scaleFractional(i)*radiusScale ) - radialVelocityDispersionNumerical (i)=darkMatterProfileTruncated__ %radialVelocityDispersionNumerical (node_, scaleFractional(i)*radiusScale ) - end do - call Skip ("Energy , E ","implemented numerically" ) - call Skip ("Energy growth rate , ̇E ","implemented numerically" ) - call Skip ("Radial moment , ℛ₁ ","implemented numerically" ) - call Skip ("Radial moment , ℛ₂ ","implemented numerically" ) - call Skip ("Radial moment , ℛ₃ ","implemented numerically" ) - call Skip ("Rotation normalization , A ","implemented numerically" ) - call Skip ("Peak circular velocity , Vmax","implemented numerically" ) - call Skip ("Potential , Φ(r)","implemented numerically" ) - call Skip ("Circular velocity , V(r)","implemented numerically" ) - call Assert("Radial velocity dispersion , σ(r)", radialVelocityDispersionNumerical , radialVelocityDispersion ,relTol=1.0d-2 ) - call Skip ("Fourier transform , u(k)","implemented numerically" ) - call Skip ("Freefall radius , r(t)","implemented numerically" ) - call Skip ("Freefall radius increase rate , ̇r(t)","implemented numerically" ) - call Skip ("Radius enclosing density , r(ρ)","implemented numerically" ) - call Skip ("Radius enclosing mass , r(M)","implemented numerically" ) - call Skip ("Radius-specific angular momentum, r(j)","implemented numerically" ) - call Assert("Density log gradient , α(r)", densityLogSlopeNumerical , densityLogSlope ,relTol=1.0d-3 ) + massDistribution_ => darkMatterProfileTruncated__%get (node_) + kinematicsDistribution_ => massDistribution_ %kinematicsDistribution( ) + select type (massDistribution_) + class is (massDistributionSpherical) + do i=1,size(scaleFractional) + coordinates =[scaleFractional(i)*radiusScale,0.0d0,0.0d0] + radialVelocityDispersion (i)=kinematicsDistribution_%velocityDispersion1D ( coordinates ,massDistribution_ ) + radialVelocityDispersionNumerical (i)=kinematicsDistribution_%velocityDispersion1DNumerical ( coordinates ,massDistribution_ ) + densityLogSlope (i)=massDistribution_ %densityGradientRadial ( coordinates ,logarithmic=.true.) + densityLogSlopeNumerical (i)=massDistribution_ %densityGradientRadialNumerical ( coordinates ,logarithmic=.true.) + end do + call Skip ("Energy , E ","implemented numerically" ) + call Skip ("Energy growth rate , ̇E ","implemented numerically" ) + call Skip ("Radial moment , ℛ₁ ","implemented numerically" ) + call Skip ("Radial moment , ℛ₂ ","implemented numerically" ) + call Skip ("Radial moment , ℛ₃ ","implemented numerically" ) + call Skip ("Rotation normalization , A ","implemented numerically" ) + call Skip ("Peak circular velocity , Vmax","implemented numerically" ) + call Skip ("Potential , Φ(r)","implemented numerically" ) + call Skip ("Circular velocity , V(r)","implemented numerically" ) + call Assert("Radial velocity dispersion , σ(r)", radialVelocityDispersionNumerical , radialVelocityDispersion ,relTol=1.0d-2 ) + call Skip ("Fourier transform , u(k)","implemented numerically" ) + call Skip ("Freefall radius , r(t)","implemented numerically" ) + call Skip ("Freefall radius increase rate , ̇r(t)","implemented numerically" ) + call Skip ("Radius enclosing density , r(ρ)","implemented numerically" ) + call Skip ("Radius enclosing mass , r(M)","implemented numerically" ) + call Skip ("Radius-specific angular momentum, r(j)","implemented numerically" ) + call Assert("Density log gradient , α(r)", densityLogSlopeNumerical , densityLogSlope ,relTol=1.0d-3 ) + end select + !![ + + + !!] call Unit_Tests_End_Group () call Unit_Tests_Begin_Group("Exponentially-truncated NFW profile") - do i=1,size(scaleFractional) - densityLogSlope (i)=darkMatterProfileTruncatedExponential__ %densityLogSlope (node_, scaleFractional(i)*radiusScale ) - densityLogSlopeNumerical (i)=darkMatterProfileTruncatedExponential__ %densityLogSlopeNumerical (node_, scaleFractional(i)*radiusScale ) - radialVelocityDispersion (i)=darkMatterProfileTruncatedExponential__ %radialVelocityDispersion (node_, scaleFractional(i)*radiusScale ) - radialVelocityDispersionNumerical (i)=darkMatterProfileTruncatedExponential__ %radialVelocityDispersionNumerical (node_, scaleFractional(i)*radiusScale ) - end do - call Skip ("Energy , E ","implemented numerically" ) - call Skip ("Energy growth rate , ̇E ","implemented numerically" ) - call Skip ("Radial moment , ℛ₁ ","implemented numerically" ) - call Skip ("Radial moment , ℛ₂ ","implemented numerically" ) - call Skip ("Radial moment , ℛ₃ ","implemented numerically" ) - call Skip ("Rotation normalization , A ","implemented numerically" ) - call Skip ("Peak circular velocity , Vmax","implemented numerically" ) - call Skip ("Potential , Φ(r)","implemented numerically" ) - call Skip ("Circular velocity , V(r)","implemented numerically" ) - call Assert("Radial velocity dispersion , σ(r)", radialVelocityDispersionNumerical , radialVelocityDispersion ,relTol=3.0d-3 ) - call Skip ("Fourier transform , u(k)","implemented numerically" ) - call Skip ("Freefall radius , r(t)","implemented numerically" ) - call Skip ("Freefall radius increase rate , ̇r(t)","implemented numerically" ) - call Skip ("Radius enclosing density , r(ρ)","implemented numerically" ) - call Skip ("Radius enclosing mass , r(M)","implemented numerically" ) - call Skip ("Radius-specific angular momentum, r(j)","implemented numerically" ) - call Assert("Density log gradient , α(r)", densityLogSlopeNumerical , densityLogSlope ,relTol=1.0d-3 ) + massDistribution_ => darkMatterProfileTruncatedExponential__%get (node_) + kinematicsDistribution_ => massDistribution_ %kinematicsDistribution( ) + select type (massDistribution_) + class is (massDistributionSpherical) + do i=1,size(scaleFractional) + coordinates =[scaleFractional(i)*radiusScale,0.0d0,0.0d0] + radialVelocityDispersion (i)=kinematicsDistribution_%velocityDispersion1D ( coordinates ,massDistribution_ ) + radialVelocityDispersionNumerical (i)=kinematicsDistribution_%velocityDispersion1DNumerical ( coordinates ,massDistribution_ ) + densityLogSlope (i)=massDistribution_ %densityGradientRadial ( coordinates ,logarithmic=.true.) + densityLogSlopeNumerical (i)=massDistribution_ %densityGradientRadialNumerical ( coordinates ,logarithmic=.true.) + end do + call Skip ("Energy , E ","implemented numerically" ) + call Skip ("Energy growth rate , ̇E ","implemented numerically" ) + call Skip ("Radial moment , ℛ₁ ","implemented numerically" ) + call Skip ("Radial moment , ℛ₂ ","implemented numerically" ) + call Skip ("Radial moment , ℛ₃ ","implemented numerically" ) + call Skip ("Rotation normalization , A ","implemented numerically" ) + call Skip ("Peak circular velocity , Vmax","implemented numerically" ) + call Skip ("Potential , Φ(r)","implemented numerically" ) + call Skip ("Circular velocity , V(r)","implemented numerically" ) + call Assert("Radial velocity dispersion , σ(r)", radialVelocityDispersionNumerical , radialVelocityDispersion ,relTol=3.0d-3 ) + call Skip ("Fourier transform , u(k)","implemented numerically" ) + call Skip ("Freefall radius , r(t)","implemented numerically" ) + call Skip ("Freefall radius increase rate , ̇r(t)","implemented numerically" ) + call Skip ("Radius enclosing density , r(ρ)","implemented numerically" ) + call Skip ("Radius enclosing mass , r(M)","implemented numerically" ) + call Skip ("Radius-specific angular momentum, r(j)","implemented numerically" ) + call Assert("Density log gradient , α(r)", densityLogSlopeNumerical , densityLogSlope ,relTol=1.0d-3 ) + end select + !![ + + + !!] call Unit_Tests_End_Group () ! The Zhao1996 profile is a generalized NFW-type profile. So, compare to the NFW profile. call Unit_Tests_Begin_Group("Zhao1996 profile" ) - do i=1,size(scaleFractional) - density (i)=darkMatterProfileZhao1996__ %density (node_, scaleFractional(i)*radiusScale ) - densityReference (i)=darkMatterProfileNFW__ %density (node_, scaleFractional(i)*radiusScale ) - enclosedMass (i)=darkMatterProfileZhao1996__ %enclosedMass (node_, scaleFractional(i)*radiusScale ) - enclosedMassNumerical (i)=darkMatterProfileNFW__ %enclosedMass (node_, scaleFractional(i)*radiusScale ) - potential (i)=darkMatterProfileZhao1996__ %potential (node_, scaleFractional(i)*radiusScale ) - potentialNumerical (i)=darkMatterProfileNFW__ %potential (node_, scaleFractional(i)*radiusScale ) - velocityCircular (i)=darkMatterProfileZhao1996__ %circularVelocity (node_, scaleFractional(i)*radiusScale ) - velocityCircularNumerical (i)=darkMatterProfileNFW__ %circularVelocity (node_, scaleFractional(i)*radiusScale ) - radialVelocityDispersion (i)=darkMatterProfileZhao1996__ %radialVelocityDispersion (node_, scaleFractional(i)*radiusScale ) - radialVelocityDispersionNumerical (i)=darkMatterProfileNFW__ %radialVelocityDispersion (node_, scaleFractional(i)*radiusScale ) - kSpace (i)=darkMatterProfileZhao1996__ %kSpace (node_, scaleFractional(i)/radiusScale ) - kSpaceNumerical (i)=darkMatterProfileNFW__ %kSpace (node_, scaleFractional(i)/radiusScale ) - freefallRadius (i)=darkMatterProfileZhao1996__ %freefallRadius (node_, scaleFractional(i)*timeDynamical) - freefallRadiusNumerical (i)=darkMatterProfileNFW__ %freefallRadius (node_, scaleFractional(i)*timeDynamical) - freefallRadiusIncreaseRate (i)=darkMatterProfileZhao1996__ %freefallRadiusIncreaseRate (node_, scaleFractional(i)*timeDynamical) - freefallRadiusIncreaseRateNumerical (i)=darkMatterProfileNFW__ %freefallRadiusIncreaseRate (node_, scaleFractional(i)*timeDynamical) - radiusEnclosingDensity (i)=darkMatterProfileZhao1996__ %radiusEnclosingDensity (node_,darkMatterProfileZhao1996__ %density (node_,scaleFractional(i)*radiusScale) ) - radiusEnclosingDensityNumerical (i)=darkMatterProfileNFW__ %radiusEnclosingDensityNumerical (node_,darkMatterProfileNFW__ %density (node_,scaleFractional(i)*radiusScale) ) - radiusEnclosingMass (i)=darkMatterProfileZhao1996__ %radiusEnclosingMass (node_,darkMatterProfileZhao1996__ %enclosedMass (node_,scaleFractional(i)*radiusScale) ) - radiusEnclosingMassNumerical (i)=darkMatterProfileNFW__ %radiusEnclosingMass (node_,darkMatterProfileNFW__ %enclosedMass (node_,scaleFractional(i)*radiusScale) ) - radiusFromSpecificAngularMomentum (i)=darkMatterProfileZhao1996__ %radiusFromSpecificAngularMomentum(node_,darkMatterProfileZhao1996__ %circularVelocity(node_,scaleFractional(i)*radiusScale)*scaleFractional(i)*radiusScale ) - radiusFromSpecificAngularMomentumNumerical(i)=darkMatterProfileNFW__ %radiusFromSpecificAngularMomentum(node_,darkMatterProfileNFW__ %circularVelocity(node_,scaleFractional(i)*radiusScale)*scaleFractional(i)*radiusScale ) - densityLogSlope (i)=darkMatterProfileZhao1996__ %densityLogSlope (node_, scaleFractional(i)*radiusScale ) - densityLogSlopeNumerical (i)=darkMatterProfileNFW__ %densityLogSlope (node_, scaleFractional(i)*radiusScale ) - end do - potential =potential -potential (1) - potentialNumerical=potentialNumerical-potentialNumerical(1) - call Assert("Energy , E ",darkMatterProfileNFW__%energy (node_ ),darkMatterProfileZhao1996__ %energy (node_ ),relTol=1.0d-3 ) - call Assert("Radial moment , ℛ₁ ",darkMatterProfileNFW__%radialMoment (node_,1.0d0,0.0d0,radiusVirial),darkMatterProfileZhao1996__ %radialMoment (node_,1.0d0,0.0d0,radiusVirial),relTol=1.0d-6 ) - call Assert("Radial moment , ℛ₂ ",darkMatterProfileNFW__%radialMoment (node_,2.0d0,0.0d0,radiusVirial),darkMatterProfileZhao1996__ %radialMoment (node_,2.0d0,0.0d0,radiusVirial),relTol=1.0d-6 ) - call Assert("Radial moment , ℛ₃ ",darkMatterProfileNFW__%radialMoment (node_,3.0d0,0.0d0,radiusVirial),darkMatterProfileZhao1996__ %radialMoment (node_,3.0d0,0.0d0,radiusVirial),relTol=1.0d-6 ) - call Assert("Rotation normalization , A ",darkMatterProfileNFW__%rotationNormalization (node_ ),darkMatterProfileZhao1996__ %rotationNormalization (node_ ),relTol=1.0d-3 ) - call Assert("Peak circular velocity , Vmax",darkMatterProfileNFW__%circularVelocityMaximum (node_ ),darkMatterProfileZhao1996__ %circularVelocityMaximum(node_ ),relTol=1.0d-3 ) - call Assert("Enclosed mass , M(r)", enclosedMassNumerical , enclosedMass ,relTol=1.0d-2 ) - call Assert("Potential , Φ(r)", potentialNumerical , potential ,relTol=1.0d-3 ) - call Assert("Circular velocity , V(r)", velocityCircularNumerical , velocityCircular ,relTol=1.0d-3 ) - call Assert("Radial velocity dispersion , σ(r)", radialVelocityDispersionNumerical , radialVelocityDispersion ,relTol=3.0d-3 ) - call Assert("Fourier transform , u(k)", kSpaceNumerical , kSpace ,relTol=1.0d-3,absTol=1.0d-4) - call Assert("Freefall radius , r(t)", freefallRadiusNumerical , freefallRadius ,relTol=1.0d-3 ) - call Assert("Freefall radius increase rate , ̇r(t)", freefallRadiusIncreaseRateNumerical , freefallRadiusIncreaseRate ,relTol=1.0d-2 ) - call Assert("Radius enclosing density , r(ρ)", radiusEnclosingDensityNumerical , radiusEnclosingDensity ,relTol=1.0d-3 ) - call Assert("Radius enclosing mass , r(M)", radiusEnclosingMassNumerical , radiusEnclosingMass ,relTol=1.0d-3 ) - call Assert("Radius-specific angular momentum, r(j)", radiusFromSpecificAngularMomentumNumerical , radiusFromSpecificAngularMomentum ,relTol=1.0d-3 ) - call Assert("Density , ρ(r)", densityReference , density ,relTol=1.0d-3 ) - call Assert("Density log gradient , α(r)", densityLogSlopeNumerical , densityLogSlope ,relTol=1.0d-3 ) + massDistribution_ => darkMatterProfileBurkert__%get (node_) + kinematicsDistribution_ => massDistribution_ %kinematicsDistribution( ) + select type (massDistribution_) + class is (massDistributionSpherical) + do i=1,size(scaleFractional) + coordinates =[scaleFractional(i)*radiusScale,0.0d0,0.0d0] + enclosedMass (i)=massDistribution_ %massEnclosedBySphere ( scaleFractional(i)*radiusScale ) + enclosedMassNumerical (i)=massDistribution_ %massEnclosedBySphereNumerical ( scaleFractional(i)*radiusScale ) + potential (i)=massDistribution_ %potentialDifference ( coordinates ,coordinatesOuter ) + potentialNumerical (i)=massDistribution_ %potentialDifferenceNumerical ( coordinates ,coordinatesOuter ) + velocityCircular (i)=massDistribution_ %rotationCurve ( scaleFractional(i)*radiusScale ) + velocityCircularNumerical (i)=massDistribution_ %rotationCurveNumerical ( scaleFractional(i)*radiusScale ) + radialVelocityDispersion (i)=kinematicsDistribution_%velocityDispersion1D ( coordinates ,massDistribution_ ) + radialVelocityDispersionNumerical (i)=kinematicsDistribution_%velocityDispersion1DNumerical ( coordinates ,massDistribution_ ) + kSpace (i)=massDistribution_ %fourierTransform (radiusVirial, scaleFractional(i)/radiusScale ) + kSpaceNumerical (i)=massDistribution_ %fourierTransformNumerical (radiusVirial, scaleFractional(i)/radiusScale ) + freefallRadius (i)=massDistribution_ %radiusFreeFall ( scaleFractional(i)*timeDynamical ) + freefallRadiusNumerical (i)=massDistribution_ %radiusFreefallNumerical ( scaleFractional(i)*timeDynamical ) + freefallRadiusIncreaseRate (i)=massDistribution_ %radiusFreefallIncreaseRate ( scaleFractional(i)*timeDynamical ) + freefallRadiusIncreaseRateNumerical (i)=massDistribution_ %radiusFreefallIncreaseRateNumerical ( scaleFractional(i)*timeDynamical ) + radiusEnclosingDensity (i)=massDistribution_ %radiusEnclosingDensity ( massDistribution_%density (coordinates ) ) + radiusEnclosingDensityNumerical (i)=massDistribution_ %radiusEnclosingDensityNumerical ( massDistribution_%density (coordinates ) ) + radiusEnclosingMass (i)=massDistribution_ %radiusEnclosingMass ( massDistribution_%massEnclosedBySphere(scaleFractional(i)*radiusScale) ) + radiusEnclosingMassNumerical (i)=massDistribution_ %radiusEnclosingMassNumerical ( massDistribution_%massEnclosedBySphere(scaleFractional(i)*radiusScale) ) + radiusFromSpecificAngularMomentum (i)=massDistribution_ %radiusFromSpecificAngularMomentum ( massDistribution_%rotationCurve (scaleFractional(i)*radiusScale)*scaleFractional(i)*radiusScale ) + radiusFromSpecificAngularMomentumNumerical(i)=massDistribution_ %radiusFromSpecificAngularMomentumNumerical( massDistribution_%rotationCurve (scaleFractional(i)*radiusScale)*scaleFractional(i)*radiusScale ) + densityLogSlope (i)=massDistribution_ %densityGradientRadial ( coordinates ,logarithmic=.true.) + densityLogSlopeNumerical (i)=massDistribution_ %densityGradientRadialNumerical ( coordinates ,logarithmic=.true.) + end do + call Assert("Energy , E ",massDistribution_%energyNumerical ( radiusVirial,massDistribution_),massDistribution_%energy ( radiusVirial,massDistribution_),relTol=1.0d-3 ) + call Assert("Radial moment , ℛ₁ ",massDistribution_%densityRadialMomentNumerical (1.0d0,0.0d0,radiusVirial ),massDistribution_%densityRadialMoment (1.0d0,0.0d0,radiusVirial ),relTol=1.0d-6 ) + call Assert("Radial moment , ℛ₂ ",massDistribution_%densityRadialMomentNumerical (2.0d0,0.0d0,radiusVirial ),massDistribution_%densityRadialMoment (2.0d0,0.0d0,radiusVirial ),relTol=1.0d-6 ) + call Assert("Radial moment , ℛ₃ ",massDistribution_%densityRadialMomentNumerical (3.0d0,0.0d0,radiusVirial ),massDistribution_%densityRadialMoment (3.0d0,0.0d0,radiusVirial ),relTol=1.0d-6 ) + call Assert("Radius of peak circular velocity, Rmax",massDistribution_%radiusRotationCurveMaximumNumerical( ),massDistribution_%radiusRotationCurveMaximum( ),relTol=1.0d-3 ) + call Assert("Enclosed mass , M(r)", enclosedMassNumerical , enclosedMass ,relTol=2.0d-3 ) + call Assert("Potential , Φ(r)", potentialNumerical , potential ,relTol=1.0d-3 ) + call Assert("Circular velocity , V(r)", velocityCircularNumerical , velocityCircular ,relTol=1.0d-3 ) + call Assert("Radial velocity dispersion , σ(r)", radialVelocityDispersionNumerical , radialVelocityDispersion ,relTol=3.0d-3 ) + call Assert("Fourier transform , u(k)", kSpaceNumerical , kSpace ,relTol=1.0d-3,absTol=1.0d-4) + call Assert("Freefall radius , r(t)", freefallRadiusNumerical , freefallRadius ,relTol=1.0d-3 ) + call Assert("Freefall radius increase rate , ̇r(t)", freefallRadiusIncreaseRateNumerical , freefallRadiusIncreaseRate ,relTol=5.0d-3 ) + call Assert("Radius enclosing density , r(ρ)", radiusEnclosingDensityNumerical , radiusEnclosingDensity ,relTol=1.0d-3 ) + call Assert("Radius enclosing mass , r(M)", radiusEnclosingMassNumerical , radiusEnclosingMass ,relTol=1.0d-3 ) + call Assert("Radius-specific angular momentum, r(j)", radiusFromSpecificAngularMomentumNumerical , radiusFromSpecificAngularMomentum ,relTol=1.0d-3 ) + call Assert("Density log gradient , α(r)", densityLogSlopeNumerical , densityLogSlope ,relTol=1.0d-3 ) + end select + !![ + + + !!] call Unit_Tests_End_Group () call Unit_Tests_End_Group () call Unit_Tests_Finish () diff --git a/source/tests.dark_matter_profiles.heated.F90 b/source/tests.dark_matter_profiles.heated.F90 index a1d3c9b03b..e848722e7b 100644 --- a/source/tests.dark_matter_profiles.heated.F90 +++ b/source/tests.dark_matter_profiles.heated.F90 @@ -23,32 +23,43 @@ program Test_Dark_Matter_Profiles_Heated !!{ - Tests heated dark matter profile implementations. An isothermal dark matter halo is used, since analytic solutions are - available for this case. Specifically, the initial radius in the unheated profile is given by $r_\mathrm{i}(r) = - (r_\mathrm{h}^4/4 r^2 + r_\mathrm{h}^2)^{1/2}-r_\mathrm{h}^2/2 r$ where $r$ is the radius in the heated profile, and - $r_\mathrm{h}=(\mathrm{G} M_\mathrm{v} / 2 Q r_\mathrm{v})^{1/2}$ is a characteristic heating radius. Here, $M_\mathrm{v}$, - and $r_\mathrm{v}$ are the virial mass and radius of the halo respectively, and $Q r_\mathrm{i}^2$ is the specific heat input - to the density profile, with $Q$ assumed to be a constant (as expected for tidal heating). Assuming no shell crossing, the - enclosed mass in the final profile is simply $M(r) = M_\mathrm{v} r_\mathrm{i}(r)/r_\mathrm{v}$, from which the density of - the final profile is found as $\rho(r) = (4 \pi r^2)^{-1} \mathrm{d} M(r) / \mathrm{d} r$. + Tests heated dark matter profile implementations. An isothermal dark matter halo is used, since analytic solutions are available + for this case. Specifically, the initial radius in the unheated profile is given by $r_\mathrm{i}(r) = (r_\mathrm{h}^4/4 r^2 + + r_\mathrm{h}^2)^{1/2}-r_\mathrm{h}^2/2 r$ where $r$ is the radius in the heated profile, and $r_\mathrm{h}=(\mathrm{G} + M_\mathrm{v} / 2 Q r_\mathrm{v})^{1/2}$ is a characteristic heating radius. Here, $M_\mathrm{v}$, and $r_\mathrm{v}$ are the + virial mass and radius of the halo respectively, and $Q r_\mathrm{i}^2$ is the specific heat input to the density profile, with + $Q$ assumed to be a constant (as expected for tidal heating). Assuming no shell crossing, the enclosed mass in the final profile + is simply $M(r) = M_\mathrm{v} r_\mathrm{i}(r)/r_\mathrm{v}$, from which the density of the final profile is found as $\rho(r) = + (4 \pi r^2)^{-1} \mathrm{d} M(r) / \mathrm{d} r$. The velocity dispersion can also be found analytically in this + case. Integrating the Jeans equation in the variables of the initial profile gives a result: + \begin{eqnarray} + \rho(r) \sigma^2(r) &=& \int_{r_\mathrm{i}(r)}^{r_\mathrm{h}} \mathrm{d}r^\prime_\mathrm{i} \frac{\mathrm{G} M(r^\prime_\mathrm{i})}{r^{\prime 2}_\mathrm{i}} \rho_\mathrm{i}(r^\prime_\mathrm{i}) \left(\frac{r^\prime_\mathrm{i}}{r}\right)^4 \nonumber \\ + &=& \frac{10}{6} \frac{\mathrm{G} M(r_\mathrm{h}) \rho_\mathrm{i}(r_\mathrm{h}}{r_\mathrm{h}} - \frac{(-3+18 y^4 - 6 y^6 + y^8 - 24 y^2 \log(y))}{6} \frac{\mathrm{G} M(r_\mathrm{i}) \rho_\mathrm{i}(r_\mathrm{i}}{r_\mathrm{i}}, + \end{eqnarray} + where $y=r_\mathrm{i}/r_\mathrm{h}$. !!} + use :: Coordinates , only : coordinateSpherical use :: Cosmology_Parameters , only : cosmologyParametersSimple use :: Cosmology_Functions , only : cosmologyFunctionsMatterLambda - use :: Dark_Matter_Particles , only : darkMatterParticleSelfInteractingDarkMatter , darkMatterParticleCDM + use :: Dark_Matter_Particles , only : darkMatterParticleSelfInteractingDarkMatter , darkMatterParticleCDM use :: Dark_Matter_Halo_Scales , only : darkMatterHaloScaleVirialDensityContrastDefinition use :: Virial_Density_Contrast , only : virialDensityContrastSphericalCollapseClsnlssMttrCsmlgclCnstnt - use :: Dark_Matter_Profiles_DMO , only : darkMatterProfileDMOHeated , darkMatterProfileDMOHeatedMonotonic, darkMatterProfileDMOIsothermal, darkMatterProfileHeatingTidal, & + use :: Dark_Matter_Profiles_DMO , only : darkMatterProfileDMOHeated , darkMatterProfileDMOHeatedMonotonic, darkMatterProfileDMOIsothermal , darkMatterProfileHeatingTidal , & & darkMatterProfileDMOClass - use :: Dark_Matter_Profiles_Generic , only : nonAnalyticSolversFallThrough - use :: Display , only : displayVerbositySet , verbosityLevelStandard + use :: Display , only : displayVerbositySet , verbosityLevelStandard use :: Events_Hooks , only : eventsHooksInitialize - use :: Galacticus_Nodes , only : nodeClassHierarchyFinalize , nodeClassHierarchyInitialize , nodeComponentBasic , nodeComponentSatellite , & + use :: Galacticus_Nodes , only : nodeClassHierarchyFinalize , nodeClassHierarchyInitialize , nodeComponentBasic , nodeComponentSatellite , & & treeNode - use :: ISO_Varying_String , only : assignment(=) , varying_string + use :: ISO_Varying_String , only : varying_string use :: Input_Parameters , only : inputParameters use :: Numerical_Constants_Astronomical, only : gravitationalConstantGalacticus use :: Numerical_Constants_Math , only : Pi - use :: Unit_Tests , only : Assert , Unit_Tests_Begin_Group , Unit_Tests_End_Group , Unit_Tests_Finish + use :: Mass_Distributions , only : massDistributionSphericalHeated , massDistributionClass , massDistributionSpherical , massDistributionHeatingClass , & + & nonAnalyticSolversNumerical , kinematicsDistributionHeated , massDistributionSphericalHeatedMonotonic, kinematicsDistributionCollisionless, & + & kinematicsDistributionClass , & + & nonAnalyticSolversFallThroughDMO => nonAnalyticSolversFallThrough , & + & nonAnalyticSolversNumericalDMO => nonAnalyticSolversNumerical + use :: Unit_Tests , only : Assert , Unit_Tests_Begin_Group , Unit_Tests_End_Group , Unit_Tests_Finish implicit none double precision , parameter :: time =13.8d+00 double precision , parameter :: massVirial = 1.0d+10 @@ -58,7 +69,8 @@ program Test_Dark_Matter_Profiles_Heated double precision , parameter :: toleranceRelativeVelocityDispersion = 1.0d-06 double precision , parameter :: toleranceRelativeVelocityDispersionMaximum = 1.0d-03 double precision , parameter :: toleranceRelativePotential = 1.0d-03 - logical , parameter :: velocityDispersionApproximate =.true. + logical , parameter :: velocityDispersionApproximate =.false. + logical , parameter :: tolerateVelocityMaximumFailure =.false. class (nodeComponentBasic ), pointer :: basic class (nodeComponentSatellite ), pointer :: satellite double precision , dimension(3) :: radiusVirialFractional =[0.1d0,0.5d0,1.0d0] @@ -76,11 +88,19 @@ program Test_Dark_Matter_Profiles_Heated type (darkMatterProfileHeatingTidal ) :: darkMatterProfileHeatingTidal_ type (darkMatterParticleCDM ) :: darkMatterParticleCDM_ type (darkMatterParticleSelfInteractingDarkMatter ) :: darkMatterParticleSelfInteractingDarkMatter_ + class (massDistributionSpherical ), pointer :: massDistributionSphericalHeated_ + class (kinematicsDistributionClass ), pointer :: kinematicsDistributionHeated_ + class (massDistributionHeatingClass ), pointer :: massDistributionHeatingTidal_ + class (massDistributionClass ), pointer :: massDistributionIsothermal_ + type (coordinateSpherical ) :: coordinates , coordinatesHeated , & + & coordinatesInitial double precision :: radiusVirial , radiusHeated , & - & density , densityAnalytic , & + & massEnclosedAnalytic , densityAnalytic , & & radiusInitial , radiusInitialAnalytic, & - & massEnclosed , massEnclosedAnalytic , & - & radius , toleranceRelative + & radius , toleranceRelative , & + & massEnclosed_ , density_ , & + & velocityDispersionAnalytic , radiusScaled , & + & toleranceRelativeVelocityDispersionAssert , velocityDispersion_ integer :: i , profileType character (len=5 ) :: radiusLabel character (len=128 ) :: profileName @@ -144,10 +164,10 @@ program Test_Dark_Matter_Profiles_Heated !!] - darkMatterProfileHeatingTidal_ =darkMatterProfileHeatingTidal (coefficientSecondOrder ,coefficientSecondOrder ,coefficientSecondOrder ,correlationVelocityRadius ) - darkMatterProfileDMOIsothermal_ =darkMatterProfileDMOIsothermal ( darkMatterHaloScale_ ) - darkMatterProfileDMOHeated_ =darkMatterProfileDMOHeated (nonAnalyticSolversFallThrough,velocityDispersionApproximate,toleranceRelativeVelocityDispersion,toleranceRelativeVelocityDispersionMaximum ,darkMatterProfileDMOIsothermal_,darkMatterHaloScale_,darkMatterProfileHeatingTidal_) - darkMatterProfileDMOHeatedMonotonic_=darkMatterProfileDMOHeatedMonotonic(nonAnalyticSolversFallThrough ,toleranceRelativeVelocityDispersion,toleranceRelativeVelocityDispersionMaximum,toleranceRelativePotential,darkMatterProfileDMOIsothermal_,darkMatterHaloScale_,darkMatterProfileHeatingTidal_) + darkMatterProfileHeatingTidal_ =darkMatterProfileHeatingTidal (coefficientSecondOrder ,coefficientSecondOrder ,coefficientSecondOrder ,correlationVelocityRadius ) + darkMatterProfileDMOIsothermal_ =darkMatterProfileDMOIsothermal ( darkMatterHaloScale_ ) + darkMatterProfileDMOHeated_ =darkMatterProfileDMOHeated (nonAnalyticSolversNumericalDMO,velocityDispersionApproximate,tolerateVelocityMaximumFailure,toleranceRelativeVelocityDispersion,toleranceRelativeVelocityDispersionMaximum ,darkMatterProfileDMOIsothermal_ ,darkMatterProfileHeatingTidal_) + darkMatterProfileDMOHeatedMonotonic_=darkMatterProfileDMOHeatedMonotonic(nonAnalyticSolversNumericalDMO ,toleranceRelativeVelocityDispersion,toleranceRelativeVelocityDispersionMaximum ,darkMatterProfileDMOIsothermal_,darkMatterHaloScale_,darkMatterProfileHeatingTidal_) ! Set up the node. basic => node%basic (autoCreate=.true.) satellite => node%satellite(autoCreate=.true.) @@ -155,35 +175,76 @@ program Test_Dark_Matter_Profiles_Heated call basic %timeSet (time ) call basic %timeLastIsolatedSet (time ) call satellite%tidalHeatingNormalizedSet(heatingSpecific) - radiusVirial=darkMatterHaloScale_%radiusVirial(node) + ! Get the associated mass distribution. + massDistributionIsothermal_ => darkMatterProfileDMOIsothermal_%get(node ) + massDistributionHeatingTidal_ => darkMatterProfileHeatingTidal_ %get(node) ! Compute the characteristic radius for heating. + radiusVirial=darkMatterHaloScale_%radiusVirial(node) radiusHeated=sqrt(gravitationalConstantGalacticus*massVirial/2.0d0/heatingSpecific/radiusVirial) ! Iterate over heated profile classes. do profileType=1,2 select case (profileType) case (1) - darkMatterProfileDMO_ => darkMatterProfileDMOHeated_ - profileName = "no shell crossing" - toleranceRelative = 1.0d-6 + darkMatterProfileDMO_ => darkMatterProfileDMOHeated_ + profileName = "no shell crossing" + toleranceRelative = 1.0d-6 + toleranceRelativeVelocityDispersionAssert = 1.0d-2 + select type (massDistributionIsothermal_) + class is (massDistributionSpherical) + allocate(massDistributionSphericalHeated :: massDistributionSphericalHeated_) + allocate(kinematicsDistributionHeated :: kinematicsDistributionHeated_ ) + select type (massDistributionSphericalHeated_) + type is (massDistributionSphericalHeated ) + !![ + + !!] + end select + select type (kinematicsDistributionHeated_) + type is (kinematicsDistributionHeated ) + !![ + + !!] + end select + end select case (2) - darkMatterProfileDMO_ => darkMatterProfileDMOHeatedMonotonic_ - profileName = "monotonic perturbation" - toleranceRelative = 7.0d-2 + darkMatterProfileDMO_ => darkMatterProfileDMOHeatedMonotonic_ + profileName = "monotonic perturbation" + toleranceRelative = 7.0d-2 + toleranceRelativeVelocityDispersionAssert = 1.0d-1 + select type (massDistributionIsothermal_) + class is (massDistributionSpherical) + allocate(massDistributionSphericalHeatedMonotonic :: massDistributionSphericalHeated_) + allocate(kinematicsDistributionCollisionless :: kinematicsDistributionHeated_ ) + select type (massDistributionSphericalHeated_) + type is (massDistributionSphericalHeatedMonotonic) + !![ + + !!] + end select + select type (kinematicsDistributionHeated_) + type is (kinematicsDistributionCollisionless) + !![ + + !!] + end select + end select end select call Unit_Tests_Begin_Group("Heated dark matter profiles ("//trim(profileName)//")") ! Compute initial radius, enclosed mass, and density for a variety of radii and compare to the analytic solutions. do i=1,size(radiusVirialFractional) write (radiusLabel,'(f4.2)') radiusVirialFractional(i) - radius =+ radiusVirial & - & * radiusVirialFractional( i ) - select type (darkMatterProfileDMO_) - class is (darkMatterProfileDMOHeated) - radiusInitial =+darkMatterProfileDMO_%radiusInitial (node,radius) + radius =+radiusVirial & + & *radiusVirialFractional(i) + coordinates = [radius,0.0d0,0.0d0] + select type (massDistributionSphericalHeated_) + class is (massDistributionSphericalHeated) + radiusInitial =+massDistributionSphericalHeated_%radiusInitial (radius ) class default radiusInitial =-huge(0.0d0) end select - massEnclosed =+darkMatterProfileDMO_%enclosedMass (node,radius) - density =+darkMatterProfileDMO_%density (node,radius) + massEnclosed_ =+massDistributionSphericalHeated_%massEnclosedBySphere (radius ) + density_ =+massDistributionSphericalHeated_%density (coordinates ) + velocityDispersion_ =+kinematicsDistributionHeated_ %velocityDispersion1D (coordinates,massDistributionSphericalHeated_) radiusInitialAnalytic=+sqrt( & & + radiusHeated **4 & & /4.0d0 & @@ -193,6 +254,10 @@ program Test_Dark_Matter_Profiles_Heated & - radiusHeated **2 & & /2.0d0 & & / radius + radiusScaled =+radiusInitialAnalytic & + & /radiusHeated + coordinatesHeated = [radiusHeated ,0.0d0,0.0d0] + coordinatesInitial = [radiusInitialAnalytic,0.0d0,0.0d0] massEnclosedAnalytic =+massVirial & & * radiusInitialAnalytic & & / radiusVirial @@ -215,12 +280,28 @@ program Test_Dark_Matter_Profiles_Heated & /2.0d0 & & / radius **2 & & ) + velocityDispersionAnalytic=+sqrt( & + & +gravitationalConstantGalacticus & + & *( & + & +massDistributionIsothermal_%massEnclosedBySphere(radiusHeated )*massDistributionIsothermal_%density(coordinatesHeated )/radiusHeated & + & *(+10.0d0 ) & + & -massDistributionIsothermal_%massEnclosedBySphere(radiusInitialAnalytic)*massDistributionIsothermal_%density(coordinatesInitial)/radiusInitialAnalytic & + & *(- 3.0d0+18.0d0*radiusScaled**4-6.0d0*radiusScaled**6+radiusScaled**8-24.0d0*radiusScaled**2*log(radiusScaled)) & + & ) & + & /6.0d0 & + & /densityAnalytic & + & ) if (profileType == 1) & - & call Assert('r='//trim(radiusLabel)//'rᵥ; initial radius',radiusInitial,radiusInitialAnalytic,relTol=toleranceRelative) - call Assert('r='//trim(radiusLabel)//'rᵥ; enclosed mass' ,massEnclosed ,massEnclosedAnalytic ,relTol=toleranceRelative) - call Assert('r='//trim(radiusLabel)//'rᵥ; density' ,density ,densityAnalytic ,relTol=toleranceRelative) + & call Assert('r='//trim(radiusLabel)//'rᵥ; initial radius' ,radiusInitial ,radiusInitialAnalytic ,relTol=toleranceRelative ) + call Assert('r='//trim(radiusLabel)//'rᵥ; enclosed mass' ,massEnclosed_ ,massEnclosedAnalytic ,relTol=toleranceRelative ) + call Assert('r='//trim(radiusLabel)//'rᵥ; density' ,density_ ,densityAnalytic ,relTol=toleranceRelative ) + call Assert('r='//trim(radiusLabel)//'rᵥ; velocity dispersion',velocityDispersion_,velocityDispersionAnalytic,relTol=toleranceRelativeVelocityDispersionAssert) end do call Unit_Tests_End_Group() + !![ + + + !!] end do call nodeClassHierarchyFinalize() call Unit_Tests_Finish () diff --git a/source/tests.dark_matter_profiles.projected.F90 b/source/tests.dark_matter_profiles.projected.F90 index 682289a919..78085e4360 100644 --- a/source/tests.dark_matter_profiles.projected.F90 +++ b/source/tests.dark_matter_profiles.projected.F90 @@ -31,10 +31,8 @@ program Test_Dark_Matter_Profiles_Projected use :: Cosmology_Parameters , only : cosmologyParametersSimple use :: Dark_Matter_Halo_Scales , only : darkMatterHaloScaleVirialDensityContrastDefinition use :: Virial_Density_Contrast , only : virialDensityContrastSphericalCollapseClsnlssMttrCsmlgclCnstnt - use :: Dark_Matter_Profiles , only : darkMatterProfileDarkMatterOnly use :: Dark_Matter_Profiles_DMO , only : darkMatterProfileDMONFW use :: Node_Property_Extractors , only : nodePropertyExtractorProjectedMass , nodePropertyExtractorProjectedDensity - use :: Galactic_Structure , only : galacticStructureStandard use :: Display , only : displayMessage , displayVerbositySet , verbosityLevelStandard use :: Events_Hooks , only : eventsHooksInitialize use :: Functions_Global_Utilities, only : Functions_Global_Set @@ -51,8 +49,6 @@ program Test_Dark_Matter_Profiles_Projected type (cosmologyParametersSimple ) :: cosmologyParameters_ type (cosmologyFunctionsMatterLambda ) :: cosmologyFunctions_ type (virialDensityContrastSphericalCollapseClsnlssMttrCsmlgclCnstnt) :: virialDensityContrast_ - type (darkMatterProfileDarkMatterOnly ) :: darkMatterProfile_ - type (galacticStructureStandard ) :: galacticStructure_ type (darkMatterProfileDMONFW ) :: darkMatterProfileDMO_ type (nodePropertyExtractorProjectedMass ) :: nodePropertyExtractorProjectedMass_ type (nodePropertyExtractorProjectedDensity ) :: nodePropertyExtractorProjectedDensity_ @@ -68,7 +64,7 @@ program Test_Dark_Matter_Profiles_Projected type (multiCounter ) :: instance call displayVerbositySet(verbosityLevelStandard) - call Unit_Tests_Begin_Group("Tidal track dark matter profiles") + call Unit_Tests_Begin_Group("Projected dark matter profiles") parameters=inputParameters(var_str('testSuite/parameters/darkMatterProfilesProjected.xml')) call eventsHooksInitialize() call Functions_Global_Set ( ) @@ -134,32 +130,13 @@ program Test_Dark_Matter_Profiles_Projected & darkMatterHaloScale_ =darkMatterHaloScale_ & & ) - - - - darkMatterProfileDarkMatterOnly ( & - & cosmologyParameters_ =cosmologyParameters_ , & - & darkMatterHaloScale_ =darkMatterHaloScale_ , & - & darkMatterProfileDMO_ =darkMatterProfileDMO_ & - & ) - - - - galacticStructureStandard ( & - & cosmologyFunctions_ =cosmologyFunctions_ , & - & darkMatterHaloScale_ =darkMatterHaloScale_ , & - & darkMatterProfile_ =darkMatterProfile_ & - & ) - - - + nodePropertyExtractorProjectedMass ( & & radiusSpecifiers =radiusSpecifiers , & & includeRadii =.false. , & - & darkMatterHaloScale_ =darkMatterHaloScale_ , & - & galacticStructure_ =galacticStructure_ & + & darkMatterHaloScale_ =darkMatterHaloScale_ & & ) @@ -168,14 +145,12 @@ program Test_Dark_Matter_Profiles_Projected nodePropertyExtractorProjectedDensity( & & radiusSpecifiers =radiusSpecifiers , & & includeRadii =.false. , & - & darkMatterHaloScale_ =darkMatterHaloScale_ , & - & galacticStructure_ =galacticStructure_ & + & darkMatterHaloScale_ =darkMatterHaloScale_ & & ) !!] ! Begin tests. - call Unit_Tests_Begin_Group("Projected density" ) instance =multiCounter([1_c_size_t]) densityProjected=nodePropertyExtractorProjectedDensity_%extract(node__,basic__%time(),instance) massProjected =nodePropertyExtractorProjectedMass_ %extract(node__,basic__%time(),instance) diff --git a/source/tests.dark_matter_profiles.tidal_tracks.F90 b/source/tests.dark_matter_profiles.tidal_tracks.F90 index 3e01512a99..60d3db66e4 100644 --- a/source/tests.dark_matter_profiles.tidal_tracks.F90 +++ b/source/tests.dark_matter_profiles.tidal_tracks.F90 @@ -26,6 +26,7 @@ program Test_Dark_Matter_Profiles_Tidal_Tracks Test calculations for tidal track dark matter profiles. !!} use :: Calculations_Resets , only : Calculations_Reset + use :: Coordinates , only : coordinateSpherical , assignment(=) use :: Cosmology_Functions , only : cosmologyFunctionsMatterLambda use :: Cosmology_Parameters , only : cosmologyParametersSimple use :: Dark_Matter_Halo_Scales , only : darkMatterHaloScaleVirialDensityContrastDefinition @@ -38,6 +39,7 @@ program Test_Dark_Matter_Profiles_Tidal_Tracks & treeNode , nodeComponentSatellite use :: Input_Parameters , only : inputParameters use :: Node_Components , only : Node_Components_Initialize , Node_Components_Thread_Initialize, Node_Components_Thread_Uninitialize, Node_Components_Uninitialize + use :: Mass_Distributions , only : massDistributionClass use :: Unit_Tests , only : Assert , Skip , Unit_Tests_Begin_Group , Unit_Tests_End_Group , & & Unit_Tests_Finish implicit none @@ -51,14 +53,16 @@ program Test_Dark_Matter_Profiles_Tidal_Tracks class (nodeComponentBasic ), pointer :: basic_ class (nodeComponentDarkMatterProfile ), pointer :: darkMatterProfile_ class (nodeComponentSatellite ), pointer :: satellite_ - double precision , parameter :: concentration =+8.0d0, massVirial =+1.0d12, & - & muRadius =-0.3d0, etaRadius =+0.4d-0, & - & muVelocity =+0.4d0, etaVelocity =+0.3d-0 + class (massDistributionClass ), pointer :: massDistributionNFW_ , massDistributionPenarrubia2010_ + double precision , parameter :: concentration =+8.0d0, massVirial =+1.0d12, & + & muRadius =-0.3d0, etaRadius =+0.4d-0, & + & muVelocity =+0.4d0, etaVelocity =+0.3d-0 type (inputParameters ) :: parameters - double precision :: radiusScale , radiusVirial , & - & velocityMaximumInitial , radiusMaximumInitial , & - & velocityMaximumTidalTrack , radiusMaximumTidalTrack , & + double precision :: radiusScale , radiusVirial , & + & velocityMaximumInitial , radiusMaximumInitial , & + & velocityMaximumTidalTrack , radiusMaximumTidalTrack , & & fractionMassBound + type (coordinateSpherical ) :: coordinatesScale , coordinatesVirial call displayVerbositySet(verbosityLevelStandard) call Unit_Tests_Begin_Group("Tidal track dark matter profiles") @@ -139,36 +143,56 @@ program Test_Dark_Matter_Profiles_Tidal_Tracks radiusVirial =+darkMatterHaloScale_%radiusVirial(node_) radiusScale =+radiusVirial & & /concentration + coordinatesScale =[radiusScale ,0.0d0,0.0d0] + coordinatesVirial=[radiusVirial,0.0d0,0.0d0] call darkMatterProfile_%scaleSet(radiusScale) call Calculations_Reset(node_) ! Store the initial values of rmax and Vmax. - radiusMaximumInitial =darkMatterProfileNFW_%radiusCircularVelocityMaximum(node_) - velocityMaximumInitial=darkMatterProfileNFW_% circularVelocityMaximum(node_) + massDistributionNFW_ => darkMatterProfileNFW_ %get (node_) + massDistributionPenarrubia2010_ => darkMatterProfilePenarrubia2010_%get (node_) + radiusMaximumInitial = massDistributionNFW_ % radiusRotationCurveMaximum( ) + velocityMaximumInitial = massDistributionNFW_ %velocityRotationCurveMaximum( ) ! Begin tests. call Unit_Tests_Begin_Group("Unstripped profile matches NFW" ) - call Assert("Density at scale radius" ,darkMatterProfileNFW_%density(node_,radius=radiusVirial),darkMatterProfilePenarrubia2010_%density(node_,radius=radiusVirial),relTol=1.0d-6) - call Assert("Density at virial radius",darkMatterProfileNFW_%density(node_,radius=radiusVirial),darkMatterProfilePenarrubia2010_%density(node_,radius=radiusVirial),relTol=1.0d-6) + call Assert("Density at scale radius" ,massDistributionNFW_%density(coordinates=coordinatesScale ),massDistributionPenarrubia2010_%density(coordinates=coordinatesScale ),relTol=1.0d-6) + call Assert("Density at virial radius",massDistributionNFW_%density(coordinates=coordinatesVirial),massDistributionPenarrubia2010_%density(coordinates=coordinatesVirial),relTol=1.0d-6) call Unit_Tests_End_Group ( ) + !![ + + + !!] call Unit_Tests_Begin_Group("Stripped profile (95%) tidal track") call satellite_%boundMassSet(0.95d0*massVirial) call Calculations_Reset(node_) + massDistributionNFW_ => darkMatterProfileNFW_ %get (node_) + massDistributionPenarrubia2010_ => darkMatterProfilePenarrubia2010_%get (node_) fractionMassBound =+satellite_%boundMass() & & /basic_ % mass() velocityMaximumTidalTrack=velocityMaximumInitial*2.0d0**muVelocity*fractionMassBound**etaVelocity/(1.0d0+fractionMassBound)**muVelocity radiusMaximumTidalTrack = radiusMaximumInitial*2.0d0**muRadius *fractionMassBound**etaRadius /(1.0d0+fractionMassBound)**muRadius - call Assert("Tidal track rmax",radiusMaximumTidalTrack ,darkMatterProfilePenarrubia2010_%radiusCircularVelocityMaximum(node_),relTol=1.0d-6) - call Assert("Tidal track Vmax",velocityMaximumTidalTrack,darkMatterProfilePenarrubia2010_% circularVelocityMaximum(node_),relTol=1.0d-6) + call Assert("Tidal track rmax",radiusMaximumTidalTrack ,massDistributionPenarrubia2010_% radiusRotationCurveMaximum(),relTol=1.0d-6) + call Assert("Tidal track Vmax",velocityMaximumTidalTrack,massDistributionPenarrubia2010_%velocityRotationCurveMaximum(),relTol=1.0d-6) call Unit_Tests_End_Group () + !![ + + + !!] call Unit_Tests_Begin_Group("Stripped profile (30%) tidal track") call satellite_%boundMassSet(0.30d0*massVirial) call Calculations_Reset(node_) + massDistributionNFW_ => darkMatterProfileNFW_ %get (node_) + massDistributionPenarrubia2010_ => darkMatterProfilePenarrubia2010_%get (node_) fractionMassBound =+satellite_%boundMass() & & /basic_ % mass() velocityMaximumTidalTrack=velocityMaximumInitial*2.0d0**muVelocity*fractionMassBound**etaVelocity/(1.0d0+fractionMassBound)**muVelocity radiusMaximumTidalTrack = radiusMaximumInitial*2.0d0**muRadius *fractionMassBound**etaRadius /(1.0d0+fractionMassBound)**muRadius - call Assert("Tidal track rmax",radiusMaximumTidalTrack ,darkMatterProfilePenarrubia2010_%radiusCircularVelocityMaximum(node_),relTol=1.0d-6) - call Assert("Tidal track Vmax",velocityMaximumTidalTrack,darkMatterProfilePenarrubia2010_% circularVelocityMaximum(node_),relTol=1.0d-6) + call Assert("Tidal track rmax",radiusMaximumTidalTrack ,massDistributionPenarrubia2010_% radiusRotationCurveMaximum(),relTol=1.0d-6) + call Assert("Tidal track Vmax",velocityMaximumTidalTrack,massDistributionPenarrubia2010_%velocityRotationCurveMaximum(),relTol=1.0d-6) call Unit_Tests_End_Group () + !![ + + + !!] call Unit_Tests_End_Group () call Unit_Tests_Finish () call Node_Components_Thread_Uninitialize() diff --git a/source/tests.kepler_orbits.F90 b/source/tests.kepler_orbits.F90 index 176e52541c..b7e9f5a420 100644 --- a/source/tests.kepler_orbits.F90 +++ b/source/tests.kepler_orbits.F90 @@ -22,28 +22,20 @@ program Tests_Kepler_Orbits Tests for orbital parameter conversions. !!} use :: Display , only : displayVerbositySet , verbosityLevelStandard - use :: ISO_Varying_String , only : assignment(=) , varying_string - use :: Input_Parameters , only : inputParameters use :: Kepler_Orbits , only : keplerOrbit use :: Numerical_Constants_Astronomical, only : gravitationalConstantGalacticus use :: Numerical_Constants_Math , only : Pi use :: Unit_Tests , only : Assert , Unit_Tests_Begin_Group, Unit_Tests_End_Group, Unit_Tests_Finish, & & compareEquals implicit none - type (varying_string ) :: parameterFile - type (keplerOrbit ) :: orbit - double precision :: valueActual , valueExpected, velocityScale - type (inputParameters) :: parameters + type (keplerOrbit) :: orbit + double precision :: valueActual, valueExpected, velocityScale ! Set verbosity level. call displayVerbositySet(verbosityLevelStandard) ! Begin unit tests. call Unit_Tests_Begin_Group("Orbital parameter conversions") - ! Open the parameter file. - parameterFile='parameters.xml' - parameters=inputParameters(parameterFile) - ! Compute velocity scale for unit mass and radius. velocityScale=sqrt(gravitationalConstantGalacticus) diff --git a/source/tests.mass_distributions.F90 b/source/tests.mass_distributions.F90 index 31ace3f5cf..dca5074cbd 100644 --- a/source/tests.mass_distributions.F90 +++ b/source/tests.mass_distributions.F90 @@ -32,16 +32,20 @@ program Test_Mass_Distributions use :: Events_Hooks , only : eventsHooksInitialize use :: Galactic_Structure_Options, only : componentTypeSpheroid , componentTypeDisk use :: Linear_Algebra , only : assignment(=) , vector - use :: Mass_Distributions , only : massDistributionBetaProfile , massDistributionClass , massDistributionExponentialDisk , massDistributionGaussianEllipsoid, & - & massDistributionHernquist , massDistributionSersic , massDistributionSpherical , massDistributionComposite , & - & massDistributionList , massDistributionSymmetryCylindrical, enumerationMassDistributionSymmetryType, massDistributionSphericalScaler , & - & massDistributionCylindricalScaler, massDistributionCylindrical + use :: Mass_Distributions , only : massDistributionBetaProfile , massDistributionClass , massDistributionExponentialDisk , massDistributionGaussianEllipsoid , & + & massDistributionHernquist , massDistributionSersic , massDistributionSpherical , massDistributionComposite , & + & massDistributionList , massDistributionSymmetryCylindrical, enumerationMassDistributionSymmetryType, massDistributionSphericalScaler , & + & massDistributionCylindricalScaler, massDistributionCylindrical , massDistributionPatejLoeb2015 , massDistributionNFW , & + & massDistributionIsothermal , kinematicsDistributionClass , kinematicsDistributionLocal use :: Numerical_Constants_Math , only : Pi use :: Tensors , only : assignment(=) use :: Unit_Tests , only : Assert , Unit_Tests_Begin_Group , Unit_Tests_End_Group , Unit_Tests_Finish implicit none - class (massDistributionClass ) , allocatable :: massDistribution_ , massDistributionRotated , & - & massDistributionDisk , massDistributionSpheroid + class (massDistributionClass ) , allocatable :: massDistribution_ , massDistributionRotated , & + & massDistributionDisk , massDistributionSpheroid , & + & massDistributionDMO + class (massDistributionClass ) , pointer :: massDistributionDisk_ , massDistributionSpheroid_ + class (kinematicsDistributionClass ) , allocatable :: kinematicsDistribution_ type (massDistributionList ) , pointer :: massDistributions integer , parameter :: sersicTableCount =8 double precision , dimension(sersicTableCount) :: sersicTableRadius =[1.0000d-06,1.0000d-5,1.0000d-4,1.0000d-3,1.0000d-2,1.0000d-1,1.0000d+0,1.0000d+1] @@ -51,19 +55,34 @@ program Test_Mass_Distributions double precision , dimension(sersicTableCount) :: sersicTableDensityTarget =[2.5553d+06,3.5797d+5,4.2189d+4,3.7044d+3,1.9679d+2,4.4047d+0,2.1943d-2,7.8166d-6] ! Potential targets for Sersic profile from Young (1976). double precision , dimension(sersicTableCount) :: sersicTablePotentialTarget =[1.0000d+00,9.9993d-1,9.9908d-1,9.9027d-1,9.2671d-1,6.7129d-1,2.4945d-1,3.7383d-2] - double precision , dimension(sersicTableCount) :: sersicTableDensity , sersicTableMass , & + double precision , dimension(sersicTableCount) :: sersicTableDensity , sersicTableMass , & & sersicTablePotential - type (coordinateSpherical ) :: position , positionZero + type (coordinateSpherical ) :: position , positionZero , & + & positionReference type (coordinateCartesian ) :: positionCartesian type (enumerationMassDistributionSymmetryType) :: symmetry_ integer :: i - double precision :: radiusInProjection , radius , & - & rotationCurveGradientAnalytic , rotationCurveGradientNumerical, & - & massFraction + double precision :: radiusInProjection , radius , & + & rotationCurveGradientAnalytic , rotationCurveGradientNumerical , & + & massFraction , time double precision , parameter :: epsilonFiniteDifference =0.01d0 character (len=4 ) :: label double precision , dimension(3,3) :: tidalTensorComponents , tidalTensorSphericalComponents double precision , dimension(3 ) :: acceleration + double precision , dimension(4 ) :: massPatejLoeb , densityPatejLoeb , & + & densitySlopePatejLoeb , densityMomentPatejLoeb , & + & potentialPatejLoeb + double precision , dimension(4 ) :: massIsothermal , densityIsothermal , & + & densitySlopeIsothermal , densityMomentIsothermal , & + & potentialIsothermal , fourierTransformIsothermal , & + & radiusFreeFallIsothermal , radiusFreeFallGrowthRateIsothermal , & + & massIsothermalNumerical , potentialIsothermalDifferenceNumerical , & + & fourierTransformIsothermalNumerical , radiusFreeFallIsothermalNumerical , & + & radiusFreeFallGrowthRateIsothermalNumerical , densitySlopeIsothermalNumerical , & + & radiusEnclosingMassIsothermal , radiusEnclosingMassIsothermalNumerical , & + & radiusEnclosingDensityIsothermal , radiusEnclosingDensityIsothermalNumerical , & + & radiiIsothermal , radiusFromSpecificAngularMomentumIsothermal, & + & radiusFromSpecificAngularMomentumIsothermalNumerical , velocityCircularIsothermal type (vector ), dimension(: ) , allocatable :: axes ! Set verbosity level. @@ -184,6 +203,19 @@ program Test_Mass_Distributions & +1.793209546954886d0 , & & absTol=1.0d-6 & & ) + call Assert( & + & "Density gradient (logarithmic)" , & + & +massDistribution_%densityGradientRadial(position,.true. ) , & + & -1.000000000000000d0 , & + & absTol=1.0d-6 & + & ) + call Assert( & + & "Density gradient (non-logarithmic)" , & + & +massDistribution_%densityGradientRadial(position,.false. ) , & + & +massDistribution_%densityGradientRadial(position,.true. ) & + & *massDistribution_%density (position ) , & + & absTol=1.0d-6 & + & ) end select call Unit_Tests_End_Group() deallocate(massDistribution_) @@ -455,13 +487,19 @@ program Test_Mass_Distributions massDistribution_=massDistributionComposite(massDistributions) end select symmetry_=massDistribution_%symmetry() - call Assert("Maximal symmetry [cylindrical]" ,symmetry_ %ID ,massDistributionSymmetryCylindrical%ID ) - positionCartesian=[1.0d-3,0.0d0,0.0d0] - call Assert("Density at (x,y,z)=(1,0,0) kpc" ,massDistribution_%density(positionCartesian ),1.47756308872d18 ,relTol=1.0d-6) - call Assert("Spheroid density at (x,y,z)=(1,0,0) kpc",massDistribution_%density(positionCartesian,componentType=componentTypeSpheroid),2.04086330446d17 ,relTol=1.0d-6) - call Assert("Disk density at (x,y,z)=(1,0,0) kpc" ,massDistribution_%density(positionCartesian,componentType=componentTypeDisk ),1.27347675828d18 ,relTol=1.0d-6) + call Assert("Maximal symmetry [cylindrical]" ,symmetry_ %ID ,massDistributionSymmetryCylindrical%ID ) + massDistributionDisk_ => massDistribution_%subset(componentType=componentTypeDisk ) + massDistributionSpheroid_ => massDistribution_%subset(componentType=componentTypeSpheroid) + positionCartesian = [1.0d-3,0.0d0,0.0d0] + call Assert("Density at (x,y,z)=(1,0,0) kpc" ,massDistribution_ %density(positionCartesian),1.47756308872d18 ,relTol=1.0d-6) + call Assert("Spheroid density at (x,y,z)=(1,0,0) kpc",massDistributionSpheroid_%density(positionCartesian),2.04086330446d17 ,relTol=1.0d-6) + call Assert("Disk density at (x,y,z)=(1,0,0) kpc" ,massDistributionDisk_ %density(positionCartesian),1.27347675828d18 ,relTol=1.0d-6) nullify (massDistributions) deallocate(massDistribution_) + !![ + + + !!] call Unit_Tests_End_Group() ! Composite profile with scaled components. @@ -506,6 +544,230 @@ program Test_Mass_Distributions nullify (massDistributions) deallocate(massDistribution_) call Unit_Tests_End_Group() + + ! Patej & Loeb (2015) profile. + call Unit_Tests_Begin_Group("Patej-Loeb (2015) profile") + allocate(massDistributionNFW :: massDistributionDMO) + select type (massDistributionDMO) + type is (massDistributionNFW) + massDistributionDMO=massDistributionNFW(scaleLength=30.0d-3,virialRadius=300.0d-3,mass=1.0d12,dimensionless=.false.) + end select + allocate(massDistributionPatejLoeb2015 :: massDistribution_) + select type (massDistribution_) + type is (massDistributionPatejLoeb2015) + massDistribution_=massDistributionPatejLoeb2015(gamma=1.15d0,massDistribution_=massDistributionDMO,mass=1.0d11,radiusOuter=450.0d-3,radiusShock=450.0d-3) + end select + do i=1,4 + radius =450.0d-3/2.0d0**(4-i) + position =[radius,0.0d0,0.0d0] + massPatejLoeb (i)=massDistribution_%massEnclosedBySphere (radius ) + densityPatejLoeb (i)=massDistribution_%density (position ) + densitySlopePatejLoeb (i)=massDistribution_%densityGradientRadial(position,logarithmic=.true.) + potentialPatejLoeb (i)=massDistribution_%potential (position ) + densityMomentPatejLoeb(i)=massDistribution_%densityRadialMoment (-dble(i-1),450.0d-3/8.0d0,450.0d-3/1.0d0) + end do + call Assert( & + & "M(r) at r=[⅛,¼,½,1]rₛ" , & + & massPatejLoeb , & + & [+1.555565950166512d10,+3.514143268178815d10,+6.418101338664305d10,+1.0000000000000000d11], & + & relTol=1.0d-6 & + & ) + call Assert( & + & "ρ(r) at r=[⅛,¼,½,1]rₛ" , & + & densityPatejLoeb , & + & [+9.377714853155970d12,+1.985078164671267d12,+3.3223318757716390d11,+4.809898607847662d10], & + & relTol=1.0d-6 & + & ) + call Assert( & + & "α(r) at r=[⅛,¼,½,1]rₛ" , & + & densitySlopePatejLoeb , & + & [-2.030591309692208d00,-2.431529802045667d00,-2.7035845062827400d00,-2.856250000000000d00], & + & relTol=1.0d-6 & + & ) + call Assert( & + & "φ(r) at r=[⅛,¼,½,1]rₛ" , & + & potentialPatejLoeb , & + & [-4.079034939868978d03,-3.184928656647174d03,-2.280711782065997d03,-1.5198632412001940d03], & + & relTol=1.0d-3 & + & ) + call Assert( & + & "ℛᵢ(⅛rₛ,rₛ)" , & + & densityMomentPatejLoeb , & + & [+3.765562121061061d11,+4.129007833522932d12,+5.196937885662541d13,+7.1052241045332330d14], & + & relTol=1.0d-6 & + & ) + deallocate(massDistribution_) + call Unit_Tests_End_Group() + + ! Isothermal profile. + call Unit_Tests_Begin_Group("Isothermal profile") + allocate(massDistributionIsothermal :: massDistribution_ ) + allocate(kinematicsDistributionLocal :: kinematicsDistribution_) + select type (kinematicsDistribution_) + type is (kinematicsDistributionLocal) + kinematicsDistribution_=kinematicsDistributionLocal(alpha=1.0d0/sqrt(2.0d0)) + end select + select type (massDistribution_) + type is (massDistributionIsothermal) + massDistribution_=massDistributionIsothermal(mass=1.0d12,lengthReference=300.0d-3) + call massDistribution_%setKinematicsDistribution(kinematicsDistribution_) + do i=1,4 + radius =300.0d-3/2.0d0**(4-i) + position =[radius,0.0d0,0.0d0] + positionReference =[300.0d-3,0.0d0,0.0d0] + time = 2.0d0**(i-1) + radiiIsothermal (i)=radius + massIsothermal (i)=massDistribution_%massEnclosedBySphere (radius ) + massIsothermalNumerical (i)=massDistribution_%massEnclosedBySphereNumerical (radius ) + densityIsothermal (i)=massDistribution_%density (position ) + velocityCircularIsothermal (i)=massDistribution_%rotationCurve (radius ) + radiusEnclosingMassIsothermal (i)=massDistribution_%radiusEnclosingMass ( massIsothermal(i) ) + radiusEnclosingMassIsothermalNumerical (i)=massDistribution_%radiusEnclosingMassNumerical ( massIsothermal(i) ) + radiusEnclosingDensityIsothermal (i)=massDistribution_%radiusEnclosingDensity (3.0d0*massIsothermal(i)/4.0d0/Pi/radius**3) + radiusEnclosingDensityIsothermalNumerical (i)=massDistribution_%radiusEnclosingDensityNumerical (3.0d0*massIsothermal(i)/4.0d0/Pi/radius**3) + radiusFromSpecificAngularMomentumIsothermal (i)=massDistribution_%radiusFromSpecificAngularMomentum (velocityCircularIsothermal(i)*radius ) + radiusFromSpecificAngularMomentumIsothermalNumerical(i)=massDistribution_%radiusFromSpecificAngularMomentumNumerical(velocityCircularIsothermal(i)*radius ) + densitySlopeIsothermal (i)=massDistribution_%densityGradientRadial (position,logarithmic=.true. ) + densitySlopeIsothermalNumerical (i)=massDistribution_%densityGradientRadialNumerical (position,logarithmic=.true. ) + potentialIsothermal (i)=massDistribution_%potential (position ) + potentialIsothermalDifferenceNumerical (i)=massDistribution_%potentialDifferenceNumerical (position,positionReference ) + densityMomentIsothermal (i)=massDistribution_%densityRadialMoment (-dble(i-1),300.0d-3/8.0d0,300.0d-3/1.0d0 ) + fourierTransformIsothermal (i)=massDistribution_%fourierTransform (300.0d-3,1.0d0/radius ) + fourierTransformIsothermalNumerical (i)=massDistribution_%fourierTransformNumerical (300.0d-3,1.0d0/radius ) + radiusFreefallIsothermal (i)=massDistribution_%radiusFreefall (time ) + radiusFreefallIsothermalNumerical (i)=massDistribution_%radiusFreefallNumerical (time ) + radiusFreefallGrowthRateIsothermal (i)=massDistribution_%radiusFreefallIncreaseRate (time ) + radiusFreefallGrowthRateIsothermalNumerical (i)=massDistribution_%radiusFreefallIncreaseRateNumerical (time ) + end do + end select + call Assert( & + & "M(r) at r=[⅛,¼,½,1]rₗ" , & + & massIsothermal , & + & [+1.250000000000000d+11,+2.50000000000000d+11,+5.00000000000000d+11,+1.000000000000000d+12], & + & relTol=1.0d-6 & + & ) + call Assert( & + & "M(r) at r=[⅛,¼,½,1]rₗ numerical" , & + & massIsothermal , & + & massIsothermalNumerical , & + & relTol=1.0d-6 & + & ) + call Assert( & + & "ρ(r) at r=[⅛,¼,½,1]rₗ" , & + & densityIsothermal , & + & [+1.886280807015056d+14,+4.71570201753764d+13,+1.17892550438441d+13,+2.947313760961025d+12], & + & relTol=1.0d-6 & + & ) + call Assert( & + & "V(r) at r=[⅛,¼,½,1]rₗ" , & + & velocityCircularIsothermal , & + & [+1.19735820315671d+02,+1.19735820315671d+02,+1.197358203156711d+02,+1.197358203156711d+02], & + & relTol=1.0d-4 & + & ) + call Assert( & + & "r(M) at r=[⅛,¼,½,1]rₗ" , & + & radiusEnclosingMassIsothermal , & + & radiiIsothermal , & + & relTol=1.0d-6 & + & ) + call Assert( & + & "r(M) at r=[⅛,¼,½,1]rₗ numerical" , & + & radiusEnclosingMassIsothermalNumerical , & + & radiiIsothermal , & + & relTol=1.0d-6 & + & ) + call Assert( & + & "r(ρ) at r=[⅛,¼,½,1]rₗ" , & + & radiusEnclosingDensityIsothermal , & + & radiiIsothermal , & + & relTol=1.0d-6 & + & ) + call Assert( & + & "r(ρ) at r=[⅛,¼,½,1]rₗ numerical" , & + & radiusEnclosingDensityIsothermalNumerical , & + & radiiIsothermal , & + & relTol=1.0d-6 & + & ) + call Assert( & + & "r(j) at r=[⅛,¼,½,1]rₗ" , & + & radiusFromSpecificAngularMomentumIsothermal , & + & radiiIsothermal , & + & relTol=1.0d-6 & + & ) + call Assert( & + & "r(j) at r=[⅛,¼,½,1]rₗ numerical" , & + & radiusFromSpecificAngularMomentumIsothermalNumerical , & + & radiiIsothermal , & + & relTol=1.0d-6 & + & ) + call Assert( & + & "α(r) at r=[⅛,¼,½,1]rₗ" , & + & densitySlopeIsothermal , & + & [-2.000000000000000d+00,-2.00000000000000d+00,-2.00000000000000d+00,-2.000000000000000d+00], & + & relTol=1.0d-6 & + & ) + call Assert( & + & "α(r) at r=[⅛,¼,½,1]rₗ numerical" , & + & densitySlopeIsothermal , & + & densitySlopeIsothermalNumerical , & + & relTol=1.0d-6 & + & ) + call Assert( & + & "φ(r) at r=[⅛,¼,½,1]rₗ" , & + & potentialIsothermal , & + & [-2.981226023588325d+04,-1.98748401572555d+04,-9.93742007862775d+03,+0.000000000000000d+00], & + & relTol=1.0d-3 & + & ) + call Assert( & + & "φ(r)-φ(rₗ) at r=[⅛,¼,½,1]rₗ numerical" , & + & potentialIsothermal -potentialIsothermal(4) , & + & potentialIsothermalDifferenceNumerical , & + & relTol=1.0d-3 & + & ) + call Assert( & + & "ℛᵢ(⅛rₗ,rₗ)" , & + & densityMomentIsothermal , & + & [+6.189358898018186d+12,+9.28403834702773d+13,+1.67341925761232d+15,+3.352569403093177d+16], & + & relTol=1.0d-6 & + & ) + call Assert( & + & "ℱ(rₗ,k) at k=[8,4,2,1]/rₗ" , & + & fourierTransformIsothermal , & + & fourierTransformIsothermalNumerical , & + & relTol=1.0d-6 & + & ) + call Assert( & + & "ℱ(rₗ,k) at k=[8,4,2,1]/rₗ numerical" , & + & fourierTransformIsothermalNumerical , & + & [+1.967733527133679d-01,+4.395507847372633d-01,+8.02706488401347d-01,+9.46083070367183d-01], & + & relTol=1.0d-6 & + & ) + call Assert( & + & "rᵩ(t) at t=[1,2,4,8]Gyr" , & + & radiusFreefallIsothermal , & + & [+9.769496930104140d-02,+1.953899386020827d-01,+3.907798772041654d-01,+7.8155975440833d-01], & + & relTol=1.0d-3 & + & ) + call Assert( & + & "rᵩ(t) at t=[1,2,4,8]Gyr numerical" , & + & radiusFreefallIsothermal , & + & radiusFreefallIsothermalNumerical , & + & relTol=1.0d-3 & + & ) + call Assert( & + & "drᵩ/dt(t) at t=[1,2,4,8]Gyr numerical" , & + & radiusFreefallGrowthRateIsothermal , & + & radiusFreefallGrowthRateIsothermalNumerical , & + & relTol=1.0d-3 & + & ) + call Assert( & + & "E" , & + & massDistribution_%energy(300.0d-3,massDistribution_) , & + & -3.58417d15 , & + & relTol=1.0d-3 & + & ) + call Unit_Tests_End_Group() + ! End unit tests. call Unit_Tests_End_Group() call Unit_Tests_Finish() diff --git a/source/tests.math_special_functions.F90 b/source/tests.math_special_functions.F90 index c50791d322..48519044b8 100644 --- a/source/tests.math_special_functions.F90 +++ b/source/tests.math_special_functions.F90 @@ -29,6 +29,7 @@ program Test_Math_Special_Functions & Bessel_Function_J1 , Bessel_Function_J1_Zero , Bessel_Function_Jn , Bessel_Function_Jn_Zero , & & Bessel_Function_K0 , Bessel_Function_K1 , Bessel_Function_In use :: Binomial_Coefficients , only : Binomial_Coefficient + use :: Dilogarithms , only : Dilogarithm use :: Display , only : displayVerbositySet , verbosityLevelStandard use :: Error_Functions , only : Error_Function use :: Exponential_Integrals , only : Cosine_Integral , Sine_Integral @@ -37,6 +38,7 @@ program Test_Math_Special_Functions & Inverse_Gamma_Function_Incomplete, Inverse_Gamma_Function_Incomplete_Complementary use :: Hypergeometric_Functions, only : Hypergeometric_1F1 , Hypergeometric_2F1 , Hypergeometric_pFq , Hypergeometric_pFq_Regularized use :: Polylogarithms , only : Polylogarithm_2 , Polylogarithm_3 + use :: Numerical_Constants_Math, only : Pi use :: Unit_Tests , only : Assert , Unit_Tests_Begin_Group , Unit_Tests_End_Group , Unit_Tests_Finish implicit none double precision, dimension(10) :: argument =[1.0d0,2.0d0,3.0d0,4.0d0,5.0d0,6.0d0,7.0d0,8.0d0,9.0d0,10.0d0] @@ -51,6 +53,7 @@ program Test_Math_Special_Functions & factorials , gammaFunction , & & hypergeometric1F1 , hypergeometric2F1 , & & incompleteComplementaryGammaFunction , incompleteGammaFunction , & + & incompleteComplementaryGammaFunction2 , incompleteGammaFunction2 , & & inverseGammaFunctionIncomplete , inverseGammaFunctionIncompleteComplementary, & & logGammaFunction , sineIntegral , & & hypergeometric1F2 , hypergeometric2F1approx , & @@ -58,7 +61,7 @@ program Test_Math_Special_Functions & hypergeometric3F2NegativeArgument , hypergeometric3F2Accelerated , & & polylogarithm2 , polylogarithm3 , & & hypergeometric1F2Regularized , BesselI2 , & - & BesselIHalf + & BesselIHalf , dilogarithm_ double complex , dimension(17) :: errorFunctionComplex integer :: i @@ -88,8 +91,10 @@ program Test_Math_Special_Functions doubleFactorial (i)=Logarithmic_Double_Factorial ( i ) gammaFunction (i)=Gamma_Function ( argument(i) ) logGammaFunction (i)=Gamma_Function_Logarithmic ( argument(i) ) - incompleteGammaFunction (i)=Gamma_Function_Incomplete ( argument(i),2.0d0 ) - incompleteComplementaryGammaFunction (i)=Gamma_Function_Incomplete_Complementary ( argument(i),2.0d0 ) + incompleteGammaFunction (i)=Gamma_Function_Incomplete ( argument(i), 2.0d0 ) + incompleteComplementaryGammaFunction (i)=Gamma_Function_Incomplete_Complementary ( argument(i), 2.0d0 ) + incompleteGammaFunction2 (i)=Gamma_Function_Incomplete ( argument(i),16.67d0 ) + incompleteComplementaryGammaFunction2 (i)=Gamma_Function_Incomplete_Complementary ( argument(i),16.67d0 ) inverseGammaFunctionIncomplete (i)=Inverse_Gamma_Function_Incomplete ( argument(i),P(i) ) inverseGammaFunctionIncompleteComplementary(i)=Inverse_Gamma_Function_Incomplete_Complementary( argument(i),Q(i) ) hypergeometric1F1 (i)=Hypergeometric_1F1 ([1.0d0 ],[2.0d0 ], argument(i) ) @@ -103,6 +108,7 @@ program Test_Math_Special_Functions hypergeometric1F2Regularized (i)=Hypergeometric_pFq_Regularized ([1.5d0 ],[1.5d0,0.5d0], argument(i) ) polylogarithm2 (i)=Polylogarithm_2 ( -1.0d0/ argument(i) ) polylogarithm3 (i)=Polylogarithm_3 ( -1.0d0/ argument(i) ) + dilogarithm_ (i)=Dilogarithm ( -1.0d0/ argument(i) ) end do ! Test Bessel function results. @@ -431,6 +437,39 @@ program Test_Math_Special_Functions & ], & & relTol=1.0d-6 & & ) + call Assert("incomplete gamma function, Γ(x,16.67)" , & + & incompleteGammaFunction2, & + & [ & + & 5.758521420655205d-8, & + & 1.017530735029775d-6, & + & 9.018676651091340d-6, & + & 5.347837745800675d-5, & + & 2.387641805708268d-4, & + & 8.565070481489690d-4, & + & 2.572802648570240d-3, & + & 6.660038028430611d-3, & + & 1.517681475121466d-2, & + & 3.095177785886023d-2 & + & ], & + & relTol=1.0d-6 & + & ) + call Assert("complementary gamma function, 1-Γ(x,16.67)", & + & incompleteComplementaryGammaFunction2 , & + & [ & + & 9.99999942414786d-1, & + & 9.99998982469265d-1, & + & 9.99990981323349d-1, & + & 9.99946521622542d-1, & + & 9.99761235819429d-1, & + & 9.99143492951851d-1, & + & 9.97427197351430d-1, & + & 9.93339961971569d-1, & + & 9.84823185248785d-1, & + & 9.69048222141140d-1 & + & ], & + & & + & relTol=1.0d-6 & + & ) call Assert("inverse incomplete gamma function, Γ⁻¹(x,2)", & & inverseGammaFunctionIncomplete, & & [ & @@ -626,7 +665,7 @@ program Test_Math_Special_Functions & ], & & relTol=1.0d-6 & & ) - + ! Test polylogarithm functions. call Assert("polylogarithm, Li₂(x)" , & & polylogarithm2 , & @@ -644,7 +683,7 @@ program Test_Math_Special_Functions & ], & & relTol=1.0d-6 & & ) - call Assert("polylogarithm, Li₃(x)", & + call Assert("polylogarithm, Li₃(x)" , & & polylogarithm3 , & & [ & & -0.90154267736969570d0 , & @@ -660,6 +699,29 @@ program Test_Math_Special_Functions & ], & & relTol=1.0d-6 & & ) + + ! Test dilogarithm functions. + call Assert("dilogarithm, Li₂(x)" , & + & dilogarithm_ , & + & [ & + & -0.82246703342411320d0, & + & -0.44841420692364620d0, & + & -0.30903312648780850d0, & + & -0.23590029768626350d0, & + & -0.19080013777753560d0, & + & -0.16019301354439550d0, & + & -0.13805517651807560d0, & + & -0.12129662872272650d0, & + & -0.10816821022923270d0, & + & -0.09760523522932158d0 & + & ], & + & relTol=1.0d-6 & + & ) + call Assert("dilogarithm, Li₂(2) (complex)" , & + & [real(Dilogarithm(dcmplx(2.0d0,0.0d0))),imag(Dilogarithm(dcmplx(2.0d0,0.0d0)))], & + & [Pi**2/4.0d0 ,-Pi*log(2.0d0)] , & + & relTol=1.0d-6 & + & ) ! Test error function with complex argument. errorFunctionComplex=Error_Function( & diff --git a/source/tests.merger_tree_branching.F90 b/source/tests.merger_tree_branching.F90 index a3c5a068bb..8430f47499 100644 --- a/source/tests.merger_tree_branching.F90 +++ b/source/tests.merger_tree_branching.F90 @@ -353,8 +353,7 @@ program Tests_Merger_Tree_Branching call Assert('Smooth accretion rate' ,smoothAccretionRate ,smoothAccretionRateTargetGeneral ,relTol=2.5d-2) call Unit_Tests_End_Group ( ) call Unit_Tests_End_Group ( ) - call Unit_Tests_Begin_Group("First crossing distribution (numerical)" ) - write (400,*) "variance , uncon_num, con_num, uncon_alytc, con_alytc" + call Unit_Tests_Begin_Group("First crossing distribution (numerical)" ) varianceParent =cosmologicalMassVarianceFilteredPower_%rootVariance(massParent ,time)**2 varianceResolution=cosmologicalMassVarianceFilteredPower_%rootVariance(massResolution,time)**2 do j=1,countVariance @@ -369,7 +368,6 @@ program Tests_Merger_Tree_Branching branchingRateUnconstrainedNumerical(j)=excursionSetFirstCrossingFarahiMidpoint_ %rate(varianceParent,variance_(j),time,node) branchingRateConstrainedNumerical (j)=excursionSetFirstCrossingFarahiMidpointBrownianBridge_%rate(varianceParent,variance_(j),time,node) branchingRateConstrainedAnalytic (j)=excursionSetFirstCrossingLinearBarrierBrownianBridge_ %rate(varianceParent,variance_(j),time,node) - write (400,*) variance_(j),",",branchingRateUnconstrainedNumerical(j),",",branchingRateConstrainedNumerical(j),",",branchingRateUnconstrainedAnalytic(j),",",branchingRateConstrainedAnalytic(j) end do errorMaximumUnconstrained=maxval( & & abs(branchingRateUnconstrainedNumerical-branchingRateUnconstrainedAnalytic)/branchingRateUnconstrainedAnalytic & diff --git a/source/tests.nodes.task.F90 b/source/tests.nodes.task.F90 index adecdd55a8..f0aac8dfd1 100644 --- a/source/tests.nodes.task.F90 +++ b/source/tests.nodes.task.F90 @@ -39,14 +39,12 @@ subroutine Test_Node_Task(node) Implements simple tests of mapping functions over all components in a \gls{node}. !!} use :: Display , only : displayVerbositySet, verbosityLevelStandard - use :: Galacticus_Nodes, only : nodeComponent , nodeComponentBlackHole, reductionSummation, treeNode + use :: Galacticus_Nodes, only : nodeComponent , nodeComponentBlackHole, treeNode use :: Unit_Tests , only : Assert implicit none - type (treeNode ), intent(inout) :: node - procedure (testVoidFunc ), pointer :: myFuncVoid => testVoidFunc - procedure (testFuncDouble0), pointer :: myFuncDouble0 => testFuncDouble0 - class (nodeComponent ), pointer :: component - double precision :: mapResult + type (treeNode ), intent(inout) :: node + procedure (testVoidFunc ), pointer :: myFuncVoid => testVoidFunc + class (nodeComponent), pointer :: component ! Set verbosity level. call displayVerbositySet(verbosityLevelStandard) @@ -57,13 +55,6 @@ subroutine Test_Node_Task(node) ! Map a void function (subroutine) over all components. call node%mapVoid(myFuncVoid) call Assert('Map void function over all components',all([componentBasicStandardSeen,componentBlackHoleStandardSeen]),.true.) - - ! Map a scalar double function over all components, with summation reduction - mapResult=node%mapDouble0(myFuncDouble0,reduction=reductionSummation) - select type (component) - class is (nodeComponentBlackHole) - call Assert('Summation reduction map over all components',component%mass(),mapResult) - end select return end subroutine Test_Node_Task @@ -81,19 +72,4 @@ A simple void function used in testing mapping over a function over all componen return end subroutine testVoidFunc - double precision function testFuncDouble0(component) - !!{ - A simple test function which returns the enclosed mass for a component. Used in testing mapping over a function over all - components. - !!} - use :: Galactic_Structure_Options, only : componentTypeAll, massTypeAll, radiusLarge, weightByMass, & - & weightIndexNull - use :: Galacticus_Nodes , only : nodeComponent - implicit none - class(nodeComponent), intent(inout) :: component - - testFuncDouble0=component%enclosedMass(radiusLarge,componentTypeAll,massTypeAll,weightByMass,weightIndexNull) - return - end function testFuncDouble0 - end module Test_Nodes_Tasks diff --git a/source/tests.orbits.F90 b/source/tests.orbits.F90 index af5bc56d3f..f15c8846eb 100644 --- a/source/tests.orbits.F90 +++ b/source/tests.orbits.F90 @@ -30,13 +30,12 @@ program Test_Orbits use :: Cosmology_Functions , only : cosmologyFunctionsMatterLambda use :: Dark_Matter_Halo_Scales , only : darkMatterHaloScaleVirialDensityContrastDefinition use :: Dark_Matter_Profiles_DMO , only : darkMatterProfileDMOIsothermal - use :: Dark_Matter_Profiles , only : darkMatterProfileDarkMatterOnly - use :: Galactic_Structure , only : galacticStructureStandard use :: Virial_Density_Contrast , only : virialDensityContrastSphericalCollapseClsnlssMttrCsmlgclCnstnt use :: Events_Hooks , only : eventsHooksInitialize use :: Functions_Global_Utilities, only : Functions_Global_Set use :: Display , only : displayVerbositySet , verbosityLevelStandard - use :: Galacticus_Nodes , only : nodeClassHierarchyFinalize , nodeClassHierarchyInitialize , nodeComponentBasic , treeNode + use :: Galacticus_Nodes , only : nodeClassHierarchyFinalize , nodeClassHierarchyInitialize , nodeComponentBasic , treeNode , & + & nodeComponentDarkMatterProfile use :: Input_Parameters , only : inputParameters use :: Node_Components , only : Node_Components_Initialize , Node_Components_Thread_Initialize, Node_Components_Thread_Uninitialize, Node_Components_Uninitialize use :: Kepler_Orbits , only : keplerOrbit , keplerOrbitMasses , keplerOrbitRadius @@ -46,11 +45,10 @@ program Test_Orbits implicit none type (treeNode ), pointer :: node class (nodeComponentBasic ), pointer :: basic - double precision , parameter :: massHost =1.0d12, massSatellite =0.0d0, & + class (nodeComponentDarkMatterProfile ), pointer :: darkMatterProfile + double precision , parameter :: massHost =1.0d12, massSatellite =0.00d0, & & velocityFractionalRadial=0.9d00, velocityFractionalTangential=0.75d0 type (darkMatterProfileDMOIsothermal ), pointer :: darkMatterProfileDMO_ - type (darkMatterProfileDarkMatterOnly ), pointer :: darkMatterProfile_ - type (galacticStructureStandard ), pointer :: galacticStructure_ type (cosmologyParametersSimple ), pointer :: cosmologyParameters_ type (cosmologyFunctionsMatterLambda ), pointer :: cosmologyFunctions_ type (darkMatterHaloScaleVirialDensityContrastDefinition ), pointer :: darkMatterHaloScale_ @@ -76,8 +74,6 @@ program Test_Orbits allocate(virialDensityContrast_) allocate(darkMatterHaloScale_ ) allocate(darkMatterProfileDMO_ ) - allocate(darkMatterProfile_ ) - allocate(galacticStructure_ ) !![ @@ -121,29 +117,12 @@ program Test_Orbits & ) - - - darkMatterProfileDarkMatterOnly ( & - & cosmologyParameters_ =cosmologyParameters_ , & - & darkMatterProfileDMO_ =darkMatterProfileDMO_ , & - & darkMatterHaloScale_ =darkMatterHaloScale_ & - & ) - - - - - galacticStructureStandard ( & - & cosmologyFunctions_ =cosmologyFunctions_ , & - & darkMatterHaloScale_ =darkMatterHaloScale_ , & - & darkMatterProfile_ =darkMatterProfile_ & - & ) - - !!] ! Create a node. - node => treeNode ( ) + node => treeNode ( ) ! Create components. - basic => node %basic (autoCreate=.true.) + basic => node %basic (autoCreate=.true.) + darkMatterProfile => node %darkMatterProfile(autoCreate=.true.) ! Set node properties. call basic%timeSet(cosmologyFunctions_%cosmicTime(1.0d0)) call basic%massSet(massHost ) @@ -162,16 +141,16 @@ program Test_Orbits call orbit%reset (keep=[keplerOrbitMasses,keplerOrbitRadius] ) call orbit%velocityRadialSet (darkMatterHaloScale_%velocityVirial(node)*velocityFractionalRadial ) call orbit%velocityTangentialSet(darkMatterHaloScale_%velocityVirial(node)*velocityFractionalTangential) - call Satellite_Orbit_Extremum_Phase_Space_Coordinates(node,orbit,extremumPericenter,radiusPericenter,velocityPericenter,galacticStructure_) - call Satellite_Orbit_Extremum_Phase_Space_Coordinates(node,orbit,extremumApocenter ,radiusApocenter ,velocityApocenter ,galacticStructure_) + call Satellite_Orbit_Extremum_Phase_Space_Coordinates(node,orbit,extremumPericenter,radiusPericenter,velocityPericenter,darkMatterHaloScale_) + call Satellite_Orbit_Extremum_Phase_Space_Coordinates(node,orbit,extremumApocenter ,radiusApocenter ,velocityApocenter ,darkMatterHaloScale_) call Assert('non-circular orbit, pericenter radius',radiusPericenter,0.428095d0*darkMatterHaloScale_%radiusVirial(node),relTol=1.0d-5) call Assert('non-circular orbit, apocenter radius',radiusApocenter ,1.825500d0*darkMatterHaloScale_%radiusVirial(node),relTol=1.0d-5) ! Circular orbit. call orbit%reset (keep=[keplerOrbitMasses,keplerOrbitRadius] ) call orbit%velocityRadialSet ( 0.0d0 ) call orbit%velocityTangentialSet(darkMatterHaloScale_%velocityVirial(node) ) - call Satellite_Orbit_Extremum_Phase_Space_Coordinates(node,orbit,extremumPericenter,radiusPericenter,velocityPericenter,galacticStructure_) - call Satellite_Orbit_Extremum_Phase_Space_Coordinates(node,orbit,extremumApocenter ,radiusApocenter ,velocityApocenter ,galacticStructure_) + call Satellite_Orbit_Extremum_Phase_Space_Coordinates(node,orbit,extremumPericenter,radiusPericenter,velocityPericenter,darkMatterHaloScale_) + call Satellite_Orbit_Extremum_Phase_Space_Coordinates(node,orbit,extremumApocenter ,radiusApocenter ,velocityApocenter ,darkMatterHaloScale_) call Assert(' circular orbit, pericenter radius',radiusPericenter,1.000000d0*darkMatterHaloScale_%radiusVirial(node),relTol=1.0d-5) call Assert(' circular orbit, apocenter radius',radiusApocenter ,1.000000d0*darkMatterHaloScale_%radiusVirial(node),relTol=1.0d-5) ! End unit tests. @@ -183,12 +162,10 @@ program Test_Orbits call nodeClassHierarchyFinalize () ! Clean up objects. !![ - - - - - - - + + + + + !!] end program Test_Orbits diff --git a/source/tests.parameters.F90 b/source/tests.parameters.F90 index ff8df63169..0ac6c5af61 100644 --- a/source/tests.parameters.F90 +++ b/source/tests.parameters.F90 @@ -120,6 +120,7 @@ program Test_Parameters call Assert('derived value [recursive; post-reset]',valueNumerical,-1.344442852d2,absTol=1.0d-6) call Unit_Tests_End_Group() ! End unit tests. + call Unit_Tests_End_Group() call Unit_Tests_Finish () ! Close down. call testParameters%destroy() diff --git a/source/tidal_stripping.mass_loss_rate.simple.F90 b/source/tidal_stripping.mass_loss_rate.simple.F90 index 05db6b5019..a9b8e5b873 100644 --- a/source/tidal_stripping.mass_loss_rate.simple.F90 +++ b/source/tidal_stripping.mass_loss_rate.simple.F90 @@ -22,7 +22,6 @@ !!} use :: Satellites_Tidal_Fields, only : satelliteTidalFieldClass - use :: Galactic_Structure , only : galacticStructureClass !![ @@ -52,7 +51,6 @@ !!} private class (satelliteTidalFieldClass), pointer :: satelliteTidalField_ => null() - class (galacticStructureClass ), pointer :: galacticStructure_ => null() double precision :: rateFractionalMaximum , beta contains final :: simpleDestructor @@ -78,7 +76,6 @@ function simpleConstructorParameters(parameters) result(self) type (tidalStrippingSimple ) :: self type (inputParameters ), intent(inout) :: parameters class (satelliteTidalFieldClass), pointer :: satelliteTidalField_ - class (galacticStructureClass ), pointer :: galacticStructure_ double precision :: rateFractionalMaximum, beta !![ @@ -95,18 +92,16 @@ function simpleConstructorParameters(parameters) result(self) parameters - !!] - self=tidalStrippingSimple(rateFractionalMaximum,beta,satelliteTidalField_,galacticStructure_) + self=tidalStrippingSimple(rateFractionalMaximum,beta,satelliteTidalField_) !![ - !!] return end function simpleConstructorParameters - function simpleConstructorInternal(rateFractionalMaximum,beta,satelliteTidalField_,galacticStructure_) result(self) + function simpleConstructorInternal(rateFractionalMaximum,beta,satelliteTidalField_) result(self) !!{ Internal constructor for the {\normalfont \ttfamily simple} model of tidal stripping class. !!} @@ -114,9 +109,8 @@ function simpleConstructorInternal(rateFractionalMaximum,beta,satelliteTidalFiel type (tidalStrippingSimple ) :: self double precision , intent(in ) :: rateFractionalMaximum, beta class (satelliteTidalFieldClass), intent(in ), target :: satelliteTidalField_ - class (galacticStructureClass ), intent(in ), target :: galacticStructure_ !![ - + !!] return @@ -131,7 +125,6 @@ subroutine simpleDestructor(self) !![ - !!] return end subroutine simpleDestructor @@ -140,19 +133,21 @@ double precision function simpleRateMassLoss(self,component) !!{ Computes the mass loss rate due to tidal stripping assuming a simple model. !!} - use :: Galacticus_Nodes , only : nodeComponentDisk, nodeComponentSpheroid, treeNode - use :: Numerical_Constants_Astronomical, only : gigaYear , megaParsec + use :: Galacticus_Nodes , only : nodeComponentDisk , nodeComponentSpheroid, treeNode + use :: Numerical_Constants_Astronomical, only : gigaYear , megaParsec use :: Numerical_Constants_Prefixes , only : kilo + use :: Mass_Distributions , only : massDistributionClass implicit none - class (tidalStrippingSimple), intent(inout) :: self - class (nodeComponent ), intent(inout) :: component - type (treeNode ), pointer :: node - double precision :: forceGravitational , forceTidal , & - & rateMassLossFractional, radiusHalfMass, & - & tidalTensorRadial , timeDynamical , & - & velocityRotation , velocity , & - & massGas , massStellar , & - & radius + class (tidalStrippingSimple ), intent(inout) :: self + class (nodeComponent ), intent(inout) :: component + type (treeNode ), pointer :: node + class (massDistributionClass), pointer :: massDistribution_ + double precision :: forceGravitational , forceTidal , & + & rateMassLossFractional, radiusHalfMass, & + & tidalTensorRadial , timeDynamical , & + & velocityRotation , velocity , & + & massGas , massStellar , & + & radius ! Assume no mass loss rate due to tidal by default. simpleRateMassLoss=0.0d0 @@ -189,10 +184,11 @@ double precision function simpleRateMassLoss(self,component) ! Return if the tidal field is compressive. if (forceTidal <= 0.0d0) return ! Compute the rotation curve. - velocityRotation=self%galacticStructure_%velocityRotation( & - & node , & - & radiusHalfMass & - & ) + massDistribution_ => node %massDistribution( ) + velocityRotation = massDistribution_%rotationCurve (radiusHalfMass) + !![ + + !!] ! Compute the gravitational restoring force at the half-mass radius. forceGravitational=+velocityRotation**2 & & /radiusHalfMass diff --git a/source/utility.input_parameters.F90 b/source/utility.input_parameters.F90 index 17a87ae110..5cce2fe180 100644 --- a/source/utility.input_parameters.F90 +++ b/source/utility.input_parameters.F90 @@ -125,13 +125,12 @@ module Input_Parameters type (node ), pointer, public :: document => null() type (node ), pointer :: rootNode => null() type (hdf5Object ) :: outputParameters , outputParametersContainer - type (inputParameter ), pointer, public :: parameters => null() + type (inputParameter ), pointer, public :: parameters => null() type (inputParameters), pointer, public :: parent => null() logical :: outputParametersCopied = .false., outputParametersTemporary=.false., & - & isNull = .false., warnedVersion =.false. + & isNull = .false. type (integerHash ), allocatable :: warnedDefaults type (ompLock ), pointer :: lock => null() - type (varying_string ) :: fileName contains !![ @@ -404,9 +403,9 @@ function inputParametersConstructorCopy(parameters) result(self) type (inputParameters) :: self type (inputParameters), intent(in ) :: parameters - self = inputParameters(parameters%rootNode ,noOutput=.true.,noBuild=.true.,fileName=char(parameters%fileName)) - self%parameters => parameters%parameters - self%parent => parameters%parent + self = inputParameters(parameters%rootNode ,noOutput=.true.,noBuild=.true.) + self%parameters => parameters%parameters + self%parent => parameters%parent if (allocated(parameters%warnedDefaults)) then if (allocated(self%warnedDefaults)) deallocate(self%warnedDefaults) allocate(self%warnedDefaults) @@ -424,9 +423,11 @@ function inputParametersConstructorNode(parametersNode,allowedParameterNames,out !!{ Constructor for the {\normalfont \ttfamily inputParameters} class from an FoX node. !!} - use :: Display , only : displayGreen , displayMessage , displayMagenta , displayReset + use :: Display , only : displayGreen , displayMessage , displayMagenta , displayReset , & + & verbosityLevelSilent use :: File_Utilities , only : File_Name_Temporary - use :: FoX_dom , only : getOwnerDocument , node , setLiveNodeLists, getTextContent, hasAttribute, getAttributeNode + use :: FoX_dom , only : getOwnerDocument , node , setLiveNodeLists, getTextContent, & + & hasAttribute , getAttributeNode use :: Error , only : Error_Report #ifdef GIT2AVAIL use, intrinsic :: ISO_C_Binding , only : c_null_char @@ -461,7 +462,6 @@ function inputParametersConstructorNode(parametersNode,allowedParameterNames,out #endif type (varying_string ), dimension(:), allocatable , save :: allowedParameterNamesGlobal !$omp threadprivate(allowedParameterNamesGlobal) - !$GLC attributes unused :: fileName !![ @@ -475,7 +475,6 @@ function inputParametersConstructorNode(parametersNode,allowedParameterNames,out self%parent => null ( ) self%warnedDefaults = integerHash ( ) self%lock = ompLock ( ) - if (present(fileName)) self%fileName=fileName !$omp critical (FoX_DOM_Access) self%document => getOwnerDocument(parametersNode) call setLiveNodeLists(self%document,.false.) @@ -525,51 +524,51 @@ function inputParametersConstructorNode(parametersNode,allowedParameterNames,out if (.not.allocated(allowedParameterNamesGlobal)) & & call knownParameterNames(allowedParameterNamesGlobal) ! Check for migration information. - if (XML_Path_Exists(self%rootNode,"lastModified")) then + if (present(fileName)) then + if (XML_Path_Exists(self%rootNode,"lastModified")) then #ifdef GIT2AVAIL - ! Look for a "lastModified" element in the parameter file. - !$omp critical (FoX_DOM_Access) - lastModifiedNode => XML_Get_First_Element_By_Tag_Name(self%rootNode ,'lastModified') - hasRevision = hasAttribute ( lastModifiedNode,'revision' ) - if (hasRevision) then - revisionNode => getAttributeNode(lastModifiedNode,'revision') - commitHashParameters = getTextContent (revisionNode )//c_null_char - end if - !$omp end critical (FoX_DOM_Access) - if (hasRevision.and..not.self%warnedVersion) then - ! A revision was available in the parameter file. - self%warnedVersion=.true. - !! Build an array of known migration commit hashes. - !![ - - !!] - !! Extract the commit hash at which Galacticus was built. - call Version(commitHashSelf_) - commitHashSelf=trim(commitHashSelf_)//c_null_char - !! Iterate over known migration commit hashes and check if they are ancestors. - allocate(isAncestorOfParameters(size(commitHash))) - do i=1,size(commitHash) - isAncestorOfParameters(i)=gitDescendantOf(char(inputPath(pathTypeExec))//c_null_char,commitHashParameters,commitHash(i)) - end do - if (any(isAncestorOfParameters /= 0_c_int .and. isAncestorOfParameters /= 1_c_int)) then - call displayMessage(var_str(displayMagenta()//"WARNING:"//displayReset()//" parameter file revision check failed (#1; error code; ")//maxval(isAncestorOfParameters)//")") - else if (any(isAncestorOfParameters == 0)) then - ! Parameter file is missing migrations - issue a warning. - message=displayMagenta()//"WARNING:"//displayReset()//" parameter file may be missing important parameter updates - consider updating by running:"//char(10)//char(10)//" ./scripts/aux/parametersMigrate.pl " - if (present(fileName)) message=message//trim(fileName)//" newParameterFile.xml " - call displayMessage(message) + ! Look for a "lastModified" element in the parameter file. + !$omp critical (FoX_DOM_Access) + lastModifiedNode => XML_Get_First_Element_By_Tag_Name(self%rootNode ,'lastModified') + hasRevision = hasAttribute ( lastModifiedNode,'revision' ) + if (hasRevision) then + revisionNode => getAttributeNode(lastModifiedNode,'revision') + commitHashParameters = getTextContent (revisionNode )//c_null_char end if - isAncestorOfSelf=gitDescendantOf(char(inputPath(pathTypeExec))//c_null_char,commitHashSelf,commitHashParameters) - if (isAncestorOfSelf /= 0_c_int .and. isAncestorOfSelf /= 1_c_int) then - call displayMessage(var_str(displayMagenta()//"WARNING:"//displayReset()//" parameter file revision check failed (#2; error code: ")//isAncestorOfSelf//")") - else if (isAncestorOfSelf == 0_c_int) then - ! Parameters are more recent than the executable - issue a warning. - call displayMessage(displayMagenta()//"WARNING:"//displayReset()//" parameter file revision is newer than this executable - consider updating your copy of Galacticus") + !$omp end critical (FoX_DOM_Access) + if (hasRevision) then + ! A revision was available in the parameter file. + !! Build an array of known migration commit hashes. + !![ + + !!] + !! Extract the commit hash at which Galacticus was built. + call Version(commitHashSelf_) + commitHashSelf=trim(commitHashSelf_)//c_null_char + !! Iterate over known migration commit hashes and check if they are ancestors. + allocate(isAncestorOfParameters(size(commitHash))) + do i=1,size(commitHash) + isAncestorOfParameters(i)=gitDescendantOf(char(inputPath(pathTypeExec))//c_null_char,commitHashParameters,commitHash(i)) + end do + if (any(isAncestorOfParameters /= 0_c_int .and. isAncestorOfParameters /= 1_c_int)) then + call displayMessage(var_str(displayMagenta()//"WARNING:"//displayReset()//" parameter file revision check failed (#1; error code; ")//maxval(isAncestorOfParameters)//")") + else if (any(isAncestorOfParameters == 0)) then + ! Parameter file is missing migrations - issue a warning. + message=displayMagenta()//"WARNING:"//displayReset()//" parameter file may be missing important parameter updates - consider updating by running:"//char(10)//char(10)//" ./scripts/aux/parametersMigrate.pl "//trim(fileName)//" newParameterFile.xml" + call displayMessage(message//char(10),verbosityLevelSilent) + end if + isAncestorOfSelf=gitDescendantOf(char(inputPath(pathTypeExec))//c_null_char,commitHashSelf,commitHashParameters) + if (isAncestorOfSelf /= 0_c_int .and. isAncestorOfSelf /= 1_c_int) then + call displayMessage(var_str(displayMagenta()//"WARNING:"//displayReset()//" parameter file revision check failed (#2; error code: ")//isAncestorOfSelf//")") + else if (isAncestorOfSelf == 0_c_int) then + ! Parameters are more recent than the executable - issue a warning. + call displayMessage(displayMagenta()//"WARNING:"//displayReset()//" parameter file revision is newer than this executable - consider updating your copy of Galacticus",verbosityLevelSilent) + end if end if - end if #else - call Warn(displayMagenta()//"WARNING:"//displayReset()//" can not check if parameter file is up to date (`libgit` is not available)") + call Warn(displayMagenta()//"WARNING:"//displayReset()//" can not check if parameter file is up to date (`libgit` is not available)") #endif + end if end if ! Check parameters. call self%checkParameters(allowedParameterNamesGlobal=allowedParameterNamesGlobal,allowedParameterNames=allowedParameterNames) @@ -1743,7 +1742,7 @@ function inputParametersSubParameters(self,parameterName,requireValue,requirePre inputParametersSubParameters = inputParameters (parameterNode%content,noOutput =.true. ,noBuild =.true. ) inputParametersSubParameters%parameters => parameterNode end if - inputParametersSubParameters%parent => self + inputParametersSubParameters%parent => self !$ call hdf5Access%set() if (self%outputParameters%isOpen()) then groupName=parameterName diff --git a/testSuite/parameters/barInstability.xml b/testSuite/parameters/barInstability.xml index 4ed9de37df..53baacff85 100644 --- a/testSuite/parameters/barInstability.xml +++ b/testSuite/parameters/barInstability.xml @@ -9,7 +9,7 @@ - + @@ -88,6 +88,8 @@ + + diff --git a/testSuite/parameters/cgmMassCooled.xml b/testSuite/parameters/cgmMassCooled.xml index ac05e727ca..00118d9518 100644 --- a/testSuite/parameters/cgmMassCooled.xml +++ b/testSuite/parameters/cgmMassCooled.xml @@ -8,7 +8,7 @@ - + @@ -78,6 +78,8 @@ + + diff --git a/testSuite/parameters/checkpointingCheckpoints.xml b/testSuite/parameters/checkpointingCheckpoints.xml index 10640ed3d6..4cab38c473 100644 --- a/testSuite/parameters/checkpointingCheckpoints.xml +++ b/testSuite/parameters/checkpointingCheckpoints.xml @@ -312,9 +312,7 @@ - - - + diff --git a/testSuite/parameters/checkpointingNoCheckpoints.xml b/testSuite/parameters/checkpointingNoCheckpoints.xml index d287ba5773..5bffd8a0a7 100644 --- a/testSuite/parameters/checkpointingNoCheckpoints.xml +++ b/testSuite/parameters/checkpointingNoCheckpoints.xml @@ -311,9 +311,7 @@ - - - + diff --git a/testSuite/parameters/checkpointingResume.xml b/testSuite/parameters/checkpointingResume.xml index 0c650a639c..451a0e793d 100644 --- a/testSuite/parameters/checkpointingResume.xml +++ b/testSuite/parameters/checkpointingResume.xml @@ -311,9 +311,7 @@ - - - + diff --git a/testSuite/parameters/constrainedMergerTrees.xml b/testSuite/parameters/constrainedMergerTrees.xml index 742f9c91e8..604ce970e9 100644 --- a/testSuite/parameters/constrainedMergerTrees.xml +++ b/testSuite/parameters/constrainedMergerTrees.xml @@ -98,7 +98,7 @@ - + diff --git a/testSuite/parameters/galacticStructure.xml b/testSuite/parameters/galacticStructure.xml new file mode 100644 index 0000000000..7966c670cf --- /dev/null +++ b/testSuite/parameters/galacticStructure.xml @@ -0,0 +1,15 @@ + + + + 2 + 0.9.4 + + + + + + + + + + diff --git a/testSuite/parameters/idealizedSubhaloSimulations/tidalTrackBestFit_xc_0.7_ratio_0.005_alpha_1.0_beta_3.0_gamma_1.5.xml b/testSuite/parameters/idealizedSubhaloSimulations/tidalTrackBestFit_xc_0.7_ratio_0.005_alpha_1.0_beta_3.0_gamma_1.5.xml index 9898ef183c..304a16b3a7 100644 --- a/testSuite/parameters/idealizedSubhaloSimulations/tidalTrackBestFit_xc_0.7_ratio_0.005_alpha_1.0_beta_3.0_gamma_1.5.xml +++ b/testSuite/parameters/idealizedSubhaloSimulations/tidalTrackBestFit_xc_0.7_ratio_0.005_alpha_1.0_beta_3.0_gamma_1.5.xml @@ -67,9 +67,6 @@ - - - @@ -214,9 +211,6 @@ - - - @@ -230,9 +224,6 @@ - - - diff --git a/testSuite/parameters/idealizedSubhaloSimulations/tidalTrackBestFit_xc_0.7_ratio_0.01_alpha_1.0_beta_3.0_gamma_1.5.xml b/testSuite/parameters/idealizedSubhaloSimulations/tidalTrackBestFit_xc_0.7_ratio_0.01_alpha_1.0_beta_3.0_gamma_1.5.xml index 698b0f3f13..8166756e14 100644 --- a/testSuite/parameters/idealizedSubhaloSimulations/tidalTrackBestFit_xc_0.7_ratio_0.01_alpha_1.0_beta_3.0_gamma_1.5.xml +++ b/testSuite/parameters/idealizedSubhaloSimulations/tidalTrackBestFit_xc_0.7_ratio_0.01_alpha_1.0_beta_3.0_gamma_1.5.xml @@ -67,9 +67,6 @@ - - - @@ -214,9 +211,6 @@ - - - @@ -230,9 +224,6 @@ - - - diff --git a/testSuite/parameters/idealizedSubhaloSimulations/tidalTrackBestFit_xc_0.7_ratio_0.05_alpha_1.0_beta_3.0_gamma_0.5.xml b/testSuite/parameters/idealizedSubhaloSimulations/tidalTrackBestFit_xc_0.7_ratio_0.05_alpha_1.0_beta_3.0_gamma_0.5.xml index 2dc732051f..25f389318c 100644 --- a/testSuite/parameters/idealizedSubhaloSimulations/tidalTrackBestFit_xc_0.7_ratio_0.05_alpha_1.0_beta_3.0_gamma_0.5.xml +++ b/testSuite/parameters/idealizedSubhaloSimulations/tidalTrackBestFit_xc_0.7_ratio_0.05_alpha_1.0_beta_3.0_gamma_0.5.xml @@ -67,9 +67,6 @@ - - - @@ -209,9 +206,6 @@ - - - @@ -225,9 +219,6 @@ - - - diff --git a/testSuite/parameters/idealizedSubhaloSimulations/tidalTrackBestFit_xc_0.7_ratio_0.05_alpha_1.0_beta_3.0_gamma_1.0.xml b/testSuite/parameters/idealizedSubhaloSimulations/tidalTrackBestFit_xc_0.7_ratio_0.05_alpha_1.0_beta_3.0_gamma_1.0.xml index ffd3af26c4..275156422f 100644 --- a/testSuite/parameters/idealizedSubhaloSimulations/tidalTrackBestFit_xc_0.7_ratio_0.05_alpha_1.0_beta_3.0_gamma_1.0.xml +++ b/testSuite/parameters/idealizedSubhaloSimulations/tidalTrackBestFit_xc_0.7_ratio_0.05_alpha_1.0_beta_3.0_gamma_1.0.xml @@ -63,9 +63,6 @@ - - - @@ -206,9 +203,6 @@ - - - @@ -218,9 +212,6 @@ - - - diff --git a/testSuite/parameters/idealizedSubhaloSimulations/tidalTrackBestFit_xc_0.7_ratio_0.2_alpha_1.0_beta_3.0_gamma_0.0.xml b/testSuite/parameters/idealizedSubhaloSimulations/tidalTrackBestFit_xc_0.7_ratio_0.2_alpha_1.0_beta_3.0_gamma_0.0.xml index 724106af14..cc9137bbaf 100644 --- a/testSuite/parameters/idealizedSubhaloSimulations/tidalTrackBestFit_xc_0.7_ratio_0.2_alpha_1.0_beta_3.0_gamma_0.0.xml +++ b/testSuite/parameters/idealizedSubhaloSimulations/tidalTrackBestFit_xc_0.7_ratio_0.2_alpha_1.0_beta_3.0_gamma_0.0.xml @@ -67,9 +67,6 @@ - - - @@ -207,9 +204,6 @@ - - - @@ -223,9 +217,6 @@ - - - diff --git a/testSuite/parameters/idealizedSubhaloSimulations/tidalTrackBestFit_xc_0.7_ratio_0.2_alpha_1.0_beta_3.0_gamma_0.5.xml b/testSuite/parameters/idealizedSubhaloSimulations/tidalTrackBestFit_xc_0.7_ratio_0.2_alpha_1.0_beta_3.0_gamma_0.5.xml index 797fb3bfc4..f215c7a31b 100644 --- a/testSuite/parameters/idealizedSubhaloSimulations/tidalTrackBestFit_xc_0.7_ratio_0.2_alpha_1.0_beta_3.0_gamma_0.5.xml +++ b/testSuite/parameters/idealizedSubhaloSimulations/tidalTrackBestFit_xc_0.7_ratio_0.2_alpha_1.0_beta_3.0_gamma_0.5.xml @@ -67,9 +67,6 @@ - - - @@ -214,9 +211,6 @@ - - - @@ -230,9 +224,6 @@ - - - diff --git a/testSuite/parameters/idealizedSubhaloSimulations/tidalTrackBestFit_xc_0.7_ratio_0.2_alpha_1.0_beta_3.0_gamma_1.0.xml b/testSuite/parameters/idealizedSubhaloSimulations/tidalTrackBestFit_xc_0.7_ratio_0.2_alpha_1.0_beta_3.0_gamma_1.0.xml index abe49a0511..0b3d98116f 100644 --- a/testSuite/parameters/idealizedSubhaloSimulations/tidalTrackBestFit_xc_0.7_ratio_0.2_alpha_1.0_beta_3.0_gamma_1.0.xml +++ b/testSuite/parameters/idealizedSubhaloSimulations/tidalTrackBestFit_xc_0.7_ratio_0.2_alpha_1.0_beta_3.0_gamma_1.0.xml @@ -63,9 +63,6 @@ - - - @@ -204,9 +201,6 @@ - - - @@ -216,9 +210,6 @@ - - - diff --git a/testSuite/parameters/idealizedSubhaloSimulations/tidalTrackBestFit_xc_0.7_ratio_0.4_alpha_1.0_beta_3.0_gamma_0.0.xml b/testSuite/parameters/idealizedSubhaloSimulations/tidalTrackBestFit_xc_0.7_ratio_0.4_alpha_1.0_beta_3.0_gamma_0.0.xml index 5a2edcfdff..703faad41b 100644 --- a/testSuite/parameters/idealizedSubhaloSimulations/tidalTrackBestFit_xc_0.7_ratio_0.4_alpha_1.0_beta_3.0_gamma_0.0.xml +++ b/testSuite/parameters/idealizedSubhaloSimulations/tidalTrackBestFit_xc_0.7_ratio_0.4_alpha_1.0_beta_3.0_gamma_0.0.xml @@ -67,9 +67,6 @@ - - - @@ -211,9 +208,6 @@ - - - @@ -227,9 +221,6 @@ - - - diff --git a/testSuite/parameters/impulsiveHeating.xml b/testSuite/parameters/impulsiveHeating.xml index f3dfda6b9a..1f2e85ff63 100644 --- a/testSuite/parameters/impulsiveHeating.xml +++ b/testSuite/parameters/impulsiveHeating.xml @@ -154,6 +154,7 @@ + @@ -167,10 +168,6 @@ - - - - diff --git a/testSuite/parameters/massDefinitionsTimeCurrent.xml b/testSuite/parameters/massDefinitionsTimeCurrent.xml index 0c01f399c1..0f72fceed8 100644 --- a/testSuite/parameters/massDefinitionsTimeCurrent.xml +++ b/testSuite/parameters/massDefinitionsTimeCurrent.xml @@ -15,14 +15,14 @@ - - - - - - - - + + + + + + + + @@ -95,6 +95,8 @@ + + diff --git a/testSuite/parameters/massDefinitionsTimeInfall.xml b/testSuite/parameters/massDefinitionsTimeInfall.xml index c1146853d7..6bc4fefc2f 100644 --- a/testSuite/parameters/massDefinitionsTimeInfall.xml +++ b/testSuite/parameters/massDefinitionsTimeInfall.xml @@ -15,14 +15,14 @@ - - - - - - - - + + + + + + + + @@ -95,6 +95,8 @@ + + diff --git a/testSuite/parameters/massHostMaximum.xml b/testSuite/parameters/massHostMaximum.xml index 0061b7497f..bf21d5ef82 100644 --- a/testSuite/parameters/massHostMaximum.xml +++ b/testSuite/parameters/massHostMaximum.xml @@ -8,7 +8,7 @@ - + @@ -50,7 +50,7 @@ - + @@ -89,6 +89,8 @@ + + diff --git a/testSuite/parameters/noninstantaneous_recycling.xml b/testSuite/parameters/noninstantaneous_recycling.xml index 4a46cf99d1..5102d6d52e 100644 --- a/testSuite/parameters/noninstantaneous_recycling.xml +++ b/testSuite/parameters/noninstantaneous_recycling.xml @@ -276,12 +276,11 @@ - + - diff --git a/testSuite/parameters/orbits.xml b/testSuite/parameters/orbits.xml index 660438676c..d0cc68627b 100644 --- a/testSuite/parameters/orbits.xml +++ b/testSuite/parameters/orbits.xml @@ -4,7 +4,7 @@ 2 - + @@ -32,6 +32,4 @@ - - diff --git a/testSuite/parameters/reproducibility/closedBox.xml b/testSuite/parameters/reproducibility/closedBox.xml index a9ae69ab93..b1f655b47d 100644 --- a/testSuite/parameters/reproducibility/closedBox.xml +++ b/testSuite/parameters/reproducibility/closedBox.xml @@ -9,7 +9,7 @@ - + @@ -88,6 +88,8 @@ + + diff --git a/testSuite/parameters/reproducibility/cooling.xml b/testSuite/parameters/reproducibility/cooling.xml index 2dd39c0b02..9bf2082e16 100644 --- a/testSuite/parameters/reproducibility/cooling.xml +++ b/testSuite/parameters/reproducibility/cooling.xml @@ -9,7 +9,7 @@ - + @@ -111,6 +111,8 @@ + + diff --git a/testSuite/parameters/reproducibility/leakyBox.xml b/testSuite/parameters/reproducibility/leakyBox.xml index 2e3e8d9378..63d072ea9c 100644 --- a/testSuite/parameters/reproducibility/leakyBox.xml +++ b/testSuite/parameters/reproducibility/leakyBox.xml @@ -9,7 +9,7 @@ - + @@ -100,6 +100,8 @@ + + diff --git a/testSuite/parameters/stellarMassWeightedAgesMerging.xml b/testSuite/parameters/stellarMassWeightedAgesMerging.xml index 6c70182c4e..4773b5339e 100644 --- a/testSuite/parameters/stellarMassWeightedAgesMerging.xml +++ b/testSuite/parameters/stellarMassWeightedAgesMerging.xml @@ -8,7 +8,7 @@ - + @@ -109,6 +109,8 @@ + + diff --git a/testSuite/parameters/stellarMassWeightedAgesSimple.xml b/testSuite/parameters/stellarMassWeightedAgesSimple.xml index df0bdf27b6..faef296ec7 100644 --- a/testSuite/parameters/stellarMassWeightedAgesSimple.xml +++ b/testSuite/parameters/stellarMassWeightedAgesSimple.xml @@ -8,7 +8,7 @@ - + @@ -97,6 +97,8 @@ + + diff --git a/testSuite/parameters/test-allowed-parameters.xml b/testSuite/parameters/test-allowed-parameters.xml index 93723f63ba..b825dd6e1e 100644 --- a/testSuite/parameters/test-allowed-parameters.xml +++ b/testSuite/parameters/test-allowed-parameters.xml @@ -15,7 +15,7 @@ - + @@ -144,8 +144,6 @@ - - @@ -170,7 +168,6 @@ - @@ -216,7 +213,6 @@ - diff --git a/testSuite/parameters/test-splitForests-split.xml b/testSuite/parameters/test-splitForests-split.xml index 4032868589..26d3fe08ed 100644 --- a/testSuite/parameters/test-splitForests-split.xml +++ b/testSuite/parameters/test-splitForests-split.xml @@ -25,7 +25,7 @@ - + @@ -216,9 +216,8 @@ - - - + + diff --git a/testSuite/parameters/test-splitForests-unsplit.xml b/testSuite/parameters/test-splitForests-unsplit.xml index 7da510bb35..933dcdd3c2 100644 --- a/testSuite/parameters/test-splitForests-unsplit.xml +++ b/testSuite/parameters/test-splitForests-unsplit.xml @@ -25,7 +25,7 @@ - + @@ -215,9 +215,8 @@ - - - + + diff --git a/testSuite/parameters/tidalTracksMonotonic_gamma0.0.xml b/testSuite/parameters/tidalTracksMonotonic_gamma0.0.xml index 83144a43a4..c287077501 100644 --- a/testSuite/parameters/tidalTracksMonotonic_gamma0.0.xml +++ b/testSuite/parameters/tidalTracksMonotonic_gamma0.0.xml @@ -61,9 +61,6 @@ - - - diff --git a/testSuite/parameters/tidalTracksMonotonic_gamma0.5.xml b/testSuite/parameters/tidalTracksMonotonic_gamma0.5.xml index 3ad5843ffe..da29834eb5 100644 --- a/testSuite/parameters/tidalTracksMonotonic_gamma0.5.xml +++ b/testSuite/parameters/tidalTracksMonotonic_gamma0.5.xml @@ -61,9 +61,6 @@ - - - diff --git a/testSuite/parameters/tidalTracksMonotonic_gamma1.0.xml b/testSuite/parameters/tidalTracksMonotonic_gamma1.0.xml index 1006effd98..4d329c7e58 100644 --- a/testSuite/parameters/tidalTracksMonotonic_gamma1.0.xml +++ b/testSuite/parameters/tidalTracksMonotonic_gamma1.0.xml @@ -61,9 +61,6 @@ - - - diff --git a/testSuite/parameters/tidalTracksNonMonotonic_gamma1.0.xml b/testSuite/parameters/tidalTracksNonMonotonic_gamma1.0.xml index dfc28a582e..5d23da6458 100644 --- a/testSuite/parameters/tidalTracksNonMonotonic_gamma1.0.xml +++ b/testSuite/parameters/tidalTracksNonMonotonic_gamma1.0.xml @@ -61,10 +61,7 @@ - - - - + diff --git a/testSuite/parameters/validation/duplicate-value-invalid.xml b/testSuite/parameters/validation/duplicate-value-invalid.xml index f76f272648..a8818a4aac 100644 --- a/testSuite/parameters/validation/duplicate-value-invalid.xml +++ b/testSuite/parameters/validation/duplicate-value-invalid.xml @@ -39,10 +39,10 @@ - + random random - + diff --git a/testSuite/regressions/finalTimeBeforeOutputTime.xml b/testSuite/regressions/finalTimeBeforeOutputTime.xml index 107a0cf948..210f5149f5 100644 --- a/testSuite/regressions/finalTimeBeforeOutputTime.xml +++ b/testSuite/regressions/finalTimeBeforeOutputTime.xml @@ -4,7 +4,7 @@ 2 - + @@ -24,6 +24,8 @@ + + diff --git a/testSuite/regressions/immediateSubSubMergerThenBranchJump.xml b/testSuite/regressions/immediateSubSubMergerThenBranchJump.xml index 2013d13076..ab4f73faf5 100644 --- a/testSuite/regressions/immediateSubSubMergerThenBranchJump.xml +++ b/testSuite/regressions/immediateSubSubMergerThenBranchJump.xml @@ -7,7 +7,7 @@ 2 - + @@ -22,6 +22,8 @@ + + diff --git a/testSuite/regressions/initialSatelliteNoPrimaryProgenitor.xml b/testSuite/regressions/initialSatelliteNoPrimaryProgenitor.xml index 9a9e19a183..437d2cdb50 100644 --- a/testSuite/regressions/initialSatelliteNoPrimaryProgenitor.xml +++ b/testSuite/regressions/initialSatelliteNoPrimaryProgenitor.xml @@ -8,7 +8,7 @@ 2 - + @@ -16,6 +16,8 @@ + + diff --git a/testSuite/regressions/mergerAtFinalTimeInTree.xml b/testSuite/regressions/mergerAtFinalTimeInTree.xml index 19e2cbe46a..571834c5fb 100644 --- a/testSuite/regressions/mergerAtFinalTimeInTree.xml +++ b/testSuite/regressions/mergerAtFinalTimeInTree.xml @@ -6,7 +6,7 @@ 2 - + @@ -21,6 +21,8 @@ + + diff --git a/testSuite/regressions/particulate.xml b/testSuite/regressions/particulate.xml index 686d1af587..ac1a0c4d6a 100644 --- a/testSuite/regressions/particulate.xml +++ b/testSuite/regressions/particulate.xml @@ -149,7 +149,9 @@ - + + + diff --git a/testSuite/regressions/satellitePresetBoundMassNonZero.xml b/testSuite/regressions/satellitePresetBoundMassNonZero.xml index 2a06293a46..bdcc5022ab 100644 --- a/testSuite/regressions/satellitePresetBoundMassNonZero.xml +++ b/testSuite/regressions/satellitePresetBoundMassNonZero.xml @@ -5,7 +5,7 @@ 2 - + @@ -180,6 +180,8 @@ + + diff --git a/testSuite/regressions/subhaloMergesAtFinalTimeOfTree.xml b/testSuite/regressions/subhaloMergesAtFinalTimeOfTree.xml index b7204250f1..be27ddd76e 100644 --- a/testSuite/regressions/subhaloMergesAtFinalTimeOfTree.xml +++ b/testSuite/regressions/subhaloMergesAtFinalTimeOfTree.xml @@ -3,7 +3,7 @@ 2 - + @@ -24,6 +24,8 @@ + + diff --git a/testSuite/regressions/subhaloTwoConsecutiveMergers.xml b/testSuite/regressions/subhaloTwoConsecutiveMergers.xml index b221542408..d7dec94d45 100644 --- a/testSuite/regressions/subhaloTwoConsecutiveMergers.xml +++ b/testSuite/regressions/subhaloTwoConsecutiveMergers.xml @@ -3,7 +3,7 @@ 2 - + @@ -24,6 +24,8 @@ + + diff --git a/testSuite/test-halo-mass-functions.py b/testSuite/test-halo-mass-functions.py index 4e7284fc60..6fdd981eef 100755 --- a/testSuite/test-halo-mass-functions.py +++ b/testSuite/test-halo-mass-functions.py @@ -99,7 +99,7 @@ # Run Galacticus to generate the mass function. status = subprocess.run("cd ..; ./Galacticus.exe testSuite/outputs/HMFcalc/"+massFunctionType['label']+".xml",shell=True) if status.returncode != 0: - print("FAILED: Galacticus failed") + print("FAILED: Galacticus failed for '"+massFunctionType['label']+"'") sys.exit() # Read the mass function generated by Galacticus. diff --git a/testSuite/test-methods.xml b/testSuite/test-methods.xml index f68508e412..e4a754f057 100644 --- a/testSuite/test-methods.xml +++ b/testSuite/test-methods.xml @@ -15,6 +15,13 @@ 1 10 + + no + 24 + 40 + 1 + 10 + 2 @@ -106,12 +113,8 @@ - - + - - - @@ -575,7 +578,7 @@ 2 - + @@ -1298,7 +1301,7 @@ - + @@ -1570,13 +1573,13 @@ - - - - - - - + + + + + + + @@ -1585,13 +1588,15 @@ + + - + @@ -1639,6 +1644,8 @@ + + @@ -1646,7 +1653,7 @@ - + @@ -1682,13 +1689,13 @@ - - - - - - - + + + + + + + @@ -1696,6 +1703,8 @@ + + @@ -1703,7 +1712,7 @@ - + @@ -1742,6 +1751,8 @@ + + @@ -1749,7 +1760,7 @@ - + @@ -1791,6 +1802,8 @@ + + @@ -1798,7 +1811,7 @@ - + @@ -1839,6 +1852,8 @@ + + @@ -1846,7 +1861,7 @@ - + @@ -1887,6 +1902,8 @@ + + @@ -1894,7 +1911,7 @@ - + @@ -1935,6 +1952,8 @@ + + @@ -1942,7 +1961,7 @@ - + @@ -1989,13 +2008,15 @@ + + - + @@ -2036,6 +2057,8 @@ + + @@ -2043,7 +2066,7 @@ - + @@ -2100,6 +2123,8 @@ + + @@ -2107,7 +2132,7 @@ - + @@ -2160,6 +2185,8 @@ + + @@ -2167,7 +2194,7 @@ - + @@ -2223,6 +2250,8 @@ + + @@ -2232,7 +2261,7 @@ - + @@ -2328,7 +2357,7 @@ - + diff --git a/testSuite/test-noninstantaneous-recycling.pl b/testSuite/test-noninstantaneous-recycling.pl index bdfc5df9eb..fbe3a8703f 100755 --- a/testSuite/test-noninstantaneous-recycling.pl +++ b/testSuite/test-noninstantaneous-recycling.pl @@ -20,6 +20,38 @@ } else { print "SUCCESS: model run\n"; } + +my $yields; +open(my $logFile,"outputs/noninstantaneous_recycling.log"); +while ( my $line = <$logFile> ) { + if ( $line =~ m/(\S+\/yield(Metals|Fe)_[a-z0-9]+.hdf5)/ ) { + my $fileName = $1; + my $type = $2; + $yields->{'file'}->{$type} = $fileName; + my $file = new PDL::IO::HDF5($fileName); + $yields ->{$type} = $file->dataset("yield".$type)->get(); + } +} +close($logFile); +unless ( exists($yields->{'Metals'}) && exists($yields->{'Fe'}) ) { + foreach my $type ( "Metals", "Fe" ) { + if ( exists($yields->{'file'}->{$type}) ) { + print "found ".$type." yield in '".$yields->{'file'}->{$type}."'\n"; + } else { + print "failed to find file for ".$type." yield\n"; + print "log file is:\n"; + system("cat outputs/noninstantaneous_recycling.log"); + } + } + die("failed to read yield tables"); +} + +my $haveMetals = which($yields->{'Metals'} > 0.0); +my $ratioMaximum = maximum( + +$yields->{'Fe' }->flat()->($haveMetals) + /$yields->{'Metals'}->flat()->($haveMetals) + ); + # Read the model data and check for consistency. my $model = new PDL::IO::HDF5("outputs/noninstantaneous_recycling.hdf5"); my $outputs = $model ->group('Outputs' ); @@ -38,6 +70,6 @@ my $nonZero = which($massMetals > 1.0); my $ratio = +$massFe ->($nonZero) /$massMetals->($nonZero); -my $status = all($ratio < 0.16) ? "SUCCESS" : "FAILED"; +my $status = all($ratio < $ratioMaximum) ? "SUCCESS" : "FAILED"; print $status.": Fe/Z ratio\n"; exit 0; diff --git a/testSuite/test-tidalTracks.pl b/testSuite/test-tidalTracks.pl index d97140b7a5..22ac51853a 100755 --- a/testSuite/test-tidalTracks.pl +++ b/testSuite/test-tidalTracks.pl @@ -19,7 +19,7 @@ { label => "nonMonotonic", gamma => 1.00 , - fitMetric => 0.03 + fitMetric => 0.0301 }, { label => "monotonic", @@ -34,7 +34,7 @@ { label => "monotonic", gamma => 0.000 , - fitMetric => 0.028 + fitMetric => 0.0280 } ); foreach my $testCase ( @testCases ) { @@ -124,7 +124,6 @@ my $status = $fitMetric < $testCase->{'fitMetric'} ? "SUCCESS" : "FAILED"; print $status.": subhalo tidal tracks '".$testCase->{'label'}." gamma=".sprintf("%3.1f",$testCase->{'gamma'})."'\n"; - } exit; diff --git a/testSuite/validate-baryonicSuppression.pl b/testSuite/validate-baryonicSuppression.pl index 437f3543ef..77fa9fb09e 100755 --- a/testSuite/validate-baryonicSuppression.pl +++ b/testSuite/validate-baryonicSuppression.pl @@ -120,13 +120,13 @@ # Define χ² targets for each dataset. my $chiSquaredTarget; $chiSquaredTarget->{'withBaryons' }->[1] = 6.0; -$chiSquaredTarget->{'withBaryons_noReionization'}->[1] = 4.0; -$chiSquaredTarget->{'withBaryons' }->[2] = 3.0; -$chiSquaredTarget->{'withBaryons_noReionization'}->[2] = 2.0; +$chiSquaredTarget->{'withBaryons_noReionization'}->[1] = 6.0; +$chiSquaredTarget->{'withBaryons' }->[2] = 4.0; +$chiSquaredTarget->{'withBaryons_noReionization'}->[2] = 3.0; $chiSquaredTarget->{'withBaryons' }->[3] = 5.0; -$chiSquaredTarget->{'withBaryons_noReionization'}->[3] = 1.0; +$chiSquaredTarget->{'withBaryons_noReionization'}->[3] = 4.0; $chiSquaredTarget->{'withBaryons' }->[4] = 3.0; -$chiSquaredTarget->{'withBaryons_noReionization'}->[4] = 2.0; +$chiSquaredTarget->{'withBaryons_noReionization'}->[4] = 4.0; # Make output directory. system("mkdir -p outputs/"); @@ -223,6 +223,7 @@ # Compute ratios of mass functions with the dark matter only model mass function. my $output; my $chiSquared; +my $failed = 0; for(my $outputIndex=1;$outputIndex<=4;++$outputIndex) { foreach my $suffix ( "withBaryons", "withBaryons_noReionization" ) { $haloMassFunction->{$suffix}->[$outputIndex]->{'ratio' } = $haloMassFunction->{$suffix}->[$outputIndex]->{'massFunction'}/$haloMassFunction->{'withoutBaryons'}->[$outputIndex]->{'massFunction'}; @@ -253,6 +254,8 @@ @{$output->{'model'}->{$suffix }->[$outputIndex]->{'ratioError'}} = $haloMassFunction->{$suffix }->[$outputIndex]->{'ratioError'}->list(); # Report. my $status = $chiSquared->{$suffix}->[$outputIndex] < $chiSquaredTarget->{$suffix}->[$outputIndex] ? "SUCCESS" : "FAILED"; + $failed = 1 + if ( $status eq "FAILED" ); my $inequality = $chiSquared->{$suffix}->[$outputIndex] < $chiSquaredTarget->{$suffix}->[$outputIndex] ? "<" : "≥" ; print $status.": model '".$suffix.(" " x (length("withBaryons_noReionization")-length($suffix)))."' at z=".$redshifts[$outputIndex]." validation (χ² = ".sprintf("%5.3f",$chiSquared->{$suffix}->[$outputIndex])." ".$inequality." ".sprintf("%5.3f",$chiSquaredTarget->{$suffix}->[$outputIndex]).")\n"; } @@ -295,5 +298,9 @@ print $reportFile "window.BARYONICSUPPRESSION_DATA = "; print $reportFile $json; close($reportFile); +if ( $failed ) { + print "model failed - results were:\n\n"; + system("cat outputs/results_baryonicSuppression.json"); +} exit;