diff --git a/dev/.documenter-siteinfo.json b/dev/.documenter-siteinfo.json index b55a97b..bcf72b6 100644 --- a/dev/.documenter-siteinfo.json +++ b/dev/.documenter-siteinfo.json @@ -1 +1 @@ -{"documenter":{"julia_version":"1.10.3","generation_timestamp":"2024-05-20T10:36:38","documenter_version":"1.4.1"}} \ No newline at end of file +{"documenter":{"julia_version":"1.10.3","generation_timestamp":"2024-05-20T11:24:33","documenter_version":"1.4.1"}} \ No newline at end of file diff --git a/dev/binaries/index.html b/dev/binaries/index.html index dc4f65a..7d972ed 100644 --- a/dev/binaries/index.html +++ b/dev/binaries/index.html @@ -1,2 +1,2 @@ -Multi-Star Systems · StarFormationHistories.jl

Multi-Star Systems

StarFormationHistories.AbstractBinaryModelType

StarFormationHistories.AbstractBinaryModel is the abstract supertype for all types that are used to model multi-star systems in the package. All concrete subtypes must implement the StarFormationHistories.sample_system method and the Base.length method, which should return an integer indicating the number of stars per system that can be sampled by the model; this is equivalent to the length of the mass vector returned by sample_system.

source
StarFormationHistories.sample_systemFunction
masses = sample_system(imf, rng::AbstractRNG, binarymodel::StarFormationHistories.AbstractBinaryModel)

Simulates the effects of non-interacting, unresolved stellar companions on stellar photometry. Implementation depends on the choice of binarymodel.

Arguments

  • imf: an object implementing rand(imf) to draw a random mass for a single star or a stellar system (depends on choice of binarymodel)
  • rng::AbstractRNG: the random number generator to use when sampling stars
  • binarymodel::StarFormationHistories.AbstractBinaryModel: an instance of a binary model that determines which implementation will be used; currently provided options are NoBinaries, RandomBinaryPairs, and BinaryMassRatio.

Returns

  • masses::SVector{N,eltype(imf)}: the masses of the individual stars sampled in the system in descending order where N is the maximum number of stars that can be sampled by the provided binarymodel as given by Base.length(binarymodel).
source
StarFormationHistories.RandomBinaryPairsType
RandomBinaryPairs(fraction::Real)

The RandomBinaryPairs type takes one argument 0 <= fraction::Real <= 1 that denotes the number fraction of binaries (e.g., 0.3 for 30% binary fraction) and will sample binaries as random pairs of two stars drawn from the same single-star IMF. This model will ONLY generate up to one additional star – it will not generate any 3+ star systems. This model typically incurs a 10–20% speed penalty relative to NoBinaries.

source
StarFormationHistories.BinaryMassRatioType
BinaryMassRatio(fraction::Real, qdist::Distributions.ContinuousUnivariateDistribution=Distributions.Uniform())

The BinaryMassRatio type takes two arguments; the binary fraction 0 <= fraction::Real <= 1 and a continuous univariate distribution qdist from which to sample binary mass ratios, defined as the ratio of the secondary mass to the primary mass: $q = \text{M}_S / \text{M}_P$. The provided qdist must have the proper support of (minimum(qdist) >= 0) & (maximum(qdist) <= 1); users may find the Distributions.truncated method useful for enforcing this support on more general distributions. The default qdist is a uniform distribution from 0.1 to 1, which appears to give reasonably good agreement to observations (see, e.g., Goodwin 2013).

source
+Multi-Star Systems · StarFormationHistories.jl

Multi-Star Systems

StarFormationHistories.AbstractBinaryModelType

StarFormationHistories.AbstractBinaryModel is the abstract supertype for all types that are used to model multi-star systems in the package. All concrete subtypes must implement the StarFormationHistories.sample_system method and the Base.length method, which should return an integer indicating the number of stars per system that can be sampled by the model; this is equivalent to the length of the mass vector returned by sample_system.

source
StarFormationHistories.sample_systemFunction
masses = sample_system(imf, rng::AbstractRNG, binarymodel::StarFormationHistories.AbstractBinaryModel)

Simulates the effects of non-interacting, unresolved stellar companions on stellar photometry. Implementation depends on the choice of binarymodel.

Arguments

  • imf: an object implementing rand(imf) to draw a random mass for a single star or a stellar system (depends on choice of binarymodel)
  • rng::AbstractRNG: the random number generator to use when sampling stars
  • binarymodel::StarFormationHistories.AbstractBinaryModel: an instance of a binary model that determines which implementation will be used; currently provided options are NoBinaries, RandomBinaryPairs, and BinaryMassRatio.

Returns

  • masses::SVector{N,eltype(imf)}: the masses of the individual stars sampled in the system in descending order where N is the maximum number of stars that can be sampled by the provided binarymodel as given by Base.length(binarymodel).
source
StarFormationHistories.RandomBinaryPairsType
RandomBinaryPairs(fraction::Real)

The RandomBinaryPairs type takes one argument 0 <= fraction::Real <= 1 that denotes the number fraction of binaries (e.g., 0.3 for 30% binary fraction) and will sample binaries as random pairs of two stars drawn from the same single-star IMF. This model will ONLY generate up to one additional star – it will not generate any 3+ star systems. This model typically incurs a 10–20% speed penalty relative to NoBinaries.

source
StarFormationHistories.BinaryMassRatioType
BinaryMassRatio(fraction::Real, qdist::Distributions.ContinuousUnivariateDistribution=Distributions.Uniform())

The BinaryMassRatio type takes two arguments; the binary fraction 0 <= fraction::Real <= 1 and a continuous univariate distribution qdist from which to sample binary mass ratios, defined as the ratio of the secondary mass to the primary mass: $q = \text{M}_S / \text{M}_P$. The provided qdist must have the proper support of (minimum(qdist) >= 0) & (maximum(qdist) <= 1); users may find the Distributions.truncated method useful for enforcing this support on more general distributions. The default qdist is a uniform distribution from 0.1 to 1, which appears to give reasonably good agreement to observations (see, e.g., Goodwin 2013).

source
diff --git a/dev/doc_index/index.html b/dev/doc_index/index.html index c32abd5..1ab12fa 100644 --- a/dev/doc_index/index.html +++ b/dev/doc_index/index.html @@ -1,2 +1,2 @@ -Index · StarFormationHistories.jl

Index

+Index · StarFormationHistories.jl

Index

diff --git a/dev/examples/index.html b/dev/examples/index.html index 4b314ec..ad859de 100644 --- a/dev/examples/index.html +++ b/dev/examples/index.html @@ -1,2 +1,2 @@ -Examples · StarFormationHistories.jl

Examples

We have constructed an example Jupyter notebook that highlights some common use cases supported by the package. It is recommended that you read some of the background in our main documentation before or concurrently with the example. The notebook is available in the repository as examples/fitting1.ipynb and can be viewed in rendered form at this link. It relies on a custom isochrone file which can be made available upon request to the package author.

+Examples · StarFormationHistories.jl

Examples

We have constructed an example Jupyter notebook that highlights some common use cases supported by the package. It is recommended that you read some of the background in our main documentation before or concurrently with the example. The notebook is available in the repository as examples/fitting1.ipynb and can be viewed in rendered form at this link. It relies on a custom isochrone file which can be made available upon request to the package author.

diff --git a/dev/fitting/fitting_intro/index.html b/dev/fitting/fitting_intro/index.html index 1cb3e41..7815c97 100644 --- a/dev/fitting/fitting_intro/index.html +++ b/dev/fitting/fitting_intro/index.html @@ -1,7 +1,7 @@ Background and Template Construction · StarFormationHistories.jl

Background and Template Construction

Background

In the classic formulation of star formation history fitting from resolved-star photometry (Dolphin 2002), an observed color-magnitude diagram (CMD) is binned into a 2-D histogram known as a Hess diagram. Such a CMD and Hess diagram pair is shown below.

Comparison of CMD and a Hess diagram generated from the same observational data.

The representation of the observations as a Hess diagram allows one to apply Poisson statistics, specifically the Poisson likelihood ratio (Equations 7–10 in Dolphin 2002), to model the observations. As the CMD of a complex stellar population is simply the sum of the CMDs of its sub-populations, one need only prepare a number of templates for each simple stellar population (SSP) which may make up the complex population in question and model the observed Hess diagram as a linear combination of these templates. Keeping the same notation as Dolphin 2002 (Equation 1), the complex model Hess diagram is simply

\[m_i = \sum_j \, r_j \, c_{i,j}\]

where $m_i$ is the value of the complex model in bin $i$, $c_{i,j}$ is the value of simple template $j$ in bin $i$, and $r_j$ is the multiplicative coefficient determining how significant template $j$ is to the complex population. In Dolphin 2002, he normalizes the templates to identical star formation rates (SFRs) and so the $r_j$ are SFRs as well. In this package, we prefer to normalize our templates to identical population stellar masses, so our $r_j$ are stellar masses, but the principal is the same.

Construction of the templates is, however, not a trivial exercise. Ideally, a template constructed to represent a particular SSP would accurately reflect the expectation of how such a population would be observed. Thus, these templates must be adjusted for photometric error, incompleteness, and other effects such as those caused by unresolved binary- and multi-star systems. Observational effects such as photometric error and incompleteness are best measured from artificial star tests (ASTs). It is worth mentioning that ASTs can often return "best case" results, as they typically neglect systematics like uncertainty in the point-spread model used for the photometry; as such it is sometimes necessary to add a systematic error floor to photometric error results from ASTs.

Such templates can be constructed by sampling many mock stars from an initial mass function (IMF), interpolating their absolute magnitudes from an isochrone of the relevant SSP, and "mock observing" them by applying photometric error and completeness functions (for example, by looking up the $1\sigma$ photometric error and completeness value from a catalog of artificial stars). Such Monte Carlo templates can be slow to construct and exhibit Poisson shot-noise, requiring a statistical data–data comparison rather than a model–data comparison. Thus this method is non-optimal from both a practical and statistical perspective.

It is better to form what Dolphin 2002 calls a "blurred isochrone;" in this form of template, the SSP isochrone is first interpolated in initial stellar mass to improve the point density along the isochrone. The number of interpolated points is generally a function of the size of the bins in the Hess diagram and the observational error; more points are required as the bin size or photometric errors become smaller. These points are then weighted according to the IMF and the photometric completeness, and this weight is distributed into the Hess diagram following the photometric error distribution determined by similar artificial stars. Dolphin 2002 also mentions interpolating across stellar age/metallicity when constructing such templates; for example, for an SSP with an age of 1 Gyr and a metallicity of [M/H]=-1.0, you could interpolate the isochrones to introduce a Gaussian metallicity spread of 0.05 dex or an age spread of 100 Myr. The general effects of this form of interpolation is to broaden the model templates, particularly features that are very sharp in true SSP models. We neglect this form of interpolation in our implementation as it adds significant complexity and requires users to provide more information about the isochrones that are providing. Such widening of the individual templates is most impactful when photometric errors in the observational data are low (perhaps <0.10 mag).

Constructing Templates

While the above description summarizes the necessary components for constructing such a blurred isochrone, it can be a bit difficult to figure out how best to actually construct them. Specifically there are many ways that one could implement the observational effects of photometric error and incompleteness. We provide a method partial_cmd_smooth to construct such templates under the assumption of Gaussian photometric error distributions, which is often a good approximation in the high-completeness regime. This method makes use of user-defined functions for the mean photometric error and completeness as a function of magnitude and filter, such that these can be defined in a number of ways; for example, as direct lookups from a large table of ASTs or as simple function evaluations of analytic approximations or fits to the ASTs.

This method begins by interpolating the provided SSP isochrone to increase point density. For every such point with $i$ band apparent magnitude $m_i$, it calls a user-defined function to estimate the $1\sigma$ photometric error as $\sigma_i = f_i(m_i)$. The photometric error on the x-axis color for the Hess diagram is estimated from the individual-band $\sigma_i$. These errors are used to define an asymmetric Gaussian kernel for each point in the interpolated isochrone. This kernel describes the shape of the probability distribution for where in the Hess diagram the isochrone point would be observed. However, it also must be normalized (weighted) according to the IMF and observational completeness functions.

Assume that the vector of initial stellar masses for the points in the interpolated isochrone are $m_i$ and that they are sorted such that $m_i < m_{i+1}$. The IMF weight on point $m_i$ can be approximated as the number fraction of stars born between $m_i$ and $m_{i+1}$ divided by the mean mass per star born $\langle m \rangle$, such that the weight effectively represents the number of stars expected to be born with masses between $m_i$ and $m_{i+1}$ per solar mass of star formation:

\[\begin{aligned} w_{i,\text{IMF}} &= \frac{ \int_0^{m_{i+1}} \frac{dN(m)}{dm} dm - \int_0^{m_{i}} \frac{dN(m)}{dm} dm }{\int_0^\infty m \times \frac{dN(m)}{dm} dm} = \frac{ \int_{m_i}^{m_{i+1}} \frac{dN(m)}{dm} dm }{\langle m \rangle} -\end{aligned}\]

The numerator can either be calculated as the difference in the cumulative distribution function across the bin or approximated efficiently via the trapezoidal rule. The denominator is a function only of the IMF and need only be calculated once. Multiplying this weight by the probability of detection in the relevant bands gives the final weight.

Below we show a comparison of a smooth Hess diagram template constructed with partial_cmd_smooth with a Monte Carlo realization created with generate_stars_mass and mock-observed with model_cmd. These use an SSP isochrone of age 10 Gyr and metallicity [M/H] of -2 from PARSEC with identical observational error and completeness models. For the provided stellar mass of $10^7 \, \text{M}_\odot$, the Monte Carlo model is fairly well-sampled but still noticably noisy in regions of the Hess diagram that are less well-populated.

Comparison of smooth Hess diagram template from `partial_cmd_smooth` and a Monte Carlo model made with `generate_stars_mass`.

StarFormationHistories.partial_cmd_smoothFunction
result::StatsBase.Histogram = partial_cmd_smooth( m_ini::AbstractVector{<:Number}, mags::AbstractVector{<:AbstractVector{<:Number}}, mag_err_funcs, y_index, color_indices, imf, completeness_funcs=[one for i in mags]; dmod::Number=0, normalize_value::Number=1, mean_mass=mean(imf), edges=nothing, xlim=nothing, ylim=nothing, nbins=nothing, xwidth=nothing, ywidth=nothing )

Main function for generating template Hess diagrams from a simple stellar population of stars from an isochrone, including photometric error and completeness.

Arguments

  • m_ini::AbstractVector{<:Number} is a vector containing the initial stellar masses of the stars from the isochrone.
  • mags::AbstractVector{<:AbstractVector{<:Number}} is a vector of vectors. Each constituent vector with index i should have length(mags[i]) == length(m_ini), representing the magnitudes of the isochrone stars in each of the magnitudes considered. In most cases, mags should contain 2 (if y-axis mag is also involved in the x-axis color) or 3 vectors.
  • mag_err_funcs must be an indexable object (e.g., a vector or tuple) that contains callables (e.g., a Function) to compute the 1σ photometric errors in the filters provided in mags. Each callable must take a single argument and return a Number. The length mag_err_funcs must be equal to the length of mags.
  • y_index gives a valid index (e.g., an Int or CartesianIndex) into mags for the filter you want to have on the y-axis of the Hess diagram. For example, if the mags argument contains the B and V band magnitudes as mags=[B, V] and you want V on the y-axis, you would set y_index as 2.
  • color_indices is a length-2 indexable object giving the indices into mags that are to be used to compute the x-axis color. For example, if the mags argument contains the B and V band magnitudes as mags=[B, V], and you want B-V to be the x-axis color, then color_indices should be [1,2] or (1,2) or similar.
  • imf is a callable that takes an initial stellar mass as its sole argument and returns the (properly normalized) probability density of your initial mass function model. All the models from InitialMassFunctions.jl are valid for imf.
  • completeness_functions must be an indexable object (e.g., a vector or tuple) that contains callables (e.g., a Function) to compute the single-filter completeness fractions as a function of magnitude. Each callable in this argument must correspond to the matching filter provided in mags.

Keyword Arguments

  • dmod::Number=0 distance modulus in magnitudes to apply to the input mags.
  • normalize_value::Number=1 gives the total stellar mass of the population you wish to model.
  • mean_mass::Number gives the expectation value for a random star drawn from your provided imf. This will be computed for you if your provided imf is a valid continuous, univariate Distributions.Distribution object.
  • edges is a tuple of vector-like objects defining the left-side edges of the bins along the x-axis (edges[1]) and the y-axis (edges[2]). Example: (-1.0:0.1:1.5, 22:0.1:27.2). If edges is provided, it overrides the following keyword arguments that offer other ways to specify the extent of the Hess diagram.
  • xlim; a length-2 indexable object (e.g., a vector or tuple) giving the lower and upper bounds on the x-axis corresponding to the provided colors array. Example: [-1.0, 1.5]. This is only used if edges is not provided.
  • ylim; as xlim but for the y-axis corresponding to the provided mags array. Example [25.0, 20.0]. This is only used if edges is not provided.
  • nbins::NTuple{2,<:Integer} is a 2-tuple of integers providing the number of bins to use along the x- and y-axes. This is only used if edges is not provided.
  • xwidth; the bin width along the x-axis for the colors array. This is only used if edges and nbins are not provided. Example: 0.1.
  • ywidth; as xwidth but for the y-axis corresponding to the provided mags array. Example: 0.1.

Returns

This method returns the Hess diagram as a StatsBase.Histogram; you should refer to the StatsBase documentation for more information. In short, if the output of this method is result, then the Hess diagram represented as a Matrix is available as result.weights (this is what you would want for fit_templates and similar functions) and the edges of the histogram are available as result.edges.

source

We note that in many cases it can also be helpful to add in a foreground/background template that models contamination of the Hess diagram from stars not in your population of interest – this is often done using observations of parallel fields though there are several other possible methods.

Photometric catalogs can be processed into Hess diagrams meeting our formatting requirements with the method bin_cmd.

StarFormationHistories.bin_cmdFunction
result::StatsBase.Histogram =
+\end{aligned}\]

The numerator can either be calculated as the difference in the cumulative distribution function across the bin or approximated efficiently via the trapezoidal rule. The denominator is a function only of the IMF and need only be calculated once. Multiplying this weight by the probability of detection in the relevant bands gives the final weight.

Below we show a comparison of a smooth Hess diagram template constructed with partial_cmd_smooth with a Monte Carlo realization created with generate_stars_mass and mock-observed with model_cmd. These use an SSP isochrone of age 10 Gyr and metallicity [M/H] of -2 from PARSEC with identical observational error and completeness models. For the provided stellar mass of $10^7 \, \text{M}_\odot$, the Monte Carlo model is fairly well-sampled but still noticably noisy in regions of the Hess diagram that are less well-populated.

Comparison of smooth Hess diagram template from `partial_cmd_smooth` and a Monte Carlo model made with `generate_stars_mass`.

StarFormationHistories.partial_cmd_smoothFunction
result::StatsBase.Histogram = partial_cmd_smooth( m_ini::AbstractVector{<:Number}, mags::AbstractVector{<:AbstractVector{<:Number}}, mag_err_funcs, y_index, color_indices, imf, completeness_funcs=[one for i in mags]; dmod::Number=0, normalize_value::Number=1, mean_mass=mean(imf), edges=nothing, xlim=nothing, ylim=nothing, nbins=nothing, xwidth=nothing, ywidth=nothing )

Main function for generating template Hess diagrams from a simple stellar population of stars from an isochrone, including photometric error and completeness.

Arguments

  • m_ini::AbstractVector{<:Number} is a vector containing the initial stellar masses of the stars from the isochrone.
  • mags::AbstractVector{<:AbstractVector{<:Number}} is a vector of vectors. Each constituent vector with index i should have length(mags[i]) == length(m_ini), representing the magnitudes of the isochrone stars in each of the magnitudes considered. In most cases, mags should contain 2 (if y-axis mag is also involved in the x-axis color) or 3 vectors.
  • mag_err_funcs must be an indexable object (e.g., a vector or tuple) that contains callables (e.g., a Function) to compute the 1σ photometric errors in the filters provided in mags. Each callable must take a single argument and return a Number. The length mag_err_funcs must be equal to the length of mags.
  • y_index gives a valid index (e.g., an Int or CartesianIndex) into mags for the filter you want to have on the y-axis of the Hess diagram. For example, if the mags argument contains the B and V band magnitudes as mags=[B, V] and you want V on the y-axis, you would set y_index as 2.
  • color_indices is a length-2 indexable object giving the indices into mags that are to be used to compute the x-axis color. For example, if the mags argument contains the B and V band magnitudes as mags=[B, V], and you want B-V to be the x-axis color, then color_indices should be [1,2] or (1,2) or similar.
  • imf is a callable that takes an initial stellar mass as its sole argument and returns the (properly normalized) probability density of your initial mass function model. All the models from InitialMassFunctions.jl are valid for imf.
  • completeness_functions must be an indexable object (e.g., a vector or tuple) that contains callables (e.g., a Function) to compute the single-filter completeness fractions as a function of magnitude. Each callable in this argument must correspond to the matching filter provided in mags.

Keyword Arguments

  • dmod::Number=0 distance modulus in magnitudes to apply to the input mags.
  • normalize_value::Number=1 gives the total stellar mass of the population you wish to model.
  • mean_mass::Number gives the expectation value for a random star drawn from your provided imf. This will be computed for you if your provided imf is a valid continuous, univariate Distributions.Distribution object.
  • edges is a tuple of vector-like objects defining the left-side edges of the bins along the x-axis (edges[1]) and the y-axis (edges[2]). Example: (-1.0:0.1:1.5, 22:0.1:27.2). If edges is provided, it overrides the following keyword arguments that offer other ways to specify the extent of the Hess diagram.
  • xlim; a length-2 indexable object (e.g., a vector or tuple) giving the lower and upper bounds on the x-axis corresponding to the provided colors array. Example: [-1.0, 1.5]. This is only used if edges is not provided.
  • ylim; as xlim but for the y-axis corresponding to the provided mags array. Example [25.0, 20.0]. This is only used if edges is not provided.
  • nbins::NTuple{2,<:Integer} is a 2-tuple of integers providing the number of bins to use along the x- and y-axes. This is only used if edges is not provided.
  • xwidth; the bin width along the x-axis for the colors array. This is only used if edges and nbins are not provided. Example: 0.1.
  • ywidth; as xwidth but for the y-axis corresponding to the provided mags array. Example: 0.1.

Returns

This method returns the Hess diagram as a StatsBase.Histogram; you should refer to the StatsBase documentation for more information. In short, if the output of this method is result, then the Hess diagram represented as a Matrix is available as result.weights (this is what you would want for fit_templates and similar functions) and the edges of the histogram are available as result.edges.

source

We note that in many cases it can also be helpful to add in a foreground/background template that models contamination of the Hess diagram from stars not in your population of interest – this is often done using observations of parallel fields though there are several other possible methods.

Photometric catalogs can be processed into Hess diagrams meeting our formatting requirements with the method bin_cmd.

StarFormationHistories.bin_cmdFunction
result::StatsBase.Histogram =
    bin_cmd( colors::AbstractVector{<:Number},
             mags::AbstractVector{<:Number};
             weights::AbstractVector{<:Number} = ones(promote_type(eltype(colors),
@@ -11,6 +11,6 @@
             ylim   = extrema(mags),
             nbins  = nothing,
             xwidth = nothing,
-            ywidth = nothing)

Returns a StatsBase.Histogram type containing the Hess diagram from the provided x-axis photometric colors and y-axis photometric magnitudes mags. These must all be vectors equal in length. You can either specify the bin edges directly via the edges keyword (e.g., edges = (range(-0.5, 1.6, length=100), range(17.0, 26.0, length=100))), or you can set the x- and y-limits via xlim and ylim and the number of bins as nbins, or you can omit nbins and instead pass the bin width in the x and y directions, xwidth and ywidth. See below for more info on the keyword arguments. To plot this with PyPlot you should do plt.imshow(result.weights', origin="lower", ...).

Keyword Arguments

  • weights::AbstractVector{<:Number} is a array of length equal to colors and mags that contains the probabilistic weights associated with each point. This is passed to StatsBase.fit as StatsBase.Weights(weights).

The following keyword arguments are passed to StarFormationHistories.calculate_edges to determine the bin edges of the histogram.

  • edges is a tuple of vector-like objects defining the left-side edges of the bins along the x-axis (edges[1]) and the y-axis (edges[2]). Example: (-1.0:0.1:1.5, 22:0.1:27.2). If edges is provided, weights is the only other keyword that will be read; edges supercedes the other construction methods.
  • xlim; a length-2 indexable object (e.g., a vector or tuple) giving the lower and upper bounds on the x-axis corresponding to the provided colors array. Example: [-1.0, 1.5]. This is only used if edges is not provided.
  • ylim; as xlim but for the y-axis corresponding to the provided mags array. Example [25.0, 20.0]. This is only used if edges is not provided.
  • nbins::NTuple{2,<:Integer} is a 2-tuple of integers providing the number of bins to use along the x- and y-axes. This is only used if edges is not provided.
  • xwidth; the bin width along the x-axis for the colors array. This is only used if edges and nbins are not provided. Example: 0.1.
  • ywidth; as xwidth but for the y-axis corresponding to the provided mags array. Example: 0.1.
source

A Note on Array Formatting

It is expected that the user will typically have model templates stored as two-dimensional matrices as these are the obvious choice for representing a binned two-dimensional histogram. We fully support supplying the list of model templates as a list of matrices (e.g., a Vector{Matrix{<:Number}}) to the fitting functions discussed below. The important computational kernels composite! and ∇loglikelihood! have custom loops for these input types.

However, additional optimizations are possible by flattening the data. By flattening each matrix in the list of model templates into a column vector and concatenating them such that the list of model templates becomes a single matrix, we can compute the complex model Hess diagram as a single matrix-vector product rather than using a custom loop. The same optimization can be made when computing the gradient of the loglikelihood (discussed more below). The majority of all computation for the fitting methods below is spent in these two functions, so optimizing their performance translates directly to improved fitting runtimes. With this flattened memory arrangement we can use the highly optimized LinearAlgbera.mul! method to do the in-place matrix-vector product. This will typically be translated into a call to a BLAS function like gemv!. As such, we can benefit from Julia's ability to switch BLAS implementations at runtime to use Intel's Math Kernel Library, Apple's Accelerate, and others.

Most of the fitting methods below support both the natural and flattened data layouts. We provide the stack_models method to produce the optimized layout for the list of model templates.

StarFormationHistories.stack_modelsFunction
stack_models(models::AbstractVector{<:AbstractMatrix{<:Number}}) = reduce(hcat,map(vec,models))

Transforms a vector of matrices into a single matrix, with each matrix from models being transcribed into a single column in the output matrix. This data layout enables more efficient calculations in some of our internal functions like composite! and ∇loglikelihood!.

Examples

julia> stack_models([rand(5,5) for i in 1:10])
+            ywidth = nothing)

Returns a StatsBase.Histogram type containing the Hess diagram from the provided x-axis photometric colors and y-axis photometric magnitudes mags. These must all be vectors equal in length. You can either specify the bin edges directly via the edges keyword (e.g., edges = (range(-0.5, 1.6, length=100), range(17.0, 26.0, length=100))), or you can set the x- and y-limits via xlim and ylim and the number of bins as nbins, or you can omit nbins and instead pass the bin width in the x and y directions, xwidth and ywidth. See below for more info on the keyword arguments. To plot this with PyPlot you should do plt.imshow(result.weights', origin="lower", ...).

Keyword Arguments

  • weights::AbstractVector{<:Number} is a array of length equal to colors and mags that contains the probabilistic weights associated with each point. This is passed to StatsBase.fit as StatsBase.Weights(weights).

The following keyword arguments are passed to StarFormationHistories.calculate_edges to determine the bin edges of the histogram.

  • edges is a tuple of vector-like objects defining the left-side edges of the bins along the x-axis (edges[1]) and the y-axis (edges[2]). Example: (-1.0:0.1:1.5, 22:0.1:27.2). If edges is provided, weights is the only other keyword that will be read; edges supercedes the other construction methods.
  • xlim; a length-2 indexable object (e.g., a vector or tuple) giving the lower and upper bounds on the x-axis corresponding to the provided colors array. Example: [-1.0, 1.5]. This is only used if edges is not provided.
  • ylim; as xlim but for the y-axis corresponding to the provided mags array. Example [25.0, 20.0]. This is only used if edges is not provided.
  • nbins::NTuple{2,<:Integer} is a 2-tuple of integers providing the number of bins to use along the x- and y-axes. This is only used if edges is not provided.
  • xwidth; the bin width along the x-axis for the colors array. This is only used if edges and nbins are not provided. Example: 0.1.
  • ywidth; as xwidth but for the y-axis corresponding to the provided mags array. Example: 0.1.
source

A Note on Array Formatting

It is expected that the user will typically have model templates stored as two-dimensional matrices as these are the obvious choice for representing a binned two-dimensional histogram. We fully support supplying the list of model templates as a list of matrices (e.g., a Vector{Matrix{<:Number}}) to the fitting functions discussed below. The important computational kernels composite! and ∇loglikelihood! have custom loops for these input types.

However, additional optimizations are possible by flattening the data. By flattening each matrix in the list of model templates into a column vector and concatenating them such that the list of model templates becomes a single matrix, we can compute the complex model Hess diagram as a single matrix-vector product rather than using a custom loop. The same optimization can be made when computing the gradient of the loglikelihood (discussed more below). The majority of all computation for the fitting methods below is spent in these two functions, so optimizing their performance translates directly to improved fitting runtimes. With this flattened memory arrangement we can use the highly optimized LinearAlgbera.mul! method to do the in-place matrix-vector product. This will typically be translated into a call to a BLAS function like gemv!. As such, we can benefit from Julia's ability to switch BLAS implementations at runtime to use Intel's Math Kernel Library, Apple's Accelerate, and others.

Most of the fitting methods below support both the natural and flattened data layouts. We provide the stack_models method to produce the optimized layout for the list of model templates.

StarFormationHistories.stack_modelsFunction
stack_models(models::AbstractVector{<:AbstractMatrix{<:Number}}) = reduce(hcat,map(vec,models))

Transforms a vector of matrices into a single matrix, with each matrix from models being transcribed into a single column in the output matrix. This data layout enables more efficient calculations in some of our internal functions like composite! and ∇loglikelihood!.

Examples

julia> stack_models([rand(5,5) for i in 1:10])
 25×10 Matrix{Float64}:
-...
source

A Note on Threading

While generally the methods using BLAS routines offer significant performance improvements, there is a caveat when multithreading from within Julia. By default Julia will allow BLAS to use multiple threads even if Julia itself is started with a single thread (i.e., by running julia -t 1). BLAS threads do not compose with Julia threads. That is, if you start Julia with N>1 threads (julia -t N) and write a threaded workload where each Julia thread is doing BLAS operations concurrently, you can easily oversubscribe the CPU. Specific recommendations vary depending on BLAS vendor (see this page and the linked discourse threads), but generally this package is in the regime of doing many small calculations that do not individually benefit much from BLAS threading (e.g., performance for OpenBLAS with 8 threads is only ~2x the 1 thread performance). As such it is often sufficient to set BLAS to use a single thread (via LinearAlgbera.BLAS.set_num_threads(1) or environment variables; see above link).

+...source

A Note on Threading

While generally the methods using BLAS routines offer significant performance improvements, there is a caveat when multithreading from within Julia. By default Julia will allow BLAS to use multiple threads even if Julia itself is started with a single thread (i.e., by running julia -t 1). BLAS threads do not compose with Julia threads. That is, if you start Julia with N>1 threads (julia -t N) and write a threaded workload where each Julia thread is doing BLAS operations concurrently, you can easily oversubscribe the CPU. Specific recommendations vary depending on BLAS vendor (see this page and the linked discourse threads), but generally this package is in the regime of doing many small calculations that do not individually benefit much from BLAS threading (e.g., performance for OpenBLAS with 8 threads is only ~2x the 1 thread performance). As such it is often sufficient to set BLAS to use a single thread (via LinearAlgbera.BLAS.set_num_threads(1) or environment variables; see above link).

diff --git a/dev/fitting/fixed_amr/index.html b/dev/fitting/fixed_amr/index.html index 79fe5cf..514c0b5 100644 --- a/dev/fitting/fixed_amr/index.html +++ b/dev/fitting/fixed_amr/index.html @@ -14,4 +14,4 @@ relweights::AbstractVector{<:Number}; relweightsmin::Number=0, x0=construct_x0_mdf(logAge, convert(S,13.7)), - kws...) where S <: Number

Method that fits a linear combination of the provided Hess diagrams models to the observed Hess diagram data, under an externally-imposed age-metallicity relation (AMR) and/or metallicity distribution function (MDF). As such, a number of coefficients equal to length(unique(logAge)) are returned; that is, only one coefficient is derived per unique entry in logAge.

The second call signature supports the flattened formats for models and data. See the notes for the flattened call signature of StarFormationHistories.composite! for more details.

Arguments

Keyword Arguments

Other kws... are passed to Optim.options to set things like convergence criteria for the optimization.

Notes

source + kws...) where S <: Number

Method that fits a linear combination of the provided Hess diagrams models to the observed Hess diagram data, under an externally-imposed age-metallicity relation (AMR) and/or metallicity distribution function (MDF). As such, a number of coefficients equal to length(unique(logAge)) are returned; that is, only one coefficient is derived per unique entry in logAge.

The second call signature supports the flattened formats for models and data. See the notes for the flattened call signature of StarFormationHistories.composite! for more details.

Arguments

Keyword Arguments

Other kws... are passed to Optim.options to set things like convergence criteria for the optimization.

Notes

source diff --git a/dev/fitting/internals/index.html b/dev/fitting/internals/index.html index 91479e0..e34ff86 100644 --- a/dev/fitting/internals/index.html +++ b/dev/fitting/internals/index.html @@ -7,7 +7,7 @@ julia> coeffs = rand(length(models)); julia> composite!(C, coeffs, models); julia> C ≈ sum( coeffs .* models) -truesource
composite!(composite::AbstractVector{<:Number},
+true
source
composite!(composite::AbstractVector{<:Number},
            coeffs::AbstractVector{<:Number},
            models::AbstractMatrix{<:Number})

Updates the composite vector with the matrix-vector product of models * coeffs. This is equation 1 in Dolphin 2002, $m_i = \sum_j \, r_j \, c_{i,j}$.

Examples

julia> hist_size = (5,10);
 julia> models = reduce(hcat,rand(prod(hist_size)) for i in 1:20);
@@ -15,11 +15,11 @@
 julia> C = zeros(length(axes(models,1)));
 julia> composite!(C, coeffs, models);
 julia> C ≈ models * coeffs
-true

Notes

While the other call signature for this function more closely mirrors the natural data structure for Hess diagrams (2D matrices for composite and each entry in models), this method operates on the same data but flattened. Thus composite becomes a vector rather than a matrix and models becomes a single matrix rather than a vector of matrices. The method StarFormationHistories.stack_models is provided to stack the models into this format. This data layout enables us to use the highly optimized LinearAlgebra.mul! function to perform the matrix-vector product which typically achieves >30% speedup relative to the more natural formulation. Additionally, as mul! will typically call to a BLAS matrix-vector product function like gemv! for our use-case, we can switch out Julia's default OpenBLAS at runtime for other BLAS libraries with Julia bindings like MKL and Apple Accelerate, enabling even greater performance improvements.

source
StarFormationHistories.loglikelihoodFunction
loglikelihood(composite::AbstractArray{<:Number}, data::AbstractArray{<:Number})

Returns the logarithm of the Poisson likelihood ratio given by equation 10 in Dolphin 2002,

\[\text{ln} \, \mathscr{L} = \sum_i -m_i + n_i \times \left( 1 - \text{ln} \, \left( \frac{n_i}{m_i} \right) \right)\]

with composite being the complex Hess model diagram $m_i$ (see StarFormationHistories.composite!) and data being the observed Hess diagram $n_i$.

Performance Notes

  • ~18.57 μs for composite=Matrix{Float64}(undef,99,99) and data=similar(composite).
  • ~20 μs for composite=Matrix{Float64}(undef,99,99) and data=Matrix{Int64}(undef,99,99).
  • ~9.3 μs for composite=Matrix{Float32}(undef,99,99) and data=similar(composite).
  • ~9.6 μs for composite=Matrix{Float32}(undef,99,99) and data=Matrix{Int64}(undef,99,99).
source
loglikelihood(coeffs::AbstractVector{<:Number}, models::AbstractVector{T}, data::AbstractMatrix{<:Number}) where T <: AbstractMatrix{<:Number}
-loglikelihood(coeffs::AbstractVector{<:Number}, models::AbstractMatrix{<:Number}, data::AbstractVector{<:Number})

Returns the logarithm of the Poisson likelihood ratio, but constructs the complex Hess diagram model as sum(coeffs .* models) rather than taking composite directly as an argument. Second call signature supports the flattened formats for models and data. See the notes for the flattened call signature of StarFormationHistories.composite! for more details.

source
StarFormationHistories.∇loglikelihoodFunction
∇loglikelihood(model::AbstractArray{<:Number}, composite::AbstractArray{<:Number}, data::AbstractArray{<:Number})

Returns the partial derivative of the logarithm of the Poisson likelihood ratio (StarFormationHistories.loglikelihood) with respect to the coefficient $r_j$ on the provided model. If the complex Hess diagram model is $m_i = \sum_j \, r_j \, c_{i,j}$, then model is $c_{i,j}$, and this function computes the partial derivative of $\text{log} \, \mathscr{L}$ with respect to the coefficient $r_j$. This is given by equation 21 in Dolphin 2002,

\[\frac{\partial \, \text{log} \, \mathscr{L}}{\partial \, r_j} = \sum_i c_{i,j} \left( \frac{n_i}{m_i} - 1 \right)\]

where $n_i$ is bin $i$ of the observed Hess diagram data.

Performance Notes

  • ~4.1 μs for model, composite, data all being Matrix{Float64}(undef,99,99).
  • ~1.3 μs for model, composite, data all being Matrix{Float32}(undef,99,99).
source
∇loglikelihood(models::AbstractVector{T}, composite::AbstractMatrix{<:Number}, data::AbstractMatrix{<:Number}) where T <: AbstractMatrix{<:Number}
-∇loglikelihood(models::AbstractMatrix{<:Number}, composite::AbstractVector{<:Number}, data::AbstractVector{<:Number})

Computes the gradient of the logarithm of the Poisson likelihood ratio with respect to the coefficients by calling the single-model ∇loglikelihood for every model in models. Second call signature supports the flattened formats for models and data. See the notes for the flattened call signature of StarFormationHistories.composite! for more details.

source
∇loglikelihood(coeffs::AbstractVector{<:Number}, models::AbstractVector{T}, data::AbstractMatrix{<:Number}) where T <: AbstractMatrix{<:Number}
-∇loglikelihood(coeffs::AbstractVector{<:Number}, models::AbstractMatrix{<:Number}, data::AbstractVector{<:Number})

Forms the composite matrix from coefficients coeffs and model templates models and returns the gradient of the loglikelihood with respect to the coefficients. Second call signature supports the flattened formats for models and data. See the notes for the flattened call signature of StarFormationHistories.composite! for more details.

source
StarFormationHistories.∇loglikelihood!Function
 ∇loglikelihood!(G::AbstractVector, composite::AbstractMatrix{<:Number}, models::AbstractVector{S}, data::AbstractMatrix{<:Number}) where S <: AbstractMatrix{<:Number}

Efficiently computes the gradient of StarFormationHistories.loglikelihood with respect to all coefficients by updating G with the gradient. This will overwrite composite with the result of 1 .- (data ./ composite) so it shouldn't be reused after being passed to this function.

Arguments

  • G::AbstractVector is the vector that will be mutated in-place with the computed gradient values.
  • models::AbstractVector{<:AbstractMatrix{<:Number}} is the vector of matrices giving the model Hess diagrams.
  • composite::AbstractMatrix{<:Number} is a matrix that contains the composite model sum(coeffs .* models).
  • data::AbstractMatrix{<:Number} contains the observed Hess diagram that is being fit.
source
G = ∇loglikelihood!(G::AbstractVector, composite::AbstractVector{<:Number}, models::AbstractMatrix{<:Number}, data::AbstractVector{<:Number})

Updates and returns G with the gradient of the loglikelihood with respect to all coefficients. This call signature supports the flattened formats for models and data. See the notes for the flattened call signature of StarFormationHistories.composite! for more details.

source
StarFormationHistories.fg!Function
-logL = fg!(F, G, coeffs::AbstractVector{<:Number}, models::AbstractVector{T}, data::AbstractMatrix{<:Number}, composite::AbstractMatrix{<:Number}) where T <: AbstractMatrix{<:Number}
--logL = fg!(F, G, coeffs::AbstractVector{<:Number}, models::AbstractMatrix{<:Number}, data::AbstractVector{<:Number}, composite::AbstractVector{<:Number})

Computes -loglikelihood and its gradient simultaneously for use with Optim.jl and other optimization APIs. See documentation here. Second call signature supports the flattened formats for models and data. See the notes for the flattened call signature of StarFormationHistories.composite! for more details.

source
StarFormationHistories.truncate_relweightsFunction
keep_idx::Vector{Int} = truncate_relweights(relweightsmin::Number, relweights::AbstractVector{<:Number}, logAge::AbstractVector{<:Number})

Method to truncate an isochrone grid with log10(age [yr]) values logAge and relative weights relweights due to an age-metallicity relation to only include models with relweights greater than relweightsmin times the maximum relative weight for each unique entry in logAge. The input vectors are the same as those for StarFormationHistories.fixed_amr, which includes more information. Returns a vector of the indices into relweights and logAge of the isochrone models whose relative weights are significant given the provided relweightsmin.

Examples

When using a fixed input age-metallicity relation as enabled by, for example, StarFormationHistories.fixed_amr, only the star formation rate (or total stellar mass) coefficients need to be fit, as the metallicity distribution is no longer a free parameter in the model. As such, the relative weights of each model with identical logAge but different metallicities only need to be computed once at the start of the optimization. As the metallicity distribution is not a free parameter, it is also possible to truncate the list of models to only those that contribute significantly to the final composite model to improve runtime performance. That is what this method does.

A simple isochrone grid will be two-dimensional, encompassing age and metallicity. Consider a subset of the model grid with the same age such that unique(logAge) = [10.0] but a series of different metallicities, metallicities = -2.5:0.25:0. If we model the metallicity distribution function for this age as having a mean [M/H] of -2.0 and a Gaussian spread of 0.2 dex, then the relative weights of these models can be approximated as

import Distributions: Normal, pdf
+true

Notes

While the other call signature for this function more closely mirrors the natural data structure for Hess diagrams (2D matrices for composite and each entry in models), this method operates on the same data but flattened. Thus composite becomes a vector rather than a matrix and models becomes a single matrix rather than a vector of matrices. The method StarFormationHistories.stack_models is provided to stack the models into this format. This data layout enables us to use the highly optimized LinearAlgebra.mul! function to perform the matrix-vector product which typically achieves >30% speedup relative to the more natural formulation. Additionally, as mul! will typically call to a BLAS matrix-vector product function like gemv! for our use-case, we can switch out Julia's default OpenBLAS at runtime for other BLAS libraries with Julia bindings like MKL and Apple Accelerate, enabling even greater performance improvements.

source
StarFormationHistories.loglikelihoodFunction
loglikelihood(composite::AbstractArray{<:Number}, data::AbstractArray{<:Number})

Returns the logarithm of the Poisson likelihood ratio given by equation 10 in Dolphin 2002,

\[\text{ln} \, \mathscr{L} = \sum_i -m_i + n_i \times \left( 1 - \text{ln} \, \left( \frac{n_i}{m_i} \right) \right)\]

with composite being the complex Hess model diagram $m_i$ (see StarFormationHistories.composite!) and data being the observed Hess diagram $n_i$.

Performance Notes

  • ~18.57 μs for composite=Matrix{Float64}(undef,99,99) and data=similar(composite).
  • ~20 μs for composite=Matrix{Float64}(undef,99,99) and data=Matrix{Int64}(undef,99,99).
  • ~9.3 μs for composite=Matrix{Float32}(undef,99,99) and data=similar(composite).
  • ~9.6 μs for composite=Matrix{Float32}(undef,99,99) and data=Matrix{Int64}(undef,99,99).
source
loglikelihood(coeffs::AbstractVector{<:Number}, models::AbstractVector{T}, data::AbstractMatrix{<:Number}) where T <: AbstractMatrix{<:Number}
+loglikelihood(coeffs::AbstractVector{<:Number}, models::AbstractMatrix{<:Number}, data::AbstractVector{<:Number})

Returns the logarithm of the Poisson likelihood ratio, but constructs the complex Hess diagram model as sum(coeffs .* models) rather than taking composite directly as an argument. Second call signature supports the flattened formats for models and data. See the notes for the flattened call signature of StarFormationHistories.composite! for more details.

source
StarFormationHistories.∇loglikelihoodFunction
∇loglikelihood(model::AbstractArray{<:Number}, composite::AbstractArray{<:Number}, data::AbstractArray{<:Number})

Returns the partial derivative of the logarithm of the Poisson likelihood ratio (StarFormationHistories.loglikelihood) with respect to the coefficient $r_j$ on the provided model. If the complex Hess diagram model is $m_i = \sum_j \, r_j \, c_{i,j}$, then model is $c_{i,j}$, and this function computes the partial derivative of $\text{log} \, \mathscr{L}$ with respect to the coefficient $r_j$. This is given by equation 21 in Dolphin 2002,

\[\frac{\partial \, \text{log} \, \mathscr{L}}{\partial \, r_j} = \sum_i c_{i,j} \left( \frac{n_i}{m_i} - 1 \right)\]

where $n_i$ is bin $i$ of the observed Hess diagram data.

Performance Notes

  • ~4.1 μs for model, composite, data all being Matrix{Float64}(undef,99,99).
  • ~1.3 μs for model, composite, data all being Matrix{Float32}(undef,99,99).
source
∇loglikelihood(models::AbstractVector{T}, composite::AbstractMatrix{<:Number}, data::AbstractMatrix{<:Number}) where T <: AbstractMatrix{<:Number}
+∇loglikelihood(models::AbstractMatrix{<:Number}, composite::AbstractVector{<:Number}, data::AbstractVector{<:Number})

Computes the gradient of the logarithm of the Poisson likelihood ratio with respect to the coefficients by calling the single-model ∇loglikelihood for every model in models. Second call signature supports the flattened formats for models and data. See the notes for the flattened call signature of StarFormationHistories.composite! for more details.

source
∇loglikelihood(coeffs::AbstractVector{<:Number}, models::AbstractVector{T}, data::AbstractMatrix{<:Number}) where T <: AbstractMatrix{<:Number}
+∇loglikelihood(coeffs::AbstractVector{<:Number}, models::AbstractMatrix{<:Number}, data::AbstractVector{<:Number})

Forms the composite matrix from coefficients coeffs and model templates models and returns the gradient of the loglikelihood with respect to the coefficients. Second call signature supports the flattened formats for models and data. See the notes for the flattened call signature of StarFormationHistories.composite! for more details.

source
StarFormationHistories.∇loglikelihood!Function
 ∇loglikelihood!(G::AbstractVector, composite::AbstractMatrix{<:Number}, models::AbstractVector{S}, data::AbstractMatrix{<:Number}) where S <: AbstractMatrix{<:Number}

Efficiently computes the gradient of StarFormationHistories.loglikelihood with respect to all coefficients by updating G with the gradient. This will overwrite composite with the result of 1 .- (data ./ composite) so it shouldn't be reused after being passed to this function.

Arguments

  • G::AbstractVector is the vector that will be mutated in-place with the computed gradient values.
  • models::AbstractVector{<:AbstractMatrix{<:Number}} is the vector of matrices giving the model Hess diagrams.
  • composite::AbstractMatrix{<:Number} is a matrix that contains the composite model sum(coeffs .* models).
  • data::AbstractMatrix{<:Number} contains the observed Hess diagram that is being fit.
source
G = ∇loglikelihood!(G::AbstractVector, composite::AbstractVector{<:Number}, models::AbstractMatrix{<:Number}, data::AbstractVector{<:Number})

Updates and returns G with the gradient of the loglikelihood with respect to all coefficients. This call signature supports the flattened formats for models and data. See the notes for the flattened call signature of StarFormationHistories.composite! for more details.

source
StarFormationHistories.fg!Function
-logL = fg!(F, G, coeffs::AbstractVector{<:Number}, models::AbstractVector{T}, data::AbstractMatrix{<:Number}, composite::AbstractMatrix{<:Number}) where T <: AbstractMatrix{<:Number}
+-logL = fg!(F, G, coeffs::AbstractVector{<:Number}, models::AbstractMatrix{<:Number}, data::AbstractVector{<:Number}, composite::AbstractVector{<:Number})

Computes -loglikelihood and its gradient simultaneously for use with Optim.jl and other optimization APIs. See documentation here. Second call signature supports the flattened formats for models and data. See the notes for the flattened call signature of StarFormationHistories.composite! for more details.

source
StarFormationHistories.truncate_relweightsFunction
keep_idx::Vector{Int} = truncate_relweights(relweightsmin::Number, relweights::AbstractVector{<:Number}, logAge::AbstractVector{<:Number})

Method to truncate an isochrone grid with log10(age [yr]) values logAge and relative weights relweights due to an age-metallicity relation to only include models with relweights greater than relweightsmin times the maximum relative weight for each unique entry in logAge. The input vectors are the same as those for StarFormationHistories.fixed_amr, which includes more information. Returns a vector of the indices into relweights and logAge of the isochrone models whose relative weights are significant given the provided relweightsmin.

Examples

When using a fixed input age-metallicity relation as enabled by, for example, StarFormationHistories.fixed_amr, only the star formation rate (or total stellar mass) coefficients need to be fit, as the metallicity distribution is no longer a free parameter in the model. As such, the relative weights of each model with identical logAge but different metallicities only need to be computed once at the start of the optimization. As the metallicity distribution is not a free parameter, it is also possible to truncate the list of models to only those that contribute significantly to the final composite model to improve runtime performance. That is what this method does.

A simple isochrone grid will be two-dimensional, encompassing age and metallicity. Consider a subset of the model grid with the same age such that unique(logAge) = [10.0] but a series of different metallicities, metallicities = -2.5:0.25:0. If we model the metallicity distribution function for this age as having a mean [M/H] of -2.0 and a Gaussian spread of 0.2 dex, then the relative weights of these models can be approximated as

import Distributions: Normal, pdf
 metallicities = -2.5:0.25:0
 relweights = pdf.(Normal(-2.0, 0.2), metallicities)
 relweights ./= sum(relweights) # Normalize the relative weights to unity sum
11-element Vector{Float64}:
@@ -36,4 +36,4 @@
  9.622444440364979e-23

Several of these models with very low relative weights are unlikely to contribute significantly to the final composite model. We can select out only the significant ones with, say, relative weights greater than 10% of the maximum as StarFormationHistories.truncate_relweights(0.1, relweights, fill(10.0,length(metallicities))) which will return indices into relweights whose values are greater than 0.1 * maximum(relweights) = 0.04988954088848224,

3-element Vector{Int64}:
  2
  3
- 4

which correspond to relweights[2,3,4] = [ 0.2284109622221623, 0.4988954088848224, 0.2284109622221623 ]. If we use only these 3 templates in the fit, instead of the original 11, we will achieve a speedup of almost 4x with a minor loss in precision which, in most cases, will be less than the numerical uncertainties on the individual star formation rate parameters. However, as fits of these sort are naturally quite fast, we recommend use of this type of truncation only in applications where many fits are required (e.g., Monte Carlo experiments). For most applications, this level of optimization is not necessary.

source
StarFormationHistories.calculate_edgesFunction
calculate_edges(edges, xlim, ylim, nbins, xwidth, ywidth)

Function to calculate the bin edges for 2D histograms. Returns (xbins, ybins) with both entries being ranges.

Keyword Arguments

  • edges is a tuple of vectors-like objects defining the left-side edges of the bins along the x-axis (edges[1]) and the y-axis (edges[2]). Example: (-1.0:0.1:1.5, 22:0.1:27.2). If edges is provided, it will simply be returned.
  • xlim; a length-2 indexable object (e.g., a Vector{Float64} or NTuple{2,Float64)) giving the lower and upper bounds on the x-axis corresponding to the provided colors array. Example: [-1.0, 1.5]. This is only used if edges==nothing.
  • ylim; as xlim but for the y-axis corresponding to the provided mags array. Example [25, 20]. This is only used if edges==nothing.
  • nbins::NTuple{2,<:Integer} is a 2-tuple of integers providing the number of bins to use along the x- and y-axes. This is only used if edges==nothing.
  • xwidth; the bin width along the x-axis for the colors array. This is only used if edges==nothing and nbins==nothing. Example: 0.1.
  • ywidth; as xwidth but for the y-axis corresponding to the provided mags array. Example: 0.1.
source
+ 4

which correspond to relweights[2,3,4] = [ 0.2284109622221623, 0.4988954088848224, 0.2284109622221623 ]. If we use only these 3 templates in the fit, instead of the original 11, we will achieve a speedup of almost 4x with a minor loss in precision which, in most cases, will be less than the numerical uncertainties on the individual star formation rate parameters. However, as fits of these sort are naturally quite fast, we recommend use of this type of truncation only in applications where many fits are required (e.g., Monte Carlo experiments). For most applications, this level of optimization is not necessary.

source
StarFormationHistories.calculate_edgesFunction
calculate_edges(edges, xlim, ylim, nbins, xwidth, ywidth)

Function to calculate the bin edges for 2D histograms. Returns (xbins, ybins) with both entries being ranges.

Keyword Arguments

  • edges is a tuple of vectors-like objects defining the left-side edges of the bins along the x-axis (edges[1]) and the y-axis (edges[2]). Example: (-1.0:0.1:1.5, 22:0.1:27.2). If edges is provided, it will simply be returned.
  • xlim; a length-2 indexable object (e.g., a Vector{Float64} or NTuple{2,Float64)) giving the lower and upper bounds on the x-axis corresponding to the provided colors array. Example: [-1.0, 1.5]. This is only used if edges==nothing.
  • ylim; as xlim but for the y-axis corresponding to the provided mags array. Example [25, 20]. This is only used if edges==nothing.
  • nbins::NTuple{2,<:Integer} is a 2-tuple of integers providing the number of bins to use along the x- and y-axes. This is only used if edges==nothing.
  • xwidth; the bin width along the x-axis for the colors array. This is only used if edges==nothing and nbins==nothing. Example: 0.1.
  • ywidth; as xwidth but for the y-axis corresponding to the provided mags array. Example: 0.1.
source
diff --git a/dev/fitting/linear_amr/index.html b/dev/fitting/linear_amr/index.html index c1a5fed..983e52e 100644 --- a/dev/fitting/linear_amr/index.html +++ b/dev/fitting/linear_amr/index.html @@ -19,7 +19,7 @@ [, σ::Number]; x0 = vcat(construct_x0_mdf(logAge, convert(S,13.7)), [0.05, -2.0, 0.2]), - kws...) where {S <: Number}

Method that fits a linear combination of the provided Hess diagrams models to the observed Hess diagram data, constrained to have a linear age-metallicity relation with the mean metallicity of element i of unique(logAge) being μ[i] = α * (T_max - exp10(unique(logAge)[i]) / 1e9) + β. α is therefore a slope in the units of metallicities per Gyr, and β is the mean metallicity value of stars being born at a lookback time of T_max, which has units of Gyr. Individual weights for each isochrone template are then determined via Gaussian weighting with the above mean and the standard deviation σ, which can either be fixed or fit.

This function is designed to work best with a "grid" of stellar models, defined by the outer product of N unique entries in logAge and M unique entries in metallicities. See the examples for more information on usage.

The second call signature supports the flattened formats for models and data. See the notes for the flattened call signature of StarFormationHistories.composite! for more details.

Arguments

Optional Arguments

Keyword Arguments

Returns

Notes

source
StarFormationHistories.LogTransformMDFσResultType
LogTransformMDFσResult(μ::AbstractVector{<:Number},
+                  kws...) where {S <: Number}

Method that fits a linear combination of the provided Hess diagrams models to the observed Hess diagram data, constrained to have a linear age-metallicity relation with the mean metallicity of element i of unique(logAge) being μ[i] = α * (T_max - exp10(unique(logAge)[i]) / 1e9) + β. α is therefore a slope in the units of metallicities per Gyr, and β is the mean metallicity value of stars being born at a lookback time of T_max, which has units of Gyr. Individual weights for each isochrone template are then determined via Gaussian weighting with the above mean and the standard deviation σ, which can either be fixed or fit.

This function is designed to work best with a "grid" of stellar models, defined by the outer product of N unique entries in logAge and M unique entries in metallicities. See the examples for more information on usage.

The second call signature supports the flattened formats for models and data. See the notes for the flattened call signature of StarFormationHistories.composite! for more details.

Arguments

  • models are the template Hess diagrams for the simple stellar populations that compose the observed Hess diagram.
  • data is the Hess diagram for the observed data.
  • logAge::AbstractVector{<:Number} is the vector containing the effective ages of the stellar populations used to create the templates in models, in units of log10(age [yr]). For example, if a population has an age of 1 Myr, its entry in logAge should be log10(10^6) = 6.0.
  • metallicities::AbstractVector{<:Number} is the vector containing the effective metallicities of the stellar populations used to create the templates in models. This is most commonly a logarithmic abundance like [M/H] or [Fe/H], but you could use a linear abundance like the metal mass fraction Z if you wanted to. There are some notes on the Wikipedia that might be useful.
  • T_max::Number is the time at which the age-metallicity relation has a value of eta in Gyr. For example, if the oldest stellar populations in your isochrone grid are 12 Gyr old, you could set T_max = 12.0.

Optional Arguments

  • If provided, σ::Number is the fixed width of the Gaussian the defines the metallicity distribution function (MDF) at fixed logAge. If this argument is omitted, σ will be a free parameter in the fit.

Keyword Arguments

  • x0 is the vector of initial guesses for the stellar mass coefficients per unique entry in logAge, plus the variables that define the metallicity evolution model. You should basically always be calculating and passing this keyword argument. We provide StarFormationHistories.construct_x0_mdf to prepare the first part of x0 assuming constant star formation rate, which is typically a good initial guess. You then have to concatenate that result with an initial guess for the metallicity evolution parameters. For example, x0=vcat(construct_x0_mdf(logAge, 13.7; normalize_value=1e4), [0.05,-2.0,0.2]), where logAge is a valid argument for this function (see above), and the initial guesses on the parameters are [α, β, σ] = [0.05, -2.0, 0.2]. If the provided metallicities are, for example, [M/H] values, then this mean metallicity evolution is μ(t) [dex] = 0.05 [dex/Gyr] * (T_max - t) [Gyr] - 2.0 [dex], and at fixed time, the metallicity distribution function is Gaussian with mean μ(t) and standard deviation σ. If you provide σ as an optional argument, then you should not include an entry for it in x0.
  • Other kws... are passed to Optim.options to set things like convergence criteria for the optimization.

Returns

  • This function returns an object (say, result) of similar structure to the object returned by fit_templates. Specifically, this method will return a NamedTuple with entries result.mle and result.map for the maximum likelihood and maximum a posteriori estimates, respectively. If you provide a fixed σ, those objects will be instances of StarFormationHistories.LogTransformMDFσResult. If you allow σ to be freely fit, those objects will be instances of StarFormationHistories.LogTransformMDFResult. Both of these types support sampling via, e.g., rand(result.map, 10).

Notes

  • α and σ are optimized under a logarithmic transformation, so they are constrained to be positive. β is not and may be negative. This method also uses the BFGS method from Optim.jl internally just like fit_templates; please see the notes section of that method.
source
StarFormationHistories.LogTransformMDFσResultType
LogTransformMDFσResult(μ::AbstractVector{<:Number},
                        σ::AbstractVector{<:Number},
                        invH::AbstractMatrix{<:Number},
                        result)

Type for containing the maximum likelihood estimate (MLE) and maximum a posteriori (MAP) results from fit_templates_mdf for fixed σ. The fitted coefficients are available in the μ field. Estimates of the standard errors are available in the σ field. These have both been transformed from the native logarithmic fitting space into natural units (i.e., stellar mass or star formation rate). The linear age-metallicity relation parameters α (slope [dex/Gyr]) and β (intercept at T_max [dex]) are available in the second-to-last and last elements of μ and σ, respectively.

invH contains the estimated inverse Hessian of the likelihood / posterior at the maximum point in the logarithmic fitting units. result is the full result object returned by the optimization routine.

This type is implemented as a subtype of Distributions.Sampleable{Multivariate, Continuous} to enable sampling from an estimate of the likelihood / posterior distribution. We approximate the distribution as a multivariate Gaussian in the native (logarithmically transformed) fitting variables with covariance matrix invH and means log.(μ). We find this approximation is good for the MAP result but less robust for the MLE. You can obtain N::Integer samples from the distribution by rand(R, N) where R is an instance of this type; this will return a size (length(μ)+2) x N matrix, or fail if invH is not positive definite.

Examples

julia> result = fit_templates_mdf(models, data, model_logAge, model_MH, 0.3);
@@ -28,7 +28,7 @@
 StarFormationHistories.LogTransformMDFσResult{...}
 
 julia> size(rand(result.map, 3)) == (length(models)+2,3)
-true
source
StarFormationHistories.LogTransformMDFResultType
LogTransformMDFResult(μ::AbstractVector{<:Number},
+true
source
StarFormationHistories.LogTransformMDFResultType
LogTransformMDFResult(μ::AbstractVector{<:Number},
                       σ::AbstractVector{<:Number},
                       invH::AbstractMatrix{<:Number},
                       result)

Type for containing the maximum likelihood estimate (MLE) and maximum a posteriori (MAP) results from fit_templates_mdf when freely fitting σ. The fitted coefficients are available in the μ field. Estimates of the standard errors are available in the σ field. These have both been transformed from the native logarithmic fitting space into natural units (i.e., stellar mass or star formation rate). The linear age-metallicity relation parameters α (slope [dex/Gyr]) and β (intercept at T_max [dex]) are available in the third-to-last and second-to-last elements of μ and σ, respectively. The static Gaussian width of the MDF at fixed age is provided in the last element of μ and σ.

invH contains the estimated inverse Hessian of the likelihood / posterior at the maximum point in the logarithmic fitting units. result is the full result object returned by the optimization routine.

This type is implemented as a subtype of Distributions.Sampleable{Multivariate, Continuous} to enable sampling from an estimate of the likelihood / posterior distribution. We approximate the distribution as a multivariate Gaussian in the native (logarithmically transformed) fitting variables with covariance matrix invH and means log.(μ). We find this approximation is good for the MAP result but less robust for the MLE. You can obtain N::Integer samples from the distribution by rand(R, N) where R is an instance of this type; this will return a size (length(μ)+3) x N matrix, or fail if invH is not positive definite.

Examples

julia> result = fit_templates_mdf(models, data, model_logAge, model_MH);
@@ -37,7 +37,7 @@
 StarFormationHistories.LogTransformMDFσResult{...}
 
 julia> size(rand(result.map, 3)) == (length(models)+3,3)
-true
source

The method StarFormationHistories.construct_x0_mdf can be used to construct the stellar mass components $R_j$ of the initial guess vector x0

StarFormationHistories.construct_x0_mdfFunction
x0::Vector = construct_x0_mdf(logAge::AbstractVector{T},
+true
source

The method StarFormationHistories.construct_x0_mdf can be used to construct the stellar mass components $R_j$ of the initial guess vector x0

StarFormationHistories.construct_x0_mdfFunction
x0::Vector = construct_x0_mdf(logAge::AbstractVector{T},
                               T_max::Number;
                               normalize_value::Number = one(T)) where T <: Number

Generates a vector of initial stellar mass normalizations for input to StarFormationHistories.fit_templates_mdf or StarFormationHistories.hmc_sample_mdf with a total stellar mass of normalize_value such that the implied star formation rate is constant across the provided logAge vector that contains the log10(Age [yr]) of each isochrone that you are going to input as models. For the purposes of computing the constant star formation rate, the provided logAge are treated as left-bin edges, with the final right-bin edge being T_max, which has units of Gyr. For example, you might have logAge=[6.6, 6.7, 6.8] in which case a final logAge of 6.9 would give equal bin widths. In this case you would set T_max = exp10(6.9) / 1e9 ≈ 0.0079 so that the width of the final bin for the star formation rate calculation has the same log10(Age [yr]) step as the other bins.

The difference between this function and StarFormationHistories.construct_x0 is that this function generates an x0 vector that is of length length(unique(logage)) (that is, a single normalization factor for each unique entry in logAge) while StarFormationHistories.construct_x0 returns an x0 vector that is of length length(logAge); that is, a normalization factor for every entry in logAge. The order of the coefficients is such that the coefficient x[i] corresponds to the entry unique(logAge)[i].

Notes

Examples

julia> construct_x0_mdf([9.0,8.0,7.0], 10.0; normalize_value=5.0)
 3-element Vector{Float64}:
@@ -52,7 +52,7 @@
  0.04504504504504504
 
 julia> construct_x0_mdf(repeat([9.0,8.0,7.0,8.0],3), 10.0; normalize_value=5.0) ≈ construct_x0([9.0,8.0,7.0], 10.0; normalize_value=5.0)
-true
source

and StarFormationHistories.calculate_coeffs_mdf can be used to calculate per-template stellar mass coefficients (the $r_{j,k}$ above) given the results of a fit (which will be the $R_j$ in the equations above)

StarFormationHistories.calculate_coeffs_mdfFunction
calculate_coeffs_mdf(variables::AbstractVector{<:Number},
+true
source

and StarFormationHistories.calculate_coeffs_mdf can be used to calculate per-template stellar mass coefficients (the $r_{j,k}$ above) given the results of a fit (which will be the $R_j$ in the equations above)

StarFormationHistories.calculate_coeffs_mdfFunction
calculate_coeffs_mdf(variables::AbstractVector{<:Number},
                      logAge::AbstractVector{<:Number},
                      metallicities::AbstractVector{<:Number},
                      T_max::Number
@@ -63,7 +63,7 @@
                      logAge::AbstractVector{<:Number},
                      metallicities::AbstractVector{<:Number},
                      T_max::Number)

Calculates per-model stellar mass coefficients coeffs from the fitting parameters of StarFormationHistories.fit_templates_mdf and StarFormationHistories.hmc_sample_mdf. The variables returned by these functions is of length length(unique(logAge))+3. The first length(logAge) entries are stellar mass coefficients, one per unique entry in logAge. The final three elements are α, β, and σ defining a metallicity evolution such that the mean for element i of unique(logAge) is μ[i] = α * (T_max - exp10(unique(logAge)[i]) / 1e9) + β. The individual weights per each isochrone are then determined via Gaussian weighting with the above mean and the provided σ. The second call signature can be used on samples that include α, β, and σ.

Examples

julia> calculate_coeffs_mdf([1,1], [7,7,8,8], [-2,-1,-2,-1], 12, 0.05, -2.0, 0.2) ≈ [ 0.07673913563377144, 0.9232608643662287, 0.08509904500701986, 0.9149009549929802 ]
-true
source

Sampling Methods

We additionally offer a sampling method for this linear age-metallicity relation using HMC:

StarFormationHistories.hmc_sample_mdfFunction
hmc_sample_mdf(models::AbstractVector{T},
+true
source

Sampling Methods

We additionally offer a sampling method for this linear age-metallicity relation using HMC:

StarFormationHistories.hmc_sample_mdfFunction
hmc_sample_mdf(models::AbstractVector{T},
                data::AbstractMatrix{<:Number},
                logAge::AbstractVector{<:Number},
                metallicities::AbstractVector{<:Number},
@@ -71,7 +71,7 @@
                nsteps::Integer;
                composite=Matrix{S}(undef,size(data)),
                rng::Random.AbstractRNG=Random.default_rng(),
-               kws...) where {S <: Number, T <: AbstractMatrix{S}}

Method to sample the posterior of the star formation history coefficients constrained to have a linear age-metallicity relation with the mean metallicity of element i of unique(logAge) being μ[i] = α * (T_max - exp10(unique(logAge)[i]) / 1e9) + β. α is therefore a slope in the units of metallicities per Gyr, and β is the mean metallicity value of stars born at lookback time T_max which has units of Gyr. Individual weights for each isochrone template are then determined via Gaussian weighting with the above mean and the standard deviation σ, which can either be fixed or fit. This method is essentially an analog of StarFormationHistories.fit_templates_mdf that samples the posterior rather than using optimization methods to find the maximum likelihood estimate. This method uses the No-U-Turn sampler as implemented in DynamicHMC.jl, which is a form of dynamic Hamiltonian Monte Carlo.

This function is designed to work best with a "grid" of stellar models, defined by the outer product of N unique entries in logAge and M unique entries in metallicities. See the examples for more information on usage.

Arguments

  • models::AbstractVector{<:AbstractMatrix{<:Number}} is a vector of equal-sized matrices that represent the template Hess diagrams for the simple stellar populations that compose the observed Hess diagram.
  • data::AbstractMatrix{<:Number} is the Hess diagram for the observed data.
  • logAge::AbstractVector{<:Number} is the vector containing the effective ages of the stellar populations used to create the templates in models, in units of log10(age [yr]). For example, if a population has an age of 1 Myr, its entry in logAge should be log10(10^6) = 6.0.
  • metallicities::AbstractVector{<:Number} is the vector containing the effective metallicities of the stellar populations used to create the templates in models. This is most commonly a logarithmic abundance like [M/H] or [Fe/H], but you could use a linear abundance like the metal mass fraction Z if you wanted to. There are some notes on the Wikipedia that might be useful.
  • T_max::Number is the time at which the age-metallicity relation has a value of eta in Gyr. For example, if the oldest stellar populations in your isochrone grid are 12 Gyr old, you could set T_max = 12.0.
  • nsteps::Integer is the number of samples to draw per chain.

Optional Arguments (NOT YET IMPLEMENTED)

  • nchains::Integer: If this argument is not provided, this method will return a single chain. If this argument is provided, it will sample nchains chains using all available threads and will return a vector of the individual chains. If nchains is set, composite must be a vector of matrices containing a working matrix for each chain.

Keyword Arguments

  • composite is the working matrix (or vector of matrices, if the argument nchains is provided) that will be used to store the composite Hess diagram model during computation; must be of the same size as the templates contained in models and the observed Hess diagram data.
  • rng::Random.AbstractRNG is the random number generator that will be passed to DynamicHMC.jl. If nchains is provided this method will attempt to sample in parallel, requiring a thread-safe rng such as that provided by Random.default_rng().

All other keyword arguments kws... will be passed to DynamicHMC.mcmc_with_warmup or DynamicHMC.mcmc_keep_warmup depending on whether nchains is provided.

Returns (NEEDS UPDATED)

  • If nchains is not provided, returns a NamedTuple as summarized in DynamicHMC.jl's documentation. In short, the matrix of samples can be extracted and transformed as exp.( result.posterior_matrix ). Statistics about the chain can be obtained with DynamicHMC.Diagnostics.summarize_tree_statistics(result.tree_statistics); you want to see a fairly high acceptance rate (>0.5) and the majority of samples having termination criteria being "turning." See DynamicHMC.jl's documentation for more information.
  • If nchains is provided, returns a vector of length nchains of the same NamedTuples described above. The samples from each chain in the returned vector can be stacked to a single (nsamples, nchains, length(models)) matrix with DynamicHMC.stack_posterior_matrices(result).
source

Implementation

While one could optimize the above model without an analytic gradient, such gradient-free methods are typically slower and less robust. One could also calculate the gradient numerically using finite differences or auto-differentiation, but these are still slower than analytic calculations. We will show that the gradient of this hierarchical model is analytic, allowing us to design an efficient optimization scheme.

Equation 21 in Dolphin 2001 gives the gradient of our objective function with respect to the underlying coefficients

\[\begin{aligned} + kws...) where {S <: Number, T <: AbstractMatrix{S}}

Method to sample the posterior of the star formation history coefficients constrained to have a linear age-metallicity relation with the mean metallicity of element i of unique(logAge) being μ[i] = α * (T_max - exp10(unique(logAge)[i]) / 1e9) + β. α is therefore a slope in the units of metallicities per Gyr, and β is the mean metallicity value of stars born at lookback time T_max which has units of Gyr. Individual weights for each isochrone template are then determined via Gaussian weighting with the above mean and the standard deviation σ, which can either be fixed or fit. This method is essentially an analog of StarFormationHistories.fit_templates_mdf that samples the posterior rather than using optimization methods to find the maximum likelihood estimate. This method uses the No-U-Turn sampler as implemented in DynamicHMC.jl, which is a form of dynamic Hamiltonian Monte Carlo.

This function is designed to work best with a "grid" of stellar models, defined by the outer product of N unique entries in logAge and M unique entries in metallicities. See the examples for more information on usage.

Arguments

Optional Arguments (NOT YET IMPLEMENTED)

Keyword Arguments

All other keyword arguments kws... will be passed to DynamicHMC.mcmc_with_warmup or DynamicHMC.mcmc_keep_warmup depending on whether nchains is provided.

Returns (NEEDS UPDATED)

source

Implementation

While one could optimize the above model without an analytic gradient, such gradient-free methods are typically slower and less robust. One could also calculate the gradient numerically using finite differences or auto-differentiation, but these are still slower than analytic calculations. We will show that the gradient of this hierarchical model is analytic, allowing us to design an efficient optimization scheme.

Equation 21 in Dolphin 2001 gives the gradient of our objective function with respect to the underlying coefficients

\[\begin{aligned} F \equiv - \text{ln} \, \mathscr{L} &= \sum_i m_i - n_i \times \left( 1 - \text{ln} \, \left( \frac{n_i}{m_i} \right) \right) \\ \frac{\partial \, F}{\partial \, r_{j,k}} &= \sum_i c_{i,j,k} \left( 1 - \frac{n_i}{m_i} \right) \end{aligned}\]

where $c_{i,j,k}$ is the value of template $j,k$ in bin $i$ and $n_i$ is bin $i$ of the observed Hess diagram. These partial derivatives are easy to obtain, but we need partials with respect to the per-age-bin fitting parameters $R_j$. Given the above relation between $r_{j,k}$ and $R_j$, we can calculate these derivatives as

\[\begin{aligned} @@ -93,4 +93,4 @@ \frac{\partial \, F}{\partial \, \sigma} &= \sum_{j,k} \frac{\partial \, F}{\partial \, r_{j,k}} \, \frac{\partial \, r_{j,k}}{\partial \, \sigma} \\ \frac{\partial \, r_{j,k}}{\partial \, \sigma} &= R_j \left( \frac{1}{\sum_k \, A_{j,k}} \, \frac{\partial \, A_{j,k}}{\partial \, \sigma} - \frac{A_{j,k}}{\left( \sum_k \, A_{j,k} \right)^2} \, \frac{\partial \, \sum_k \, A_{j,k}}{\partial \, \sigma} \right) \\ &= \frac{R_j}{\sum_k \, A_{j,k}} \left( \frac{\partial \, A_{j,k}}{\partial \, \sigma} - \frac{A_{j,k}}{\sum_k \, A_{j,k}} \sum_k \frac{\partial \, A_{j,k}}{\partial \, \sigma} \right) \\ -\end{aligned}\]

Then all we need is

\[\frac{\partial \, A_{j,k}}{\partial \, \sigma} = \frac{A_{j,k} \, \left( [\text{M}/\text{H}]_k - \mu_j \right)^2}{\sigma^3}\]

which we can substitute into the above expressions to find $\frac{\partial \, F}{\partial \, \sigma}$.

+\end{aligned}\]

Then all we need is

\[\frac{\partial \, A_{j,k}}{\partial \, \sigma} = \frac{A_{j,k} \, \left( [\text{M}/\text{H}]_k - \mu_j \right)^2}{\sigma^3}\]

which we can substitute into the above expressions to find $\frac{\partial \, F}{\partial \, \sigma}$.

diff --git a/dev/fitting/log_amr/index.html b/dev/fitting/log_amr/index.html index 0e7cb7a..244409b 100644 --- a/dev/fitting/log_amr/index.html +++ b/dev/fitting/log_amr/index.html @@ -7,7 +7,7 @@ metallicities::AbstractVector{<:Number}, T_max::Number [, α::Number, β::Number, σ::Number]; - MH_func = StarFormationHistories.MH_from_Z)

Calculates per-model stellar mass coefficients coeffs from the fitting parameters of StarFormationHistories.fit_templates_logamr and StarFormationHistories.hmc_sample_logamr. The variables returned by these functions is of length length(unique(logAge))+3. The first length(logAge) entries are stellar mass coefficients, one per unique entry in logAge. The final three elements are α, β, and σ defining a metallicity evolution such that the mean metal mass fraction Z for element i of unique(logAge) is μ_Z[i] = α * (T_max - exp10(unique(logAge)[i]) / 1e9) + β. This is converted to a mean metallicity in [M/H] via the provided callable keyword argument MH_func which defaults to StarFormationHistories.MH_from_Z. The individual weights per each isochrone are then determined via Gaussian weighting with the above mean [M/H] and the provided σ in dex. The provided metallicities vector should be in [M/H].

Notes

source

Fitting Functions

The main function we provide to fit star formation histories to Hess diagrams under the logarithmic age-metallicity relation is fit_templates_logamr. This function operates similarly to the fitting function for the linear AMR model, fit_templates_mdf.

StarFormationHistories.fit_templates_logamrFunction
result = fit_templates_logamr(models::AbstractVector{<:AbstractMatrix{S}},
+                        MH_func = StarFormationHistories.MH_from_Z)

Calculates per-model stellar mass coefficients coeffs from the fitting parameters of StarFormationHistories.fit_templates_logamr and StarFormationHistories.hmc_sample_logamr. The variables returned by these functions is of length length(unique(logAge))+3. The first length(logAge) entries are stellar mass coefficients, one per unique entry in logAge. The final three elements are α, β, and σ defining a metallicity evolution such that the mean metal mass fraction Z for element i of unique(logAge) is μ_Z[i] = α * (T_max - exp10(unique(logAge)[i]) / 1e9) + β. This is converted to a mean metallicity in [M/H] via the provided callable keyword argument MH_func which defaults to StarFormationHistories.MH_from_Z. The individual weights per each isochrone are then determined via Gaussian weighting with the above mean [M/H] and the provided σ in dex. The provided metallicities vector should be in [M/H].

Notes

  • Physically, the metal mass fraction Z must always be positive. Under the above model, this means α and β must be greater than or equal to 0. With σ being a Gaussian width, it must be positive.
  • If T_max is lower than the maximum of the logAge argument you provide, a warning will be raised which may be ignored if it does not result in any of the mean metal mass fractions Z being less than 0 for any of the provided logAge.
  • An error will be thrown if the provided age-metallicity relation variables (α, β) and T_max argument result in a mean metal mass fraction less than 0 for any time in the provided logAge vector.
source

Fitting Functions

The main function we provide to fit star formation histories to Hess diagrams under the logarithmic age-metallicity relation is fit_templates_logamr. This function operates similarly to the fitting function for the linear AMR model, fit_templates_mdf.

StarFormationHistories.fit_templates_logamrFunction
result = fit_templates_logamr(models::AbstractVector{<:AbstractMatrix{S}},
                               data::AbstractMatrix{<:Number},
                               logAge::AbstractVector{<:Number},
                               metallicities::AbstractVector{<:Number},
@@ -28,7 +28,7 @@
                                         [1e-4, 5e-5, 0.2]),
                               MH_func = StarFormationHistories.MH_from_Z,
                               MH_deriv_Z = StarFormationHistories.dMH_dZ,
-                              kws...) where {S <: Number}

Method that fits a linear combination of the provided Hess diagrams models to the observed Hess diagram data, constrained to have a logarithmic age-metallicity relation with the mean metal mass fraction μ_Z of element i of unique(logAge) being μ_Z[i] = α * (T_max - exp10(unique(logAge)[i]) / 1e9) + β. This is converted to a mean metallicity in [M/H] via the provided callable keyword argument MH_func which defaults to MH_from_Z. α is therefore a slope in the units of inverse Gyr, and β is the mean metal mass fraction of stars born at the earliest valid lookback time, determined by keyword argument T_max which has units of Gyr. Individual weights for each isochrone template are then determined via Gaussian weighting with the above mean [M/H] and the standard deviation σ in dex, which can either be fixed or fit.

This function is designed to work best with a "grid" of stellar models, defined by the outer product of N unique entries in logAge and M unique entries in metallicities. See the examples for more information on usage.

The second call signature supports the flattened formats for models and data. See the notes for the flattened call signature of StarFormationHistories.composite! for more details.

Arguments

  • models are the template Hess diagrams for the simple stellar populations that compose the observed Hess diagram.
  • data is the Hess diagram for the observed data.
  • logAge::AbstractVector{<:Number} is the vector containing the effective ages of the stellar populations used to create the templates in models, in units of log10(age [yr]). For example, if a population has an age of 1 Myr, its entry in logAge should be log10(10^6) = 6.0.
  • metallicities::AbstractVector{<:Number} is the vector containing the effective metallicities of the stellar populations used to create the templates in models. This should be a logarithmic abundance like [M/H] or [Fe/H].
  • T_max::Number is the maximum lookback time in Gyr for which the age-metallicity relation is to be valid. In most cases this should just be the maximum of the logAge vector argument maximum(logAge) or slightly larger. By definition, this is the lookback time at which μ_Z = β.

Optional Arguments

  • If provided, σ::Number is the fixed width of the Gaussian the defines the metallicity distribution function (MDF) at fixed logAge. If this argument is omitted, σ will be a free parameter in the fit.

Keyword Arguments

  • x0 is the vector of initial guesses for the stellar mass coefficients per unique entry in logAge, plus the variables that define the metallicity evolution model. You should basically always be calculating and passing this keyword argument. We provide construct_x0_mdf to prepare the first part of x0 assuming constant star formation rate, which is typically a good initial guess. You then have to concatenate that result with an initial guess for the metallicity evolution parameters. For example, x0=vcat(construct_x0_mdf(logAge, 13.7; normalize_value=1e4), [1e-4, 5e-5, 0.2]), where logAge is a valid argument for this function (see above), and the initial guesses on the parameters are [α, β, σ] = [1e-4, 5e-5, 0.2]. If you provide σ as an optional argument, then you should not include an entry for it in x0.
  • MH_func is a callable that takes a metal mass fraction Z and returns the logarithmic abundance [M/H]; by default uses MH_from_Z.
  • MH_deriv_Z is a callable that takes a metal mass fraction Zj and returns the derivative of MH_func with respect to the metal mass fraction Z evaluated at Zj. For the default value of MH_func, dMH_dZ provides the correct derivative. You only need to change this if you use an alternate MH_func.
  • Other kws... are passed to Optim.options to set things like convergence criteria for the optimization.
source

Sampling Functions

StarFormationHistories.hmc_sample_logamrFunction

Not yet implemented

source

Fixed Logarithmic Age-Metallicity Relation

We support fitting only the star formation parameters by adopting fixed values for $\alpha$, $\beta$, and $\sigma$ through the fixed_log_amr method.

StarFormationHistories.fixed_log_amrFunction
fixed_log_amr(models,
+                              kws...) where {S <: Number}

Method that fits a linear combination of the provided Hess diagrams models to the observed Hess diagram data, constrained to have a logarithmic age-metallicity relation with the mean metal mass fraction μ_Z of element i of unique(logAge) being μ_Z[i] = α * (T_max - exp10(unique(logAge)[i]) / 1e9) + β. This is converted to a mean metallicity in [M/H] via the provided callable keyword argument MH_func which defaults to MH_from_Z. α is therefore a slope in the units of inverse Gyr, and β is the mean metal mass fraction of stars born at the earliest valid lookback time, determined by keyword argument T_max which has units of Gyr. Individual weights for each isochrone template are then determined via Gaussian weighting with the above mean [M/H] and the standard deviation σ in dex, which can either be fixed or fit.

This function is designed to work best with a "grid" of stellar models, defined by the outer product of N unique entries in logAge and M unique entries in metallicities. See the examples for more information on usage.

The second call signature supports the flattened formats for models and data. See the notes for the flattened call signature of StarFormationHistories.composite! for more details.

Arguments

  • models are the template Hess diagrams for the simple stellar populations that compose the observed Hess diagram.
  • data is the Hess diagram for the observed data.
  • logAge::AbstractVector{<:Number} is the vector containing the effective ages of the stellar populations used to create the templates in models, in units of log10(age [yr]). For example, if a population has an age of 1 Myr, its entry in logAge should be log10(10^6) = 6.0.
  • metallicities::AbstractVector{<:Number} is the vector containing the effective metallicities of the stellar populations used to create the templates in models. This should be a logarithmic abundance like [M/H] or [Fe/H].
  • T_max::Number is the maximum lookback time in Gyr for which the age-metallicity relation is to be valid. In most cases this should just be the maximum of the logAge vector argument maximum(logAge) or slightly larger. By definition, this is the lookback time at which μ_Z = β.

Optional Arguments

  • If provided, σ::Number is the fixed width of the Gaussian the defines the metallicity distribution function (MDF) at fixed logAge. If this argument is omitted, σ will be a free parameter in the fit.

Keyword Arguments

  • x0 is the vector of initial guesses for the stellar mass coefficients per unique entry in logAge, plus the variables that define the metallicity evolution model. You should basically always be calculating and passing this keyword argument. We provide construct_x0_mdf to prepare the first part of x0 assuming constant star formation rate, which is typically a good initial guess. You then have to concatenate that result with an initial guess for the metallicity evolution parameters. For example, x0=vcat(construct_x0_mdf(logAge, 13.7; normalize_value=1e4), [1e-4, 5e-5, 0.2]), where logAge is a valid argument for this function (see above), and the initial guesses on the parameters are [α, β, σ] = [1e-4, 5e-5, 0.2]. If you provide σ as an optional argument, then you should not include an entry for it in x0.
  • MH_func is a callable that takes a metal mass fraction Z and returns the logarithmic abundance [M/H]; by default uses MH_from_Z.
  • MH_deriv_Z is a callable that takes a metal mass fraction Zj and returns the derivative of MH_func with respect to the metal mass fraction Z evaluated at Zj. For the default value of MH_func, dMH_dZ provides the correct derivative. You only need to change this if you use an alternate MH_func.
  • Other kws... are passed to Optim.options to set things like convergence criteria for the optimization.
source

Sampling Functions

StarFormationHistories.hmc_sample_logamrFunction

Not yet implemented

source

Fixed Logarithmic Age-Metallicity Relation

We support fitting only the star formation parameters by adopting fixed values for $\alpha$, $\beta$, and $\sigma$ through the fixed_log_amr method.

StarFormationHistories.fixed_log_amrFunction
fixed_log_amr(models,
               data,
               logAge::AbstractVector{<:Number},
               metallicities::AbstractVector{<:Number},
@@ -37,7 +37,7 @@
               β::Number,
               σ::Number;
               MH_func = StarFormationHistories.MH_from_Z,
-              kws...)

Given a fully specified logarithmic age-metallicity relation with parameters (α, β, σ), fits maximum likelihood and maximum a posteriori star formation parameters. MH_func is a callable that returns a logarithmic metallicity [M/H] for a metal mass fraction argument and defaults to MH_from_Z. T_max is the lookback time in Gyr at which the mean metal mass fraction is eta. See fixed_amr for info on format of returned result.

source
fixed_log_amr(models,
+              kws...)

Given a fully specified logarithmic age-metallicity relation with parameters (α, β, σ), fits maximum likelihood and maximum a posteriori star formation parameters. MH_func is a callable that returns a logarithmic metallicity [M/H] for a metal mass fraction argument and defaults to MH_from_Z. T_max is the lookback time in Gyr at which the mean metal mass fraction is eta. See fixed_amr for info on format of returned result.

source
fixed_log_amr(models,
               data,
               logAge::AbstractVector{<:Number},
               metallicities::AbstractVector{<:Number},
@@ -46,10 +46,10 @@
               constraint2,
               σ::Number;
               Z_func = StarFormationHistories.Z_from_MH,
-              kws...)

Call signature that takes two fixed points low_constraint and high_constraint that define points that must lie on the logarithmic age-metallicity relation and calculates the slope paramters α and β for you. Format is ([M/H], age [Gyr]), i.e. constraint1 = (-2.5, 13.7) for the first point at [M/H] = -2.5 at 13.7 Gyr lookback time and constraint2 = (-0.8, 0.0) for the second point at [M/H] = -0.8 at present-day (0.0 Gyr lookback time). The AMR is normalized so that the mean metal mass fraction at a lookback time in Gyr of T_max is Z = β. Metallicities in [M/H] are converted to metal mass fractions Z via the provided callable keyword argument Z_func which defaults to Z_from_MH. See fixed_amr for info on format of returned result.

source

We provide the calculate_αβ_logamr convenience function to calculate the slope $\alpha$ and intercept $\beta$ from two points on the age-metallicity relation.

StarFormationHistories.calculate_αβ_logamrFunction
(α, β) = calculate_αβ_logamr(low_constraint,
+              kws...)

Call signature that takes two fixed points low_constraint and high_constraint that define points that must lie on the logarithmic age-metallicity relation and calculates the slope paramters α and β for you. Format is ([M/H], age [Gyr]), i.e. constraint1 = (-2.5, 13.7) for the first point at [M/H] = -2.5 at 13.7 Gyr lookback time and constraint2 = (-0.8, 0.0) for the second point at [M/H] = -0.8 at present-day (0.0 Gyr lookback time). The AMR is normalized so that the mean metal mass fraction at a lookback time in Gyr of T_max is Z = β. Metallicities in [M/H] are converted to metal mass fractions Z via the provided callable keyword argument Z_func which defaults to Z_from_MH. See fixed_amr for info on format of returned result.

source

We provide the calculate_αβ_logamr convenience function to calculate the slope $\alpha$ and intercept $\beta$ from two points on the age-metallicity relation.

StarFormationHistories.calculate_αβ_logamrFunction
(α, β) = calculate_αβ_logamr(low_constraint,
                              high_constraint,
                              T_max,
-                             Z_func=Z_from_MH)

Calculates linear Z (log [M/H]) age-metallicity relation (AMR) slope α and intercept β from two points on the line with form ([M/H], age [Gyr]) given by the first two arguments. The AMR is normalized so that the mean metal mass fraction at a lookback time in Gyr of T_max is Z = β. More info given in fixed_log_amr.

source

Implementation

As the only part of the model that differs from the linear AMR case is the mean age-metallicity relation, most of the derivation for the linear AMR case is still valid here. In particular, only the partial derivatives of the relative weights $A_{j,k} \equiv \text{exp} \left( -\frac{1}{2 \, \sigma^2} \, \left( [\text{M}/\text{H}]_k - \mu_j \right)^2\right)$ with respect to the fitting parameters $\alpha$ and $\beta$ need to be recalculated under the new model. The partial derivative with respect to $\sigma$ is the same, as the mean metallicity in time bin $j$, denoted $\mu_j$, does not depend on $\sigma$.

\[\begin{aligned} + Z_func=Z_from_MH)

Calculates linear Z (log [M/H]) age-metallicity relation (AMR) slope α and intercept β from two points on the line with form ([M/H], age [Gyr]) given by the first two arguments. The AMR is normalized so that the mean metal mass fraction at a lookback time in Gyr of T_max is Z = β. More info given in fixed_log_amr.

source

Implementation

As the only part of the model that differs from the linear AMR case is the mean age-metallicity relation, most of the derivation for the linear AMR case is still valid here. In particular, only the partial derivatives of the relative weights $A_{j,k} \equiv \text{exp} \left( -\frac{1}{2 \, \sigma^2} \, \left( [\text{M}/\text{H}]_k - \mu_j \right)^2\right)$ with respect to the fitting parameters $\alpha$ and $\beta$ need to be recalculated under the new model. The partial derivative with respect to $\sigma$ is the same, as the mean metallicity in time bin $j$, denoted $\mu_j$, does not depend on $\sigma$.

\[\begin{aligned} Z_j &\equiv \langle Z \left(t_j\right) \rangle = \alpha \, \left( T_\text{max} - t_j \right) + \beta \\ \\ \mu_j &\equiv \langle [\text{M}/\text{H}] \rangle \left(t_j\right) = \text{log} \left( \frac{\langle Z\left(t_j\right) \rangle}{X_j} \right) - \text{log} \left( \frac{Z_\odot}{X_\odot} \right) \\ @@ -82,4 +82,4 @@ %% \frac{\partial \, A_{j,k}}{\partial \, \beta} &= \frac{\partial \, A_{j,k}}{\partial \, \mu_j} \, \frac{\partial \mu_j}{\partial \beta} = \left( \frac{A_{j,k} \, \left( [\text{M}/\text{H}]_k - \mu_j \right)}{\sigma^2} \right) \, \left( \frac{1}{\left( t_j \, \alpha + \beta \right) \, \text{ln}(10)} \right) \\ %% &= \frac{A_{j,k} \, \left( [\text{M}/\text{H}]_k - \mu_j \right)}{\text{ln}(10) \, \sigma^2 \, \left( t_j \, \alpha + \beta \right)} \\ %% \frac{\partial \, A_{j,k}}{\partial \, \alpha} &= \frac{\partial \, A_{j,k}}{\partial \, \mu_j} \, \frac{\partial \mu_j}{\partial \alpha} = t \, \frac{\partial \, A_{j,k}}{\partial \, \beta} -\end{aligned}\]

+\end{aligned}\]

diff --git a/dev/fitting/log_amr_plot.svg b/dev/fitting/log_amr_plot.svg index b76842e..277855d 100644 --- a/dev/fitting/log_amr_plot.svg +++ b/dev/fitting/log_amr_plot.svg @@ -1,81 +1,81 @@ - + - + - + - + - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + - + - + - - + + - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + - + - + - - + + - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + - + - + - + diff --git a/dev/fitting/unconstrained/index.html b/dev/fitting/unconstrained/index.html index fa335be..84e5737 100644 --- a/dev/fitting/unconstrained/index.html +++ b/dev/fitting/unconstrained/index.html @@ -5,29 +5,29 @@ 9-element Vector{Float64}: ... julia> sum(x0) -4.99... # Close to `normalize_value`.source

When it comes to performing the optimization, the simplest method we offer is StarFormationHistories.fit_templates_lbfgsb. This will optimize one coefficient per template; there is no overarching metallicity evolution or other constraint, besides that the stellar masses of the populations cannot be negative. This performs a maximum likelihood optimization with the bounded quasi-Newton LBFGS method as implemented in L-BFGS-B and wrapped in LBFGS.jl with analytic gradients. It is fast and converges fairly reliably, even when the initial guess is not particularly close to the maximum likelihood estimate. It provides no uncertainty estimation. It is normal for some of the coefficients to converge to zero.

StarFormationHistories.fit_templates_lbfgsbFunction
(-logL, coeffs) = 
+4.99... # Close to `normalize_value`.
source

When it comes to performing the optimization, the simplest method we offer is StarFormationHistories.fit_templates_lbfgsb. This will optimize one coefficient per template; there is no overarching metallicity evolution or other constraint, besides that the stellar masses of the populations cannot be negative. This performs a maximum likelihood optimization with the bounded quasi-Newton LBFGS method as implemented in L-BFGS-B and wrapped in LBFGS.jl with analytic gradients. It is fast and converges fairly reliably, even when the initial guess is not particularly close to the maximum likelihood estimate. It provides no uncertainty estimation. It is normal for some of the coefficients to converge to zero.

StarFormationHistories.fit_templates_lbfgsbFunction
(-logL, coeffs) = 
 fit_templates_lbfgsb(models::AbstractVector{T},
                      data::AbstractMatrix{<:Number};
                      x0::AbstractVector{<:Number} = ones(S,length(models)),
                      factr::Number=1e-12,
                      pgtol::Number=1e-5,
                      iprint::Integer=0,
-                     kws...) where {S <: Number, T <: AbstractMatrix{S}}

Finds the coefficients coeffs that maximize the Poisson likelihood ratio (equations 7–10 in Dolphin 2002) for the composite Hess diagram model sum(models .* coeffs) given the provided templates models and the observed Hess diagram data using the box-constrained LBFGS method provided by LBFGSB.jl.

Arguments

  • models::AbstractVector{AbstractMatrix{<:Number}}: the list of template Hess diagrams for the simple stellar populations (SSPs) being considered; all must have the same size.
  • data::AbstractMatrix{<:Number}: the observed Hess diagram; must match the size of the templates contained in models.

Keyword Arguments

  • x0: The vector of initial guesses for the stellar mass coefficients. You should basically always be calculating and passing this keyword argument; we provide StarFormationHistories.construct_x0 to prepare x0 assuming constant star formation rate, which is typically a good initial guess.
  • factr::Number: Keyword argument passed to LBFGSB.lbfgsb; essentially a relative tolerance for convergence based on the inter-iteration change in the objective function.
  • pgtol::Number: Keyword argument passed to LBFGSB.lbfgsb; essentially a relative tolerance for convergence based on the inter-iteration change in the projected gradient of the objective.
  • iprint::Integer: Keyword argument passed to LBFGSB.lbfgsb controlling how much information is printed to the terminal. Setting to 1 can sometimes be helpful to diagnose convergence issues. Setting to -1 will disable printing.

Other kws... are passed to LBFGSB.lbfgsb.

Returns

  • -logL::Number: the minimum negative log-likelihood found by the optimizer.
  • coeffs::Vector{<:Number}: the maximum likelihood estimate for the coefficient vector.

Notes

  • It can be helpful to normalize your models to contain realistic total stellar masses to aid convergence stability; for example, if the total stellar mass of your population is 10^7 solar masses, then you might normalize your templates to contain 10^3 solar masses. If you are using partial_cmd_smooth to construct the templates, you can specify this normalization via the normalize_value keyword.
source
fit_templates_lbfgsb(models::AbstractMatrix{S},
+                     kws...) where {S <: Number, T <: AbstractMatrix{S}}

Finds the coefficients coeffs that maximize the Poisson likelihood ratio (equations 7–10 in Dolphin 2002) for the composite Hess diagram model sum(models .* coeffs) given the provided templates models and the observed Hess diagram data using the box-constrained LBFGS method provided by LBFGSB.jl.

Arguments

  • models::AbstractVector{AbstractMatrix{<:Number}}: the list of template Hess diagrams for the simple stellar populations (SSPs) being considered; all must have the same size.
  • data::AbstractMatrix{<:Number}: the observed Hess diagram; must match the size of the templates contained in models.

Keyword Arguments

  • x0: The vector of initial guesses for the stellar mass coefficients. You should basically always be calculating and passing this keyword argument; we provide StarFormationHistories.construct_x0 to prepare x0 assuming constant star formation rate, which is typically a good initial guess.
  • factr::Number: Keyword argument passed to LBFGSB.lbfgsb; essentially a relative tolerance for convergence based on the inter-iteration change in the objective function.
  • pgtol::Number: Keyword argument passed to LBFGSB.lbfgsb; essentially a relative tolerance for convergence based on the inter-iteration change in the projected gradient of the objective.
  • iprint::Integer: Keyword argument passed to LBFGSB.lbfgsb controlling how much information is printed to the terminal. Setting to 1 can sometimes be helpful to diagnose convergence issues. Setting to -1 will disable printing.

Other kws... are passed to LBFGSB.lbfgsb.

Returns

  • -logL::Number: the minimum negative log-likelihood found by the optimizer.
  • coeffs::Vector{<:Number}: the maximum likelihood estimate for the coefficient vector.

Notes

  • It can be helpful to normalize your models to contain realistic total stellar masses to aid convergence stability; for example, if the total stellar mass of your population is 10^7 solar masses, then you might normalize your templates to contain 10^3 solar masses. If you are using partial_cmd_smooth to construct the templates, you can specify this normalization via the normalize_value keyword.
source
fit_templates_lbfgsb(models::AbstractMatrix{S},
                      data::AbstractVector{<:Number};
                      x0::AbstractVector{<:Number} = ones(S,size(models,2)),
                      factr::Number=1e-12,
                      pgtol::Number=1e-5,
                      iprint::Integer=0,
-                     kws...) where S <: Number

This call signature supports the flattened formats for models and data. See the notes for the flattened call signature of StarFormationHistories.composite! for more details.

source

This method simply minimizes the negative logarithm of the Poisson likelihood ratio (Equation 10 in Dolphin 2002),

\[- \text{ln} \, \mathscr{L} = \sum_i m_i - n_i \times \left( 1 - \text{ln} \, \left( \frac{n_i}{m_i} \right) \right)\]

where $m_i$ is bin $i$ of the complex model and $n_i$ is bin $i$ of the observed Hess diagram; this can therefore be thought of as computing the maximum likelihood estimate.

We also provide StarFormationHistories.fit_templates_fast, which is the fastest method we offer for deriving a maximum likelihood estimate for the type of model described above.

StarFormationHistories.fit_templates_fastFunction
(coeffs::Vector{::eltype(x0)}, result::Optim.MultivariateOptimizationResults) =
+                     kws...) where S <: Number

This call signature supports the flattened formats for models and data. See the notes for the flattened call signature of StarFormationHistories.composite! for more details.

source

This method simply minimizes the negative logarithm of the Poisson likelihood ratio (Equation 10 in Dolphin 2002),

\[- \text{ln} \, \mathscr{L} = \sum_i m_i - n_i \times \left( 1 - \text{ln} \, \left( \frac{n_i}{m_i} \right) \right)\]

where $m_i$ is bin $i$ of the complex model and $n_i$ is bin $i$ of the observed Hess diagram; this can therefore be thought of as computing the maximum likelihood estimate.

We also provide StarFormationHistories.fit_templates_fast, which is the fastest method we offer for deriving a maximum likelihood estimate for the type of model described above.

StarFormationHistories.fit_templates_fastFunction
(coeffs::Vector{::eltype(x0)}, result::Optim.MultivariateOptimizationResults) =
 fit_templates_fast(models::AbstractVector{T},
                    data::AbstractMatrix{<:Number};
                    x0::AbstractVector{<:Number} = ones(S,length(models)),
                    kws...)
-                   where {S <: Number, T <: AbstractMatrix{S}}

Finds only the maximum likelihood estimate (MLE) for the coefficients coeffs given the provided data such that the best-fit composite Hess diagram model is sum(models .* coeffs). This is a simplification of the main fit_templates function, which will return the MLE as well as the maximum a posteriori estimate, and further supports uncertainty quantification. For additional details on arguments to this method, see the documentation for fit_templates.

This method optimizes parameters θ such that coeffs = θ.^2 – this allows for faster convergence than both the fit_templates_lbfgsb method, which does not use a variable transformation, and the logarithmic transformation used in fit_templates. However, the inverse Hessian is not useful for uncertainty estimation under this transformation. As such this method only returns the MLE for coeffs as a vector and the result object returned by Optim.optimize. While this method offers fewer features than fit_templates, this method's runtime is typically half as long (or less). As such, this method is recommended for use in performance-sensitive applications like hierarchical models or hyperparameter estimation where the additional features of fit_templates are unnecessary. In these applications, the value of the objective function at the derived MLE is typically desired as well; this can be obtained the from result::Optim.MultivariateOptimizationResults object as Optim.minimum(result). Note that this will return the negative loglikelihood, which is what is minimized in this application.

Notes

  1. By passing additional convergence keyword arguments supported by Optim.Options (see this guide), it is possible to converge to the MLE in fewer than 30 iterations with fewer than 100 calls to the likelihood and gradient methods. For example, the main convergence criterion is typically the magnitude of the gradient vector, which by default is g_abstol=1e-8, terminating the optimization when the magnitude of the gradient is less than 1e-8. We find results are typically sufficiently accurate with g_abstol=1e-3, which often uses half as many objective evaluations as the default value.
source
fit_templates_fast(models::AbstractMatrix{S},
+                   where {S <: Number, T <: AbstractMatrix{S}}

Finds only the maximum likelihood estimate (MLE) for the coefficients coeffs given the provided data such that the best-fit composite Hess diagram model is sum(models .* coeffs). This is a simplification of the main fit_templates function, which will return the MLE as well as the maximum a posteriori estimate, and further supports uncertainty quantification. For additional details on arguments to this method, see the documentation for fit_templates.

This method optimizes parameters θ such that coeffs = θ.^2 – this allows for faster convergence than both the fit_templates_lbfgsb method, which does not use a variable transformation, and the logarithmic transformation used in fit_templates. However, the inverse Hessian is not useful for uncertainty estimation under this transformation. As such this method only returns the MLE for coeffs as a vector and the result object returned by Optim.optimize. While this method offers fewer features than fit_templates, this method's runtime is typically half as long (or less). As such, this method is recommended for use in performance-sensitive applications like hierarchical models or hyperparameter estimation where the additional features of fit_templates are unnecessary. In these applications, the value of the objective function at the derived MLE is typically desired as well; this can be obtained the from result::Optim.MultivariateOptimizationResults object as Optim.minimum(result). Note that this will return the negative loglikelihood, which is what is minimized in this application.

Notes

  1. By passing additional convergence keyword arguments supported by Optim.Options (see this guide), it is possible to converge to the MLE in fewer than 30 iterations with fewer than 100 calls to the likelihood and gradient methods. For example, the main convergence criterion is typically the magnitude of the gradient vector, which by default is g_abstol=1e-8, terminating the optimization when the magnitude of the gradient is less than 1e-8. We find results are typically sufficiently accurate with g_abstol=1e-3, which often uses half as many objective evaluations as the default value.
source
fit_templates_fast(models::AbstractMatrix{S},
                    data::AbstractVector{<:Number};
                    x0::AbstractVector{<:Number} = ones(S,size(models,2)),
                    kws...)
-                   where S <: Number

This call signature supports the flattened formats for models and data. See the notes for the flattened call signature of StarFormationHistories.composite! for more details.

source

Posterior Sampling: MCMC

For low-dimensional problems, Markov Chain Monte Carlo (MCMC) methods can be an efficient way to sample the posterior and obtain uncertainty estimates on the fitting coefficients $r_j$. We provide StarFormationHistories.mcmc_sample for this purpose. Internally this uses the multi-threaded affine-invariant MCMC sampler from KissMCMC.jl to perform the sampling, which is based on the same algorithm as Python's emcee (specifically, their emcee.moves.StretchMove). There are other MCMC packages like AdvancedMH.jl that offer additional features like distributed execution.

StarFormationHistories.mcmc_sampleFunction
result::MCMCChains.Chains =
+                   where S <: Number

This call signature supports the flattened formats for models and data. See the notes for the flattened call signature of StarFormationHistories.composite! for more details.

source

Posterior Sampling: MCMC

For low-dimensional problems, Markov Chain Monte Carlo (MCMC) methods can be an efficient way to sample the posterior and obtain uncertainty estimates on the fitting coefficients $r_j$. We provide StarFormationHistories.mcmc_sample for this purpose. Internally this uses the multi-threaded affine-invariant MCMC sampler from KissMCMC.jl to perform the sampling, which is based on the same algorithm as Python's emcee (specifically, their emcee.moves.StretchMove). There are other MCMC packages like AdvancedMH.jl that offer additional features like distributed execution.

StarFormationHistories.mcmc_sampleFunction
result::MCMCChains.Chains =
 mcmc_sample(models::AbstractVector{<:AbstractMatrix{T}},
             data::AbstractMatrix{S},
             x0::Union{AbstractVector{<:AbstractVector{<:Number}}, AbstractMatrix{<:Number}},
@@ -47,7 +47,7 @@
 nsteps = 400
 x0 = rand(nwalkers, length(coeffs)) # Initial walker positions
 result = mcmc_sample(models, data, x0, nwalkers, nsteps) # Sample
-Chains MCMC chain (400×10×1000 Array{Float64, 3}) ...
source

Posterior Sampling: Change of Variables and HMC

Dolphin 2013 examined methods for obtaining uncertainties on the fitted coefficients (the $r_j$ in Equation 1 of Dolphin 2002) and found that the Hamiltonian Monte Carlo (HMC) approach allowed for relatively efficient sampling of the posterior distribution when considering many isochrones in the modelling process. HMC requires that the variables to be fit are continuous over the real numbers and so requires a change of variables. Rather than sampling the variables $r_j$ directly, we can sample $\theta_j = \text{ln} \left( r_j \right)$ such that the sampled variables are continuous over the real numbers $-\infty < \theta_j < \infty$ while the $r_j=\text{exp} \left( \theta_j \right)$ coefficients are bounded from $0 < r_j < \infty$. Using a logarithmic transformation has the additional benefit that the gradient of the Poisson likelihood ratio is still continuous and easy to compute analytically.

While maximum likelihood estimates are invariant under variable transformations, sampling methods like HMC are not, as formally the posterior being sampled from is a distribution and therefore must be integrable over the sampling coefficients. We can write the posterior from which we wish to sample as

\[\begin{aligned} +Chains MCMC chain (400×10×1000 Array{Float64, 3}) ...source

Posterior Sampling: Change of Variables and HMC

Dolphin 2013 examined methods for obtaining uncertainties on the fitted coefficients (the $r_j$ in Equation 1 of Dolphin 2002) and found that the Hamiltonian Monte Carlo (HMC) approach allowed for relatively efficient sampling of the posterior distribution when considering many isochrones in the modelling process. HMC requires that the variables to be fit are continuous over the real numbers and so requires a change of variables. Rather than sampling the variables $r_j$ directly, we can sample $\theta_j = \text{ln} \left( r_j \right)$ such that the sampled variables are continuous over the real numbers $-\infty < \theta_j < \infty$ while the $r_j=\text{exp} \left( \theta_j \right)$ coefficients are bounded from $0 < r_j < \infty$. Using a logarithmic transformation has the additional benefit that the gradient of the Poisson likelihood ratio is still continuous and easy to compute analytically.

While maximum likelihood estimates are invariant under variable transformations, sampling methods like HMC are not, as formally the posterior being sampled from is a distribution and therefore must be integrable over the sampling coefficients. We can write the posterior from which we wish to sample as

\[\begin{aligned} p(r_j | D) &= \frac{p(D | r_j) \; p(r_j)}{Z} \\ p(\boldsymbol{r} | D) &= \frac{1}{Z} \; \prod_j p(D | r_j) \; p(r_j) \\ -\text{ln} \; p(\boldsymbol{r} | D) &= \text{ln} \, Z - \sum_j \, \text{ln} \, p(D | r_j) + \text{ln} \, p(r_j) \\ @@ -106,16 +106,16 @@ t_result = hmc_sample( models, data, 1000, Threads.nthreads(); reporter=DynamicHMC.ProgressMeterReport()) # Combine the multiple chains into a single matrix and transform # Can then use the same way as `mc_matrix` above -mc_matrix = exp.( DynamicHMC.pool_posterior_matrices(t_result) )source

See the DynamicHMC.jl documentation for more information on how to use the chains that are output by this method.

Inspection of the samples generated by hmc_sample shows that the posterior defined by the above model is typically smooth, well-behaved, and unimodal. In particular, we find that the sampled $r_j$ for coefficients that are non-zero in the MLE are approximately Gaussian distributed while the logarithms of the sampled $r_j$ are roughly Gaussian distributed for coefficients that are zero in the MLE; i.e.

\[\begin{cases} +mc_matrix = exp.( DynamicHMC.pool_posterior_matrices(t_result) )source

See the DynamicHMC.jl documentation for more information on how to use the chains that are output by this method.

Inspection of the samples generated by hmc_sample shows that the posterior defined by the above model is typically smooth, well-behaved, and unimodal. In particular, we find that the sampled $r_j$ for coefficients that are non-zero in the MLE are approximately Gaussian distributed while the logarithms of the sampled $r_j$ are roughly Gaussian distributed for coefficients that are zero in the MLE; i.e.

\[\begin{cases} X_j \sim \mathcal{N}; & \hat r_j > 0 \\ \text{ln} \left( X_j \right) \sim \mathcal{N}; & \hat r_j = 0 \\ \end{cases}\]

where $X_j$ are the samples of $r_j$ obtained from the posterior and $\hat r_j$ is the maximum likelihood estimate of $r_j$.

This indicates we may be able to approximate the posterior in the region surrounding the maximum a posteriori (MAP) value by the inverse of the Hessian matrix (see, e.g., Dovi et al. 1991), allowing us to estimate parameter uncertainties very cheaply. The inverse of the Hessian matrix is exactly equal to the variance-covariance matrix of the parameters for a Gaussian probability distribution; for other probability distributions, the inverse of the Hessian approximates the variance-covariance matrix of the parameters when the second-order expansion defined by the Hessian at the maximum is a reasonable approximation to the real objective function being optimized. A particularly simple form arises when the logarithm of the objective is quadratic in the fitting parameters, as in the Gaussian case, because the second derivatives of the objective are constant and do not depend on the fitting parameters or the MAP estimate.

Maximum a Posteriori Optimization

Direct computation of the Hessian and its inverse is expensive, so we'd like another way to obtain it. The first-order, quasi-Newton BFGS optimization algorithm provides such a method as it iteratively builds a dense approximation to the inverse Hessian using the change in the gradient of the objective, which we can compute analytically. It is, however, much less memory efficient than the LBFGS algorithm we use in StarFormationHistories.fit_templates_lbfgsb. For moderate isochrone grids up to a few hundred model templates, this is not a problem. Beyond this it may be better to use StarFormationHistories.fit_templates_lbfgsb to obtain the MLE and hmc_sample to obtain posterior samples.

We implement this optimization scheme in fit_templates, which is our recommended method for unconstrained SFH fitting (i.e., direct fitting of the $r_j$ coefficients). See the next section for notes on more complicated, hierarchical models that can incorporate features like metallicity distribution functions.

StarFormationHistories.fit_templatesFunction
result = fit_templates(models::AbstractVector{T},
                        data::AbstractMatrix{<:Number};
                        x0::AbstractVector{<:Number} = ones(S,length(models)),
-                       kws...) where {S <: Number, T <: AbstractMatrix{S}}

Finds both maximum likelihood estimate (MLE) and maximum a posteriori estimate (MAP) for the coefficients coeffs such that the composite Hess diagram model is sum(models .* coeffs) using the provided templates models and the observed Hess diagram data. Utilizes the Poisson likelihood ratio (equations 7–10 in Dolphin 2002) for the likelihood of the data given the model. See the examples in the documentation for comparisons of the results of this method and hmc_sample which samples the posterior via Hamiltonian Monte Carlo.

Arguments

  • models::AbstractVector{AbstractMatrix{<:Number}}: the list of template Hess diagrams for the simple stellar populations (SSPs) being considered; all must have the same size.
  • data::AbstractMatrix{<:Number}: the observed Hess diagram; must match the size of the templates contained in models.

Keyword Arguments

  • x0: The vector of initial guesses for the stellar mass coefficients. You should basically always be calculating and passing this keyword argument; we provide StarFormationHistories.construct_x0 to prepare x0 assuming constant star formation rate, which is typically a good initial guess.

Other kws... are passed to Optim.options to set things like convergence criteria for the optimization.

Returns

result is a NamedTuple containing two StarFormationHistories.LogTransformFTResult. The two components of result are result.map or result[1], which contains the results of the MAP optimization, and result.mle or result[2], which contains the results of the MLE optimization. The documentation for StarFormationHistories.LogTransformFTResult contains more information about these types, but briefly they contain the following fields, accessible as, e.g., result.map.μ, result.map.σ, etc.:

  • μ::Vector{<:Number} are the optimized coeffs at the maximum.
  • σ::Vector{<:Number} are the standard errors on the coeffs μ calculated from an estimate of the inverse Hessian matrix evaluated at μ. The inverse of the Hessian matrix at the maximum of the likelihood (or posterior) is a estimator for the variance-covariance matrix of the parameters, but is only accurate when the second-order expansion given by the Hessian at the maximum is a good approximation to the function being optimized (i.e., when the optimized function is approximately quadratic around the maximum; see Dovi et al. 1991 for more information). We find this is often the case for the MAP estimate, but the errors found for coefficients that are ≈0 in the MLE are typically unrealistically small. For coefficients significantly greater than 0, the σ values from the MAP and MLE are typically consistent to 5–10%.
  • invH::Matrix{<:Number} is the estimate of the inverse Hessian matrix at μ that was used to derive σ. The optimization is done under a logarithmic transform, such that θ[j] = log(coeffs[j]) are the actual parameters optimized, so the entries in the Hessian are actually

\[H^{(j,k)} ( \boldsymbol{\hat \theta} ) = \left. \frac{\partial^2 \, J(\boldsymbol{\theta})}{\partial \theta_j \, \partial \theta_k} \right\vert_{\boldsymbol{\theta}=\boldsymbol{\hat \theta}}\]

  • result is the full object returned by the optimization from Optim.jl; this is of type Optim.MultivariateOptimizationResults. Remember that the optimization is done with parameters θ[j] = log(coeffs[j]) when dealing with this raw output. This means that, for example, we calculate result.map.μ as exp.(Optim.minimizer(result.map.result)).

The special property of the StarFormationHistories.LogTransformFTResult type is that you can draw a set of N::Integer random parameter samples from the result using the inverse Hessian approximation discussed above by doing rand(result.map, N). This type implements the random sampling API of Distributions.jl so the other standard sampling methods should work as well. In our tests these samples compare very favorably against those from hmc_sample, which samples the posterior via Hamiltonian Monte Carlo and is therefore more robust but much more expensive. We compare these methods in the examples.

Notes

  • This method uses the BFGS method from Optim.jl internally because it builds and tracks the inverse Hessian matrix approximation which can be used to estimate parameter uncertainties. BFGS is much more memory-intensive than LBFGS (as used for StarFormationHistories.fit_templates_lbfgsb) for large numbers of parameters (equivalently, many models), so you should consider LBFGS to solve for the MLE along with hmc_sample to sample the posterior if you are using a large grid of models (greater than a few hundred).
  • The BFGS implementation we use from Optim.jl uses BLAS operations during its iteration. The OpenBLAS that Julia ships with will often default to running on multiple threads even if Julia itself is started with only a single thread. You can check the current number of BLAS threads with import LinearAlgebra: BLAS; BLAS.get_num_threads(). For the problem sizes typical of this function we actually see performance regression with larger numbers of BLAS threads. For this reason you may wish to use BLAS in single-threaded mode; you can set this as import LinearAlgebra: BLAS; BLAS.set_num_threads(1).
source
fit_templates(models::AbstractMatrix{S},
+                       kws...) where {S <: Number, T <: AbstractMatrix{S}}

Finds both maximum likelihood estimate (MLE) and maximum a posteriori estimate (MAP) for the coefficients coeffs such that the composite Hess diagram model is sum(models .* coeffs) using the provided templates models and the observed Hess diagram data. Utilizes the Poisson likelihood ratio (equations 7–10 in Dolphin 2002) for the likelihood of the data given the model. See the examples in the documentation for comparisons of the results of this method and hmc_sample which samples the posterior via Hamiltonian Monte Carlo.

Arguments

  • models::AbstractVector{AbstractMatrix{<:Number}}: the list of template Hess diagrams for the simple stellar populations (SSPs) being considered; all must have the same size.
  • data::AbstractMatrix{<:Number}: the observed Hess diagram; must match the size of the templates contained in models.

Keyword Arguments

  • x0: The vector of initial guesses for the stellar mass coefficients. You should basically always be calculating and passing this keyword argument; we provide StarFormationHistories.construct_x0 to prepare x0 assuming constant star formation rate, which is typically a good initial guess.

Other kws... are passed to Optim.options to set things like convergence criteria for the optimization.

Returns

result is a NamedTuple containing two StarFormationHistories.LogTransformFTResult. The two components of result are result.map or result[1], which contains the results of the MAP optimization, and result.mle or result[2], which contains the results of the MLE optimization. The documentation for StarFormationHistories.LogTransformFTResult contains more information about these types, but briefly they contain the following fields, accessible as, e.g., result.map.μ, result.map.σ, etc.:

  • μ::Vector{<:Number} are the optimized coeffs at the maximum.
  • σ::Vector{<:Number} are the standard errors on the coeffs μ calculated from an estimate of the inverse Hessian matrix evaluated at μ. The inverse of the Hessian matrix at the maximum of the likelihood (or posterior) is a estimator for the variance-covariance matrix of the parameters, but is only accurate when the second-order expansion given by the Hessian at the maximum is a good approximation to the function being optimized (i.e., when the optimized function is approximately quadratic around the maximum; see Dovi et al. 1991 for more information). We find this is often the case for the MAP estimate, but the errors found for coefficients that are ≈0 in the MLE are typically unrealistically small. For coefficients significantly greater than 0, the σ values from the MAP and MLE are typically consistent to 5–10%.
  • invH::Matrix{<:Number} is the estimate of the inverse Hessian matrix at μ that was used to derive σ. The optimization is done under a logarithmic transform, such that θ[j] = log(coeffs[j]) are the actual parameters optimized, so the entries in the Hessian are actually

\[H^{(j,k)} ( \boldsymbol{\hat \theta} ) = \left. \frac{\partial^2 \, J(\boldsymbol{\theta})}{\partial \theta_j \, \partial \theta_k} \right\vert_{\boldsymbol{\theta}=\boldsymbol{\hat \theta}}\]

  • result is the full object returned by the optimization from Optim.jl; this is of type Optim.MultivariateOptimizationResults. Remember that the optimization is done with parameters θ[j] = log(coeffs[j]) when dealing with this raw output. This means that, for example, we calculate result.map.μ as exp.(Optim.minimizer(result.map.result)).

The special property of the StarFormationHistories.LogTransformFTResult type is that you can draw a set of N::Integer random parameter samples from the result using the inverse Hessian approximation discussed above by doing rand(result.map, N). This type implements the random sampling API of Distributions.jl so the other standard sampling methods should work as well. In our tests these samples compare very favorably against those from hmc_sample, which samples the posterior via Hamiltonian Monte Carlo and is therefore more robust but much more expensive. We compare these methods in the examples.

Notes

  • This method uses the BFGS method from Optim.jl internally because it builds and tracks the inverse Hessian matrix approximation which can be used to estimate parameter uncertainties. BFGS is much more memory-intensive than LBFGS (as used for StarFormationHistories.fit_templates_lbfgsb) for large numbers of parameters (equivalently, many models), so you should consider LBFGS to solve for the MLE along with hmc_sample to sample the posterior if you are using a large grid of models (greater than a few hundred).
  • The BFGS implementation we use from Optim.jl uses BLAS operations during its iteration. The OpenBLAS that Julia ships with will often default to running on multiple threads even if Julia itself is started with only a single thread. You can check the current number of BLAS threads with import LinearAlgebra: BLAS; BLAS.get_num_threads(). For the problem sizes typical of this function we actually see performance regression with larger numbers of BLAS threads. For this reason you may wish to use BLAS in single-threaded mode; you can set this as import LinearAlgebra: BLAS; BLAS.set_num_threads(1).
source
fit_templates(models::AbstractMatrix{S},
               data::AbstractVector{<:Number};
               x0::AbstractVector{<:Number} = ones(S,length(models)),
-              kws...) where S <: Number

This call signature supports the flattened formats for models and data. See the notes for the flattened call signature of StarFormationHistories.composite! for more details.

source
StarFormationHistories.LogTransformFTResultType
LogTransformFTResult(μ::AbstractVector{<:Number},
+              kws...) where S <: Number

This call signature supports the flattened formats for models and data. See the notes for the flattened call signature of StarFormationHistories.composite! for more details.

source
StarFormationHistories.LogTransformFTResultType
LogTransformFTResult(μ::AbstractVector{<:Number},
                      σ::AbstractVector{<:Number},
                      invH::AbstractMatrix{<:Number},
                      result)

Type for containing the maximum likelihood estimate (MLE) and maximum a posteriori (MAP) results from fit_templates. The fitted coefficients are available in the μ field. Estimates of the standard errors are available in the σ field. These have both been transformed from the native logarithmic fitting space into natural units (i.e., stellar mass or star formation rate).

invH contains the estimated inverse Hessian of the likelihood / posterior at the maximum point in the logarithmic fitting units. result is the full result object returned by the optimization routine.

This type is implemented as a subtype of Distributions.Sampleable{Multivariate, Continuous} to enable sampling from an estimate of the likelihood / posterior distribution. We approximate the distribution as a multivariate Gaussian in the native (logarithmically transformed) fitting variables with covariance matrix invH and means log.(μ). We find this approximation is good for the MAP result but less robust for the MLE. You can obtain N::Integer samples from the distribution by rand(R, N) where R is an instance of this type; this will return a size length(μ) x N matrix, or fail if invH is not positive definite.

Examples

julia> result = fit_templates(models, data);
@@ -124,10 +124,10 @@
 StarFormationHistories.LogTransformFTResult{...}
 
 julia> size(rand(result.map, 3)) == (length(models),3)
-true
source

Once you have obtained stellar mass coefficients from the above methods, you can convert them into star formation rates and compute per-age mean metallicities with StarFormationHistories.calculate_cum_sfr.

StarFormationHistories.calculate_cum_sfrFunction
(unique_logAge, cum_sfh, sfr, mean_MH) =
+true
source

Once you have obtained stellar mass coefficients from the above methods, you can convert them into star formation rates and compute per-age mean metallicities with StarFormationHistories.calculate_cum_sfr.

StarFormationHistories.calculate_cum_sfrFunction
(unique_logAge, cum_sfh, sfr, mean_MH) =
     calculate_cum_sfr(coeffs::AbstractVector,
                       logAge::AbstractVector,
                       MH::AbstractVector,
                       T_max::Number;
                       normalize_value=1,
-                      sorted::Bool=false)

Calculates cumulative star formation history, star formation rates, and mean metallicity evolution as functions of logAge = log10(age [yr]).

Arguments

  • coeffs::AbstractVector is a vector of stellar mass coefficients such as those returned by fit_templates, for example. Actual stellar mass in stellar population j is coeffs[j] * normalize_value.
  • logAge::AbstractVector is a vector giving the log10(age [yr]) of the stellar populations corresponding to the provided coeffs. For the purposes of calculating star formation rates, these are assumed to be left-bin edges.
  • MH::AbstractVector is a vector giving the metallicities of the stellar populations corresponding to the provided coeffs.
  • T_max::Number is the rightmost final bin edge for calculating star formation rates. For example, you might have logAge=[6.6, 6.7, 6.8] in which case a final logAge of 6.9 would give equal bin widths. In this case you would set T_max = exp10(6.9) / 1e9 ≈ 0.0079 so that the width of the final bin for the star formation rate calculation has the same log10(Age [yr]) step as the other bins.

Keyword Arguments

  • normalize_value is a multiplicative prefactor to apply to all the coeffs; same as the keyword in partial_cmd_smooth.
  • sorted::Bool is either true or false and signifies whether to assume logAge is sorted.

Returns

  • unique_logAge::Vector is essentially unique(sort(logAge)) and provides the x-values you would plot the other returned vectors against.
  • cum_sfh::Vector is the normalized cumulative SFH implied by the provided coeffs. This is ~1 at the most recent time in logAge and decreases as logAge increases.
  • sfr::Vector gives the star formation rate across each bin in unique_logAge. If coeffs .* normalize_value are in units of solar masses, then sfr is in units of solar masses per year.
  • mean_MH::Vector gives the stellar-mass-weighted mean metallicity of the stellar population as a function of unique_logAge.
source
+ sorted::Bool=false)

Calculates cumulative star formation history, star formation rates, and mean metallicity evolution as functions of logAge = log10(age [yr]).

Arguments

Keyword Arguments

Returns

source diff --git a/dev/helpers/index.html b/dev/helpers/index.html index b14bcf8..56eda60 100644 --- a/dev/helpers/index.html +++ b/dev/helpers/index.html @@ -1,6 +1,6 @@ -Helper Functions · StarFormationHistories.jl

Helper Functions

Distances and Sizes

StarFormationHistories.arcsec_to_pcFunction
arcsec_to_pc(arcsec, dist_mod)

Converts on-sky angle in arcseconds to physical separation based on distance modulus under the small-angle approximation.

\[r ≈ 10^{μ/5 + 1} \times \text{atan}(θ/3600)\]

source

Magnitudes and Luminosities

Metallicities

StarFormationHistories.Y_from_ZFunction
Y_from_Z(Z, Y_p=0.2485, γ=1.78)

Calculates the helium mass fraction (Y) for a star given its metal mass fraction (Z) using the approximation Y = Y_p + γ * Z, with Y_p being the primordial helium abundance Y_p=0.2485 as assumed for PARSEC isochrones and γ=1.78 matching the input scaling for PARSEC as well.

source
StarFormationHistories.X_from_ZFunction
X_from_Z(Z[, Yp, γ])

Calculates the hydrogen mass fraction (X) for a star given its metal mass fraction (Z) via X = 1 - (Z + Y), with the helium mass fraction Y approximated via StarFormationHistories.Y_from_Z. You may also provide the primordial helium abundance Y_p and γ such that Y = Y_p + γ * Z; these are passed through to X_from_Z.

source
StarFormationHistories.MH_from_ZFunction
MH_from_Z(Z, solZ=0.01524; Y_p = 0.2485, γ = 1.78)

Calculates [M/H] = log(Z/X) - log(Z/X)⊙. Given the provided solar metal mass fraction solZ, it calculates the hydrogen mass fraction X for both the Sun and the provided Z with StarFormationHistories.X_from_Z. You may also provide the primordial helium abundance Y_p and γ such that Y = Y_p + γ * Z; these are passed through to X_from_Z.

The present-day solar Z is measured to be 0.01524 (Caffau et al. 2011), but for PARSEC isochrones an [M/H] of 0 corresponds to Z=0.01471. This is because of a difference between the Sun's initial and present helium content caused by diffusion. If you want to reproduce PARSEC's scaling, you should set solZ=0.01471.

This function is an approximation and may not be suitable for precision calculations.

source
StarFormationHistories.Z_from_MHFunction
Z_from_MH(MH, solZ=0.01524; Y_p = 0.2485, γ = 1.78)

Calculates metal mass fraction Z assuming

  • the PARSEC relation for the helium mass fraction Y = Y_p + γ * Z with primordial helium abundance Y_p = 0.2485, and γ = 1.78, and
  • the solar metal mass fraction solZ = 0.01524.
source
StarFormationHistories.mdf_amrFunction
(unique_MH, mass_mdf) =
+Helper Functions · StarFormationHistories.jl

Helper Functions

Distances and Sizes

StarFormationHistories.arcsec_to_pcFunction
arcsec_to_pc(arcsec, dist_mod)

Converts on-sky angle in arcseconds to physical separation based on distance modulus under the small-angle approximation.

\[r ≈ 10^{μ/5 + 1} \times \text{atan}(θ/3600)\]

source

Magnitudes and Luminosities

Metallicities

StarFormationHistories.Y_from_ZFunction
Y_from_Z(Z, Y_p=0.2485, γ=1.78)

Calculates the helium mass fraction (Y) for a star given its metal mass fraction (Z) using the approximation Y = Y_p + γ * Z, with Y_p being the primordial helium abundance Y_p=0.2485 as assumed for PARSEC isochrones and γ=1.78 matching the input scaling for PARSEC as well.

source
StarFormationHistories.X_from_ZFunction
X_from_Z(Z[, Yp, γ])

Calculates the hydrogen mass fraction (X) for a star given its metal mass fraction (Z) via X = 1 - (Z + Y), with the helium mass fraction Y approximated via StarFormationHistories.Y_from_Z. You may also provide the primordial helium abundance Y_p and γ such that Y = Y_p + γ * Z; these are passed through to X_from_Z.

source
StarFormationHistories.MH_from_ZFunction
MH_from_Z(Z, solZ=0.01524; Y_p = 0.2485, γ = 1.78)

Calculates [M/H] = log(Z/X) - log(Z/X)⊙. Given the provided solar metal mass fraction solZ, it calculates the hydrogen mass fraction X for both the Sun and the provided Z with StarFormationHistories.X_from_Z. You may also provide the primordial helium abundance Y_p and γ such that Y = Y_p + γ * Z; these are passed through to X_from_Z.

The present-day solar Z is measured to be 0.01524 (Caffau et al. 2011), but for PARSEC isochrones an [M/H] of 0 corresponds to Z=0.01471. This is because of a difference between the Sun's initial and present helium content caused by diffusion. If you want to reproduce PARSEC's scaling, you should set solZ=0.01471.

This function is an approximation and may not be suitable for precision calculations.

source
StarFormationHistories.Z_from_MHFunction
Z_from_MH(MH, solZ=0.01524; Y_p = 0.2485, γ = 1.78)

Calculates metal mass fraction Z assuming

  • the PARSEC relation for the helium mass fraction Y = Y_p + γ * Z with primordial helium abundance Y_p = 0.2485, and γ = 1.78, and
  • the solar metal mass fraction solZ = 0.01524.
source
StarFormationHistories.mdf_amrFunction
(unique_MH, mass_mdf) =
 mdf_amr(coeffs::AbstractVector{<:Number},
         logAge::AbstractVector{<:Number},
         metallicities::AbstractVector{<:Number})

Calculates the mass-weighted metallicity distribution function given a set of stellar mass coefficients coeffs for stellar populations with logarithmic ages logAge=log10(age [yr]) and metallicities given by metallicities. This is calculated as

\[P_j = \frac{ \sum_k r_{j,k} \, [\text{M} / \text{H}]_k}{\sum_{j,k} r_{j,k} \, [\text{M} / \text{H}]_k}\]

where $r_{j,k}$ are the elements of coeffs where $j$ indexes over unique entries in logAge and $k$ indexes over unique entries in metallicities. This is the same nomenclature used in the the documentation on constrained metallicity evolutions.

Examples

julia> mdf_amr([1.0, 2.0, 1.0], [10, 10, 10], [-2, -1.5, -1])
-([-2.0, -1.5, -1.0], [0.25, 0.5, 0.25])
source

Photometric Error and Completeness Models

StarFormationHistories.Martin2016_completeFunction
η(m) = Martin2016_complete(m, A, m50, ρ)

Completeness model of Martin et al. 2016 implemented as their Equation 7:

\[\eta(m) = \frac{A}{1 + \text{exp} \left( \frac{m - m_{50}}{\rho} \right)}\]

m is the magnitude of interest, A is the maximum completeness, m50 is the magnitude at which the data are 50% complete, and ρ is an effective slope modifier.

source
StarFormationHistories.exp_photerrFunction
exp_photerr(m, a, b, c, d)

Exponential model for photometric errors of the form

\[\sigma(m) = a^{b \times \left( m-c \right)} + d\]

Reported values for some HST data were a=1.05, b=10.0, c=32.0, d=0.01.

source
+([-2.0, -1.5, -1.0], [0.25, 0.5, 0.25])
source

Photometric Error and Completeness Models

StarFormationHistories.Martin2016_completeFunction
η(m) = Martin2016_complete(m, A, m50, ρ)

Completeness model of Martin et al. 2016 implemented as their Equation 7:

\[\eta(m) = \frac{A}{1 + \text{exp} \left( \frac{m - m_{50}}{\rho} \right)}\]

m is the magnitude of interest, A is the maximum completeness, m50 is the magnitude at which the data are 50% complete, and ρ is an effective slope modifier.

source
StarFormationHistories.exp_photerrFunction
exp_photerr(m, a, b, c, d)

Exponential model for photometric errors of the form

\[\sigma(m) = a^{b \times \left( m-c \right)} + d\]

Reported values for some HST data were a=1.05, b=10.0, c=32.0, d=0.01.

source
diff --git a/dev/index.html b/dev/index.html index c375ae4..397b3b1 100644 --- a/dev/index.html +++ b/dev/index.html @@ -1,2 +1,2 @@ -Overview · StarFormationHistories.jl

Overview

This package enables many different kinds of analysis related to astrophysical star formation histories (SFHs). Core among these functionalities are generation of mock color-magnitude diagrams (CMDs) from input SFHs and fitting of SFHs from observed CMDs. The methods of this package have been designed to be simple to use but easy to extend. It is recommended that you use this package in conjunction with InitialMassFunctions.jl which provides implementations of the most popular stellar initial mass functions as new types which are natively supported by this package's methods.

+Overview · StarFormationHistories.jl

Overview

This package enables many different kinds of analysis related to astrophysical star formation histories (SFHs). Core among these functionalities are generation of mock color-magnitude diagrams (CMDs) from input SFHs and fitting of SFHs from observed CMDs. The methods of this package have been designed to be simple to use but easy to extend. It is recommended that you use this package in conjunction with InitialMassFunctions.jl which provides implementations of the most popular stellar initial mass functions as new types which are natively supported by this package's methods.

diff --git a/dev/simulate/index.html b/dev/simulate/index.html index be1674a..a2d1052 100644 --- a/dev/simulate/index.html +++ b/dev/simulate/index.html @@ -1,9 +1,9 @@ -Simulating Color-Magnitude Diagrams · StarFormationHistories.jl

Simulating Color-Magnitude Diagrams

Modelling observations of resolved stellar populations (e.g., color-magnitude or Hess diagrams) with user-defined star formation histories can be useful for comparison to actual observations, but also enables a number of other scientific activities (e.g., making predictions to motivate observational proposals). To support these uses we offer methods for sampling stellar populations from isochrones using user-defined star formation histories, initial mass functions, and stellar binary models. These methods require data from user-provided isochrones (this package does not provide any), an initial mass function model (such as those provided in InitialMassFunctions.jl), and a model specifying how (or if) to sample binary or multi-star systems.

The simplest methods only sample stars from a single stellar population. We provide a method that samples up to a provided stellar mass, generate_stars_mass (e.g., $10^7 \, \text{M}_\odot$) and a method that samples up to a provided absolute magnitude generate_stars_mag (e.g., $M_V=-10$). These are documented under the first subsection below. These methods are single-threaded.

We also offer methods for sampling populations with complex star formation histories; these are implicitly multi-threaded across the separate populations if you start Julia with multiple threads (e.g., with julia -t 4 or similar). We provide generate_stars_mass_composite for sampling such populations up to a provided stellar mass and generate_stars_mag_composite for sampling such populations up to a provided absolute magnitude. These are documented under the second subsection below.

Simple Stellar Populations

StarFormationHistories.generate_stars_massFunction
(sampled_masses, sampled_mags) = generate_stars_mass(mini_vec::AbstractVector{<:Number}, mags, mag_names::AbstractVector{String}, limit::Number, imf::Distributions.Sampleable{Distributions.Univariate, Distributions.Continuous}; dist_mod::Number=0, rng::Random.AbstractRNG=Random.default_rng(), mag_lim::Number=Inf, mag_lim_name::String="V", binary_model::StarFormationHistories.AbstractBinaryModel=StarFormationHistories.RandomBinaryPairs(0.3))

Generates a random sample of stars from an isochrone defined by the provided initial stellar masses mini_vec, absolute magnitudes mags, and filter names mag_names with total population birth stellar mass limit (e.g., 1e5 solar masses). Initial stellar masses are sampled from the provided imf.

Arguments

  • mini_vec::AbstractVector{<:Number} contains the initial masses (in solar masses) for the stars in the isochrone; must be mutable as we call Interpolations.deduplicate_knots!(mini_vec).
  • mags contains the absolute magnitudes from the isochrone in the desired filters corresponding to the same stars as provided in mini_vec. mags is internally interpreted and converted into a standard format by StarFormationHistories.ingest_mags. Valid inputs are:
    • mags::AbstractVector{AbstractVector{<:Number}}, in which case the length of the outer vector length(mags) can either be equal to length(mini_vec), in which case the length of the inner vectors must all be equal to the number of filters you are providing, or the length of the outer vector can be equal to the number of filters you are providing, and the length of the inner vectors must all be equal to length(mini_vec); this is the more common use-case.
    • mags::AbstractMatrix{<:Number}, in which case mags must be 2-dimensional. Valid shapes are size(mags) == (length(mini_vec), nfilters) or size(mags) == (nfilters, length(mini_vec)), with nfilters being the number of filters you are providing.
  • mag_names::AbstractVector{String} contains strings describing the filters you are providing in mags; an example might be ["B","V"]. These are used when mag_lim is finite to determine what filter you want to use to limit the faintest stars you want returned.
  • limit::Number gives the total birth stellar mass of the population you want to sample. See the "Notes" section on population masses for more information.
  • imf::Distributions.Sampleable{Distributions.Univariate, Distributions.Continuous} is a sampleable continuous univariate distribution implementing a stellar initial mass function with a defined rand(rng::Random.AbstractRNG, imf) method to use for sampling masses. All instances of Distributions.ContinuousUnivariateDistribution are also valid. Implementations of commonly used IMFs are available in InitialMassFunctions.jl.

Keyword Arguments

  • dist_mod::Number=0 is the distance modulus (see StarFormationHistories.distance_modulus) you wish to have added to the returned magnitudes to simulate a population at a particular distance.
  • rng::Random.AbstractRNG=Random.default_rng() is the rng instance that will be used to sample the stellar initial masses from imf.
  • mag_lim::Number=Inf gives the faintest apparent magnitude for stars you want to be returned in the output. Stars fainter than this magnitude will still be sampled and contribute properly to the total mass of the population, but they will not be returned.
  • mag_lim_name::String="V" gives the filter name (as contained in mag_names) to use when considering if a star is fainter than mag_lim. This is unused if mag_lim is infinite.
  • binary_model::StarFormationHistories.AbstractBinaryModel=StarFormationHistories.RandomBinaryPairs(0.3) is an instance of a model for treating binaries; currently provided options are NoBinaries, RandomBinaryPairs, and BinaryMassRatio.

Returns

  • sampled_masses::Vector{SVector{N,eltype(imf)}}: a vector containing the initial stellar masses of the stars sampled by sample_system; see that method's documentation for details on format. In short, each StaticArrays.SVector represents one stellar system and each entry in each StaticArrays.SVector is one star in that system. Entries will be 0 when companions could have been sampled but were not (i.e., when using a binary_model that supports multi-star systems).
  • sampled_mags::Vector{SVector{N,<:Number}}: a vector containing StaticArrays.SVectors with the multi-band magnitudes of the sampled stars. To get the magnitude of star i in band j, you index as sampled_mags[i][j]. This can be reinterpreted as a 2-dimensional Matrix with reduce(hcat,sampled_mags).

Notes

Population Masses

Given a particular isochrone with an initial mass vector mini_vec, it will never cover the full range of stellar birth masses because stars that die before present-day are not included in the isochrone. However, these stars were born, and so contribute to the total birth mass of the system. There are two ways to properly account for this lost mass when sampling:

  1. Set the upper limit for masses that can be sampled from the imf distribution to a physical value for the maximum birth mass of stars (e.g., 100 solar masses). In this case, these stars will be sampled from imf, and will contribute their masses to the system, but they will not be returned if their birth mass is greater than maximum(mini_vec). This is typically easiest for the user and only results in ∼15% loss of efficiency for 10 Gyr isochrones.
  2. Set the upper limit for masses that can be sampled from the imf distribution to maximum(mini_vec) and adjust limit to respect the amount of initial stellar mass lost by not sampling higher mass stars. This can be calculated as new_limit = limit * ( QuadGK.quadgk(x->x*pdf(imf,x), minimum(mini_vec), maximum(mini_vec))[1] / QuadGK.quadgk(x->x*pdf(imf,x), minimum(imf), maximum(imf))[1] ), with the multiplicative factor being the fraction of the population stellar mass contained in stars with initial masses between minimum(mini_vec) and maximum(mini_vec); this factor is the ratio

\[\frac{\int_a^b \ m \times \frac{dN(m)}{dm} \ dm}{\int_0^∞ \ m \times \frac{dN(m)}{dm} \ dm}\]

source
StarFormationHistories.generate_stars_magFunction
(sampled_masses, sampled_mags) =  generate_stars_mag(mini_vec::AbstractVector{<:Number}, mags, mag_names::AbstractVector{String}, absmag::Real, absmag_name::String, imf::Distributions.Sampleable{Distributions.Univariate,Distributions.Continuous}; dist_mod::Number=0, rng::AbstractRNG=default_rng(), mag_lim::Number=Inf, mag_lim_name::String="V", binary_model::StarFormationHistories.AbstractBinaryModel=RandomBinaryPairs(0.3))

Generates a mock stellar population from an isochrone defined by the provided initial stellar masses mini_vec, absolute magnitudes mags, and filter names mag_names. The population is sampled to a total absolute magnitude absmag::Real (e.g., -7 or -12) in the filter absmag_name::String (e.g., "V" or "F606W") which is contained in the provided mag_names::AbstractVector{String}. Other arguments are shared with generate_stars_mass, which contains the main documentation.

Notes

Population Magnitudes

Unlike when sampling a population to a fixed initial birth mass, as is implemented in generate_stars_mass, when generating a population up to a fixed absolute magnitude, only stars that survive to present-day contribute to the flux of the population. If you choose to limit the apparent magnitude of stars returned by passing the mag_lim and mag_lim_name keyword arguments, stars fainter than your chosen limit will still be sampled and will still contribute their luminosity to the total population, but they will not be contained in the returned output.

source

Complex Stellar Populations

StarFormationHistories.generate_stars_mass_compositeFunction
(sampled_masses, sampled_mags) = generate_stars_mass_composite(mini_vec::AbstractVector{<:AbstractVector{<:Number}}, mags::AbstractVector, mag_names::AbstractVector{String}, limit::Number, massfrac::AbstractVector{<:Number}, imf::Sampleable{Univariate,Continuous}; kws...)

Generates a random sample of stars with a complex star formation history using multiple isochrones. Very similar to generate_stars_mass except the isochrone-related arguments mini_vec and mags should now be vectors of vectors containing the relevant data for the full set of isochrones to be considered. The total birth stellar mass of the sampled population is given by limit. The proportion of this mass allotted to each of the individual isochrones is given by the entries of the massfrac vector. This basically just proportions limit according to massfrac and calls generate_stars_mass for each of the individual stellar populations; as such it is set up to multi-thread across the multiple stellar populations.

Arguments

  • mini_vec::AbstractVector{<:AbstractVector{<:Number}} contains the initial masses (in solar masses) for the stars in each isochrone; the internal vectors must be mutable as we will call Interpolations.deduplicate_knots! on each. The length of mini_vec should be equal to the number of isochrones.
  • mags contains the absolute magnitudes from the isochrones in the desired filters corresponding to the same stars as provided in mini_vec. The length of mags should be equal to the number of isochrones. The individual elements of mags are each internally interpreted and converted into a standard format by StarFormationHistories.ingest_mags. The valid formats for the individual elements of mags are:
    • AbstractVector{AbstractVector{<:Number}}, in which case the length of the vector length(mags[i]) can either be equal to length(mini_vec[i]), in which case the length of the inner vectors must all be equal to the number of filters you are providing, or the length of the outer vector can be equal to the number of filters you are providing, and the length of the inner vectors must all be equal to length(mini_vec[i]); this is the more common use-case.
    • AbstractMatrix{<:Number}, in which case mags[i] must be 2-dimensional. Valid shapes are size(mags[i]) == (length(mini_vec[i]), nfilters) or size(mags[i]) == (nfilters, length(mini_vec[i])), with nfilters being the number of filters you are providing.
  • mag_names::AbstractVector{String} contains strings describing the filters you are providing in mags; an example might be ["B","V"]. These are used when mag_lim is finite to determine what filter you want to use to limit the faintest stars you want returned. These are assumed to be the same for all isochrones.
  • limit::Number gives the total birth stellar mass of the population you want to sample.
  • massfrac::AbstractVector{<:Number} is vector giving the relative fraction of mass allotted to each individual stellar population; length must be equal to the length of mini_vec and mags.
  • imf::Distributions.Sampleable{Distributions.Univariate, Distributions.Continuous} is a sampleable continuous univariate distribution implementing a stellar initial mass function with a defined rand(rng::Random.AbstractRNG, imf) method to use for sampling masses. All instances of Distributions.ContinuousUnivariateDistribution are also valid. Implementations of commonly used IMFs are available in InitialMassFunctions.jl.

Keyword Arguments

All keyword arguments kws... are passed to generate_stars_mass; you should refer to that method's documentation for more information.

Returns

  • sampled_masses::Vector{Vector{SVector{N,eltype(imf)}}} is a vector of vectors containing the initial stellar masses of the sampled stars. The outer vectors are separated by the isochrone the stars were generated from; i.e., all of sampled_masses[1] were sampled from mini_vec[1] and so on. These can be concatenated into a single vector with reduce(vcat,sampled_masses). The format of the contained StaticArrays.SVectors are as output from sample_system; see that method's documentation for more details.
  • sampled_mags::Vector{Vector{SVector{N,<:Number}}} is a vector of vectors containing StaticArrays.SVectors with the multi-band magnitudes of the sampled stars. The outer vectors are separated by the isochrone the stars were generated from; i.e. all of sampled_mags[1] were sampled from mags[1] and so on. To get the magnitude of star i in band j sampled from isochrone k, you would do sampled_mags[k][i][j]. This can be concatenated into a Vector{SVector} with reduce(vcat,sampled_mags) and a 2-D Matrix with reduce(hcat,reduce(vcat,sampled_mags)).
source
StarFormationHistories.generate_stars_mag_compositeFunction
(sampled_masses, sampled_mags) = generate_stars_mag_composite(mini_vec::AbstractVector{<:AbstractVector{<:Number}}, mags::AbstractVector, mag_names::AbstractVector{String}, absmag::Number, absmag_name::String, fracs::AbstractVector{<:Number}, imf::Sampleable{Univariate,Continuous}; frac_type::String="lum", kws...)

Generates a random sample of stars with a complex star formation history using multiple isochrones. Very similar to generate_stars_mag except the isochrone-related arguments mini_vec and mags should now be vectors of vectors containing the relevant data for the full set of isochrones to be considered. The total absolute magnitude of the sampled population is given by absmag. The proportion of the luminosity allotted to each of the individual isochrones is given by the entries of the frac vector. This basically just proportions the luminosity according to frac and calls generate_stars_mag for each of the individual stellar populations; as such it is set up to multi-thread across the multiple stellar populations.

Arguments

  • mini_vec::AbstractVector{<:AbstractVector{<:Number}} contains the initial masses (in solar masses) for the stars in each isochrone; the internal vectors must be mutable as we will call Interpolations.deduplicate_knots! on each. The length of mini_vec should be equal to the number of isochrones.
  • mags contains the absolute magnitudes from the isochrones in the desired filters corresponding to the same stars as provided in mini_vec. The length of mags should be equal to the number of isochrones. The individual elements of mags are each internally interpreted and converted into a standard format by StarFormationHistories.ingest_mags. The valid formats for the individual elements of mags are:
    • AbstractVector{AbstractVector{<:Number}}, in which case the length of the vector length(mags[i]) can either be equal to length(mini_vec[i]), in which case the length of the inner vectors must all be equal to the number of filters you are providing, or the length of the outer vector can be equal to the number of filters you are providing, and the length of the inner vectors must all be equal to length(mini_vec[i]); this is the more common use-case.
    • AbstractMatrix{<:Number}, in which case mags[i] must be 2-dimensional. Valid shapes are size(mags[i]) == (length(mini_vec[i]), nfilters) or size(mags[i]) == (nfilters, length(mini_vec[i])), with nfilters being the number of filters you are providing.
  • mag_names::AbstractVector{String} contains strings describing the filters you are providing in mags; an example might be ["B","V"]. These are used when mag_lim is finite to determine what filter you want to use to limit the faintest stars you want returned. These are assumed to be the same for all isochrones.
  • absmag::Number gives the total absolute magnitude of the complex population to be sampled.
  • fracs::AbstractVector{<:Number} is a vector giving the relative fraction of luminosity or mass (determined by the frac_type keyword argument) allotted to each individual stellar population; length must be equal to the length of mini_vec and mags.
  • imf::Distributions.Sampleable{Distributions.Univariate, Distributions.Continuous} is a sampleable continuous univariate distribution implementing a stellar initial mass function with a defined rand(rng::Random.AbstractRNG, imf) method to use for sampling masses. All instances of Distributions.ContinuousUnivariateDistribution are also valid. Implementations of commonly used IMFs are available in InitialMassFunctions.jl.

Keyword Arguments

  • frac_type::String either "lum", in which case fracs is assumed to contain the relative luminosity fractions for each individual isochrone, or "mass", in which case it is assumed that fracs contains mass fractions ("mass" is not yet implemented).

All other keyword arguments kws... are passed to generate_stars_mag; you should refer to that method's documentation for more information.

Returns

  • sampled_masses::Vector{Vector{SVector{N,eltype(imf)}}} is a vector of vectors containing the initial stellar masses of the sampled stars. The outer vectors are separated by the isochrone the stars were generated from; i.e., all of sampled_masses[1] were sampled from mini_vec[1] and so on. These can be concatenated into a single vector with reduce(vcat,sampled_masses). The format of the contained StaticArrays.SVectors are as output from sample_system; see that method's documentation for more details.
  • sampled_mags::Vector{Vector{SVector{N,<:Number}}} is a vector of vectors containing StaticArrays.SVectors with the multi-band magnitudes of the sampled stars. The outer vectors are separated by the isochrone the stars were generated from; i.e. all of sampled_mags[1] were sampled from mags[1] and so on. To get the magnitude of star i in band j sampled from isochrone k, you would do sampled_mags[k][i][j]. This can be concatenated into a Vector{SVector} with reduce(vcat,sampled_mags) and a 2-D Matrix with reduce(hcat,reduce(vcat,sampled_mags)).
source

Observational Effects

The output produced from the above methods are clean in the sense that they do not include any observational effects like photometric error or incompleteness. These effects should be implemented in a post-processing step. We provide a simple method model_cmd that accepts user-defined photometric error and completeness functions and applies them to the initial catalog, returning a Monte Carlo realization of a possible observed catalog. This method assumes Gaussian photometric errors and that the photometric error and completeness functions are separable by filter – these assumptions are not applicable for all types of data, but the source code for the method is exceedingly simple (~20 lines) and should provide an example for how you could write a similar method that more accurately reflects your data.

StarFormationHistories.model_cmdFunction
new_mags = model_cmd(mags::AbstractVector{<:AbstractVector{<:Number}}, errfuncs, completefuncs; rng::Random.AbstractRNG=Random.default_rng())

Simple method for modelling photometric error and incompleteness to "mock observe" a pure catalog of stellar photometry, such as those produced by generate_stars_mass and generate_stars_mag. This method assumes Gaussian photometric errors and that the photometric error and completeness functions are separable by filter.

Arguments

  • mags::AbstractVector{<:AbstractVector{<:Number}}: a vector of vectors giving the magnitudes of each star to be modelled. The first index is the per-star index and the second index is the per-filter index (so mags[10][2] would give the magnitude of the tenth star in the second filter). This is the same format as the magnitudes returned by generate_stars_mass and generate_stars_mag; to use output from the composite versions, you must first reduce(vcat,mags) before passing to this function.
  • errfuncs: an iterable (typically a vector or tuple) of callables (typically functions or interpolators) with length equal to the number of filters contained in the elements of mags. This iterable must contain callables that, when called with the associated magnitudes from mags, will return the expected 1-σ photometric error at that magnitude. The organization is such that the photometric error for star i in band j is σ_ij = errfuncs[j](mags[i][j]).
  • completefuncs: an iterable (typically a vector or tuple) of callables (typically functions or interpolators) with length equal to the number of filters contained in the elements of mags. This iterable must contain callables that, when called with the associated magnitudes from mags, will return the probability that a star with that magnitude in that band will be found in your color-magnitude diagram (this should include the original detection probability and any post-detection quality, morphology, or other cuts). The organization is such that the detection probability for star i in band j is c_ij = completefuncs[j](mags[i][j]).

Keyword Arguments

  • rng::AbstractRNG=Random.default_rng(): The object to use for random number generation.

Returns

  • new_mags: an object similar to mags (i.e., a Vector{Vector{<:Number}}, Vector{SVector{N,<:Number}}, etc.) containing the magnitudes of the mock-observed stars. This will be shorter than the provided mags vector as we are modelling photometric incompleteness, and the magnitudes will also have random photometric errors added to them. This can be reinterpreted as a 2-dimensional Matrix with reduce(hcat,new_mags).

Notes

  • This is a simple implementation that seeks to show a simple example of how one can post-process catalogs of "pure" stars from methods like generate_stars_mass and generate_stars_mag to include observational effects. This method assumes Gaussian photometric errors, which may not, in general, be accurate. It also assumes that the total detection probability can be modelled as the product of the single-filter detection probabilities as computed by completefuncs (i.e., that the completeness functions are separable across filters). This can be a reasonable assumption when you have separate photometric catalogs derived for each filter and you only collate them afterwards, but it is generally not a good assumption for detection algorithms that operate on simultaneously on multi-band photometry – the completeness functions for these types of algorithms are generally not separable.
source

Developer Internals

StarFormationHistories.ingest_magsFunction
new_mags = ingest_mags(mini_vec::AbstractVector, mags::AbstractVector{T}) where {S <: Number, T <: AbstractVector{S}}
-new_mags = ingest_mags(mini_vec::AbstractVector, mags::AbstractMatrix{S}) where S <: Number

Reinterprets provided mags to be in the correct format for input to Interpolations.interpolate.

Returns

  • new_mags::Base.ReinterpretArray{StaticArrays.SVector}: a length(mini_vec) vector of StaticArrays.SVectors containing the same data as mags but formatted for input to Interpolations.interpolate.
source
StarFormationHistories.sort_ingestedFunction
(new_mini_vec, new_mags) = sort_ingested(mini_vec::AbstractVector, mags::AbstractVector)

Takes mini_vec and mags and ensures that mini_vec is sorted (sometimes in PARSEC isochrones they are not) and calls Interpolations.deduplicate_knots! on mini_vec to ensure there are no repeat entries. Arguments must satisfy length(mini_vec) == length(mags).

source
StarFormationHistories.mass_limitsFunction
(mmin, mmax) = mass_limits(mini_vec::AbstractVector{<:Number}, mags::AbstractVector{T},
+Simulating Color-Magnitude Diagrams · StarFormationHistories.jl

Simulating Color-Magnitude Diagrams

Modelling observations of resolved stellar populations (e.g., color-magnitude or Hess diagrams) with user-defined star formation histories can be useful for comparison to actual observations, but also enables a number of other scientific activities (e.g., making predictions to motivate observational proposals). To support these uses we offer methods for sampling stellar populations from isochrones using user-defined star formation histories, initial mass functions, and stellar binary models. These methods require data from user-provided isochrones (this package does not provide any), an initial mass function model (such as those provided in InitialMassFunctions.jl), and a model specifying how (or if) to sample binary or multi-star systems.

The simplest methods only sample stars from a single stellar population. We provide a method that samples up to a provided stellar mass, generate_stars_mass (e.g., $10^7 \, \text{M}_\odot$) and a method that samples up to a provided absolute magnitude generate_stars_mag (e.g., $M_V=-10$). These are documented under the first subsection below. These methods are single-threaded.

We also offer methods for sampling populations with complex star formation histories; these are implicitly multi-threaded across the separate populations if you start Julia with multiple threads (e.g., with julia -t 4 or similar). We provide generate_stars_mass_composite for sampling such populations up to a provided stellar mass and generate_stars_mag_composite for sampling such populations up to a provided absolute magnitude. These are documented under the second subsection below.

Simple Stellar Populations

StarFormationHistories.generate_stars_massFunction
(sampled_masses, sampled_mags) = generate_stars_mass(mini_vec::AbstractVector{<:Number}, mags, mag_names::AbstractVector{String}, limit::Number, imf::Distributions.Sampleable{Distributions.Univariate, Distributions.Continuous}; dist_mod::Number=0, rng::Random.AbstractRNG=Random.default_rng(), mag_lim::Number=Inf, mag_lim_name::String="V", binary_model::StarFormationHistories.AbstractBinaryModel=StarFormationHistories.RandomBinaryPairs(0.3))

Generates a random sample of stars from an isochrone defined by the provided initial stellar masses mini_vec, absolute magnitudes mags, and filter names mag_names with total population birth stellar mass limit (e.g., 1e5 solar masses). Initial stellar masses are sampled from the provided imf.

Arguments

  • mini_vec::AbstractVector{<:Number} contains the initial masses (in solar masses) for the stars in the isochrone; must be mutable as we call Interpolations.deduplicate_knots!(mini_vec).
  • mags contains the absolute magnitudes from the isochrone in the desired filters corresponding to the same stars as provided in mini_vec. mags is internally interpreted and converted into a standard format by StarFormationHistories.ingest_mags. Valid inputs are:
    • mags::AbstractVector{AbstractVector{<:Number}}, in which case the length of the outer vector length(mags) can either be equal to length(mini_vec), in which case the length of the inner vectors must all be equal to the number of filters you are providing, or the length of the outer vector can be equal to the number of filters you are providing, and the length of the inner vectors must all be equal to length(mini_vec); this is the more common use-case.
    • mags::AbstractMatrix{<:Number}, in which case mags must be 2-dimensional. Valid shapes are size(mags) == (length(mini_vec), nfilters) or size(mags) == (nfilters, length(mini_vec)), with nfilters being the number of filters you are providing.
  • mag_names::AbstractVector{String} contains strings describing the filters you are providing in mags; an example might be ["B","V"]. These are used when mag_lim is finite to determine what filter you want to use to limit the faintest stars you want returned.
  • limit::Number gives the total birth stellar mass of the population you want to sample. See the "Notes" section on population masses for more information.
  • imf::Distributions.Sampleable{Distributions.Univariate, Distributions.Continuous} is a sampleable continuous univariate distribution implementing a stellar initial mass function with a defined rand(rng::Random.AbstractRNG, imf) method to use for sampling masses. All instances of Distributions.ContinuousUnivariateDistribution are also valid. Implementations of commonly used IMFs are available in InitialMassFunctions.jl.

Keyword Arguments

  • dist_mod::Number=0 is the distance modulus (see StarFormationHistories.distance_modulus) you wish to have added to the returned magnitudes to simulate a population at a particular distance.
  • rng::Random.AbstractRNG=Random.default_rng() is the rng instance that will be used to sample the stellar initial masses from imf.
  • mag_lim::Number=Inf gives the faintest apparent magnitude for stars you want to be returned in the output. Stars fainter than this magnitude will still be sampled and contribute properly to the total mass of the population, but they will not be returned.
  • mag_lim_name::String="V" gives the filter name (as contained in mag_names) to use when considering if a star is fainter than mag_lim. This is unused if mag_lim is infinite.
  • binary_model::StarFormationHistories.AbstractBinaryModel=StarFormationHistories.RandomBinaryPairs(0.3) is an instance of a model for treating binaries; currently provided options are NoBinaries, RandomBinaryPairs, and BinaryMassRatio.

Returns

  • sampled_masses::Vector{SVector{N,eltype(imf)}}: a vector containing the initial stellar masses of the stars sampled by sample_system; see that method's documentation for details on format. In short, each StaticArrays.SVector represents one stellar system and each entry in each StaticArrays.SVector is one star in that system. Entries will be 0 when companions could have been sampled but were not (i.e., when using a binary_model that supports multi-star systems).
  • sampled_mags::Vector{SVector{N,<:Number}}: a vector containing StaticArrays.SVectors with the multi-band magnitudes of the sampled stars. To get the magnitude of star i in band j, you index as sampled_mags[i][j]. This can be reinterpreted as a 2-dimensional Matrix with reduce(hcat,sampled_mags).

Notes

Population Masses

Given a particular isochrone with an initial mass vector mini_vec, it will never cover the full range of stellar birth masses because stars that die before present-day are not included in the isochrone. However, these stars were born, and so contribute to the total birth mass of the system. There are two ways to properly account for this lost mass when sampling:

  1. Set the upper limit for masses that can be sampled from the imf distribution to a physical value for the maximum birth mass of stars (e.g., 100 solar masses). In this case, these stars will be sampled from imf, and will contribute their masses to the system, but they will not be returned if their birth mass is greater than maximum(mini_vec). This is typically easiest for the user and only results in ∼15% loss of efficiency for 10 Gyr isochrones.
  2. Set the upper limit for masses that can be sampled from the imf distribution to maximum(mini_vec) and adjust limit to respect the amount of initial stellar mass lost by not sampling higher mass stars. This can be calculated as new_limit = limit * ( QuadGK.quadgk(x->x*pdf(imf,x), minimum(mini_vec), maximum(mini_vec))[1] / QuadGK.quadgk(x->x*pdf(imf,x), minimum(imf), maximum(imf))[1] ), with the multiplicative factor being the fraction of the population stellar mass contained in stars with initial masses between minimum(mini_vec) and maximum(mini_vec); this factor is the ratio

\[\frac{\int_a^b \ m \times \frac{dN(m)}{dm} \ dm}{\int_0^∞ \ m \times \frac{dN(m)}{dm} \ dm}\]

source
StarFormationHistories.generate_stars_magFunction
(sampled_masses, sampled_mags) =  generate_stars_mag(mini_vec::AbstractVector{<:Number}, mags, mag_names::AbstractVector{String}, absmag::Real, absmag_name::String, imf::Distributions.Sampleable{Distributions.Univariate,Distributions.Continuous}; dist_mod::Number=0, rng::AbstractRNG=default_rng(), mag_lim::Number=Inf, mag_lim_name::String="V", binary_model::StarFormationHistories.AbstractBinaryModel=RandomBinaryPairs(0.3))

Generates a mock stellar population from an isochrone defined by the provided initial stellar masses mini_vec, absolute magnitudes mags, and filter names mag_names. The population is sampled to a total absolute magnitude absmag::Real (e.g., -7 or -12) in the filter absmag_name::String (e.g., "V" or "F606W") which is contained in the provided mag_names::AbstractVector{String}. Other arguments are shared with generate_stars_mass, which contains the main documentation.

Notes

Population Magnitudes

Unlike when sampling a population to a fixed initial birth mass, as is implemented in generate_stars_mass, when generating a population up to a fixed absolute magnitude, only stars that survive to present-day contribute to the flux of the population. If you choose to limit the apparent magnitude of stars returned by passing the mag_lim and mag_lim_name keyword arguments, stars fainter than your chosen limit will still be sampled and will still contribute their luminosity to the total population, but they will not be contained in the returned output.

source

Complex Stellar Populations

StarFormationHistories.generate_stars_mass_compositeFunction
(sampled_masses, sampled_mags) = generate_stars_mass_composite(mini_vec::AbstractVector{<:AbstractVector{<:Number}}, mags::AbstractVector, mag_names::AbstractVector{String}, limit::Number, massfrac::AbstractVector{<:Number}, imf::Sampleable{Univariate,Continuous}; kws...)

Generates a random sample of stars with a complex star formation history using multiple isochrones. Very similar to generate_stars_mass except the isochrone-related arguments mini_vec and mags should now be vectors of vectors containing the relevant data for the full set of isochrones to be considered. The total birth stellar mass of the sampled population is given by limit. The proportion of this mass allotted to each of the individual isochrones is given by the entries of the massfrac vector. This basically just proportions limit according to massfrac and calls generate_stars_mass for each of the individual stellar populations; as such it is set up to multi-thread across the multiple stellar populations.

Arguments

  • mini_vec::AbstractVector{<:AbstractVector{<:Number}} contains the initial masses (in solar masses) for the stars in each isochrone; the internal vectors must be mutable as we will call Interpolations.deduplicate_knots! on each. The length of mini_vec should be equal to the number of isochrones.
  • mags contains the absolute magnitudes from the isochrones in the desired filters corresponding to the same stars as provided in mini_vec. The length of mags should be equal to the number of isochrones. The individual elements of mags are each internally interpreted and converted into a standard format by StarFormationHistories.ingest_mags. The valid formats for the individual elements of mags are:
    • AbstractVector{AbstractVector{<:Number}}, in which case the length of the vector length(mags[i]) can either be equal to length(mini_vec[i]), in which case the length of the inner vectors must all be equal to the number of filters you are providing, or the length of the outer vector can be equal to the number of filters you are providing, and the length of the inner vectors must all be equal to length(mini_vec[i]); this is the more common use-case.
    • AbstractMatrix{<:Number}, in which case mags[i] must be 2-dimensional. Valid shapes are size(mags[i]) == (length(mini_vec[i]), nfilters) or size(mags[i]) == (nfilters, length(mini_vec[i])), with nfilters being the number of filters you are providing.
  • mag_names::AbstractVector{String} contains strings describing the filters you are providing in mags; an example might be ["B","V"]. These are used when mag_lim is finite to determine what filter you want to use to limit the faintest stars you want returned. These are assumed to be the same for all isochrones.
  • limit::Number gives the total birth stellar mass of the population you want to sample.
  • massfrac::AbstractVector{<:Number} is vector giving the relative fraction of mass allotted to each individual stellar population; length must be equal to the length of mini_vec and mags.
  • imf::Distributions.Sampleable{Distributions.Univariate, Distributions.Continuous} is a sampleable continuous univariate distribution implementing a stellar initial mass function with a defined rand(rng::Random.AbstractRNG, imf) method to use for sampling masses. All instances of Distributions.ContinuousUnivariateDistribution are also valid. Implementations of commonly used IMFs are available in InitialMassFunctions.jl.

Keyword Arguments

All keyword arguments kws... are passed to generate_stars_mass; you should refer to that method's documentation for more information.

Returns

  • sampled_masses::Vector{Vector{SVector{N,eltype(imf)}}} is a vector of vectors containing the initial stellar masses of the sampled stars. The outer vectors are separated by the isochrone the stars were generated from; i.e., all of sampled_masses[1] were sampled from mini_vec[1] and so on. These can be concatenated into a single vector with reduce(vcat,sampled_masses). The format of the contained StaticArrays.SVectors are as output from sample_system; see that method's documentation for more details.
  • sampled_mags::Vector{Vector{SVector{N,<:Number}}} is a vector of vectors containing StaticArrays.SVectors with the multi-band magnitudes of the sampled stars. The outer vectors are separated by the isochrone the stars were generated from; i.e. all of sampled_mags[1] were sampled from mags[1] and so on. To get the magnitude of star i in band j sampled from isochrone k, you would do sampled_mags[k][i][j]. This can be concatenated into a Vector{SVector} with reduce(vcat,sampled_mags) and a 2-D Matrix with reduce(hcat,reduce(vcat,sampled_mags)).
source
StarFormationHistories.generate_stars_mag_compositeFunction
(sampled_masses, sampled_mags) = generate_stars_mag_composite(mini_vec::AbstractVector{<:AbstractVector{<:Number}}, mags::AbstractVector, mag_names::AbstractVector{String}, absmag::Number, absmag_name::String, fracs::AbstractVector{<:Number}, imf::Sampleable{Univariate,Continuous}; frac_type::String="lum", kws...)

Generates a random sample of stars with a complex star formation history using multiple isochrones. Very similar to generate_stars_mag except the isochrone-related arguments mini_vec and mags should now be vectors of vectors containing the relevant data for the full set of isochrones to be considered. The total absolute magnitude of the sampled population is given by absmag. The proportion of the luminosity allotted to each of the individual isochrones is given by the entries of the frac vector. This basically just proportions the luminosity according to frac and calls generate_stars_mag for each of the individual stellar populations; as such it is set up to multi-thread across the multiple stellar populations.

Arguments

  • mini_vec::AbstractVector{<:AbstractVector{<:Number}} contains the initial masses (in solar masses) for the stars in each isochrone; the internal vectors must be mutable as we will call Interpolations.deduplicate_knots! on each. The length of mini_vec should be equal to the number of isochrones.
  • mags contains the absolute magnitudes from the isochrones in the desired filters corresponding to the same stars as provided in mini_vec. The length of mags should be equal to the number of isochrones. The individual elements of mags are each internally interpreted and converted into a standard format by StarFormationHistories.ingest_mags. The valid formats for the individual elements of mags are:
    • AbstractVector{AbstractVector{<:Number}}, in which case the length of the vector length(mags[i]) can either be equal to length(mini_vec[i]), in which case the length of the inner vectors must all be equal to the number of filters you are providing, or the length of the outer vector can be equal to the number of filters you are providing, and the length of the inner vectors must all be equal to length(mini_vec[i]); this is the more common use-case.
    • AbstractMatrix{<:Number}, in which case mags[i] must be 2-dimensional. Valid shapes are size(mags[i]) == (length(mini_vec[i]), nfilters) or size(mags[i]) == (nfilters, length(mini_vec[i])), with nfilters being the number of filters you are providing.
  • mag_names::AbstractVector{String} contains strings describing the filters you are providing in mags; an example might be ["B","V"]. These are used when mag_lim is finite to determine what filter you want to use to limit the faintest stars you want returned. These are assumed to be the same for all isochrones.
  • absmag::Number gives the total absolute magnitude of the complex population to be sampled.
  • fracs::AbstractVector{<:Number} is a vector giving the relative fraction of luminosity or mass (determined by the frac_type keyword argument) allotted to each individual stellar population; length must be equal to the length of mini_vec and mags.
  • imf::Distributions.Sampleable{Distributions.Univariate, Distributions.Continuous} is a sampleable continuous univariate distribution implementing a stellar initial mass function with a defined rand(rng::Random.AbstractRNG, imf) method to use for sampling masses. All instances of Distributions.ContinuousUnivariateDistribution are also valid. Implementations of commonly used IMFs are available in InitialMassFunctions.jl.

Keyword Arguments

  • frac_type::String either "lum", in which case fracs is assumed to contain the relative luminosity fractions for each individual isochrone, or "mass", in which case it is assumed that fracs contains mass fractions ("mass" is not yet implemented).

All other keyword arguments kws... are passed to generate_stars_mag; you should refer to that method's documentation for more information.

Returns

  • sampled_masses::Vector{Vector{SVector{N,eltype(imf)}}} is a vector of vectors containing the initial stellar masses of the sampled stars. The outer vectors are separated by the isochrone the stars were generated from; i.e., all of sampled_masses[1] were sampled from mini_vec[1] and so on. These can be concatenated into a single vector with reduce(vcat,sampled_masses). The format of the contained StaticArrays.SVectors are as output from sample_system; see that method's documentation for more details.
  • sampled_mags::Vector{Vector{SVector{N,<:Number}}} is a vector of vectors containing StaticArrays.SVectors with the multi-band magnitudes of the sampled stars. The outer vectors are separated by the isochrone the stars were generated from; i.e. all of sampled_mags[1] were sampled from mags[1] and so on. To get the magnitude of star i in band j sampled from isochrone k, you would do sampled_mags[k][i][j]. This can be concatenated into a Vector{SVector} with reduce(vcat,sampled_mags) and a 2-D Matrix with reduce(hcat,reduce(vcat,sampled_mags)).
source

Observational Effects

The output produced from the above methods are clean in the sense that they do not include any observational effects like photometric error or incompleteness. These effects should be implemented in a post-processing step. We provide a simple method model_cmd that accepts user-defined photometric error and completeness functions and applies them to the initial catalog, returning a Monte Carlo realization of a possible observed catalog. This method assumes Gaussian photometric errors and that the photometric error and completeness functions are separable by filter – these assumptions are not applicable for all types of data, but the source code for the method is exceedingly simple (~20 lines) and should provide an example for how you could write a similar method that more accurately reflects your data.

StarFormationHistories.model_cmdFunction
new_mags = model_cmd(mags::AbstractVector{<:AbstractVector{<:Number}}, errfuncs, completefuncs; rng::Random.AbstractRNG=Random.default_rng())

Simple method for modelling photometric error and incompleteness to "mock observe" a pure catalog of stellar photometry, such as those produced by generate_stars_mass and generate_stars_mag. This method assumes Gaussian photometric errors and that the photometric error and completeness functions are separable by filter.

Arguments

  • mags::AbstractVector{<:AbstractVector{<:Number}}: a vector of vectors giving the magnitudes of each star to be modelled. The first index is the per-star index and the second index is the per-filter index (so mags[10][2] would give the magnitude of the tenth star in the second filter). This is the same format as the magnitudes returned by generate_stars_mass and generate_stars_mag; to use output from the composite versions, you must first reduce(vcat,mags) before passing to this function.
  • errfuncs: an iterable (typically a vector or tuple) of callables (typically functions or interpolators) with length equal to the number of filters contained in the elements of mags. This iterable must contain callables that, when called with the associated magnitudes from mags, will return the expected 1-σ photometric error at that magnitude. The organization is such that the photometric error for star i in band j is σ_ij = errfuncs[j](mags[i][j]).
  • completefuncs: an iterable (typically a vector or tuple) of callables (typically functions or interpolators) with length equal to the number of filters contained in the elements of mags. This iterable must contain callables that, when called with the associated magnitudes from mags, will return the probability that a star with that magnitude in that band will be found in your color-magnitude diagram (this should include the original detection probability and any post-detection quality, morphology, or other cuts). The organization is such that the detection probability for star i in band j is c_ij = completefuncs[j](mags[i][j]).

Keyword Arguments

  • rng::AbstractRNG=Random.default_rng(): The object to use for random number generation.

Returns

  • new_mags: an object similar to mags (i.e., a Vector{Vector{<:Number}}, Vector{SVector{N,<:Number}}, etc.) containing the magnitudes of the mock-observed stars. This will be shorter than the provided mags vector as we are modelling photometric incompleteness, and the magnitudes will also have random photometric errors added to them. This can be reinterpreted as a 2-dimensional Matrix with reduce(hcat,new_mags).

Notes

  • This is a simple implementation that seeks to show a simple example of how one can post-process catalogs of "pure" stars from methods like generate_stars_mass and generate_stars_mag to include observational effects. This method assumes Gaussian photometric errors, which may not, in general, be accurate. It also assumes that the total detection probability can be modelled as the product of the single-filter detection probabilities as computed by completefuncs (i.e., that the completeness functions are separable across filters). This can be a reasonable assumption when you have separate photometric catalogs derived for each filter and you only collate them afterwards, but it is generally not a good assumption for detection algorithms that operate on simultaneously on multi-band photometry – the completeness functions for these types of algorithms are generally not separable.
source

Developer Internals

StarFormationHistories.ingest_magsFunction
new_mags = ingest_mags(mini_vec::AbstractVector, mags::AbstractVector{T}) where {S <: Number, T <: AbstractVector{S}}
+new_mags = ingest_mags(mini_vec::AbstractVector, mags::AbstractMatrix{S}) where S <: Number

Reinterprets provided mags to be in the correct format for input to Interpolations.interpolate.

Returns

  • new_mags::Base.ReinterpretArray{StaticArrays.SVector}: a length(mini_vec) vector of StaticArrays.SVectors containing the same data as mags but formatted for input to Interpolations.interpolate.
source
StarFormationHistories.sort_ingestedFunction
(new_mini_vec, new_mags) = sort_ingested(mini_vec::AbstractVector, mags::AbstractVector)

Takes mini_vec and mags and ensures that mini_vec is sorted (sometimes in PARSEC isochrones they are not) and calls Interpolations.deduplicate_knots! on mini_vec to ensure there are no repeat entries. Arguments must satisfy length(mini_vec) == length(mags).

source
StarFormationHistories.mass_limitsFunction
(mmin, mmax) = mass_limits(mini_vec::AbstractVector{<:Number}, mags::AbstractVector{T},
                  mag_names::AbstractVector{String}, mag_lim::Number,
                  mag_lim_name::String) where T <: AbstractVector{<:Number}

Calculates initial mass limits that reflect a given faint-end magnitude limit.

Arguments

  • mini_vec::AbstractVector{<:Number}: a length nstars vector containing initial stellar masses.
  • mags::AbstractVector{<:AbstractVector{<:Number}}: a length nstars vector, with each element being a length nfilters vector giving the magnitudes of each star in the filters mag_names.
  • mag_names::AbstractVector{String}: a vector giving the names of each filter as strings.
  • mag_lim::Number: the faint-end magnitude limit you wish to use.
  • mag_lim_name::String: the name of the filter in which mag_lim is to be applied. Must be contained in mag_names.

Returns

  • mmin::eltype(mini_vec): the initial mass corresponding to your requested mag_lim in the filter mag_lim_name. If all stars provided are brighter than your requested mag_lim, then this will be equal to minimum(mini_vec).
  • mmax::eltype(mini_vec): the maximum valid mass in mini_vec; simply maximum(mini_vec).

Examples

julia> mass_limits([0.05,0.1,0.2,0.3], [[4.0],[3.0],[2.0],[1.0]], ["F090W"], 2.5, "F090W")
 (0.15, 0.3)
 
 julia> mass_limits([0.05,0.1,0.2,0.3], [[4.0,3.0],[3.0,2.0],[2.0,1.0],[1.0,0.0]], ["F090W","F150W"], 2.5, "F090W")
-(0.15, 0.3)
source
+(0.15, 0.3)
source