diff --git a/dev/.documenter-siteinfo.json b/dev/.documenter-siteinfo.json index ae2bb020..7886c797 100644 --- a/dev/.documenter-siteinfo.json +++ b/dev/.documenter-siteinfo.json @@ -1 +1 @@ -{"documenter":{"julia_version":"1.10.4","generation_timestamp":"2024-06-29T11:41:25","documenter_version":"1.5.0"}} \ No newline at end of file +{"documenter":{"julia_version":"1.10.4","generation_timestamp":"2024-06-30T12:34:58","documenter_version":"1.5.0"}} \ No newline at end of file diff --git a/dev/datasets/datasets/index.html b/dev/datasets/datasets/index.html index 4204a660..3c6b6a92 100644 --- a/dev/datasets/datasets/index.html +++ b/dev/datasets/datasets/index.html @@ -6,5 +6,5 @@ match_domains = true, background = nothing, ancillary = nothing, -)source

Dataset abstraction

Datasets must define a small API to make fitting possible. The picture to have in mind when considering the different domains is as follows: the model is trying to predict the objective. It does so by taking in input domain and maps it to some output domain.

That means make_output_domain and make_objective_domain correspond to the $(X,Y)$ values of the data that the model is trying to fit, whilst the model is evaluated on the make_model_domain, which need not be the same as the output domain.

In other cases, the objective_transformer acts to transform the output of the model onto the output domain.

Mathematically, expressing the output domain $X$, the model domain $D$, the model output $M(D)$ and objective $S$, along with the transformer as $T$, then the relationship between the different domains is

\[\hat{S} = T \times M(D),\]

Both $\hat{S}$ and $S$ are defined over $X$. The various fitting operations try to find model paramters that make $\hat{S}$ and $S$ as close as possible.

SpectralFitting.AbstractDatasetType
abstract type AbstractDataset

Abstract type for use in fitting routines. High level representation of some underlying data structures.

Fitting data is considered to have an objective and a domain. As the domain may be, for example, energy bins (high and low), or fourier frequencies (single value), the purpose of this abstraction is to provide some facility for translating between these representations for the models to fit with. This is done by checking that the AbstractLayout of the model and data are compatible, or at least have compatible translations.

Must implement a minimal set of accessor methods. These are paired with objective and domain parlance. Note that these functions are prefixed with make_* and not get_* to represent that there may be allocations or work going into the translation. Usage of these functions should be sparse in the interest of performance.

The arrays returned by the make_* functions must correspond to the AbstractLayout specified by the caller.

Additionally there is an objective transformer that transforms the output of the model onto the output domain:

Finally, to make all of the fitting for different statistical regimes work efficiently, datasets should inform which units are preferred to fit. They may also give the error statistics they prefer, and a label name primarily used to disambiguate:

source
SpectralFitting.make_objective_varianceFunction
make_objective_variance(layout::AbstractLayout, dataset::AbstractDataset)

Make the variance vector associated with each objective point.

source
SpectralFitting.make_objectiveFunction
make_objective(layout::AbstractLayout, dataset::AbstractDataset)

Returns the array used as the target for model fitting. The array must correspond to the data AbstractLayout specified by the layout parameter.

In as far as it can be guarunteed, the memory in the returned array will not be mutated by any fitting procedures.

Domain for this objective should be returned by make_model_domain.

source
SpectralFitting.make_domain_varianceFunction
make_domain_variance(layout::AbstractLayout, dataset::AbstractDataset)

Make the variance vector associated with the domain.

source
SpectralFitting.make_model_domainFunction
make_model_domain(layout::AbstractLayout, dataset::AbstractDataset)

Returns the array used as the domain for the modelling. This is paired with make_domain_variance

source
SpectralFitting.make_output_domainFunction
make_output_domain(layout::AbstractLayout, dataset::AbstractDataset)

Returns the array used as the output domain. That is, in cases where the model input and output map to different domains, the input domain is said to be the model domain, the input domain is said to be the model domain.

The distinction is mainly used for the purposes of simulating data and for visualising data.

source

Underlying data layouts

SpectralFitting.AbstractLayoutType
abstract type AbstractLayout end

The data layout primarily concerns the relationship between the objective and the domain. It is used to work out whether a model and a dataset are fittable, and if not, whether a translation in the output of the model to the domain of the model is possible.

The following methods may be used to interrogate support:

  • preferred_support for inferring the preferred support of a model when multiple supports are possible.
  • common_support to obtain the common support of two structures

The following method is also used to define the support of a model or dataset:

For cases where unit information needs to be propagated, an AbstractLayout can also be used to ensure the units are compatible. To query the units of a layout, use

source
SpectralFitting.OneToOneType
struct OneToOne <: AbstractLayout end

Indicates there is a one-to-one (injective) correspondence between each input value and each output value. That is to say

length(objective) == length(domain)
source
SpectralFitting.ContiguouslyBinnedType
struct ContiguouslyBinned <: AbstractLayout end

Contiguously binned data layout means that the domain describes high and low bins, with the objective being the value in that bin. This means

length(objective) + 1== length(domain)

Note that the contiguous qualifer is to mean there is no gaps in the bins, and that

\[\Delta E_i = E_{i+1} - E_{i}\]

source
SpectralFitting.common_supportFunction
common_support(x, y)

Find the common AbstractLayout of x and y, following the ordering of preferred_support.

source
SpectralFitting.preferred_supportFunction
preferred_support(x)

Get the preferred AbstractLayout of x. If multiple supports are available, the DEFAULT_SUPPORT_ORDERING is followed:

DEFAULT_SUPPORT_ORDERING = (ContiguouslyBinned{Nothing}(nothing), OneToOne{Nothing}(nothing))
source
SpectralFitting.supportsFunction
supports(x::Type)

Used to define whether a given type has support for a specific AbstractLayout. Should return a tuple of the supported layouts. This method should be implemented to express new support, not the query method.

To query, there is

supports(layout::AbstractLayout, x)::Bool

Example

supports(::Type{typeof(x)}) = (OneToOne(),)
-@assert supports(ContiguouslyBinned(), x) == false
source
+)source

Dataset abstraction

Datasets must define a small API to make fitting possible. The picture to have in mind when considering the different domains is as follows: the model is trying to predict the objective. It does so by taking in input domain and maps it to some output domain.

That means make_output_domain and make_objective_domain correspond to the $(X,Y)$ values of the data that the model is trying to fit, whilst the model is evaluated on the make_model_domain, which need not be the same as the output domain.

In other cases, the objective_transformer acts to transform the output of the model onto the output domain.

Mathematically, expressing the output domain $X$, the model domain $D$, the model output $M(D)$ and objective $S$, along with the transformer as $T$, then the relationship between the different domains is

\[\hat{S} = T \times M(D),\]

Both $\hat{S}$ and $S$ are defined over $X$. The various fitting operations try to find model paramters that make $\hat{S}$ and $S$ as close as possible.

SpectralFitting.AbstractDatasetType
abstract type AbstractDataset

Abstract type for use in fitting routines. High level representation of some underlying data structures.

Fitting data is considered to have an objective and a domain. As the domain may be, for example, energy bins (high and low), or fourier frequencies (single value), the purpose of this abstraction is to provide some facility for translating between these representations for the models to fit with. This is done by checking that the AbstractLayout of the model and data are compatible, or at least have compatible translations.

Must implement a minimal set of accessor methods. These are paired with objective and domain parlance. Note that these functions are prefixed with make_* and not get_* to represent that there may be allocations or work going into the translation. Usage of these functions should be sparse in the interest of performance.

The arrays returned by the make_* functions must correspond to the AbstractLayout specified by the caller.

Additionally there is an objective transformer that transforms the output of the model onto the output domain:

Finally, to make all of the fitting for different statistical regimes work efficiently, datasets should inform which units are preferred to fit. They may also give the error statistics they prefer, and a label name primarily used to disambiguate:

source
SpectralFitting.make_objective_varianceFunction
make_objective_variance(layout::AbstractLayout, dataset::AbstractDataset)

Make the variance vector associated with each objective point.

source
SpectralFitting.make_objectiveFunction
make_objective(layout::AbstractLayout, dataset::AbstractDataset)

Returns the array used as the target for model fitting. The array must correspond to the data AbstractLayout specified by the layout parameter.

In as far as it can be guarunteed, the memory in the returned array will not be mutated by any fitting procedures.

Domain for this objective should be returned by make_model_domain.

source
SpectralFitting.make_domain_varianceFunction
make_domain_variance(layout::AbstractLayout, dataset::AbstractDataset)

Make the variance vector associated with the domain.

source
SpectralFitting.make_model_domainFunction
make_model_domain(layout::AbstractLayout, dataset::AbstractDataset)

Returns the array used as the domain for the modelling. This is paired with make_domain_variance

source
SpectralFitting.make_output_domainFunction
make_output_domain(layout::AbstractLayout, dataset::AbstractDataset)

Returns the array used as the output domain. That is, in cases where the model input and output map to different domains, the input domain is said to be the model domain, the input domain is said to be the model domain.

The distinction is mainly used for the purposes of simulating data and for visualising data.

source

Underlying data layouts

SpectralFitting.AbstractLayoutType
abstract type AbstractLayout end

The data layout primarily concerns the relationship between the objective and the domain. It is used to work out whether a model and a dataset are fittable, and if not, whether a translation in the output of the model to the domain of the model is possible.

The following methods may be used to interrogate support:

  • preferred_support for inferring the preferred support of a model when multiple supports are possible.
  • common_support to obtain the common support of two structures

The following method is also used to define the support of a model or dataset:

For cases where unit information needs to be propagated, an AbstractLayout can also be used to ensure the units are compatible. To query the units of a layout, use

source
SpectralFitting.OneToOneType
struct OneToOne <: AbstractLayout end

Indicates there is a one-to-one (injective) correspondence between each input value and each output value. That is to say

length(objective) == length(domain)
source
SpectralFitting.ContiguouslyBinnedType
struct ContiguouslyBinned <: AbstractLayout end

Contiguously binned data layout means that the domain describes high and low bins, with the objective being the value in that bin. This means

length(objective) + 1== length(domain)

Note that the contiguous qualifer is to mean there is no gaps in the bins, and that

\[\Delta E_i = E_{i+1} - E_{i}\]

source
SpectralFitting.common_supportFunction
common_support(x, y)

Find the common AbstractLayout of x and y, following the ordering of preferred_support.

source
SpectralFitting.preferred_supportFunction
preferred_support(x)

Get the preferred AbstractLayout of x. If multiple supports are available, the DEFAULT_SUPPORT_ORDERING is followed:

DEFAULT_SUPPORT_ORDERING = (ContiguouslyBinned{Nothing}(nothing), OneToOne{Nothing}(nothing))
source
SpectralFitting.supportsFunction
supports(x::Type)

Used to define whether a given type has support for a specific AbstractLayout. Should return a tuple of the supported layouts. This method should be implemented to express new support, not the query method.

To query, there is

supports(layout::AbstractLayout, x)::Bool

Example

supports(::Type{typeof(x)}) = (OneToOne(),)
+@assert supports(ContiguouslyBinned(), x) == false
source
diff --git a/dev/datasets/mission-support/index.html b/dev/datasets/mission-support/index.html index 3f5dff45..d269b2a1 100644 --- a/dev/datasets/mission-support/index.html +++ b/dev/datasets/mission-support/index.html @@ -1,2 +1,2 @@ -Mission support · SpectralFitting.jl
+Mission support · SpectralFitting.jl
diff --git a/dev/examples/examples/34dbe8e7.svg b/dev/examples/examples/8089ac4e.svg similarity index 83% rename from dev/examples/examples/34dbe8e7.svg rename to dev/examples/examples/8089ac4e.svg index ca671c0f..b58c0c81 100644 --- a/dev/examples/examples/34dbe8e7.svg +++ b/dev/examples/examples/8089ac4e.svg @@ -1,44 +1,44 @@ - + - + - + - + - + - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/dev/examples/examples/index.html b/dev/examples/examples/index.html index 6a6a6d30..ad48d8ab 100644 --- a/dev/examples/examples/index.html +++ b/dev/examples/examples/index.html @@ -9,4 +9,4 @@ flux = invokemodel(energy, model) -plot(energy[1:end-1], flux)Example block output

Note this energy grid may be arbitrarily spaced, but, like XSPEC, assumes the bins are contiguous, i.e. that the high energy limit of one bin is the low energy limit of the next.

The full model library of available models is listed in Model index.

+plot(energy[1:end-1], flux)Example block output

Note this energy grid may be arbitrarily spaced, but, like XSPEC, assumes the bins are contiguous, i.e. that the high energy limit of one bin is the low energy limit of the next.

The full model library of available models is listed in Model index.

diff --git a/dev/examples/optimizers/index.html b/dev/examples/optimizers/index.html index 9edf13ee..64c1e35d 100644 --- a/dev/examples/optimizers/index.html +++ b/dev/examples/optimizers/index.html @@ -17,4 +17,4 @@ ylims = (1e-3, 1.3) ) -my_plot(data) +my_plot(data) diff --git a/dev/examples/sherpa-example/10af53a2.svg b/dev/examples/sherpa-example/6f98728d.svg similarity index 84% rename from dev/examples/sherpa-example/10af53a2.svg rename to dev/examples/sherpa-example/6f98728d.svg index 9b32e68c..5c5e296c 100644 --- a/dev/examples/sherpa-example/10af53a2.svg +++ b/dev/examples/sherpa-example/6f98728d.svg @@ -1,46 +1,46 @@ - + - + - + - + - + - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/dev/examples/sherpa-example/0e91a652.svg b/dev/examples/sherpa-example/a0d64fea.svg similarity index 67% rename from dev/examples/sherpa-example/0e91a652.svg rename to dev/examples/sherpa-example/a0d64fea.svg index e8997bb0..a1d59a93 100644 --- a/dev/examples/sherpa-example/0e91a652.svg +++ b/dev/examples/sherpa-example/a0d64fea.svg @@ -1,243 +1,243 @@ - + - + - + - + - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/dev/examples/sherpa-example/9c5cc33f.svg b/dev/examples/sherpa-example/b5ebc148.svg similarity index 86% rename from dev/examples/sherpa-example/9c5cc33f.svg rename to dev/examples/sherpa-example/b5ebc148.svg index 3c7fb1f0..75d7e3e9 100644 --- a/dev/examples/sherpa-example/9c5cc33f.svg +++ b/dev/examples/sherpa-example/b5ebc148.svg @@ -1,56 +1,56 @@ - + - + - + - + - + - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + - + - - - - - - - - + + + + + + + + diff --git a/dev/examples/sherpa-example/cecf40f3.svg b/dev/examples/sherpa-example/c63d3a65.svg similarity index 68% rename from dev/examples/sherpa-example/cecf40f3.svg rename to dev/examples/sherpa-example/c63d3a65.svg index 7d88aeed..bc0a22c8 100644 --- a/dev/examples/sherpa-example/cecf40f3.svg +++ b/dev/examples/sherpa-example/c63d3a65.svg @@ -1,321 +1,321 @@ - + - + - + - + - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/dev/examples/sherpa-example/a31a44c7.svg b/dev/examples/sherpa-example/cc8edd68.svg similarity index 64% rename from dev/examples/sherpa-example/a31a44c7.svg rename to dev/examples/sherpa-example/cc8edd68.svg index 098c9447..a0918ff2 100644 --- a/dev/examples/sherpa-example/a31a44c7.svg +++ b/dev/examples/sherpa-example/cc8edd68.svg @@ -1,319 +1,319 @@ - + - + - + - + - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/dev/examples/sherpa-example/index.html b/dev/examples/sherpa-example/index.html index d1e6a62a..4f13bb14 100644 --- a/dev/examples/sherpa-example/index.html +++ b/dev/examples/sherpa-example/index.html @@ -14,15 +14,15 @@ y_noisy = y .+ (0.2 * randn(length(y))) -scatter(x, y_noisy)Example block output

To make this into a fittable dataset, we observe that our layout is injective (i.e. length(x) == length(y)). This is subtly different from how the majority of spectral models are implemented, which usually assume some kind of binning (length(x) == length(y) + 1). Fortunately, SpectralFitting.jl can track this for us, and do various conversion to make the models work correctly for the data. We need only tell the package what our AbstractLayout is:

data = InjectiveData(x, y_noisy; name = "example")
InjectiveData with 200 data points:
+scatter(x, y_noisy)
Example block output

To make this into a fittable dataset, we observe that our layout is injective (i.e. length(x) == length(y)). This is subtly different from how the majority of spectral models are implemented, which usually assume some kind of binning (length(x) == length(y) + 1). Fortunately, SpectralFitting.jl can track this for us, and do various conversion to make the models work correctly for the data. We need only tell the package what our AbstractLayout is:

data = InjectiveData(x, y_noisy; name = "example")
InjectiveData with 200 data points:
 │   Name                  : example
 │   . Domain (min/max)    : (-5.0000, 5.0)
 │   . Codomain (min/max)  : (-0.42323, 3.3787)
-└ 

The data prints the data card, which provides us some high level information about our data at a glance. We can plot the data trivially using one of the Plots.jl recipes

plot(data, markersize = 3)
Example block output

Next we want to specify a model to fit to this data. Models that are prefixed with XS_ are models that are linked from the XSPEC model library, provided via LibXSPEC_jll. For a full list of the models, see Models index.

Warning

It is advised to use the Julia implemented models. This allows various calculations to benefit from automatic differentiation, efficient multi-threading, GPU offloading, and various other useful things, see Why & how.

model = GaussianLine(μ = FitParam(0.0))
┌ GaussianLine
+└ 

The data prints the data card, which provides us some high level information about our data at a glance. We can plot the data trivially using one of the Plots.jl recipes

plot(data, markersize = 3)
Example block output

Next we want to specify a model to fit to this data. Models that are prefixed with XS_ are models that are linked from the XSPEC model library, provided via LibXSPEC_jll. For a full list of the models, see Models index.

Warning

It is advised to use the Julia implemented models. This allows various calculations to benefit from automatic differentiation, efficient multi-threading, GPU offloading, and various other useful things, see Why & how.

model = GaussianLine(μ = FitParam(0.0))
┌ GaussianLine
 │      K ->  1 ± 0.1  ∈ [ 0, Inf ]   FREE
 │      μ ->  0 ± 0    ∈ [ 0, Inf ]   FREE
 │      σ ->  1 ± 0.1  ∈ [ 0, Inf ]   FREE
-└ 

We can plot our model over the same domain range quite easily too:

plot(data.domain[1:end-1], invokemodel(data.domain, model))
Example block output

Note that we've had to adjust the domain here. As stated before, most models are implemented for binned data, and therefore return one fewer bin than given.

SpectralFitting.jl adopts the SciML problem-solver abstraction, so to fit a model to data we specify a FittingProblem:

prob = FittingProblem(model => data)
┌ FittingProblem:
+└ 

We can plot our model over the same domain range quite easily too:

plot(data.domain[1:end-1], invokemodel(data.domain, model))
Example block output

Note that we've had to adjust the domain here. As stated before, most models are implemented for binned data, and therefore return one fewer bin than given.

SpectralFitting.jl adopts the SciML problem-solver abstraction, so to fit a model to data we specify a FittingProblem:

prob = FittingProblem(model => data)
┌ FittingProblem:
 │   Models:
 │     . GaussianLine{FitParam{Float64}}((1 ± 0.1), (0 ± 0), (1 ± 0.1))
 │   Data:
@@ -33,7 +33,7 @@
 │   . σᵤ    : [9.2751, 0.071547, 0.071547]
 │   . χ²    : 7.7600 
 └ 

The result card tells us a little bit about how successful the fit was. We further inspect the fit by overplotting result on the data:

plot(data, markersize = 3)
-plot!(result)
Example block output

We can create a contour plot of the fit statistic by evaluating the result everywhere on the grid and measuring the statistic:

amps = range(50, 200, 50)
+plot!(result)
Example block output

We can create a contour plot of the fit statistic by evaluating the result everywhere on the grid and measuring the statistic:

amps = range(50, 200, 50)
 devs = range(0.5, 1.2, 50)
 
 stats = [
@@ -51,4 +51,4 @@
     xlabel = "K",
     ylabel = "σ"
 )
-scatter!([result.u[1]], [result.u[3]])
Example block output

Simultaneous fits

+scatter!([result.u[1]], [result.u[3]])Example block output

Simultaneous fits

diff --git a/dev/fitting/index.html b/dev/fitting/index.html index 21a91785..684a99d6 100644 --- a/dev/fitting/index.html +++ b/dev/fitting/index.html @@ -1,2 +1,2 @@ -Fitting spectral models · SpectralFitting.jl
+Fitting spectral models · SpectralFitting.jl
diff --git a/dev/index.html b/dev/index.html index 08dcfbde..83a9f285 100644 --- a/dev/index.html +++ b/dev/index.html @@ -2,4 +2,4 @@ Home · SpectralFitting.jl

SpectralFitting.jl Documentation

Fast and flexible spectral fitting in Julia.

SpectralFitting.jl is a package for defining and using spectral models, with a number of utilities to make model composition easy and invocation fast. SpectralFitting wraps LibXSPEC_jll.jl to expose the library of models from HEASoft XSPEC, and provides helper functions for operating with spectral data from a number of different missions. The package natively uses LsqFit.jl to fit parameters using the Levenberg-Marquardt algorithm, but makes it easy to use Optim.jl for more specialized fitting algorithms, or Turing.jl for Bayesian inference and MCMC.

SpectralFitting is designed to be extended, such that new models are simple to create, and new dataset processing pipelines for different missions are brief to define. Where performance is key, SpectralFitting helps you define fast and AD-compatible surrogates of spectral models using Surrogates.jl, and embed them in the model composition algebra.

To get started, add the AstroRegistry from the University of Bristol and then install:

julia>]
 pkg> registry add https://github.com/astro-group-bristol/AstroRegistry
 pkg> add SpectralFitting

Then use

using SpectralFitting
-# ....

to get started. See Walkthrough for an example walkthrough the package.

For more University of Bristol Astrophysics Group codes, see our GitHub organisation.

+# ....

to get started. See Walkthrough for an example walkthrough the package.

For more University of Bristol Astrophysics Group codes, see our GitHub organisation.

diff --git a/dev/models/composite-models/index.html b/dev/models/composite-models/index.html index 30be674a..afe6f822 100644 --- a/dev/models/composite-models/index.html +++ b/dev/models/composite-models/index.html @@ -1,8 +1,8 @@ Composite models · SpectralFitting.jl

Composite models

The model algebra defined by the AbstractSpectralModelKind yields instances of CompositeModel, nested to various degrees. These composite models are designed to make as much information about the spectral model available at compile-time, such that rich and optimized generated functions may be assembled purely from the Julia types (see Why & how).

SpectralFitting.CompositeModelType
CompositeModel{M1,M2,O} <: AbstractSpectralModel
 CompositeModel(left_model, right_model, op::AbstractCompositeOperator)

Type resulting from operations combining any number of AbstractSpectralModel via the model algebra defined from AbstractSpectralModelKind.

Each operation binary operation in the model algebra is encoded in the parametric types of the CompositeModel, where the operation is given by an AbstractCompositeOperator. Composite models adopt the model kind of the right model, i.e. M2, and obey the model algebra accordingly.

Composite models very rarely need to be constructed directly, and are instead obtained by regular model operations.

Example

model = PhotoelectricAbsorption() * (PowerLaw() + BlackBody())
-typeof(model) <: CompositeModel # true
source
+typeof(model) <: CompositeModel # truesource diff --git a/dev/models/models/index.html b/dev/models/models/index.html index 788ead6e..2233034a 100644 --- a/dev/models/models/index.html +++ b/dev/models/models/index.html @@ -19,7 +19,7 @@ 0 │ │ └────────────────────────────────────────┘ 0 20 - E (keV)source
SpectralFitting.BlackBodyType
XS_BlackBody(K, T)
  • K: Normalisation.

  • kT: Temperature (keV).

Example

energy = collect(range(0.1, 20.0, 100))
 invokemodel(energy, BlackBody())
                        BlackBody
        ┌────────────────────────────────────────┐
    0.2 │                                        │
@@ -39,7 +39,7 @@
      0 │:                                    '''│
        └────────────────────────────────────────┘
         0                                     20
-                         E (keV)
source

XSPEC models

XSPEC models frequently have tabular data dependencies, without which the models fail to invoke (see Model data availability). If the data files are known but not present, the XSPEC models will throw an error with instructions for downloading the missing data. If the data files are unknown, Julia may crash catastrophically. If this is the case, often a single line will be printed with the LibXSPEC error, specifying the name of the missing source file. This can be registered as a data dependency of a model using SpectralFitting.register_model_data.

The first time any XSPEC model is invoked, SpectralFitting checks to see whether requisite data is needed, and whether the data is downloaded. Subsequent calls will hit a lookup cache instead to avoid run-time costs of performing this check.

XSPEC models

XSPEC models frequently have tabular data dependencies, without which the models fail to invoke (see Model data availability). If the data files are known but not present, the XSPEC models will throw an error with instructions for downloading the missing data. If the data files are unknown, Julia may crash catastrophically. If this is the case, often a single line will be printed with the LibXSPEC error, specifying the name of the missing source file. This can be registered as a data dependency of a model using SpectralFitting.register_model_data.

The first time any XSPEC model is invoked, SpectralFitting checks to see whether requisite data is needed, and whether the data is downloaded. Subsequent calls will hit a lookup cache instead to avoid run-time costs of performing this check.

SpectralFitting.XS_PowerLawType
XS_PowerLaw(K, a)
  • K: Normalisation.

  • a: Photon index.

Example

energy = collect(range(0.1, 20.0, 100))
 invokemodel(energy, XS_PowerLaw())
                      XS_PowerLaw
        ┌────────────────────────────────────────┐
    0.5 │                                        │
@@ -59,7 +59,7 @@
      0 │                                        │
        └────────────────────────────────────────┘
         0                                     20
-                         E (keV)
source
SpectralFitting.XS_BlackBodyType
XS_BlackBody(K, T)
  • K: Normalisation.

  • T: Temperature (keV).

Example

energy = collect(range(0.1, 20.0, 100))
 invokemodel(energy, XS_BlackBody())
                      XS_BlackBody
        ┌────────────────────────────────────────┐
    0.2 │                                        │
@@ -79,7 +79,7 @@
      0 │'                                    '''│
        └────────────────────────────────────────┘
         0                                     20
-                         E (keV)
source
SpectralFitting.XS_BremsStrahlungType
XS_BremsStrahlung(K, T)
  • K: Normalisation.

  • T: Plasma temperature (keV).

Example

energy = collect(range(0.1, 20.0, 100))
 invokemodel(energy, XS_BremsStrahlung())
                  XS_BremsStrahlung
      ┌────────────────────────────────────────┐
    2 │                                        │
@@ -99,7 +99,7 @@
    0 │  ':....................................│
      └────────────────────────────────────────┘
       0                                     20
-                       E (keV)
source
SpectralFitting.XS_GaussianType
XS_Gaussian(K, E, σ)
  • K: Normalisation

  • E: Line wavelength in Angstrom.

  • σ: Line width in Angstrom.

Example

energy = collect(range(4.0, 8.0, 100))
+                       E (keV)
source
SpectralFitting.XS_GaussianType
XS_Gaussian(K, E, σ)
  • K: Normalisation

  • E: Line wavelength in Angstrom.

  • σ: Line width in Angstrom.

Example

energy = collect(range(4.0, 8.0, 100))
 invokemodel(energy, XS_Gaussian())
                        XS_Gaussian                
         ┌────────────────────────────────────────┐ 
    0.09 │                                        │ 
@@ -119,7 +119,7 @@
       0 │.......:         :......................│ 
         └────────────────────────────────────────┘ 
          0                                     20  
-                          E (keV)                  
source
SpectralFitting.XS_LaorType
XS_Laor(K, lineE, a, inner_r, outer_r, incl)
  • K: Normalisation.

  • lineE: Rest frame line energy (keV).

  • a: Power law dependence of emissitivy. Scales R⁻ᵅ.

  • inner_r: Inner radius of the accretion disk (GM/c).

  • outer_r: Outer radius of the accretion disk (GM/c).

  • θ: Disk inclination angle to line of sight (degrees, 0 is pole on).

Example

energy = collect(range(0.1, 10.0, 100))
+                          E (keV)                  
source
SpectralFitting.XS_LaorType
XS_Laor(K, lineE, a, inner_r, outer_r, incl)
  • K: Normalisation.

  • lineE: Rest frame line energy (keV).

  • a: Power law dependence of emissitivy. Scales R⁻ᵅ.

  • inner_r: Inner radius of the accretion disk (GM/c).

  • outer_r: Outer radius of the accretion disk (GM/c).

  • θ: Disk inclination angle to line of sight (degrees, 0 is pole on).

Example

energy = collect(range(0.1, 10.0, 100))
 invokemodel(energy, XS_Laor())
                          XS_Laor
         ┌────────────────────────────────────────┐
    0.06 │                                        │
@@ -139,7 +139,7 @@
       0 │.......:''                 :............│
         └────────────────────────────────────────┘
          0                                     10
-                          E (keV)
source
SpectralFitting.XS_DiskLineType
XS_DiskLine(K, lineE, β, inner_r, outer_r, incl)
  • K: Normalisation.

  • lineE: Rest frame line energy (keV).

  • β: Power law dependence of emissitivy. If < 10, scales Rᵅ.

  • inner_r: Inner radius of the accretion disk (GM/c).

  • outer_r: Outer radius of the accretion disk (GM/c).

  • θ: Disk inclination angle to line of sight (degrees, 0 is pole on).

Example

energy = collect(range(4.0, 8.0, 100))
+                          E (keV)
source
SpectralFitting.XS_DiskLineType
XS_DiskLine(K, lineE, β, inner_r, outer_r, incl)
  • K: Normalisation.

  • lineE: Rest frame line energy (keV).

  • β: Power law dependence of emissitivy. If < 10, scales Rᵅ.

  • inner_r: Inner radius of the accretion disk (GM/c).

  • outer_r: Outer radius of the accretion disk (GM/c).

  • θ: Disk inclination angle to line of sight (degrees, 0 is pole on).

Example

energy = collect(range(4.0, 8.0, 100))
 invokemodel(energy, XS_DiskLine())
                        XS_DiskLine
         ┌────────────────────────────────────────┐
    0.09 │                                        │
@@ -159,7 +159,7 @@
       0 │...............:'''           :.........│
         └────────────────────────────────────────┘
          4                                      8
-                          E (keV)
source
SpectralFitting.XS_PhotoelectricAbsorptionType
XS_PhotoelectricAbsorption(ηH)
  • ηH: Equivalent hydrogen column (units of 10²² atoms per cm⁻²).

Example

energy = collect(range(0.1, 20.0, 100))
 invokemodel(energy, XS_PhotoelectricAbsorption())
             XS_PhotoelectricAbsorption
      ┌────────────────────────────────────────┐
    1 │       ...''''''''''''''''''''''''''''''│
@@ -179,7 +179,7 @@
    0 │.:                                      │
      └────────────────────────────────────────┘
       0                                     20
-                       E (keV)
source
SpectralFitting.XS_WarmAbsorptionType
XS_WarmAbsorption(ηH, Ew)
  • ηH: Equivalent hydrogen column (units of 10²² atoms per cm⁻²).

  • Ew: Window energy (keV).

Example

energy = collect(range(0.1, 20.0, 100))
+                       E (keV)
source
SpectralFitting.XS_WarmAbsorptionType
XS_WarmAbsorption(ηH, Ew)
  • ηH: Equivalent hydrogen column (units of 10²² atoms per cm⁻²).

  • Ew: Window energy (keV).

Example

energy = collect(range(0.1, 20.0, 100))
 invokemodel(energy, XS_WarmAbsorption())
                    XS_WarmAbsorption
        ┌────────────────────────────────────────┐
      1 │':      ...''':'''''''''''''''''''''''''│
@@ -199,7 +199,7 @@
    0.2 │  :                                     │
        └────────────────────────────────────────┘
         0                                     20
-                         E (keV)
source
SpectralFitting.XS_KerrDiskType
XS_KerrDisk(K, lineE, index1, index2, break_r, a, θ, inner_r, outer_r)
  • K: Normalisation.

  • lineE: Rest frame line energy (keV).

  • index1: Emissivity index for inner disk.

  • index2: Emissivity index for outer disk.

  • break_r: Break radius seperating inner and outer disk (gᵣ).

  • a: Dimensionless black hole spin.

  • θ: Disk inclination angle to line of sight (degrees).

  • inner_r: Inner radius of the disk in units of rₘₛ.

  • outer_r: Outer radius of the disk in units of rₘₛ.

  • z: Redshift.

Example

energy = collect(range(0.1, 20.0, 100))
+                         E (keV)
source
SpectralFitting.XS_KerrDiskType
XS_KerrDisk(K, lineE, index1, index2, break_r, a, θ, inner_r, outer_r)
  • K: Normalisation.

  • lineE: Rest frame line energy (keV).

  • index1: Emissivity index for inner disk.

  • index2: Emissivity index for outer disk.

  • break_r: Break radius seperating inner and outer disk (gᵣ).

  • a: Dimensionless black hole spin.

  • θ: Disk inclination angle to line of sight (degrees).

  • inner_r: Inner radius of the disk in units of rₘₛ.

  • outer_r: Outer radius of the disk in units of rₘₛ.

  • z: Redshift.

Example

energy = collect(range(0.1, 20.0, 100))
 invokemodel(energy, XS_KerrDisk())
                        XS_KerrDisk
         ┌────────────────────────────────────────┐
    0.05 │                                        │
@@ -219,7 +219,7 @@
       0 │.........:'''                    :......│
         └────────────────────────────────────────┘
          0                                      8
-                          E (keV)
source
SpectralFitting.XS_KyrLineType
XS_KyrLine(K, a, θ_obs, inner_r, ms_flag, outer_r, lineE, α, β, break_r, z, limb)
  • K: Normalisation.

  • a: Dimensionless black hole spin.

  • θ: Observer inclination (0 is on pole, degrees).

  • inner_r: Inner radius of the disk in units of GM/c²

  • ms_flag: 0: integrate from rᵢₙ. 1: integrate from rₘₛ.

  • outer_r: Outer radius of the disk in units of GM/c²

  • lineE: Rest frame line energy (keV).

  • α

  • β

  • break_r: Break radius seperating inner and outer disk (GM/c²).

  • z: Overall Doppler shift.

  • limb: 0: isotropic emission, 1: Laor's limb darkening, 2: Haard's limb brightening.

Example

energy = collect(range(0.1, 20.0, 100))
+                          E (keV)
source
SpectralFitting.XS_KyrLineType
XS_KyrLine(K, a, θ_obs, inner_r, ms_flag, outer_r, lineE, α, β, break_r, z, limb)
  • K: Normalisation.

  • a: Dimensionless black hole spin.

  • θ: Observer inclination (0 is on pole, degrees).

  • inner_r: Inner radius of the disk in units of GM/c²

  • ms_flag: 0: integrate from rᵢₙ. 1: integrate from rₘₛ.

  • outer_r: Outer radius of the disk in units of GM/c²

  • lineE: Rest frame line energy (keV).

  • α

  • β

  • break_r: Break radius seperating inner and outer disk (GM/c²).

  • z: Overall Doppler shift.

  • limb: 0: isotropic emission, 1: Laor's limb darkening, 2: Haard's limb brightening.

Example

energy = collect(range(0.1, 20.0, 100))
 invokemodel(energy, XS_KyrLine())
                        XS_KyrLine
         ┌────────────────────────────────────────┐
    0.05 │                                        │
@@ -239,7 +239,7 @@
       0 │.........:'''                    :......│
         └────────────────────────────────────────┘
          0                                      8
-                          E (keV)
source

Wrapping new XSPEC models

SpectralFitting exports a helpful macro to facilitate wrapping additional XSPEC models.

SpectralFitting.@xspecmodelMacro
@xspecmodel [type=Float64] [ff_call_site] model

Used to wrap additional XSPEC models, generating the needed AbstractSpectralModel implementation.

The type keyword specifies the underlying type to coerce input and output arrays to, as different implementations may have incompatible number of bits. The ff_call_site is the foreign fuction call site, which is the first argument to ccall, and follows the same conventions. The model is a struct, which must subtype AbstractSpectralModel.

If the callsite is not specified, the user must implement _unsafe_ffi_invoke!.

Examples

@xspecmodel :C_powerlaw struct XS_PowerLaw{T} <: AbstractSpectralModel{T, Additive}
+                          E (keV)
source

Wrapping new XSPEC models

SpectralFitting exports a helpful macro to facilitate wrapping additional XSPEC models.

SpectralFitting.@xspecmodelMacro
@xspecmodel [type=Float64] [ff_call_site] model

Used to wrap additional XSPEC models, generating the needed AbstractSpectralModel implementation.

The type keyword specifies the underlying type to coerce input and output arrays to, as different implementations may have incompatible number of bits. The ff_call_site is the foreign fuction call site, which is the first argument to ccall, and follows the same conventions. The model is a struct, which must subtype AbstractSpectralModel.

If the callsite is not specified, the user must implement _unsafe_ffi_invoke!.

Examples

@xspecmodel :C_powerlaw struct XS_PowerLaw{T} <: AbstractSpectralModel{T, Additive}
     "Normalisation."
     K::T
     "Photon index."
@@ -249,13 +249,13 @@
 # constructor has default values
 function XS_PowerLaw(; K = FitParam(1.0), a = FitParam(1.0))
     XS_PowerLaw{typeof(K)}(K, a)
-end

We define a new structure XS_PowerLaw with two parameters, but since the model is Additive, only a single parameter (a) is passed to the XSPEC function. The function we bind to this model is :C_powerlaw from the XSPEC C wrappers.

The macro will then generate the following functions

If a callsite was specified, it will also generate:

source
SpectralFitting.register_model_dataFunction
SpectralFitting.register_model_data(M::Type{<:AbstractSpectralModel}, model_data::ModelDataInfo...)
+end

We define a new structure XS_PowerLaw with two parameters, but since the model is Additive, only a single parameter (a) is passed to the XSPEC function. The function we bind to this model is :C_powerlaw from the XSPEC C wrappers.

The macro will then generate the following functions

If a callsite was specified, it will also generate:

source
SpectralFitting.register_model_dataFunction
SpectralFitting.register_model_data(M::Type{<:AbstractSpectralModel}, model_data::ModelDataInfo...)
 SpectralFitting.register_model_data(M::Type{<:AbstractSpectralModel}, remote_and_local::Tuple{String,String}...)
 SpectralFitting.register_model_data(M::Type{<:AbstractSpectralModel}, filenames::String...)
 SpectralFitting.register_model_data(s::Symbol, filenames::String...)

Register filenames as model data associated with the model given by type M or symbol s. This function does not download any files, but rather adds the relevant filenames to a lookup which SpectralFitting.download_model_data consults when invoked, and consequently model data is only downloaded when needed.

Note

It is good practice to use this method immediately after defining a new model with @xspecmodel to register any required datafiles from the HEASoft source code, and therefore keep relevant information together.

Example

# by type
 register_model_data(XS_Laor, "ari.mod")
 # by symbol
-register_model_data(:XS_KyrLine, "KBHline01.fits")
source

Generating model fingerprints

To generate the unicode plot to add as a fingerprint, we use a simple function:

using SpectralFitting, UnicodePlots
+register_model_data(:XS_KyrLine, "KBHline01.fits")
source

Generating model fingerprints

To generate the unicode plot to add as a fingerprint, we use a simple function:

using SpectralFitting, UnicodePlots
 
 function plotmodel(energy, model)
     flux = invokemodel(energy, model)
@@ -289,4 +289,4 @@
    0    '''.................................. 
      └────────────────────────────────────────┘ 
       0                                     20  
-                       E (keV)                  
+ E (keV) diff --git a/dev/models/surrogate-models/a419f169.svg b/dev/models/surrogate-models/514091c1.svg similarity index 98% rename from dev/models/surrogate-models/a419f169.svg rename to dev/models/surrogate-models/514091c1.svg index 662050c9..b71330e9 100644 --- a/dev/models/surrogate-models/a419f169.svg +++ b/dev/models/surrogate-models/514091c1.svg @@ -1,62 +1,62 @@ - + - + - + - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + - + - + diff --git a/dev/models/surrogate-models/2068b997.svg b/dev/models/surrogate-models/710d5f1d.svg similarity index 87% rename from dev/models/surrogate-models/2068b997.svg rename to dev/models/surrogate-models/710d5f1d.svg index 6e736955..4f99d753 100644 --- a/dev/models/surrogate-models/2068b997.svg +++ b/dev/models/surrogate-models/710d5f1d.svg @@ -1,50 +1,50 @@ - + - + - + - + - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/dev/models/surrogate-models/30f91467.svg b/dev/models/surrogate-models/7865fad5.svg similarity index 86% rename from dev/models/surrogate-models/30f91467.svg rename to dev/models/surrogate-models/7865fad5.svg index 9da82219..452f9c53 100644 --- a/dev/models/surrogate-models/30f91467.svg +++ b/dev/models/surrogate-models/7865fad5.svg @@ -1,48 +1,48 @@ - + - + - + - + - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/dev/models/surrogate-models/fd5d033d.svg b/dev/models/surrogate-models/8b1c44d3.svg similarity index 98% rename from dev/models/surrogate-models/fd5d033d.svg rename to dev/models/surrogate-models/8b1c44d3.svg index 1613853e..a2662b40 100644 --- a/dev/models/surrogate-models/fd5d033d.svg +++ b/dev/models/surrogate-models/8b1c44d3.svg @@ -1,62 +1,62 @@ - + - + - + - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + - + - + diff --git a/dev/models/surrogate-models/index.html b/dev/models/surrogate-models/index.html index b36ff220..812b8367 100644 --- a/dev/models/surrogate-models/index.html +++ b/dev/models/surrogate-models/index.html @@ -9,7 +9,7 @@ surrogate, (FitParam(1.0),), (:ηH,) -)

The lower_bounds and upper_bounds must be tuples in the form (E, params...), where E denotes the bounds on the energy range to train over.

source

To facilitate easy surrogate builds, SpectralFitting exports a number of utility functions.

SpectralFitting.make_surrogate_harnessFunction
make_surrogate_harness(
+)

The lower_bounds and upper_bounds must be tuples in the form (E, params...), where E denotes the bounds on the energy range to train over.

source

To facilitate easy surrogate builds, SpectralFitting exports a number of utility functions.

SpectralFitting.make_surrogate_harnessFunction
make_surrogate_harness(
     model::M,
     lowerbounds::T,
     upperbounds::T;
@@ -18,7 +18,7 @@
     S::Type = RadialBasis,
     sample_type = SobolSample(),
     verbose = false,
-)

Creates and optimizes a surrogate model of type S for model, using wrap_model_as_objective and optimize_accuracy! for optimization_samples iterations. Model is initially seeded with seed_samples points prior to optimization.

Warning

Additive models integrate energies to calculate flux, which surrogate models are currently not capable of. Results for Additive models likely to be inaccurate. This will be patched in a future version.

source
SpectralFitting.optimize_accuracy!Function
optimize_accuracy!(
+)

Creates and optimizes a surrogate model of type S for model, using wrap_model_as_objective and optimize_accuracy! for optimization_samples iterations. Model is initially seeded with seed_samples points prior to optimization.

Warning

Additive models integrate energies to calculate flux, which surrogate models are currently not capable of. Results for Additive models likely to be inaccurate. This will be patched in a future version.

source
SpectralFitting.optimize_accuracy!Function
optimize_accuracy!(
     surr::AbstractSurrogate,
     obj::Function,
     lb,
@@ -27,40 +27,40 @@
     maxiters = 200,
     N_truth = 5000,
     verbose = false,
-)

Improve accuracy (faithfullness) of the surrogate model in recreating the objective function.

Samples a new space of N_truth points between lb and ub, and calculates the objective function obj at each. Finds the point with largest MSE between surrogate and objective, and adds the point to the surrogate pool. Repeats maxiters times, adding maxiters points to surrogate model.

Optionally print to stdout the MSE and iteration count with verbose = true.

Note that upper- and lower-bounds should be in the form (E, params...), where E is a single energy and params are the model parameters.

source

Creating a surrogate for XS_PhotoelectricAbsorption

Before we start, let us discuss a number of benefits the use of surrogate models may bring us:

  • SurrogateSpectralModel permit use of automatic differentiation.
  • Surrogate models may be allocation-free depending on setup, whereas XSPEC wrappers will always have to allocate for type-conversions.
  • Surrogate models may be considerably faster, especially for table models.
  • Surrogate models are shareable (see Sharing surrogate models), and are tunable in size.

XS_PhotoelectricAbsorption is an XSPEC model that is wrapped by a thin C-wrapper into Julia. The implementation of this model is a number of Fortran routines from the late 90s, including a tabulation of ~3000 lines of data that has been copied directly into the Fortran source code.

The performance of this model represents its complexity.

using SpectralFitting
+)

Improve accuracy (faithfullness) of the surrogate model in recreating the objective function.

Samples a new space of N_truth points between lb and ub, and calculates the objective function obj at each. Finds the point with largest MSE between surrogate and objective, and adds the point to the surrogate pool. Repeats maxiters times, adding maxiters points to surrogate model.

Optionally print to stdout the MSE and iteration count with verbose = true.

Note that upper- and lower-bounds should be in the form (E, params...), where E is a single energy and params are the model parameters.

source

Creating a surrogate for XS_PhotoelectricAbsorption

Before we start, let us discuss a number of benefits the use of surrogate models may bring us:

  • SurrogateSpectralModel permit use of automatic differentiation.
  • Surrogate models may be allocation-free depending on setup, whereas XSPEC wrappers will always have to allocate for type-conversions.
  • Surrogate models may be considerably faster, especially for table models.
  • Surrogate models are shareable (see Sharing surrogate models), and are tunable in size.

XS_PhotoelectricAbsorption is an XSPEC model that is wrapped by a thin C-wrapper into Julia. The implementation of this model is a number of Fortran routines from the late 90s, including a tabulation of ~3000 lines of data that has been copied directly into the Fortran source code.

The performance of this model represents its complexity.

using SpectralFitting
 
 energy = collect(range(0.1, 20.0, 200))
 model = XS_PhotoelectricAbsorption()
 
 flux = similar(energy)[1:end-1]
199-element Vector{Float64}:
- 6.9065714581172e-310
- 6.9065725350898e-310
- 6.90657145812037e-310
- 6.90657145812195e-310
- 6.9065725350898e-310
- 6.90647240721203e-310
- 6.90647242954775e-310
- 6.90647242954854e-310
- 6.90657145813143e-310
- 6.9064724072144e-310
+ 0.0
+ 0.0
+ 0.0
+ 0.0
+ 0.0
+ 0.0
+ 0.0
+ 0.0
+ 0.0
+ 0.0
  ⋮
- 6.9065725350898e-310
- 6.90657144849676e-310
- 6.90657144849834e-310
- 6.9065714485031e-310
- 6.9065725350898e-310
- 6.9064724186277e-310
- 6.90647242958253e-310
- 6.9064724295833e-310
- 6.9064724295841e-310

Benchmarking with BenchmarkTools.jl:

using BenchmarkTools
-@benchmark invokemodel!($flux, $energy, $model)
BenchmarkTools.Trial: 4775 samples with 1 evaluation.
- Range (minmax):  1.036 ms 1.630 ms   GC (min … max): 0.00% … 0.00%
- Time  (median):     1.039 ms               GC (median):    0.00%
- Time  (mean ± σ):   1.044 ms ± 37.133 μs   GC (mean ± σ):  0.00% ± 0.00%
+ 0.0
+ 0.0
+ 0.0
+ 0.0
+ 0.0
+ 0.0
+ 0.0
+ 0.0
+ 0.0

Benchmarking with BenchmarkTools.jl:

using BenchmarkTools
+@benchmark invokemodel!($flux, $energy, $model)
BenchmarkTools.Trial: 4783 samples with 1 evaluation.
+ Range (minmax):  1.031 ms 1.670 ms   GC (min … max): 0.00% … 0.00%
+ Time  (median):     1.035 ms               GC (median):    0.00%
+ Time  (mean ± σ):   1.043 ms ± 50.287 μs   GC (mean ± σ):  0.00% ± 0.00%
 
-    ▆█                                                       
-  ▃▇███▅▄▃▂▂▂▃▅▆▅▄▃▃▂▂▂▂▂▂▂▂▂▂▂▂▂▂▂▂▂▂▂▂▂▂▂▂▂▂▂▂▂▂▂▂▂▂▂▂▁▂ ▃
-  1.04 ms        Histogram: frequency by time        1.08 ms <
+  ▃█▅▂▁▅▃▁ ▂▂▁                                             ▁
+  ██████████████▇▆▄▆▅▅▄▇▇▅▆▆▆▆▃▃▃▄▅▅▃▁▁▁▁▃▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▃ █
+  1.03 ms      Histogram: log(frequency) by time     1.12 ms <
 
  Memory estimate: 160 bytes, allocs estimate: 3.

The surrogate we'll construct will have to be tailored a little to the data we wish to fit, as we need to specify the parameter ranges our surrogate should learn. For example, we might be interested in energies between $0.1$ and $20$ keV (expressed in our domain), with equivalent hydrogen column $\eta$H anywhere between $10^{-3}$ and $30$. We specify the parameter bounds using tuples:

lower_bounds = (1e-3,)
 upper_bounds = (30.0,)
Note

The first index is always the energy bounds, and the subsequent indices are the parameters in the same order they are defined in the model structure.

Next, we use make_surrogate_harness to build and optimize a surrogate function for our model. By default, the surrogate uses linear radial basis functions, and seeds the coefficients with a number of seed points. This function then improves the accuracy of the model using optimize_accuracy!, until a maximal number of iterations has been reached.

For illustration purposes, we'll omit the accuracy improving step, and perform this ourselves. We can do this by setting optimization_samples = 0 in the keyword arguments:

using Surrogates
@@ -83,19 +83,19 @@
 model.ηH.value = ηh_test
 f = invokemodel(energy, model)
 
-f̂ = harness.surrogate([ηh_test])
Example block output

Now we'll use optimize_accuracy! to improve the faithfulness of our surrogate. This requires making use of wrap_model_as_objective as a little wrapper around our model:

optimize_accuracy!(harness; maxiters=50)
+f̂ = harness.surrogate([ηh_test])
Example block output

Now we'll use optimize_accuracy! to improve the faithfulness of our surrogate. This requires making use of wrap_model_as_objective as a little wrapper around our model:

optimize_accuracy!(harness; maxiters=50)
 
-length(harness.surrogate.x)
52

We can plot the surrogate model again and see the improvement.

new_f̂ = harness.surrogate([ηh_test])
Example block output

Tight. We can also inspect the memory footprint of our model:

# in bytes
+length(harness.surrogate.x)
52

We can plot the surrogate model again and see the improvement.

new_f̂ = harness.surrogate([ηh_test])
Example block output

Tight. We can also inspect the memory footprint of our model:

# in bytes
 Base.summarysize(harness)
172072

This may be reduced by lowering maxiters in optimize_accuracy! at the cost of decreasing faithfulness. However, compare this to the Fortran tabulated source file in the XSPEC source code, which is approximately 224 Kb. The surrogate model with all it's training data is of the same order.

Using a surrogate spectral model

Now that we have the surrogate model, we use SurrogateSpectralModel to wrap it into an AbstractSpectralModel. The constructor also needs to know the model kind, have a copy of the model parameters, and know which symbols to represent the parameters with.

sm = make_model(harness)
┌ SurrogateSpectralModel
 │      ηH ->  22.9 ± 0.1  ∈ [ 0, Inf ]   FREE

We can now use the familiar API and attempt to benchmark the performance:

@benchmark invokemodel!($flux, $energy, $sm)
BenchmarkTools.Trial: 10000 samples with 10 evaluations.
- Range (minmax):  1.764 μs 20.211 μs   GC (min … max): 0.00% … 0.00%
- Time  (median):     2.531 μs                GC (median):    0.00%
- Time  (mean ± σ):   2.454 μs ± 676.349 ns   GC (mean ± σ):  0.00% ± 0.00%
+ Range (minmax):  1.771 μs 23.725 μs   GC (min … max): 0.00% … 0.00%
+ Time  (median):     2.527 μs                GC (median):    0.00%
+ Time  (mean ± σ):   2.454 μs ± 722.757 ns   GC (mean ± σ):  0.00% ± 0.00%
 
-   ▃                       ▁█▁   ▆▂                           
-  ██▅▃▂▂▂▂▂▁▁▁▂▁▁▂▁▁▂▁▂▁▁▁▂███▃▅██▆▃▃▆▇▅▃▃▂▂▂▂▂▂▂▂▂▂▂▂▂▂▂▂▂ ▃
-  1.76 μs         Histogram: frequency by time        3.23 μs <
+   ▄▇                      ▂▇   ▃█▅                           
+  ▃██▆▃▂▂▂▂▁▁▂▂▂▁▂▁▁▁▂▂▂▁▂▃██▇▄████▅▄▆▇▆▅▃▃▂▂▂▂▂▂▂▂▂▂▂▂▂▂▂▂ ▃
+  1.77 μs         Histogram: frequency by time        3.24 μs <
 
  Memory estimate: 1.77 KiB, allocs estimate: 1.

Comparing this to the initial benchmark of XS_PhotoelectricAbsorption, we see about a significant speedup, with no allocations, and this surrogate model is now automatic differentiation ready.

Evaluating the model

p_range = collect(range(1.0, 30.0))
 
@@ -105,11 +105,11 @@
 end
 fluxes_mat = reduce(hcat, fluxes_vecs)
 
-surface(p_range, energy[1:end-1], fluxes_mat, xlabel = "ηH", ylabel = "E", zlabel = "f", title = "Model")
Example block output
s_fluxes_vecs = map(p_range) do p
+surface(p_range, energy[1:end-1], fluxes_mat, xlabel = "ηH", ylabel = "E", zlabel = "f", title = "Model")
Example block output
s_fluxes_vecs = map(p_range) do p
     sm.params[1].value = p
     display(sm)
     f = invokemodel(energy, sm)
 end
 s_fluxes_mat = reduce(hcat, s_fluxes_vecs)
 
-surface(p_range, energy[1:end-1], s_fluxes_mat, xlabel = "ηH", ylabel = "E", zlabel = "f", title = "Surrogate")
Example block output

Sharing surrogate models

To export and import surrogate models, JLD2.jl is recommended.

+surface(p_range, energy[1:end-1], s_fluxes_mat, xlabel = "ηH", ylabel = "E", zlabel = "f", title = "Surrogate")Example block output

Sharing surrogate models

To export and import surrogate models, JLD2.jl is recommended.

diff --git a/dev/models/using-models/index.html b/dev/models/using-models/index.html index e4ae1c7d..126224e1 100644 --- a/dev/models/using-models/index.html +++ b/dev/models/using-models/index.html @@ -103,28 +103,28 @@ 0.1999999999999993 0.20000000000000284 0.1999999999999993 - 0.1999999999999993
Note

To add new XSPEC or foreign function models, see Wrapping new XSPEC models.

Model abstraction

All spectral models are a sub-type of AbstractSpectralModel.

SpectralFitting.AbstractSpectralModelType
abstract type AbstractSpectralModel{T,K<:AbstractSpectralModelKind} end

Supertype of all spectral models, tracking the number type T and AbstractSpectralModelKind denoted K.

Implementation

Sub-types must implement the following interface (see the function's documentation for examples):

Usage

The available API for a spectral model is detailed below:

The following query functions exist:

Model reflection is supported by the following functions. These are intended for internal use and are not exported.

The parametric type parameter T is the number type of the model and K defines the AbstractSpectralModelKind.

source
SpectralFitting.invoke!Function
SpectralFitting.invoke!(output, domain, M::Type{<:AbstractSpectralModel}, params...)

Used to define the behaviour of models. Should calculate the output of the model and write in-place into output. The model parameters are passed in the model structure.

Warning

This function should not be called directly. Use invokemodel instead. invoke! is only to define the model, not to use it. Users should always call models using invokemodel or invokemodel! to ensure normalisations and closures are accounted for.

Example

Base.@kwdef struct MyModel{T} <: AbstractSpectralModel{T,Multiplicative}
+ 0.1999999999999993
Note

To add new XSPEC or foreign function models, see Wrapping new XSPEC models.

Model abstraction

All spectral models are a sub-type of AbstractSpectralModel.

SpectralFitting.AbstractSpectralModelType
abstract type AbstractSpectralModel{T,K<:AbstractSpectralModelKind} end

Supertype of all spectral models, tracking the number type T and AbstractSpectralModelKind denoted K.

Implementation

Sub-types must implement the following interface (see the function's documentation for examples):

Usage

The available API for a spectral model is detailed below:

The following query functions exist:

Model reflection is supported by the following functions. These are intended for internal use and are not exported.

The parametric type parameter T is the number type of the model and K defines the AbstractSpectralModelKind.

source
SpectralFitting.invoke!Function
SpectralFitting.invoke!(output, domain, M::Type{<:AbstractSpectralModel}, params...)

Used to define the behaviour of models. Should calculate the output of the model and write in-place into output. The model parameters are passed in the model structure.

Warning

This function should not be called directly. Use invokemodel instead. invoke! is only to define the model, not to use it. Users should always call models using invokemodel or invokemodel! to ensure normalisations and closures are accounted for.

Example

Base.@kwdef struct MyModel{T} <: AbstractSpectralModel{T,Multiplicative}
     p1::T = FitParam(1.0)
     p2::T = FitParam(2.0)
     p3::T = FitParam(3.0)
 end

would have the arguments passed to invoke! as

function SpectralFitting.invoke!(output, domain, model::MyModel)
     # ...
-end
source
SpectralFitting.modelkindFunction
modelkind(M::Type{<:AbstractSpectralModel})
-modelkind(::AbstractSpectralModel)

Return the kind of model given by M: either Additive, Multiplicative, or Convolutional.

source
SpectralFitting.numbertypeFunction
numbertype(::AbstractSpectralModel)

Get the numerical type of the model. This goes through FitParam, so that the number type returned is as close to a primative as possible.

See also paramtype.

Example

numbertype(PowerLaw()) == Float64
source

Model methods

SpectralFitting.invokemodelFunction
invokemodel(domain, model::AbstractSpectralModel)

Invoke the AbstractSpectralModel given by model over the domain domain.

This function will perform any normalisation or post-processing tasks that a specific model kind may require, e.g. multiplying by a normalisation constant for Additive models.

Note

invokemodel allocates the needed output arrays based on the element type of free_params to allow automatic differentation libraries to calculate parameter gradients.

In-place non-allocating variants are the invokemodel! functions.

Example

model = PowerLaw()
+end
source
SpectralFitting.modelkindFunction
modelkind(M::Type{<:AbstractSpectralModel})
+modelkind(::AbstractSpectralModel)

Return the kind of model given by M: either Additive, Multiplicative, or Convolutional.

source
SpectralFitting.numbertypeFunction
numbertype(::AbstractSpectralModel)

Get the numerical type of the model. This goes through FitParam, so that the number type returned is as close to a primative as possible.

See also paramtype.

Example

numbertype(PowerLaw()) == Float64
source

Model methods

SpectralFitting.invokemodelFunction
invokemodel(domain, model::AbstractSpectralModel)

Invoke the AbstractSpectralModel given by model over the domain domain.

This function will perform any normalisation or post-processing tasks that a specific model kind may require, e.g. multiplying by a normalisation constant for Additive models.

Note

invokemodel allocates the needed output arrays based on the element type of free_params to allow automatic differentation libraries to calculate parameter gradients.

In-place non-allocating variants are the invokemodel! functions.

Example

model = PowerLaw()
 domain = collect(range(0.1, 20.0, 100))
 
-invokemodel(domain, model)
source
SpectralFitting.invokemodel!Function
invokemodel!(output, domain, model)
 invokemodel!(output, domain, model, params::AbstractVector)
 invokemodel!(output, domain, model, params::ParameterCache)

In-place variant of invokemodel, calculating the output of an AbstractSpectralModel given by model, optionally overriding the parameters using a ParameterCache or an AbstractVector.

The output may not necessarily be a single vector, and one should use allocate_model_output to allocate the output structure.

Example

model = PowerLaw()
 domain = collect(range(0.1, 20.0, 100))
 output = allocate_model_output(model, domain)
-invokemodel!(output, domain, model)
source

Model algebra

Models exist as three different kinds, defined by an AbstractSpectralModelKind trait.

Model algebra

Models exist as three different kinds, defined by an AbstractSpectralModelKind trait.

SpectralFitting.AdditiveType
Additive <: AbstractSpectralModelKind
-Additive()

Additive models are effectively the sources of photons, and are the principle building blocks of composite models. Every additive model has a normalisation parameter which re-scales the output by a constant factor K.

Note

Defining custom additive models requires special care. See Defining new models.

source
SpectralFitting.MultiplicativeType
Multiplicative <: AbstractSpectralModelKind
-Multiplicative()

Multiplicative models act on Additive models, by element-wise multiplying the output in each domain bin of the additive model by a different factor.

source

Model data availability

Many of the XSPEC implemented models use tabular data, such as FITS, and return results interpolated from these pre-calculated tables. In some cases, these table models have data files that are multiple gigabytes in size, and would be very unwieldy to ship indiscriminantly. SpectralFitting attempts to circumnavigate this bloat by downloading the model data on an ut opus basis.

SpectralFitting.AdditiveType
Additive <: AbstractSpectralModelKind
+Additive()

Additive models are effectively the sources of photons, and are the principle building blocks of composite models. Every additive model has a normalisation parameter which re-scales the output by a constant factor K.

Note

Defining custom additive models requires special care. See Defining new models.

source
SpectralFitting.MultiplicativeType
Multiplicative <: AbstractSpectralModelKind
+Multiplicative()

Multiplicative models act on Additive models, by element-wise multiplying the output in each domain bin of the additive model by a different factor.

source

Model data availability

Many of the XSPEC implemented models use tabular data, such as FITS, and return results interpolated from these pre-calculated tables. In some cases, these table models have data files that are multiple gigabytes in size, and would be very unwieldy to ship indiscriminantly. SpectralFitting attempts to circumnavigate this bloat by downloading the model data on an ut opus basis.

SpectralFitting.download_model_dataFunction
SpectralFitting.download_model_data(model::AbstractSpectralModel; kwargs...)
 SpectralFitting.download_model_data(M::Type{<:AbstractSpectralModel}; kwargs...)
-SpectralFitting.download_model_data(s::Symbol; kwargs...)

Downloads the model data for a model specified either by model, type M, or symbol s. Datafiles associated with a specific model may be registered using SpectralFitting.register_model_data. The download is currently unconfigurable, but permits slight control via a number of keyword arguments:

  • progress::Bool = true

Display a progress bar for the download.

  • model_source_url::String = "http://www.star.bris.ac.uk/fbaker/XSPEC-model-data"

The source URL used to download the model data.

All standard XSPEC spectral model data is currently being hosted on the University of Bristol astrophysics servers, and should be persistently available to anyone.

source

Special care must be taken if new XSPEC models are wrapped to ensure the data is available. For more on this, see Wrapping new XSPEC models.

Model data may also alternatively be copied in by-hand from a HEASoft XSPEC source directory. In this case, the location to copy the data to may be determined via joinpath(SpectralFitting.LibXSPEC_jll.artifact_dir, "spectral", "modelData").

+SpectralFitting.download_model_data(s::Symbol; kwargs...)

Downloads the model data for a model specified either by model, type M, or symbol s. Datafiles associated with a specific model may be registered using SpectralFitting.register_model_data. The download is currently unconfigurable, but permits slight control via a number of keyword arguments:

  • progress::Bool = true

Display a progress bar for the download.

  • model_source_url::String = "http://www.star.bris.ac.uk/fbaker/XSPEC-model-data"

The source URL used to download the model data.

All standard XSPEC spectral model data is currently being hosted on the University of Bristol astrophysics servers, and should be persistently available to anyone.

source

Special care must be taken if new XSPEC models are wrapped to ensure the data is available. For more on this, see Wrapping new XSPEC models.

Model data may also alternatively be copied in by-hand from a HEASoft XSPEC source directory. In this case, the location to copy the data to may be determined via joinpath(SpectralFitting.LibXSPEC_jll.artifact_dir, "spectral", "modelData").

diff --git a/dev/objects.inv b/dev/objects.inv index e48bac96..1fecf935 100644 Binary files a/dev/objects.inv and b/dev/objects.inv differ diff --git a/dev/parameters/index.html b/dev/parameters/index.html index 733153f4..ee907327 100644 --- a/dev/parameters/index.html +++ b/dev/parameters/index.html @@ -1,2 +1,2 @@ -Parameters · SpectralFitting.jl
+Parameters · SpectralFitting.jl
diff --git a/dev/reference/index.html b/dev/reference/index.html index 17a56df4..2f280ab5 100644 --- a/dev/reference/index.html +++ b/dev/reference/index.html @@ -1,29 +1,29 @@ -Reference · SpectralFitting.jl

API reference

Utility functions for defining new models

SpectralFitting.finite_diff_kernel!Function
finite_diff_kernel!(f::Function, flux, energy)

Calculates the finite difference of the function f over the energy bin between the high and low bin edges, via

\[c_i = f(E_{i,\text{high}}) - f(E_{i,\text{low}}),\]

similar to evaluating the limits of the integral between $E_{i,\text{high}}$ and $E_{i,\text{low}}$.

This utility function is primarily used for Additive models to ensure the flux per bin is normalised for the energy over the bin.

source
SpectralFitting.wrap_model_as_objectiveFunction
wrap_model_as_objective(model::AbstractSpectralModel; ΔE = 1e-1)
-wrap_model_as_objective(M::Type{<:AbstractSpectralModel}; ΔE = 1e-1)

Wrap a spectral model into an objective function for building/optimizing a surrogate model. Returns an anonymous function taking the tuple (E, params...) as the argument, and returning a single flux value.

source

General reference

SpectralFitting._accumulated_indicesMethod
_accumulated_indices(items)

items is a tuple or vector of lengths n1, n2, ...

Returns a tuple or array with same length as items, which gives the index boundaries of an array with size n1 + n2 + ....

source
SpectralFitting._safe_ffi_invoke!Method
function _safe_ffi_invoke!(output, input, params, ModelType::Type{<:AbstractSpectralModel})

Wrapper to do a foreign function call to an XSPEC model.

source
SpectralFitting._subtract_background!Method

Does the background subtraction and returns units of counts. That means we have multiplied through by a factor $t_D$ relative to the reference equation (2.3) in the XSPEC manual.

source
SpectralFitting._unsafe_ffi_invoke!Method
function _unsafe_ffi_invoke!(
+Reference · SpectralFitting.jl

API reference

Utility functions for defining new models

SpectralFitting.finite_diff_kernel!Function
finite_diff_kernel!(f::Function, flux, energy)

Calculates the finite difference of the function f over the energy bin between the high and low bin edges, via

\[c_i = f(E_{i,\text{high}}) - f(E_{i,\text{low}}),\]

similar to evaluating the limits of the integral between $E_{i,\text{high}}$ and $E_{i,\text{low}}$.

This utility function is primarily used for Additive models to ensure the flux per bin is normalised for the energy over the bin.

source
SpectralFitting.wrap_model_as_objectiveFunction
wrap_model_as_objective(model::AbstractSpectralModel; ΔE = 1e-1)
+wrap_model_as_objective(M::Type{<:AbstractSpectralModel}; ΔE = 1e-1)

Wrap a spectral model into an objective function for building/optimizing a surrogate model. Returns an anonymous function taking the tuple (E, params...) as the argument, and returning a single flux value.

source

General reference

Base.copyMethod
Base.copy(m::AbstractTableModel)

Create a copy of an AbstractTableModel. This will copy all fields except the table field, which is assumed to be a constant set of values that can be shared by multiple copies.

When this is not the case, the user should redefine Base.copy for their particular table model to copy the table as needed.

source
Base.copyMethod
Base.copy(m::AsConvolution)

Creates a copy of an AsConvolution wrapped model. Will make a deepcopy of the cache to elimiate possible thread contention, but does not copy the domain.

source
SpectralFitting._accumulated_indicesMethod
_accumulated_indices(items)

items is a tuple or vector of lengths n1, n2, ...

Returns a tuple or array with same length as items, which gives the index boundaries of an array with size n1 + n2 + ....

source
SpectralFitting._safe_ffi_invoke!Method
function _safe_ffi_invoke!(output, input, params, ModelType::Type{<:AbstractSpectralModel})

Wrapper to do a foreign function call to an XSPEC model.

source
SpectralFitting._subtract_background!Method

Does the background subtraction and returns units of counts. That means we have multiplied through by a factor $t_D$ relative to the reference equation (2.3) in the XSPEC manual.

source
SpectralFitting.count_errorMethod
count_error(k, σ)

Gives the error on k mean photon counts to a significance of σ standard deviations about the mean.

Derived from likelihood of binomial distributions being the beta function.

source
SpectralFitting.count_errorMethod
count_error(k, σ)

Gives the error on k mean photon counts to a significance of σ standard deviations about the mean.

Derived from likelihood of binomial distributions being the beta function.

source
SpectralFitting.download_model_dataMethod
SpectralFitting.download_model_data(model::AbstractSpectralModel; kwargs...)
 SpectralFitting.download_model_data(M::Type{<:AbstractSpectralModel}; kwargs...)
-SpectralFitting.download_model_data(s::Symbol; kwargs...)

Downloads the model data for a model specified either by model, type M, or symbol s. Datafiles associated with a specific model may be registered using SpectralFitting.register_model_data. The download is currently unconfigurable, but permits slight control via a number of keyword arguments:

  • progress::Bool = true

Display a progress bar for the download.

  • model_source_url::String = "http://www.star.bris.ac.uk/fbaker/XSPEC-model-data"

The source URL used to download the model data.

All standard XSPEC spectral model data is currently being hosted on the University of Bristol astrophysics servers, and should be persistently available to anyone.

source
SpectralFitting.finite_diff_kernel!Method
finite_diff_kernel!(f::Function, flux, energy)

Calculates the finite difference of the function f over the energy bin between the high and low bin edges, via

\[c_i = f(E_{i,\text{high}}) - f(E_{i,\text{low}}),\]

similar to evaluating the limits of the integral between $E_{i,\text{high}}$ and $E_{i,\text{low}}$.

This utility function is primarily used for Additive models to ensure the flux per bin is normalised for the energy over the bin.

source
SpectralFitting.folded_energyMethod
folded_energy(response::ResponseMatrix)

Get the contiguously binned energy corresponding to the output (folded) domain of the response matrix. That is, the channel energies as used by the spectrum.

source
SpectralFitting.invoke!Method
SpectralFitting.invoke!(output, domain, M::Type{<:AbstractSpectralModel}, params...)

Used to define the behaviour of models. Should calculate the output of the model and write in-place into output. The model parameters are passed in the model structure.

Warning

This function should not be called directly. Use invokemodel instead. invoke! is only to define the model, not to use it. Users should always call models using invokemodel or invokemodel! to ensure normalisations and closures are accounted for.

Example

Base.@kwdef struct MyModel{T} <: AbstractSpectralModel{T,Multiplicative}
+SpectralFitting.download_model_data(s::Symbol; kwargs...)

Downloads the model data for a model specified either by model, type M, or symbol s. Datafiles associated with a specific model may be registered using SpectralFitting.register_model_data. The download is currently unconfigurable, but permits slight control via a number of keyword arguments:

  • progress::Bool = true

Display a progress bar for the download.

  • model_source_url::String = "http://www.star.bris.ac.uk/fbaker/XSPEC-model-data"

The source URL used to download the model data.

All standard XSPEC spectral model data is currently being hosted on the University of Bristol astrophysics servers, and should be persistently available to anyone.

source
SpectralFitting.finite_diff_kernel!Method
finite_diff_kernel!(f::Function, flux, energy)

Calculates the finite difference of the function f over the energy bin between the high and low bin edges, via

\[c_i = f(E_{i,\text{high}}) - f(E_{i,\text{low}}),\]

similar to evaluating the limits of the integral between $E_{i,\text{high}}$ and $E_{i,\text{low}}$.

This utility function is primarily used for Additive models to ensure the flux per bin is normalised for the energy over the bin.

source
SpectralFitting.folded_energyMethod
folded_energy(response::ResponseMatrix)

Get the contiguously binned energy corresponding to the output (folded) domain of the response matrix. That is, the channel energies as used by the spectrum.

source
SpectralFitting.invoke!Method
SpectralFitting.invoke!(output, domain, M::Type{<:AbstractSpectralModel}, params...)

Used to define the behaviour of models. Should calculate the output of the model and write in-place into output. The model parameters are passed in the model structure.

Warning

This function should not be called directly. Use invokemodel instead. invoke! is only to define the model, not to use it. Users should always call models using invokemodel or invokemodel! to ensure normalisations and closures are accounted for.

Example

Base.@kwdef struct MyModel{T} <: AbstractSpectralModel{T,Multiplicative}
     p1::T = FitParam(1.0)
     p2::T = FitParam(2.0)
     p3::T = FitParam(3.0)
 end

would have the arguments passed to invoke! as

function SpectralFitting.invoke!(output, domain, model::MyModel)
     # ...
-end
source
SpectralFitting.invokemodel!Method
invokemodel!(output, domain, model)
 invokemodel!(output, domain, model, params::AbstractVector)
 invokemodel!(output, domain, model, params::ParameterCache)

In-place variant of invokemodel, calculating the output of an AbstractSpectralModel given by model, optionally overriding the parameters using a ParameterCache or an AbstractVector.

The output may not necessarily be a single vector, and one should use allocate_model_output to allocate the output structure.

Example

model = PowerLaw()
 domain = collect(range(0.1, 20.0, 100))
 output = allocate_model_output(model, domain)
-invokemodel!(output, domain, model)
source
SpectralFitting.invokemodelMethod
invokemodel(domain, model::AbstractSpectralModel)

Invoke the AbstractSpectralModel given by model over the domain domain.

This function will perform any normalisation or post-processing tasks that a specific model kind may require, e.g. multiplying by a normalisation constant for Additive models.

Note

invokemodel allocates the needed output arrays based on the element type of free_params to allow automatic differentation libraries to calculate parameter gradients.

In-place non-allocating variants are the invokemodel! functions.

Example

model = PowerLaw()
+invokemodel!(output, domain, model)
source
SpectralFitting.invokemodelMethod
invokemodel(domain, model::AbstractSpectralModel)

Invoke the AbstractSpectralModel given by model over the domain domain.

This function will perform any normalisation or post-processing tasks that a specific model kind may require, e.g. multiplying by a normalisation constant for Additive models.

Note

invokemodel allocates the needed output arrays based on the element type of free_params to allow automatic differentation libraries to calculate parameter gradients.

In-place non-allocating variants are the invokemodel! functions.

Example

model = PowerLaw()
 domain = collect(range(0.1, 20.0, 100))
 
-invokemodel(domain, model)
source
SpectralFitting.make_objectiveMethod
make_objective(layout::AbstractLayout, dataset::AbstractDataset)

Returns the array used as the target for model fitting. The array must correspond to the data AbstractLayout specified by the layout parameter.

In as far as it can be guarunteed, the memory in the returned array will not be mutated by any fitting procedures.

Domain for this objective should be returned by make_model_domain.

source
SpectralFitting.make_output_domainMethod
make_output_domain(layout::AbstractLayout, dataset::AbstractDataset)

Returns the array used as the output domain. That is, in cases where the model input and output map to different domains, the input domain is said to be the model domain, the input domain is said to be the model domain.

The distinction is mainly used for the purposes of simulating data and for visualising data.

source
SpectralFitting.make_objectiveMethod
make_objective(layout::AbstractLayout, dataset::AbstractDataset)

Returns the array used as the target for model fitting. The array must correspond to the data AbstractLayout specified by the layout parameter.

In as far as it can be guarunteed, the memory in the returned array will not be mutated by any fitting procedures.

Domain for this objective should be returned by make_model_domain.

source
SpectralFitting.make_output_domainMethod
make_output_domain(layout::AbstractLayout, dataset::AbstractDataset)

Returns the array used as the output domain. That is, in cases where the model input and output map to different domains, the input domain is said to be the model domain, the input domain is said to be the model domain.

The distinction is mainly used for the purposes of simulating data and for visualising data.

source
SpectralFitting.make_surrogate_harnessMethod
make_surrogate_harness(
     model::M,
     lowerbounds::T,
     upperbounds::T;
@@ -32,8 +32,8 @@
     S::Type = RadialBasis,
     sample_type = SobolSample(),
     verbose = false,
-)

Creates and optimizes a surrogate model of type S for model, using wrap_model_as_objective and optimize_accuracy! for optimization_samples iterations. Model is initially seeded with seed_samples points prior to optimization.

Warning

Additive models integrate energies to calculate flux, which surrogate models are currently not capable of. Results for Additive models likely to be inaccurate. This will be patched in a future version.

source
SpectralFitting.modelkindMethod
modelkind(M::Type{<:AbstractSpectralModel})
-modelkind(::AbstractSpectralModel)

Return the kind of model given by M: either Additive, Multiplicative, or Convolutional.

source
SpectralFitting.numbertypeMethod
numbertype(::AbstractSpectralModel)

Get the numerical type of the model. This goes through FitParam, so that the number type returned is as close to a primative as possible.

See also paramtype.

Example

numbertype(PowerLaw()) == Float64
source
SpectralFitting.objective_transformerMethod
objective_transformer(layout::AbstractLayout, dataset::AbstractDataset)

Used to transform the output of the model onto the output domain. For spectral fitting, this is the method used to do response folding and bin masking.

If none provided, uses the _DEFAULT_TRANSFORMER

function _DEFAULT_TRANSFORMER()
+)

Creates and optimizes a surrogate model of type S for model, using wrap_model_as_objective and optimize_accuracy! for optimization_samples iterations. Model is initially seeded with seed_samples points prior to optimization.

Warning

Additive models integrate energies to calculate flux, which surrogate models are currently not capable of. Results for Additive models likely to be inaccurate. This will be patched in a future version.

source
SpectralFitting.modelkindMethod
modelkind(M::Type{<:AbstractSpectralModel})
+modelkind(::AbstractSpectralModel)

Return the kind of model given by M: either Additive, Multiplicative, or Convolutional.

source
SpectralFitting.numbertypeMethod
numbertype(::AbstractSpectralModel)

Get the numerical type of the model. This goes through FitParam, so that the number type returned is as close to a primative as possible.

See also paramtype.

Example

numbertype(PowerLaw()) == Float64
source
SpectralFitting.objective_transformerMethod
objective_transformer(layout::AbstractLayout, dataset::AbstractDataset)

Used to transform the output of the model onto the output domain. For spectral fitting, this is the method used to do response folding and bin masking.

If none provided, uses the _DEFAULT_TRANSFORMER

function _DEFAULT_TRANSFORMER()
     function _transformer!!(domain, objective)
         objective
     end
@@ -41,8 +41,8 @@
         @. output = objective
     end
     _transformer!!
-end
source
SpectralFitting.optimize_accuracy!Method
optimize_accuracy!(
     surr::AbstractSurrogate,
     obj::Function,
     lb,
@@ -51,16 +51,16 @@
     maxiters = 200,
     N_truth = 5000,
     verbose = false,
-)

Improve accuracy (faithfullness) of the surrogate model in recreating the objective function.

Samples a new space of N_truth points between lb and ub, and calculates the objective function obj at each. Finds the point with largest MSE between surrogate and objective, and adds the point to the surrogate pool. Repeats maxiters times, adding maxiters points to surrogate model.

Optionally print to stdout the MSE and iteration count with verbose = true.

Note that upper- and lower-bounds should be in the form (E, params...), where E is a single energy and params are the model parameters.

source
SpectralFitting.preferred_supportMethod
preferred_support(x)

Get the preferred AbstractLayout of x. If multiple supports are available, the DEFAULT_SUPPORT_ORDERING is followed:

DEFAULT_SUPPORT_ORDERING = (ContiguouslyBinned{Nothing}(nothing), OneToOne{Nothing}(nothing))
source
SpectralFitting.preferred_unitsMethod
preferred_units(::Type{<:AbstractDataset}, s::AbstractStatistic)
-preferred_units(x, s::AbstractStatistic)

Get the preferred units that a given dataset would use to fit the AbstractStatistic in. For example, for ChiSquared, the units of the model may be a rate, however for Cash the preferred units might be counts.

Returning nothing from this function implies there is no unit preference.

If undefined for a derived type, returns nothing.

See also support_units.

source
SpectralFitting.register_model_dataMethod
SpectralFitting.register_model_data(M::Type{<:AbstractSpectralModel}, model_data::ModelDataInfo...)
+)

Improve accuracy (faithfullness) of the surrogate model in recreating the objective function.

Samples a new space of N_truth points between lb and ub, and calculates the objective function obj at each. Finds the point with largest MSE between surrogate and objective, and adds the point to the surrogate pool. Repeats maxiters times, adding maxiters points to surrogate model.

Optionally print to stdout the MSE and iteration count with verbose = true.

Note that upper- and lower-bounds should be in the form (E, params...), where E is a single energy and params are the model parameters.

source
SpectralFitting.preferred_supportMethod
preferred_support(x)

Get the preferred AbstractLayout of x. If multiple supports are available, the DEFAULT_SUPPORT_ORDERING is followed:

DEFAULT_SUPPORT_ORDERING = (ContiguouslyBinned{Nothing}(nothing), OneToOne{Nothing}(nothing))
source
SpectralFitting.preferred_unitsMethod
preferred_units(::Type{<:AbstractDataset}, s::AbstractStatistic)
+preferred_units(x, s::AbstractStatistic)

Get the preferred units that a given dataset would use to fit the AbstractStatistic in. For example, for ChiSquared, the units of the model may be a rate, however for Cash the preferred units might be counts.

Returning nothing from this function implies there is no unit preference.

If undefined for a derived type, returns nothing.

See also support_units.

source
SpectralFitting.register_model_dataMethod
SpectralFitting.register_model_data(M::Type{<:AbstractSpectralModel}, model_data::ModelDataInfo...)
 SpectralFitting.register_model_data(M::Type{<:AbstractSpectralModel}, remote_and_local::Tuple{String,String}...)
 SpectralFitting.register_model_data(M::Type{<:AbstractSpectralModel}, filenames::String...)
 SpectralFitting.register_model_data(s::Symbol, filenames::String...)

Register filenames as model data associated with the model given by type M or symbol s. This function does not download any files, but rather adds the relevant filenames to a lookup which SpectralFitting.download_model_data consults when invoked, and consequently model data is only downloaded when needed.

Note

It is good practice to use this method immediately after defining a new model with @xspecmodel to register any required datafiles from the HEASoft source code, and therefore keep relevant information together.

Example

# by type
 register_model_data(XS_Laor, "ari.mod")
 # by symbol
-register_model_data(:XS_KyrLine, "KBHline01.fits")
source
SpectralFitting.response_energyMethod
response_energy(response::ResponseMatrix)

Get the contiguously binned energy corresponding to the input domain of the response matrix. This is equivalent to the model domain.

source
SpectralFitting.support_unitsMethod
support_units(x)

Return the units of a particular layout. If this method returns nothing, assume the layout does not care about the units and handle that information appropriately (throw an error or set defaults).

source
SpectralFitting.supportsMethod
supports(x::Type)

Used to define whether a given type has support for a specific AbstractLayout. Should return a tuple of the supported layouts. This method should be implemented to express new support, not the query method.

To query, there is

supports(layout::AbstractLayout, x)::Bool

Example

supports(::Type{typeof(x)}) = (OneToOne(),)
-@assert supports(ContiguouslyBinned(), x) == false
source
SpectralFitting.wrap_model_as_objectiveMethod
wrap_model_as_objective(model::AbstractSpectralModel; ΔE = 1e-1)
-wrap_model_as_objective(M::Type{<:AbstractSpectralModel}; ΔE = 1e-1)

Wrap a spectral model into an objective function for building/optimizing a surrogate model. Returns an anonymous function taking the tuple (E, params...) as the argument, and returning a single flux value.

source
SpectralFitting.AbstractSpectralModelImplementationType
abstract type AbstractSpectralModelImplementation end

Details about the implementation are represented by this abstract type, used in the trait pattern. Concrete types are

  • JuliaImplementation(): means the model is implemented in the Julia programming language.
  • XSPECImplementation(): means the model is vendored from the XSPEC model library.
source
SpectralFitting.AbstractTableModelType
abstract type AbstractTableModel{T,K} <: AbstractSpectralModel{T,K} end

Abstract type representing table models, i.e. those models that interpolate or load data from a table.

First field in the struct must be table. See PhotoelectricAbsorption for an example implementation.

source
SpectralFitting.XS_CutOffPowerLawType
XS_CutOffPowerLaw(K, Γ, Ecut, z)
  • K: Normalisation.

  • Γ: Photon index.

  • Ecut: Cut-off energy (keV).

  • z: Redshift.

Example

using SpectralFitting
+register_model_data(:XS_KyrLine, "KBHline01.fits")
source
SpectralFitting.response_energyMethod
response_energy(response::ResponseMatrix)

Get the contiguously binned energy corresponding to the input domain of the response matrix. This is equivalent to the model domain.

source
SpectralFitting.support_unitsMethod
support_units(x)

Return the units of a particular layout. If this method returns nothing, assume the layout does not care about the units and handle that information appropriately (throw an error or set defaults).

source
SpectralFitting.supportsMethod
supports(x::Type)

Used to define whether a given type has support for a specific AbstractLayout. Should return a tuple of the supported layouts. This method should be implemented to express new support, not the query method.

To query, there is

supports(layout::AbstractLayout, x)::Bool

Example

supports(::Type{typeof(x)}) = (OneToOne(),)
+@assert supports(ContiguouslyBinned(), x) == false
source
SpectralFitting.wrap_model_as_objectiveMethod
wrap_model_as_objective(model::AbstractSpectralModel; ΔE = 1e-1)
+wrap_model_as_objective(M::Type{<:AbstractSpectralModel}; ΔE = 1e-1)

Wrap a spectral model into an objective function for building/optimizing a surrogate model. Returns an anonymous function taking the tuple (E, params...) as the argument, and returning a single flux value.

source
SpectralFitting.AbstractSpectralModelImplementationType
abstract type AbstractSpectralModelImplementation end

Details about the implementation are represented by this abstract type, used in the trait pattern. Concrete types are

  • JuliaImplementation(): means the model is implemented in the Julia programming language.
  • XSPECImplementation(): means the model is vendored from the XSPEC model library.
source
SpectralFitting.AbstractTableModelType
abstract type AbstractTableModel{T,K} <: AbstractSpectralModel{T,K} end

Abstract type representing table models, i.e. those models that interpolate or load data from a table.

First field in the struct must be table. See PhotoelectricAbsorption for an example implementation.

source
SpectralFitting.XS_CutOffPowerLawType
XS_CutOffPowerLaw(K, Γ, Ecut, z)
  • K: Normalisation.

  • Γ: Photon index.

  • Ecut: Cut-off energy (keV).

  • z: Redshift.

Example

using SpectralFitting
 using UnicodePlots
 energy = 10 .^collect(range(-1.0, 2.0, 100))
 m = invokemodel(energy, XS_CutOffPowerLaw())
@@ -83,7 +83,7 @@
 10⁻⁶    │                                       :│ 
         └────────────────────────────────────────┘ 
          10⁻¹                                 10²  
-                        Energy (keV)
source
SpectralFitting.XS_JetType
XS_Jet(K, mass, Dco, log_mdot, thetaobs, BulkG, phi, zdiss, B, logPrel, gmin_inj, gbreak, gmax, s1, s2, z)
  • K: MUST BE FIXED AT UNITY as the jet spectrum normalisation is set by the relativisitic particle power.

  • mass: Black hole mass in solar masses.

  • Dco: Comoving (proper) distance in Mpc.

  • log_mdot: log(L/L_Edd.

  • thetaobs: Inclination angle (deg).

  • BulkG: Bulk lorentz factor of the jet.

  • phi: Angular size scale (radians) of the jet acceleration region as seen from the black hole.

  • zdiss: Vertical distance from the black hole of the jet dissipation region (r_g).

  • B: Magnetic field in the jet (Gauss).

  • logPrel: Log of the power injected in relativisitic particles (ergs/s).

  • gmin_inj: Minimum lorentz factor of the injected electrons.

  • gbreak: Lorentz factor of the break in injected electron distribution.

  • gmax: Maximum lorentz factor.

  • s1: Injected index of the electron distribution below the break.

  • s2: Injected index of the electron distribution above the break.

  • z: Cosmological redshift corresponding to the comoving distance used above.

Example

using UnicodePlots
+                        Energy (keV)
source
SpectralFitting.XS_JetType
XS_Jet(K, mass, Dco, log_mdot, thetaobs, BulkG, phi, zdiss, B, logPrel, gmin_inj, gbreak, gmax, s1, s2, z)
  • K: MUST BE FIXED AT UNITY as the jet spectrum normalisation is set by the relativisitic particle power.

  • mass: Black hole mass in solar masses.

  • Dco: Comoving (proper) distance in Mpc.

  • log_mdot: log(L/L_Edd.

  • thetaobs: Inclination angle (deg).

  • BulkG: Bulk lorentz factor of the jet.

  • phi: Angular size scale (radians) of the jet acceleration region as seen from the black hole.

  • zdiss: Vertical distance from the black hole of the jet dissipation region (r_g).

  • B: Magnetic field in the jet (Gauss).

  • logPrel: Log of the power injected in relativisitic particles (ergs/s).

  • gmin_inj: Minimum lorentz factor of the injected electrons.

  • gbreak: Lorentz factor of the break in injected electron distribution.

  • gmax: Maximum lorentz factor.

  • s1: Injected index of the electron distribution below the break.

  • s2: Injected index of the electron distribution above the break.

  • z: Cosmological redshift corresponding to the comoving distance used above.

Example

using UnicodePlots
 energy = 10 .^collect(range(-8.0, 8.0, 100))
 m = invokemodel(energy, XS_Jet())
 lineplot(energy[1:end-1],m,xscale=:log10,yscale=:log10,xlim=(1e-8,1e8),ylim=(1e-8,1e8),xlabel="Energy (keV)",ylabel="Flux",title="XS_Jet",canvas=DotCanvas)
                        XS_Jet                   
@@ -105,7 +105,7 @@
 10⁻⁸    │                                 ''.    │ 
         └────────────────────────────────────────┘ 
         10⁻⁸                                 10⁸  
-                        Energy (keV)                
source
SpectralFitting.Reflection.add_objective_reduction!Method
add_objective_reduction!(ra::CompositeAggregation, op::Symbol)

Reduces the current objective count and applies the reduction operation op to them. For example, if op is :+, and the objective count is 3, then after this function has been called the objective count will be 2 and the reduction expression

@. flux2 = flux2 + flux3

will have been added to the CompositeAggregation.

source
SpectralFitting.Reflection.assemble_objective_unpackMethod
assemble_objective_unpack(N)

Assembles the statements for unpacking the objective cache into a number of views. Assembles the part of the model call that looks like:

objective1 = view(objectives, :, 1), objective2 = ...

for N objectives slices.

source
SpectralFitting.Reflection.make_constructorMethod
make_constructor(model::Type{<:AbstractSpectralModel}, closures::Vector, params::Vector, T::Type)

Create a constructor expression for the model. Should return something similar to

:(PowerLaw{T}(arg1, arg2, arg3)))

unpacking the closures and params vectors in the appropriate places.

source
SpectralFitting.Reflection.add_objective_reduction!Method
add_objective_reduction!(ra::CompositeAggregation, op::Symbol)

Reduces the current objective count and applies the reduction operation op to them. For example, if op is :+, and the objective count is 3, then after this function has been called the objective count will be 2 and the reduction expression

@. flux2 = flux2 + flux3

will have been added to the CompositeAggregation.

source
SpectralFitting.Reflection.assemble_objective_unpackMethod
assemble_objective_unpack(N)

Assembles the statements for unpacking the objective cache into a number of views. Assembles the part of the model call that looks like:

objective1 = view(objectives, :, 1), objective2 = ...

for N objectives slices.

source
SpectralFitting.Reflection.make_constructorMethod
make_constructor(model::Type{<:AbstractSpectralModel}, closures::Vector, params::Vector, T::Type)

Create a constructor expression for the model. Should return something similar to

:(PowerLaw{T}(arg1, arg2, arg3)))

unpacking the closures and params vectors in the appropriate places.

source
SpectralFitting.Reflection.CompositeModelInfoType
struct CompositeModelInfo
     "The parameter symbols of the model with the respective lens to the actual parameter."
     parameter_symbols::Vector{Pair{Symbol,Lens}}
     "Each model assigned to a unique symbol."
@@ -118,7 +118,7 @@
     maximum_objective_cache_count::Int
     "How many objective caches are currently active."
     objective_cache_count::Int
-end

The composite equivalent of ModelInfo, augmented to track the model symbol (a1, m3, etc.), and the model parameters (K_1, a_3, etc.)

source
SpectralFitting.Reflection.ModelInfoType
struct ModelInfo
     "All parameter symbols for the model."
     symbols::Vector{Symbol}
     "Unique symbols generated for the parameter assignment when buildin the function call."
@@ -131,4 +131,4 @@
     lens::Lens
     "The model type itself."
     model::Type
-end

All models are parsed into a ModelInfo struct relative to their parent (in the case of composite models).

The symbols field contains all of the model parameter symbols as they are in the structure, not as they have been generated. Recall when the invocation expressions are generated, we create anonymous paramter names to avoid conflicts. These are the generated_symbols instead.

source
+end

All models are parsed into a ModelInfo struct relative to their parent (in the case of composite models).

The symbols field contains all of the model parameter symbols as they are in the structure, not as they have been generated. Recall when the invocation expressions are generated, we create anonymous paramter names to avoid conflicts. These are the generated_symbols instead.

source
diff --git a/dev/search_index.js b/dev/search_index.js index 52a30db1..65c6dd41 100644 --- a/dev/search_index.js +++ b/dev/search_index.js @@ -1,3 +1,3 @@ var documenterSearchIndex = {"docs": -[{"location":"models/surrogate-models/#Surrogate-models","page":"Surrogate models","title":"Surrogate models","text":"","category":"section"},{"location":"models/surrogate-models/","page":"Surrogate models","title":"Surrogate models","text":"Surrogate models allow you to create fast or memory efficient approximations of model components, or assist in optimizing some objective function directly. SpectralFitting uses the Surrogates.jl library of models, that yields pure-Julia surrogate models. Consequently, surrogate models also permit use of automatic differentiation in fitting, and are therefore powerful tools for improving fitting performance.","category":"page"},{"location":"models/surrogate-models/#Surrogates-overview","page":"Surrogate models","title":"Surrogates overview","text":"","category":"section"},{"location":"models/surrogate-models/","page":"Surrogate models","title":"Surrogate models","text":"warning: Warning\nThe surrogate model optimization does not work well for most XSPEC models currently. This is being actively developed.","category":"page"},{"location":"models/surrogate-models/","page":"Surrogate models","title":"Surrogate models","text":"Any function may be wrapped as a surrogate model using the SurrogateSpectralModel type.","category":"page"},{"location":"models/surrogate-models/","page":"Surrogate models","title":"Surrogate models","text":"SurrogateSpectralModel","category":"page"},{"location":"models/surrogate-models/#SpectralFitting.SurrogateSpectralModel","page":"Surrogate models","title":"SpectralFitting.SurrogateSpectralModel","text":"SurrogateSpectralModel <: AbstractSpectralModel\nSurrogateSpectralModel(modelkind, surrogate, params, params_symbols)\n\nUsed to wrap a surrogate function into an AbstractSpectralModel.\n\nExample\n\nCreating a surrogate function using make_surrogate_harness:\n\n# build and optimize a surrogate model\nsurrogate = make_surrogate_harness(model, lower_bounds, upper_bounds)\n\n# create surrogate spectral model\nsm = SurrogateSpectralModel(\n Multiplicative(),\n surrogate,\n (FitParam(1.0),),\n (:ηH,)\n)\n\nThe lower_bounds and upper_bounds must be tuples in the form (E, params...), where E denotes the bounds on the energy range to train over.\n\n\n\n\n\n","category":"type"},{"location":"models/surrogate-models/","page":"Surrogate models","title":"Surrogate models","text":"To facilitate easy surrogate builds, SpectralFitting exports a number of utility functions.","category":"page"},{"location":"models/surrogate-models/","page":"Surrogate models","title":"Surrogate models","text":"make_surrogate_harness\noptimize_accuracy!","category":"page"},{"location":"models/surrogate-models/#SpectralFitting.make_surrogate_harness","page":"Surrogate models","title":"SpectralFitting.make_surrogate_harness","text":"make_surrogate_harness(\n model::M,\n lowerbounds::T,\n upperbounds::T;\n optimization_samples = 200,\n seed_samples = 50,\n S::Type = RadialBasis,\n sample_type = SobolSample(),\n verbose = false,\n)\n\nCreates and optimizes a surrogate model of type S for model, using wrap_model_as_objective and optimize_accuracy! for optimization_samples iterations. Model is initially seeded with seed_samples points prior to optimization.\n\nwarning: Warning\nAdditive models integrate energies to calculate flux, which surrogate models are currently not capable of. Results for Additive models likely to be inaccurate. This will be patched in a future version.\n\n\n\n\n\n","category":"function"},{"location":"models/surrogate-models/#SpectralFitting.optimize_accuracy!","page":"Surrogate models","title":"SpectralFitting.optimize_accuracy!","text":"optimize_accuracy!(\n surr::AbstractSurrogate,\n obj::Function,\n lb,\n ub;\n sample_type::SamplingAlgorithm = SobolSample(),\n maxiters = 200,\n N_truth = 5000,\n verbose = false,\n)\n\nImprove accuracy (faithfullness) of the surrogate model in recreating the objective function.\n\nSamples a new space of N_truth points between lb and ub, and calculates the objective function obj at each. Finds the point with largest MSE between surrogate and objective, and adds the point to the surrogate pool. Repeats maxiters times, adding maxiters points to surrogate model.\n\nOptionally print to stdout the MSE and iteration count with verbose = true.\n\nNote that upper- and lower-bounds should be in the form (E, params...), where E is a single energy and params are the model parameters.\n\n\n\n\n\n","category":"function"},{"location":"models/surrogate-models/#Creating-a-surrogate-for-XS_PhotoelectricAbsorption","page":"Surrogate models","title":"Creating a surrogate for XS_PhotoelectricAbsorption","text":"","category":"section"},{"location":"models/surrogate-models/","page":"Surrogate models","title":"Surrogate models","text":"Before we start, let us discuss a number of benefits the use of surrogate models may bring us:","category":"page"},{"location":"models/surrogate-models/","page":"Surrogate models","title":"Surrogate models","text":"SurrogateSpectralModel permit use of automatic differentiation.\nSurrogate models may be allocation-free depending on setup, whereas XSPEC wrappers will always have to allocate for type-conversions.\nSurrogate models may be considerably faster, especially for table models.\nSurrogate models are shareable (see Sharing surrogate models), and are tunable in size.","category":"page"},{"location":"models/surrogate-models/","page":"Surrogate models","title":"Surrogate models","text":"XS_PhotoelectricAbsorption is an XSPEC model that is wrapped by a thin C-wrapper into Julia. The implementation of this model is a number of Fortran routines from the late 90s, including a tabulation of ~3000 lines of data that has been copied directly into the Fortran source code.","category":"page"},{"location":"models/surrogate-models/","page":"Surrogate models","title":"Surrogate models","text":"The performance of this model represents its complexity.","category":"page"},{"location":"models/surrogate-models/","page":"Surrogate models","title":"Surrogate models","text":"using SpectralFitting \n\nenergy = collect(range(0.1, 20.0, 200))\nmodel = XS_PhotoelectricAbsorption()\n\nflux = similar(energy)[1:end-1]","category":"page"},{"location":"models/surrogate-models/","page":"Surrogate models","title":"Surrogate models","text":"Benchmarking with BenchmarkTools.jl:","category":"page"},{"location":"models/surrogate-models/","page":"Surrogate models","title":"Surrogate models","text":"using BenchmarkTools\n@benchmark invokemodel!($flux, $energy, $model)","category":"page"},{"location":"models/surrogate-models/","page":"Surrogate models","title":"Surrogate models","text":"The surrogate we'll construct will have to be tailored a little to the data we wish to fit, as we need to specify the parameter ranges our surrogate should learn. For example, we might be interested in energies between 01 and 20 keV (expressed in our domain), with equivalent hydrogen column etaH anywhere between 10^-3 and 30. We specify the parameter bounds using tuples:","category":"page"},{"location":"models/surrogate-models/","page":"Surrogate models","title":"Surrogate models","text":"lower_bounds = (1e-3,)\nupper_bounds = (30.0,)\nnothing # hide","category":"page"},{"location":"models/surrogate-models/","page":"Surrogate models","title":"Surrogate models","text":"note: Note\nThe first index is always the energy bounds, and the subsequent indices are the parameters in the same order they are defined in the model structure.","category":"page"},{"location":"models/surrogate-models/","page":"Surrogate models","title":"Surrogate models","text":"Next, we use make_surrogate_harness to build and optimize a surrogate function for our model. By default, the surrogate uses linear radial basis functions, and seeds the coefficients with a number of seed points. This function then improves the accuracy of the model using optimize_accuracy!, until a maximal number of iterations has been reached.","category":"page"},{"location":"models/surrogate-models/","page":"Surrogate models","title":"Surrogate models","text":"For illustration purposes, we'll omit the accuracy improving step, and perform this ourselves. We can do this by setting optimization_samples = 0 in the keyword arguments:","category":"page"},{"location":"models/surrogate-models/","page":"Surrogate models","title":"Surrogate models","text":"using Surrogates\n\nharness = make_surrogate_harness(\n (x, y) -> RadialBasis(x, y, lower_bounds, upper_bounds),\n energy,\n model,\n lower_bounds,\n upper_bounds;\n # default is 50, but to illustrate the training behaviour we'll set this low\n seed_samples = 2,\n)\n\n# number of points the surrogate has been trained on\nlength(harness.surrogate.x)","category":"page"},{"location":"models/surrogate-models/","page":"Surrogate models","title":"Surrogate models","text":"We can examine how well our surrogate reconstructs the model for a given test parameter:","category":"page"},{"location":"models/surrogate-models/","page":"Surrogate models","title":"Surrogate models","text":"using Plots\n# random test value\nηh_test = 22.9\n\nmodel.ηH.value = ηh_test\nf = invokemodel(energy, model)\n\nf̂ = harness.surrogate([ηh_test])\n\np = plot(energy[1:end-1], f, label=\"model\", legend=:bottomright, xlabel=\"E (keV)\") # hide\nplot!(energy[1:end-1], f̂, label=\"surr\") # hide\np # hide","category":"page"},{"location":"models/surrogate-models/","page":"Surrogate models","title":"Surrogate models","text":"Now we'll use optimize_accuracy! to improve the faithfulness of our surrogate. This requires making use of wrap_model_as_objective as a little wrapper around our model:","category":"page"},{"location":"models/surrogate-models/","page":"Surrogate models","title":"Surrogate models","text":"optimize_accuracy!(harness; maxiters=50)\n\nlength(harness.surrogate.x)","category":"page"},{"location":"models/surrogate-models/","page":"Surrogate models","title":"Surrogate models","text":"We can plot the surrogate model again and see the improvement.","category":"page"},{"location":"models/surrogate-models/","page":"Surrogate models","title":"Surrogate models","text":"new_f̂ = harness.surrogate([ηh_test])\nplot!(energy[1:end-1], new_f̂, label=\"surr+\") # hide\np # hide","category":"page"},{"location":"models/surrogate-models/","page":"Surrogate models","title":"Surrogate models","text":"Tight. We can also inspect the memory footprint of our model:","category":"page"},{"location":"models/surrogate-models/","page":"Surrogate models","title":"Surrogate models","text":"# in bytes\nBase.summarysize(harness) ","category":"page"},{"location":"models/surrogate-models/","page":"Surrogate models","title":"Surrogate models","text":"This may be reduced by lowering maxiters in optimize_accuracy! at the cost of decreasing faithfulness. However, compare this to the Fortran tabulated source file in the XSPEC source code, which is approximately 224 Kb. The surrogate model with all it's training data is of the same order.","category":"page"},{"location":"models/surrogate-models/#Using-a-surrogate-spectral-model","page":"Surrogate models","title":"Using a surrogate spectral model","text":"","category":"section"},{"location":"models/surrogate-models/","page":"Surrogate models","title":"Surrogate models","text":"Now that we have the surrogate model, we use SurrogateSpectralModel to wrap it into an AbstractSpectralModel. The constructor also needs to know the model kind, have a copy of the model parameters, and know which symbols to represent the parameters with.","category":"page"},{"location":"models/surrogate-models/","page":"Surrogate models","title":"Surrogate models","text":"sm = make_model(harness)","category":"page"},{"location":"models/surrogate-models/","page":"Surrogate models","title":"Surrogate models","text":"We can now use the familiar API and attempt to benchmark the performance:","category":"page"},{"location":"models/surrogate-models/","page":"Surrogate models","title":"Surrogate models","text":"@benchmark invokemodel!($flux, $energy, $sm)","category":"page"},{"location":"models/surrogate-models/","page":"Surrogate models","title":"Surrogate models","text":"Comparing this to the initial benchmark of XS_PhotoelectricAbsorption, we see about a significant speedup, with no allocations, and this surrogate model is now automatic differentiation ready.","category":"page"},{"location":"models/surrogate-models/#Evaluating-the-model","page":"Surrogate models","title":"Evaluating the model","text":"","category":"section"},{"location":"models/surrogate-models/","page":"Surrogate models","title":"Surrogate models","text":"p_range = collect(range(1.0, 30.0))\n\nfluxes_vecs = map(p_range) do p\n model.ηH.value = p\n f = invokemodel(energy, model)\nend\nfluxes_mat = reduce(hcat, fluxes_vecs)\n\nsurface(p_range, energy[1:end-1], fluxes_mat, xlabel = \"ηH\", ylabel = \"E\", zlabel = \"f\", title = \"Model\")","category":"page"},{"location":"models/surrogate-models/","page":"Surrogate models","title":"Surrogate models","text":"s_fluxes_vecs = map(p_range) do p\n sm.params[1].value = p\n display(sm)\n f = invokemodel(energy, sm)\nend\ns_fluxes_mat = reduce(hcat, s_fluxes_vecs)\n\nsurface(p_range, energy[1:end-1], s_fluxes_mat, xlabel = \"ηH\", ylabel = \"E\", zlabel = \"f\", title = \"Surrogate\")","category":"page"},{"location":"models/surrogate-models/#Sharing-surrogate-models","page":"Surrogate models","title":"Sharing surrogate models","text":"","category":"section"},{"location":"models/surrogate-models/","page":"Surrogate models","title":"Surrogate models","text":"To export and import surrogate models, JLD2.jl is recommended.","category":"page"},{"location":"models/models/#Model-index","page":"Model index","title":"Model index","text":"","category":"section"},{"location":"models/models/","page":"Model index","title":"Model index","text":"Models wrapped from XSPEC implementations are prefixed with XS_*, whereas pure-Julia models are simply named, e.g. XS_PowerLaw in XSPEC vs PowerLaw in Julia.","category":"page"},{"location":"models/models/","page":"Model index","title":"Model index","text":"The available models are","category":"page"},{"location":"models/models/","page":"Model index","title":"Model index","text":"Pages = [\"models.md\"]\nOrder = [:type]","category":"page"},{"location":"models/models/#Julia-models","page":"Model index","title":"Julia models","text":"","category":"section"},{"location":"models/models/","page":"Model index","title":"Model index","text":"PowerLaw\nBlackBody","category":"page"},{"location":"models/models/#SpectralFitting.PowerLaw","page":"Model index","title":"SpectralFitting.PowerLaw","text":"XS_PowerLaw(K, a)\n\nK: Normalisation.\na: Photon index.\n\nExample\n\nenergy = collect(range(0.1, 20.0, 100))\ninvokemodel(energy, PowerLaw())\n\n PowerLaw\n ┌────────────────────────────────────────┐\n 0.5 │ │\n │: │\n │: │\n │: │\n │: │\n │: │\n │: │\n │ : │\n │ : │\n │ : │\n │ :. │\n │ ':.. │\n │ ''':...... │\n │ ''''''''''''''........│\n 0 │ │\n └────────────────────────────────────────┘\n 0 20\n E (keV)\n\n\n\n\n\n","category":"type"},{"location":"models/models/#SpectralFitting.BlackBody","page":"Model index","title":"SpectralFitting.BlackBody","text":"XS_BlackBody(K, T)\n\nK: Normalisation.\nkT: Temperature (keV).\n\nExample\n\nenergy = collect(range(0.1, 20.0, 100))\ninvokemodel(energy, BlackBody())\n\n BlackBody\n ┌────────────────────────────────────────┐\n 0.2 │ │\n │ │\n │ │\n │ │\n │ │\n │ │\n │ .:''':.. │\n │ :' '':. │\n │ .' ':. │\n │ .: '.. │\n │ : ':. │\n │ .' ':.. │\n │ : ''... │\n │: '''.... │\n 0 │: '''│\n └────────────────────────────────────────┘\n 0 20\n E (keV)\n\n\n\n\n\n","category":"type"},{"location":"models/models/#XSPEC-models","page":"Model index","title":"XSPEC models","text":"","category":"section"},{"location":"models/models/","page":"Model index","title":"Model index","text":"XSPEC models frequently have tabular data dependencies, without which the models fail to invoke (see Model data availability). If the data files are known but not present, the XSPEC models will throw an error with instructions for downloading the missing data. If the data files are unknown, Julia may crash catastrophically. If this is the case, often a single line will be printed with the LibXSPEC error, specifying the name of the missing source file. This can be registered as a data dependency of a model using SpectralFitting.register_model_data.","category":"page"},{"location":"models/models/","page":"Model index","title":"Model index","text":"The first time any XSPEC model is invoked, SpectralFitting checks to see whether requisite data is needed, and whether the data is downloaded. Subsequent calls will hit a lookup cache instead to avoid run-time costs of performing this check.","category":"page"},{"location":"models/models/","page":"Model index","title":"Model index","text":"XS_PowerLaw\nXS_BlackBody\nXS_BremsStrahlung\nXS_Gaussian\nXS_Laor\nXS_DiskLine\nXS_PhotoelectricAbsorption\nXS_WarmAbsorption\nXS_CalculateFlux\nXS_KerrDisk\nXS_KyrLine","category":"page"},{"location":"models/models/#SpectralFitting.XS_PowerLaw","page":"Model index","title":"SpectralFitting.XS_PowerLaw","text":"XS_PowerLaw(K, a)\n\nK: Normalisation.\na: Photon index.\n\nExample\n\nenergy = collect(range(0.1, 20.0, 100))\ninvokemodel(energy, XS_PowerLaw())\n\n XS_PowerLaw\n ┌────────────────────────────────────────┐\n 0.5 │ │\n │: │\n │: │\n │: │\n │: │\n │: │\n │: │\n │ : │\n │ : │\n │ : │\n │ :. │\n │ ':.. │\n │ ''':...... │\n │ ''''''''''''''........│\n 0 │ │\n └────────────────────────────────────────┘\n 0 20\n E (keV)\n\n\n\n\n\n","category":"type"},{"location":"models/models/#SpectralFitting.XS_BlackBody","page":"Model index","title":"SpectralFitting.XS_BlackBody","text":"XS_BlackBody(K, T)\n\nK: Normalisation.\nT: Temperature (keV).\n\nExample\n\nenergy = collect(range(0.1, 20.0, 100))\ninvokemodel(energy, XS_BlackBody())\n\n XS_BlackBody\n ┌────────────────────────────────────────┐\n 0.2 │ │\n │ │\n │ │\n │ │\n │ │\n │ │\n │ .:''':.. │\n │ .: ''. │\n │ .' ':. │\n │ : ''.. │\n │ : ':. │\n │ : '':. │\n │.: ''.. │\n │: '':.... │\n 0 │' '''│\n └────────────────────────────────────────┘\n 0 20\n E (keV)\n\n\n\n\n\n","category":"type"},{"location":"models/models/#SpectralFitting.XS_BremsStrahlung","page":"Model index","title":"SpectralFitting.XS_BremsStrahlung","text":"XS_BremsStrahlung(K, T)\n\nK: Normalisation.\nT: Plasma temperature (keV).\n\nExample\n\nenergy = collect(range(0.1, 20.0, 100))\ninvokemodel(energy, XS_BremsStrahlung())\n\n XS_BremsStrahlung\n ┌────────────────────────────────────────┐\n 2 │ │\n │. │\n │: │\n │: │\n │: │\n │: │\n │: │\n │: │\n │: │\n │: │\n │: │\n │: │\n │'. │\n │ : │\n 0 │ ':....................................│\n └────────────────────────────────────────┘\n 0 20\n E (keV)\n\n\n\n\n\n","category":"type"},{"location":"models/models/#SpectralFitting.XS_Gaussian","page":"Model index","title":"SpectralFitting.XS_Gaussian","text":"XS_Gaussian(K, E, σ)\n\nK: Normalisation\nE: Line wavelength in Angstrom.\nσ: Line width in Angstrom.\n\nExample\n\nenergy = collect(range(4.0, 8.0, 100))\ninvokemodel(energy, XS_Gaussian())\n\n XS_Gaussian \n ┌────────────────────────────────────────┐ \n 0.09 │ │ \n │ . │ \n │ : : │ \n │ : : │ \n │ : '. │ \n │ .' : │ \n │ : : │ \n │ : : │ \n │ : '. │ \n │ : : │ \n │ : : │ \n │ : : │ \n │ .' : │ \n │ : : │ \n 0 │.......: :......................│ \n └────────────────────────────────────────┘ \n 0 20 \n E (keV) \n\n\n\n\n\n","category":"type"},{"location":"models/models/#SpectralFitting.XS_Laor","page":"Model index","title":"SpectralFitting.XS_Laor","text":"XS_Laor(K, lineE, a, inner_r, outer_r, incl)\n\nK: Normalisation.\nlineE: Rest frame line energy (keV).\na: Power law dependence of emissitivy. Scales R⁻ᵅ.\ninner_r: Inner radius of the accretion disk (GM/c).\nouter_r: Outer radius of the accretion disk (GM/c).\nθ: Disk inclination angle to line of sight (degrees, 0 is pole on).\n\nExample\n\nenergy = collect(range(0.1, 10.0, 100))\ninvokemodel(energy, XS_Laor())\n\n XS_Laor\n ┌────────────────────────────────────────┐\n 0.06 │ │\n │ │\n │ :: │\n │ :: │\n │ : : │\n │ : : │\n │ : : │\n │ :' : │\n │ .: : │\n │ :' : │\n │ .' : │\n │ .:' : │\n │ ..' : │\n │ .:' : │\n 0 │.......:'' :............│\n └────────────────────────────────────────┘\n 0 10\n E (keV)\n\n\n\n\n\n","category":"type"},{"location":"models/models/#SpectralFitting.XS_DiskLine","page":"Model index","title":"SpectralFitting.XS_DiskLine","text":"XS_DiskLine(K, lineE, β, inner_r, outer_r, incl)\n\nK: Normalisation.\nlineE: Rest frame line energy (keV).\nβ: Power law dependence of emissitivy. If < 10, scales Rᵅ.\ninner_r: Inner radius of the accretion disk (GM/c).\nouter_r: Outer radius of the accretion disk (GM/c).\nθ: Disk inclination angle to line of sight (degrees, 0 is pole on).\n\nExample\n\nenergy = collect(range(4.0, 8.0, 100))\ninvokemodel(energy, XS_DiskLine())\n\n XS_DiskLine\n ┌────────────────────────────────────────┐\n 0.09 │ │\n │ . │\n │ : │\n │ :: │\n │ . :: │\n │ : :: │\n │ :'': │\n │ .' : │\n │ : : │\n │ : : │\n │ .' : │\n │ : : │\n │ .: '. │\n │ .:' : │\n 0 │...............:''' :.........│\n └────────────────────────────────────────┘\n 4 8\n E (keV)\n\n\n\n\n\n","category":"type"},{"location":"models/models/#SpectralFitting.XS_PhotoelectricAbsorption","page":"Model index","title":"SpectralFitting.XS_PhotoelectricAbsorption","text":"XS_PhotoelectricAbsorption(ηH)\n\nηH: Equivalent hydrogen column (units of 10²² atoms per cm⁻²).\n\nExample\n\nenergy = collect(range(0.1, 20.0, 100))\ninvokemodel(energy, XS_PhotoelectricAbsorption())\n\n XS_PhotoelectricAbsorption\n ┌────────────────────────────────────────┐\n 1 │ ...''''''''''''''''''''''''''''''│\n │ .' │\n │ : │\n │ :' │\n │ : │\n │ : │\n │ : │\n │ : │\n │ : │\n │ : │\n │ : │\n │ : │\n │ : │\n │ : │\n 0 │.: │\n └────────────────────────────────────────┘\n 0 20\n E (keV)\n\n\n\n\n\n","category":"type"},{"location":"models/models/#SpectralFitting.XS_WarmAbsorption","page":"Model index","title":"SpectralFitting.XS_WarmAbsorption","text":"XS_WarmAbsorption(ηH, Ew)\n\nηH: Equivalent hydrogen column (units of 10²² atoms per cm⁻²).\nEw: Window energy (keV).\n\nExample\n\nenergy = collect(range(0.1, 20.0, 100))\ninvokemodel(energy, XS_WarmAbsorption())\n\n XS_WarmAbsorption\n ┌────────────────────────────────────────┐\n 1 │': ...''':'''''''''''''''''''''''''│\n │ : .:' │\n │ : .' │\n │ : .: │\n │ : : │\n │ : : │\n │ : : │\n │ : : │\n │ : : │\n │ : : │\n │ :: │\n │ :: │\n │ : │\n │ : │\n 0.2 │ : │\n └────────────────────────────────────────┘\n 0 20\n E (keV)\n\n\n\n\n\n","category":"type"},{"location":"models/models/#SpectralFitting.XS_CalculateFlux","page":"Model index","title":"SpectralFitting.XS_CalculateFlux","text":"XS_CalculateFlux(E_min, E_max, lg10Flux)\n\nE_min: Minimum energy.\nE_max: Maximum energy.\nlog10Flux: log (base 10) flux in erg / cm^2 / s\n\n\n\n\n\n","category":"type"},{"location":"models/models/#SpectralFitting.XS_KerrDisk","page":"Model index","title":"SpectralFitting.XS_KerrDisk","text":"XS_KerrDisk(K, lineE, index1, index2, break_r, a, θ, inner_r, outer_r)\n\nK: Normalisation.\nlineE: Rest frame line energy (keV).\nindex1: Emissivity index for inner disk.\nindex2: Emissivity index for outer disk.\nbreak_r: Break radius seperating inner and outer disk (gᵣ).\na: Dimensionless black hole spin.\nθ: Disk inclination angle to line of sight (degrees).\ninner_r: Inner radius of the disk in units of rₘₛ.\nouter_r: Outer radius of the disk in units of rₘₛ.\nz: Redshift.\n\nExample\n\nenergy = collect(range(0.1, 20.0, 100))\ninvokemodel(energy, XS_KerrDisk())\n\n XS_KerrDisk\n ┌────────────────────────────────────────┐\n 0.05 │ │\n │ │\n │ . │\n │ .: │\n │ ::: │\n │ .:' '. │\n │ .: : │\n │ ..' : │\n │ :' : │\n │ .' : │\n │ .:' : │\n │ .:'' : │\n │ .::' : │\n │ ..:' : │\n 0 │.........:''' :......│\n └────────────────────────────────────────┘\n 0 8\n E (keV)\n\n\n\n\n\n","category":"type"},{"location":"models/models/#SpectralFitting.XS_KyrLine","page":"Model index","title":"SpectralFitting.XS_KyrLine","text":"XS_KyrLine(K, a, θ_obs, inner_r, ms_flag, outer_r, lineE, α, β, break_r, z, limb)\n\nK: Normalisation.\na: Dimensionless black hole spin.\nθ: Observer inclination (0 is on pole, degrees).\ninner_r: Inner radius of the disk in units of GM/c²\nms_flag: 0: integrate from rᵢₙ. 1: integrate from rₘₛ.\nouter_r: Outer radius of the disk in units of GM/c²\nlineE: Rest frame line energy (keV).\nα\nβ\nbreak_r: Break radius seperating inner and outer disk (GM/c²).\nz: Overall Doppler shift.\nlimb: 0: isotropic emission, 1: Laor's limb darkening, 2: Haard's limb brightening.\n\nExample\n\nenergy = collect(range(0.1, 20.0, 100))\ninvokemodel(energy, XS_KyrLine())\n\n XS_KyrLine\n ┌────────────────────────────────────────┐\n 0.05 │ │\n │ │\n │ : │\n │ :. │\n │ :.': │\n │ :' : │\n │ : : │\n │ .' : │\n │ .:' : │\n │ .' : │\n │ .: : │\n │ .' : │\n │ .:' : │\n │ ..:' : │\n 0 │.........:''' :......│\n └────────────────────────────────────────┘\n 0 8\n E (keV)\n\n\n\n\n\n","category":"type"},{"location":"models/models/#Wrapping-new-XSPEC-models","page":"Model index","title":"Wrapping new XSPEC models","text":"","category":"section"},{"location":"models/models/","page":"Model index","title":"Model index","text":"SpectralFitting exports a helpful macro to facilitate wrapping additional XSPEC models.","category":"page"},{"location":"models/models/","page":"Model index","title":"Model index","text":"@xspecmodel\nSpectralFitting.register_model_data","category":"page"},{"location":"models/models/#SpectralFitting.@xspecmodel","page":"Model index","title":"SpectralFitting.@xspecmodel","text":"@xspecmodel [type=Float64] [ff_call_site] model\n\nUsed to wrap additional XSPEC models, generating the needed AbstractSpectralModel implementation.\n\nThe type keyword specifies the underlying type to coerce input and output arrays to, as different implementations may have incompatible number of bits. The ff_call_site is the foreign fuction call site, which is the first argument to ccall, and follows the same conventions. The model is a struct, which must subtype AbstractSpectralModel.\n\nIf the callsite is not specified, the user must implement _unsafe_ffi_invoke!.\n\nExamples\n\n@xspecmodel :C_powerlaw struct XS_PowerLaw{T} <: AbstractSpectralModel{T, Additive}\n \"Normalisation.\"\n K::T\n \"Photon index.\"\n a::T\nend\n\n# constructor has default values\nfunction XS_PowerLaw(; K = FitParam(1.0), a = FitParam(1.0))\n XS_PowerLaw{typeof(K)}(K, a)\nend\n\nWe define a new structure XS_PowerLaw with two parameters, but since the model is Additive, only a single parameter (a) is passed to the XSPEC function. The function we bind to this model is :C_powerlaw from the XSPEC C wrappers.\n\nThe macro will then generate the following functions\n\nimplementation \ninvoke! \n_safe_ffi_invoke! \n\nIf a callsite was specified, it will also generate:\n\n_unsafe_ffi_invoke! \n\n\n\n\n\n","category":"macro"},{"location":"models/models/#SpectralFitting.register_model_data","page":"Model index","title":"SpectralFitting.register_model_data","text":"SpectralFitting.register_model_data(M::Type{<:AbstractSpectralModel}, model_data::ModelDataInfo...)\nSpectralFitting.register_model_data(M::Type{<:AbstractSpectralModel}, remote_and_local::Tuple{String,String}...)\nSpectralFitting.register_model_data(M::Type{<:AbstractSpectralModel}, filenames::String...)\nSpectralFitting.register_model_data(s::Symbol, filenames::String...)\n\nRegister filenames as model data associated with the model given by type M or symbol s. This function does not download any files, but rather adds the relevant filenames to a lookup which SpectralFitting.download_model_data consults when invoked, and consequently model data is only downloaded when needed.\n\nnote: Note\nIt is good practice to use this method immediately after defining a new model with @xspecmodel to register any required datafiles from the HEASoft source code, and therefore keep relevant information together.\n\nExample\n\n# by type\nregister_model_data(XS_Laor, \"ari.mod\")\n# by symbol\nregister_model_data(:XS_KyrLine, \"KBHline01.fits\")\n\n\n\n\n\n","category":"function"},{"location":"models/models/#Generating-model-fingerprints","page":"Model index","title":"Generating model fingerprints","text":"","category":"section"},{"location":"models/models/","page":"Model index","title":"Model index","text":"To generate the unicode plot to add as a fingerprint, we use a simple function:","category":"page"},{"location":"models/models/","page":"Model index","title":"Model index","text":"using SpectralFitting, UnicodePlots\n\nfunction plotmodel(energy, model)\n flux = invokemodel(energy, model)\n lineplot(\n energy[1:end-1], \n flux, \n title=String(Base.typename(typeof(model)).name), \n xlabel=\"E (keV)\", \n canvas=DotCanvas\n )\nend\n\n# e.g. for XS_PowerLaw()\nenergy = collect(range(0.1, 20.0, 100))\nplotmodel(energy, XS_PowerLaw())","category":"page"},{"location":"examples/examples/#Spectral-fitting-examples","page":"Diverse examples","title":"Spectral fitting examples","text":"","category":"section"},{"location":"examples/examples/","page":"Diverse examples","title":"Diverse examples","text":"using SpectralFitting\nusing Plots\nENV[\"GKSwstype\"]=\"nul\"\nPlots.default(show=false)","category":"page"},{"location":"examples/examples/","page":"Diverse examples","title":"Diverse examples","text":"Below are a number of examples illustrating how this package may be used.","category":"page"},{"location":"examples/examples/#Using-the-model-library","page":"Diverse examples","title":"Using the model library","text":"","category":"section"},{"location":"examples/examples/","page":"Diverse examples","title":"Diverse examples","text":"The model library details a model algebra (see AbstractSpectralModelKind) for composing models together. An example use of this may be to construct a complex model from a series of simpler models, and invoke the models on a given energy grid:","category":"page"},{"location":"examples/examples/","page":"Diverse examples","title":"Diverse examples","text":"using SpectralFitting\nusing Plots \n\nmodel = PhotoelectricAbsorption() * (PowerLaw() + BlackBody()) \n\n# define energy grid\nenergy = collect(range(0.1, 12.0, 100))\n\nflux = invokemodel(energy, model)\n\nplot(energy[1:end-1], flux)","category":"page"},{"location":"examples/examples/","page":"Diverse examples","title":"Diverse examples","text":"Note this energy grid may be arbitrarily spaced, but, like XSPEC, assumes the bins are contiguous, i.e. that the high energy limit of one bin is the low energy limit of the next.","category":"page"},{"location":"examples/examples/","page":"Diverse examples","title":"Diverse examples","text":"The full model library of available models is listed in Model index.","category":"page"},{"location":"parameters/#Parameters","page":"Parameters","title":"Parameters","text":"","category":"section"},{"location":"transitioning-from-xspec/#Transitioning-from-XSPEC","page":"Transitioning from XSPEC","title":"Transitioning from XSPEC","text":"","category":"section"},{"location":"examples/optimizers/#Optimizer-galore","page":"Optimizer galore","title":"Optimizer galore","text":"","category":"section"},{"location":"examples/optimizers/","page":"Optimizer galore","title":"Optimizer galore","text":"Let's fit a spectrum:","category":"page"},{"location":"examples/optimizers/","page":"Optimizer galore","title":"Optimizer galore","text":"using SpectralFitting, Plots\n\nDATADIR = \"...\"\nDATADIR = length(get(ENV, \"CI\", \"\")) > 0 ? @__DIR__() * \"/../../ex-datadir\" : \"/home/lilith/Developer/jl/datasets/xspec/walkthrough\" # hide\nspec1_path = joinpath(DATADIR, \"s54405.pha\")\ndata = OGIPDataset(spec1_path) \nnormalize!(data)\n\nmask_energies!(data, 1, 15)\n\n# a plotting utility\nmy_plot(data) = plot(\n data, \n xscale = :log10, \n yscale = :log10,\n ylims = (1e-3, 1.3)\n)\n\nmy_plot(data)","category":"page"},{"location":"examples/sherpa-example/#A-quick-guide-to-modelling-and-fitting-in-SpectralFitting.jl","page":"A quick guide","title":"A quick guide to modelling and fitting in SpectralFitting.jl","text":"","category":"section"},{"location":"examples/sherpa-example/","page":"A quick guide","title":"A quick guide","text":"This is SpectralFitting.jl version of A quick guide to modeling and fitting in Sherpa.","category":"page"},{"location":"examples/sherpa-example/","page":"A quick guide","title":"A quick guide","text":"using SpectralFitting, Plots\nusing Random\nRandom.seed!(0)\n\nx = collect(range(-5, 5, 200))\n\nA_true = 3.0\npos_true = 1.3\nsigma_true = 0.8\nerr_true = 0.2\n\ny = @. A_true * exp(-(x - pos_true)^2 / (2 * sigma_true^2))\n\ny_noisy = y .+ (0.2 * randn(length(y)))\n\nscatter(x, y_noisy)","category":"page"},{"location":"examples/sherpa-example/","page":"A quick guide","title":"A quick guide","text":"To make this into a fittable dataset, we observe that our layout is injective (i.e. length(x) == length(y)). This is subtly different from how the majority of spectral models are implemented, which usually assume some kind of binning (length(x) == length(y) + 1). Fortunately, SpectralFitting.jl can track this for us, and do various conversion to make the models work correctly for the data. We need only tell the package what our AbstractLayout is:","category":"page"},{"location":"examples/sherpa-example/","page":"A quick guide","title":"A quick guide","text":"data = InjectiveData(x, y_noisy; name = \"example\")","category":"page"},{"location":"examples/sherpa-example/","page":"A quick guide","title":"A quick guide","text":"The data prints the data card, which provides us some high level information about our data at a glance. We can plot the data trivially using one of the Plots.jl recipes","category":"page"},{"location":"examples/sherpa-example/","page":"A quick guide","title":"A quick guide","text":"plot(data, markersize = 3)","category":"page"},{"location":"examples/sherpa-example/","page":"A quick guide","title":"A quick guide","text":"Next we want to specify a model to fit to this data. Models that are prefixed with XS_ are models that are linked from the XSPEC model library, provided via LibXSPEC_jll. For a full list of the models, see Models index.","category":"page"},{"location":"examples/sherpa-example/","page":"A quick guide","title":"A quick guide","text":"warning: Warning\nIt is advised to use the Julia implemented models. This allows various calculations to benefit from automatic differentiation, efficient multi-threading, GPU offloading, and various other useful things, see Why & how.","category":"page"},{"location":"examples/sherpa-example/","page":"A quick guide","title":"A quick guide","text":"model = GaussianLine(μ = FitParam(0.0))","category":"page"},{"location":"examples/sherpa-example/","page":"A quick guide","title":"A quick guide","text":"We can plot our model over the same domain range quite easily too:","category":"page"},{"location":"examples/sherpa-example/","page":"A quick guide","title":"A quick guide","text":"plot(data.domain[1:end-1], invokemodel(data.domain, model))","category":"page"},{"location":"examples/sherpa-example/","page":"A quick guide","title":"A quick guide","text":"Note that we've had to adjust the domain here. As stated before, most models are implemented for binned data, and therefore return one fewer bin than given.","category":"page"},{"location":"examples/sherpa-example/","page":"A quick guide","title":"A quick guide","text":"SpectralFitting.jl adopts the SciML problem-solver abstraction, so to fit a model to data we specify a FittingProblem:","category":"page"},{"location":"examples/sherpa-example/","page":"A quick guide","title":"A quick guide","text":"prob = FittingProblem(model => data)","category":"page"},{"location":"examples/sherpa-example/","page":"A quick guide","title":"A quick guide","text":"We fit problem then by calling fit:","category":"page"},{"location":"examples/sherpa-example/","page":"A quick guide","title":"A quick guide","text":"result = fit(prob, LevenbergMarquadt())","category":"page"},{"location":"examples/sherpa-example/","page":"A quick guide","title":"A quick guide","text":"The result card tells us a little bit about how successful the fit was. We further inspect the fit by overplotting result on the data:","category":"page"},{"location":"examples/sherpa-example/","page":"A quick guide","title":"A quick guide","text":"plot(data, markersize = 3)\nplot!(result)","category":"page"},{"location":"examples/sherpa-example/","page":"A quick guide","title":"A quick guide","text":"We can create a contour plot of the fit statistic by evaluating the result everywhere on the grid and measuring the statistic:","category":"page"},{"location":"examples/sherpa-example/","page":"A quick guide","title":"A quick guide","text":"amps = range(50, 200, 50)\ndevs = range(0.5, 1.2, 50)\n\nstats = [\n measure(ChiSquared(), result, [a, result.u[2], d])\n for d in devs, a in amps\n]\n\n# 1, 2, and 3 sigma contours\nlevels = [2.3, 4.61, 9.21]\ncontour(\n amps, \n devs, \n stats .- result.χ2, \n levels = levels, \n xlabel = \"K\", \n ylabel = \"σ\"\n)\nscatter!([result.u[1]], [result.u[3]])","category":"page"},{"location":"examples/sherpa-example/#Simultaneous-fits","page":"A quick guide","title":"Simultaneous fits","text":"","category":"section"},{"location":"models/composite-models/#Composite-models","page":"Composite models","title":"Composite models","text":"","category":"section"},{"location":"models/composite-models/","page":"Composite models","title":"Composite models","text":"The model algebra defined by the AbstractSpectralModelKind yields instances of CompositeModel, nested to various degrees. These composite models are designed to make as much information about the spectral model available at compile-time, such that rich and optimized generated functions may be assembled purely from the Julia types (see Why & how).","category":"page"},{"location":"models/composite-models/","page":"Composite models","title":"Composite models","text":"CompositeModel\nAbstractCompositeOperator\nAdditionOperator\nMultiplicationOperator\nConvolutionOperator\noperation_symbol","category":"page"},{"location":"models/composite-models/#SpectralFitting.CompositeModel","page":"Composite models","title":"SpectralFitting.CompositeModel","text":"CompositeModel{M1,M2,O} <: AbstractSpectralModel\nCompositeModel(left_model, right_model, op::AbstractCompositeOperator)\n\nType resulting from operations combining any number of AbstractSpectralModel via the model algebra defined from AbstractSpectralModelKind.\n\nEach operation binary operation in the model algebra is encoded in the parametric types of the CompositeModel, where the operation is given by an AbstractCompositeOperator. Composite models adopt the model kind of the right model, i.e. M2, and obey the model algebra accordingly.\n\nComposite models very rarely need to be constructed directly, and are instead obtained by regular model operations.\n\nExample\n\nmodel = PhotoelectricAbsorption() * (PowerLaw() + BlackBody())\ntypeof(model) <: CompositeModel # true\n\n\n\n\n\n","category":"type"},{"location":"models/composite-models/#SpectralFitting.AbstractCompositeOperator","page":"Composite models","title":"SpectralFitting.AbstractCompositeOperator","text":"abstract type AbstractCompositeOperator\n\nSuperype of all composition operators. Used to implement the model algebra of AbstractSpectralModelKind through a trait system.\n\nThe Julia symbol corresponding to a given AbstractCompositeOperator may be obtained through operation_symbol.\n\n\n\n\n\n","category":"type"},{"location":"models/composite-models/#SpectralFitting.AdditionOperator","page":"Composite models","title":"SpectralFitting.AdditionOperator","text":"AdditionOperator <: AbstractCompositeOperator\nAdditionOperator()\n\nCorresponds to the :(+) symbol.\n\n\n\n\n\n","category":"type"},{"location":"models/composite-models/#SpectralFitting.MultiplicationOperator","page":"Composite models","title":"SpectralFitting.MultiplicationOperator","text":"MultiplicationOperator <: AbstractCompositeOperator\nMultiplicationOperator()\n\nCorresponds to the :(*) symbol.\n\n\n\n\n\n","category":"type"},{"location":"models/composite-models/#SpectralFitting.ConvolutionOperator","page":"Composite models","title":"SpectralFitting.ConvolutionOperator","text":"ConvolutionOperator <: AbstractCompositeOperator\nConvolutionOperator()\n\nHas no corresponding symbol, since it invokes a function call C(A).\n\n\n\n\n\n","category":"type"},{"location":"models/composite-models/#SpectralFitting.operation_symbol","page":"Composite models","title":"SpectralFitting.operation_symbol","text":"operation_symbol(::AbstractCompositeOperator)\noperation_symbol(::Type{<:AbstractCompositeOperator})\n\nObtain the model symbol from a given AbstractCompositeOperator.\n\n\n\n\n\n","category":"function"},{"location":"datasets/datasets/#Datasets","page":"Using datasets","title":"Datasets","text":"","category":"section"},{"location":"datasets/datasets/","page":"Using datasets","title":"Using datasets","text":"SpectralFitting.jl supports a wide variety of datasets, and makes it easy to wrap your own.","category":"page"},{"location":"datasets/datasets/","page":"Using datasets","title":"Using datasets","text":"For spectral fitting specifics, the main dataset type is","category":"page"},{"location":"datasets/datasets/","page":"Using datasets","title":"Using datasets","text":"SpectralData","category":"page"},{"location":"datasets/datasets/#SpectralFitting.SpectralData","page":"Using datasets","title":"SpectralFitting.SpectralData","text":"SpectralData{T} <: AbstractDataset\n\nA general spectral data structure, minimally with a Spectrum and ResponseMatrix. Optionally also includes the AncillaryResponse and a background Spectrum.\n\nMethods\n\nThe following methods are made available through the SpectralData:\n\nregroup!\nrestrict_domain!\nmask_energies!\ndrop_channels!\ndrop_bad_channels!\ndrop_negative_channels!\nnormalize!\nobjective_units\nspectrum_energy\nbin_widths\nsubtract_background!\nset_domain!\nerror_statistic\n\nConstructors\n\nThe available constructors are:\n\nSpectralData(paths::SpectralDataPaths; kwargs...)\n\nUsing SpectralDataPaths.\n\nIf the spectrum and repsonse matrix have already been loaded seperately, use\n\nSpectralData(\n spectrum::Spectrum,\n response::ResponseMatrix;\n # try to match the domains of the response matrix to the data\n match_domains = true,\n background = nothing,\n ancillary = nothing,\n)\n\n\n\n\n\n","category":"type"},{"location":"datasets/datasets/#Dataset-abstraction","page":"Using datasets","title":"Dataset abstraction","text":"","category":"section"},{"location":"datasets/datasets/","page":"Using datasets","title":"Using datasets","text":"Datasets must define a small API to make fitting possible. The picture to have in mind when considering the different domains is as follows: the model is trying to predict the objective. It does so by taking in input domain and maps it to some output domain.","category":"page"},{"location":"datasets/datasets/","page":"Using datasets","title":"Using datasets","text":"That means make_output_domain and make_objective_domain correspond to the (XY) values of the data that the model is trying to fit, whilst the model is evaluated on the make_model_domain, which need not be the same as the output domain.","category":"page"},{"location":"datasets/datasets/","page":"Using datasets","title":"Using datasets","text":"In other cases, the objective_transformer acts to transform the output of the model onto the output domain. ","category":"page"},{"location":"datasets/datasets/","page":"Using datasets","title":"Using datasets","text":"Mathematically, expressing the output domain X, the model domain D, the model output M(D) and objective S, along with the transformer as T, then the relationship between the different domains is","category":"page"},{"location":"datasets/datasets/","page":"Using datasets","title":"Using datasets","text":"hatS = T times M(D)","category":"page"},{"location":"datasets/datasets/","page":"Using datasets","title":"Using datasets","text":"Both hatS and S are defined over X. The various fitting operations try to find model paramters that make hatS and S as close as possible.","category":"page"},{"location":"datasets/datasets/","page":"Using datasets","title":"Using datasets","text":"AbstractDataset\nmake_objective_variance\nmake_objective\nmake_domain_variance\nmake_model_domain\nmake_output_domain","category":"page"},{"location":"datasets/datasets/#SpectralFitting.AbstractDataset","page":"Using datasets","title":"SpectralFitting.AbstractDataset","text":"abstract type AbstractDataset\n\nAbstract type for use in fitting routines. High level representation of some underlying data structures. \n\nFitting data is considered to have an objective and a domain. As the domain may be, for example, energy bins (high and low), or fourier frequencies (single value), the purpose of this abstraction is to provide some facility for translating between these representations for the models to fit with. This is done by checking that the AbstractLayout of the model and data are compatible, or at least have compatible translations.\n\nMust implement a minimal set of accessor methods. These are paired with objective and domain parlance. Note that these functions are prefixed with make_* and not get_* to represent that there may be allocations or work going into the translation. Usage of these functions should be sparse in the interest of performance.\n\nThe arrays returned by the make_* functions must correspond to the AbstractLayout specified by the caller.\n\nmake_objective_variance\nmake_objective\nmake_domain_variance\nmake_model_domain\nmake_ouput_domain\n\nAdditionally there is an objective transformer that transforms the output of the model onto the output domain:\n\nobjective_transformer\n\nFinally, to make all of the fitting for different statistical regimes work efficiently, datasets should inform which units are preferred to fit. They may also give the error statistics they prefer, and a label name primarily used to disambiguate:\n\npreferred_units\nerror_statistic\nmake_label\n\n\n\n\n\n","category":"type"},{"location":"datasets/datasets/#SpectralFitting.make_objective_variance","page":"Using datasets","title":"SpectralFitting.make_objective_variance","text":"make_objective_variance(layout::AbstractLayout, dataset::AbstractDataset)\n\nMake the variance vector associated with each objective point.\n\n\n\n\n\n","category":"function"},{"location":"datasets/datasets/#SpectralFitting.make_objective","page":"Using datasets","title":"SpectralFitting.make_objective","text":"make_objective(layout::AbstractLayout, dataset::AbstractDataset)\n\nReturns the array used as the target for model fitting. The array must correspond to the data AbstractLayout specified by the layout parameter.\n\nIn as far as it can be guarunteed, the memory in the returned array will not be mutated by any fitting procedures.\n\nDomain for this objective should be returned by make_model_domain.\n\n\n\n\n\n","category":"function"},{"location":"datasets/datasets/#SpectralFitting.make_domain_variance","page":"Using datasets","title":"SpectralFitting.make_domain_variance","text":"make_domain_variance(layout::AbstractLayout, dataset::AbstractDataset)\n\nMake the variance vector associated with the domain.\n\n\n\n\n\n","category":"function"},{"location":"datasets/datasets/#SpectralFitting.make_model_domain","page":"Using datasets","title":"SpectralFitting.make_model_domain","text":"make_model_domain(layout::AbstractLayout, dataset::AbstractDataset)\n\nReturns the array used as the domain for the modelling. This is paired with make_domain_variance\n\n\n\n\n\n","category":"function"},{"location":"datasets/datasets/#SpectralFitting.make_output_domain","page":"Using datasets","title":"SpectralFitting.make_output_domain","text":"make_output_domain(layout::AbstractLayout, dataset::AbstractDataset)\n\nReturns the array used as the output domain. That is, in cases where the model input and output map to different domains, the input domain is said to be the model domain, the input domain is said to be the model domain. \n\nThe distinction is mainly used for the purposes of simulating data and for visualising data.\n\n\n\n\n\n","category":"function"},{"location":"datasets/datasets/#Underlying-data-layouts","page":"Using datasets","title":"Underlying data layouts","text":"","category":"section"},{"location":"datasets/datasets/","page":"Using datasets","title":"Using datasets","text":"AbstractLayout\nOneToOne\nContiguouslyBinned\ncommon_support\npreferred_support\nsupports","category":"page"},{"location":"datasets/datasets/#SpectralFitting.AbstractLayout","page":"Using datasets","title":"SpectralFitting.AbstractLayout","text":"abstract type AbstractLayout end\n\nThe data layout primarily concerns the relationship between the objective and the domain. It is used to work out whether a model and a dataset are fittable, and if not, whether a translation in the output of the model to the domain of the model is possible.\n\nThe following methods may be used to interrogate support:\n\npreferred_support for inferring the preferred support of a model when multiple supports are possible.\ncommon_support to obtain the common support of two structures\n\nThe following method is also used to define the support of a model or dataset:\n\nsupports\n\nFor cases where unit information needs to be propagated, an AbstractLayout can also be used to ensure the units are compatible. To query the units of a layout, use\n\nsupport_units\n\n\n\n\n\n","category":"type"},{"location":"datasets/datasets/#SpectralFitting.OneToOne","page":"Using datasets","title":"SpectralFitting.OneToOne","text":"struct OneToOne <: AbstractLayout end\n\nIndicates there is a one-to-one (injective) correspondence between each input value and each output value. That is to say\n\nlength(objective) == length(domain)\n\n\n\n\n\n","category":"type"},{"location":"datasets/datasets/#SpectralFitting.ContiguouslyBinned","page":"Using datasets","title":"SpectralFitting.ContiguouslyBinned","text":"struct ContiguouslyBinned <: AbstractLayout end\n\nContiguously binned data layout means that the domain describes high and low bins, with the objective being the value in that bin. This means\n\nlength(objective) + 1== length(domain)\n\nNote that the contiguous qualifer is to mean there is no gaps in the bins, and that\n\nDelta E_i = E_i+1 - E_i\n\n\n\n\n\n","category":"type"},{"location":"datasets/datasets/#SpectralFitting.common_support","page":"Using datasets","title":"SpectralFitting.common_support","text":"common_support(x, y)\n\nFind the common AbstractLayout of x and y, following the ordering of preferred_support.\n\n\n\n\n\n","category":"function"},{"location":"datasets/datasets/#SpectralFitting.preferred_support","page":"Using datasets","title":"SpectralFitting.preferred_support","text":"preferred_support(x)\n\nGet the preferred AbstractLayout of x. If multiple supports are available, the DEFAULT_SUPPORT_ORDERING is followed:\n\nDEFAULT_SUPPORT_ORDERING = (ContiguouslyBinned{Nothing}(nothing), OneToOne{Nothing}(nothing))\n\n\n\n\n\n","category":"function"},{"location":"datasets/datasets/#SpectralFitting.supports","page":"Using datasets","title":"SpectralFitting.supports","text":"supports(x::Type)\n\nUsed to define whether a given type has support for a specific AbstractLayout. Should return a tuple of the supported layouts. This method should be implemented to express new support, not the query method. \n\nTo query, there is\n\nsupports(layout::AbstractLayout, x)::Bool\n\nExample\n\nsupports(::Type{typeof(x)}) = (OneToOne(),)\n@assert supports(ContiguouslyBinned(), x) == false\n\n\n\n\n\n","category":"function"},{"location":"why-and-how/#Why-and-how","page":"Why & How","title":"Why & how","text":"","category":"section"},{"location":"why-and-how/","page":"Why & How","title":"Why & How","text":"SpectralFitting.jl is a package for fitting models to spectral data, similar to XSPEC, Sherpa or ISIS.","category":"page"},{"location":"why-and-how/","page":"Why & How","title":"Why & How","text":"The rationale for this package is to provide a unanimous interface for different model libraries, and to leverage advancements in the computional methods that are available in Julia, including the rich statistics ecosystem, with automatic-differentiation and speed.","category":"page"},{"location":"why-and-how/","page":"Why & How","title":"Why & How","text":"Longer term ambitions include","category":"page"},{"location":"why-and-how/","page":"Why & How","title":"Why & How","text":"Multi-wavelength fits\nRadiative transfer embedded into the package\nSpectral and timing fits","category":"page"},{"location":"why-and-how/","page":"Why & How","title":"Why & How","text":"SpectralFitting aims to provide highly optimised and flexible fitting algorithms, along with a library of spectral models, for use in any field of Astronomy that concerns itself with spectral data.","category":"page"},{"location":"why-and-how/#Rewriting-model-calls-during-invocation","page":"Why & How","title":"Rewriting model calls during invocation","text":"","category":"section"},{"location":"why-and-how/","page":"Why & How","title":"Why & How","text":"using SpectralFitting","category":"page"},{"location":"why-and-how/","page":"Why & How","title":"Why & How","text":"SpectralFitting.jl tries to optimise model invocation through source-rewriting. For compatibility with the XSPEC model library, this is achieved through aggressive pre-allocation and shuffling of output vectors. All XSPEC models output the result of their calculation through side effects into a flux array passed as an argument, and therefore each model invocation requires its own output before addition or multiplication of fluxes may occur.","category":"page"},{"location":"why-and-how/","page":"Why & How","title":"Why & How","text":"Principally, combining several models together would look like this:","category":"page"},{"location":"why-and-how/","page":"Why & How","title":"Why & How","text":"energy = collect(range(0.1, 20.0, 100))\n\nflux1 = invokemodel(energy, XS_PowerLaw())\nflux2 = invokemodel(energy, XS_PowerLaw(a=FitParam(3.0)))\nflux3 = invokemodel(energy, XS_BlackBody())\nflux4 = invokemodel(energy, XS_PhotoelectricAbsorption())\n\ntotal_flux = @. flux4 * (flux1 + flux2 + flux3)\nsum(total_flux)","category":"page"},{"location":"why-and-how/","page":"Why & How","title":"Why & How","text":"But these operations could also be performed in a different order:","category":"page"},{"location":"why-and-how/","page":"Why & How","title":"Why & How","text":"flux1 = invokemodel(energy, XS_PowerLaw())\nflux2 = invokemodel(energy, XS_PowerLaw(a=FitParam(3.0)))\ntotal_flux = @. flux1 + flux2\n\nflux3 = invokemodel(energy, XS_BlackBody())\n@. total_flux = total_flux + flux3\n\nflux4 = invokemodel(energy, XS_PhotoelectricAbsorption())\n@. total_flux = total_flux * flux4\nsum(total_flux)","category":"page"},{"location":"why-and-how/","page":"Why & How","title":"Why & How","text":"Doing so would allow us to only pre-allocate 2 flux arrays, instead of 4 when using the in-place variants:","category":"page"},{"location":"why-and-how/","page":"Why & How","title":"Why & How","text":"fluxes = zeros(Float64, (length(energy) - 1, 2))\nflux1, flux2 = eachcol(fluxes)\n\ninvokemodel!(flux1, energy, XS_PowerLaw())\ninvokemodel!(flux2, energy, XS_PowerLaw(a=FitParam(3.0)))\n@. flux1 = flux1 + flux2\n\ninvokemodel!(flux2, energy, XS_BlackBody())\n@. flux1 = flux1 + flux2\n\ninvokemodel!(flux2, energy, XS_PhotoelectricAbsorption())\n@. flux1 = flux1 * flux2\nsum(flux1)","category":"page"},{"location":"why-and-how/","page":"Why & How","title":"Why & How","text":"It is precisely this re-writing that SpectralFitting performs via @generated functions. We can inspect the code used to generate the invocation body after defining a CompositeModel:","category":"page"},{"location":"why-and-how/","page":"Why & How","title":"Why & How","text":"model = XS_PhotoelectricAbsorption() * (\n XS_PowerLaw() + XS_PowerLaw(a=FitParam(3.0)) + XS_BlackBody()\n)\n\nparams = get_value.(SpectralFitting.parameter_tuple(model))\nSpectralFitting.Reflection.assemble_composite_model_call(typeof(model), typeof(params))","category":"page"},{"location":"why-and-how/","page":"Why & How","title":"Why & How","text":"This generated function also takes care of some other things for us, such as unpacking parameters (optionally unpacking frozen parameters separately), and ensuring any closure are passed to invokemodel if a model needs them (e.g., SurrogateSpectralModel).","category":"page"},{"location":"why-and-how/","page":"Why & How","title":"Why & How","text":"This is achieved by moving as much information as possible about the model and its construction to its type, such that all of the invocation and parameter unpacking may be inferred at compile time.","category":"page"},{"location":"why-and-how/","page":"Why & How","title":"Why & How","text":"note: Note\nWith the addition of more pure-Julia models, non-allocating methods without aggressive pre-allocation are possible, and will be added in the future. Such methods may allow models to add or multiply in-place on the total flux array, instead of relying on later broadcasts.","category":"page"},{"location":"fitting/#Fitting-spectral-models","page":"Fitting spectral models","title":"Fitting spectral models","text":"","category":"section"},{"location":"reference/#API-reference","page":"Reference","title":"API reference","text":"","category":"section"},{"location":"reference/#Utility-functions-for-defining-new-models","page":"Reference","title":"Utility functions for defining new models","text":"","category":"section"},{"location":"reference/","page":"Reference","title":"Reference","text":"SpectralFitting.finite_diff_kernel!\nwrap_model_as_objective","category":"page"},{"location":"reference/#SpectralFitting.finite_diff_kernel!","page":"Reference","title":"SpectralFitting.finite_diff_kernel!","text":"finite_diff_kernel!(f::Function, flux, energy)\n\nCalculates the finite difference of the function f over the energy bin between the high and low bin edges, via\n\nc_i = f(E_itexthigh) - f(E_itextlow)\n\nsimilar to evaluating the limits of the integral between E_itexthigh and E_itextlow.\n\nThis utility function is primarily used for Additive models to ensure the flux per bin is normalised for the energy over the bin.\n\n\n\n\n\n","category":"function"},{"location":"reference/#SpectralFitting.wrap_model_as_objective","page":"Reference","title":"SpectralFitting.wrap_model_as_objective","text":"wrap_model_as_objective(model::AbstractSpectralModel; ΔE = 1e-1)\nwrap_model_as_objective(M::Type{<:AbstractSpectralModel}; ΔE = 1e-1)\n\nWrap a spectral model into an objective function for building/optimizing a surrogate model. Returns an anonymous function taking the tuple (E, params...) as the argument, and returning a single flux value.\n\n\n\n\n\n","category":"function"},{"location":"reference/#General-reference","page":"Reference","title":"General reference","text":"","category":"section"},{"location":"reference/","page":"Reference","title":"Reference","text":"Modules = [SpectralFitting, SpectralFitting.Reflection]\nOrder = [:function, :type]","category":"page"},{"location":"reference/#SpectralFitting._accumulated_indices-Tuple{Any}","page":"Reference","title":"SpectralFitting._accumulated_indices","text":"_accumulated_indices(items)\n\nitems is a tuple or vector of lengths n1, n2, ...\n\nReturns a tuple or array with same length as items, which gives the index boundaries of an array with size n1 + n2 + ....\n\n\n\n\n\n","category":"method"},{"location":"reference/#SpectralFitting._bind_pairs!-Tuple{FittingProblem, Vararg{Pair{Int64, Symbol}}}","page":"Reference","title":"SpectralFitting._bind_pairs!","text":"Bind the symbols of last(pair) in all models indexes by first(pair).\n\n\n\n\n\n","category":"method"},{"location":"reference/#SpectralFitting._safe_ffi_invoke!-Tuple{Any, Any, Any, Type{<:AbstractSpectralModel}}","page":"Reference","title":"SpectralFitting._safe_ffi_invoke!","text":"function _safe_ffi_invoke!(output, input, params, ModelType::Type{<:AbstractSpectralModel})\n\nWrapper to do a foreign function call to an XSPEC model.\n\n\n\n\n\n","category":"method"},{"location":"reference/#SpectralFitting._subtract_background!-NTuple{9, Any}","page":"Reference","title":"SpectralFitting._subtract_background!","text":"Does the background subtraction and returns units of counts. That means we have multiplied through by a factor t_D relative to the reference equation (2.3) in the XSPEC manual.\n\n\n\n\n\n","category":"method"},{"location":"reference/#SpectralFitting._unsafe_ffi_invoke!-Tuple{Any, Any, Any, Any, Type{<:AbstractSpectralModel}}","page":"Reference","title":"SpectralFitting._unsafe_ffi_invoke!","text":"function _unsafe_ffi_invoke!(\n output,\n error_vec,\n input,\n params,\n ModelType::Type{<:AbstractSpectralModel},\n)\n\nWrapper to do a foreign function call to an XSPEC model.\n\nSee also _safe_ffi_invoke!.\n\n\n\n\n\n","category":"method"},{"location":"reference/#SpectralFitting.allocate_model_output-Union{Tuple{T2}, Tuple{T1}, Tuple{AbstractSpectralModel{T1}, AbstractVector{T2}}} where {T1, T2}","page":"Reference","title":"SpectralFitting.allocate_model_output","text":"allocate_model_output(model::AbstractSpectralModel, domain::AbstractVector)\n\nAllocate the output space for the AbstractSpectralModel for a given domain. The output type will be the promoted element type of the model and domain.\n\nUses construct_objective_cache to construct the appropriate layout.\n\n\n\n\n\n","category":"method"},{"location":"reference/#SpectralFitting.common_support-Tuple{Any, Any}","page":"Reference","title":"SpectralFitting.common_support","text":"common_support(x, y)\n\nFind the common AbstractLayout of x and y, following the ordering of preferred_support.\n\n\n\n\n\n","category":"method"},{"location":"reference/#SpectralFitting.count_error-Tuple{Any, Any}","page":"Reference","title":"SpectralFitting.count_error","text":"count_error(k, σ)\n\nGives the error on k mean photon counts to a significance of σ standard deviations about the mean.\n\nDerived from likelihood of binomial distributions being the beta function.\n\n\n\n\n\n","category":"method"},{"location":"reference/#SpectralFitting.download_all_model_data-Tuple{}","page":"Reference","title":"SpectralFitting.download_all_model_data","text":"SpectralFitting.download_all_model_data()\n\nDownloads all model data for the models currently registered with SpectralFitting.register_model_data. Calls SpectralFitting.download_model_data to perform the download.\n\n\n\n\n\n","category":"method"},{"location":"reference/#SpectralFitting.download_model_data-Tuple{Type{<:AbstractSpectralModel}}","page":"Reference","title":"SpectralFitting.download_model_data","text":"SpectralFitting.download_model_data(model::AbstractSpectralModel; kwargs...)\nSpectralFitting.download_model_data(M::Type{<:AbstractSpectralModel}; kwargs...)\nSpectralFitting.download_model_data(s::Symbol; kwargs...)\n\nDownloads the model data for a model specified either by model, type M, or symbol s. Datafiles associated with a specific model may be registered using SpectralFitting.register_model_data. The download is currently unconfigurable, but permits slight control via a number of keyword arguments:\n\nprogress::Bool = true\n\nDisplay a progress bar for the download.\n\nmodel_source_url::String = \"http://www.star.bris.ac.uk/fbaker/XSPEC-model-data\"\n\nThe source URL used to download the model data.\n\nAll standard XSPEC spectral model data is currently being hosted on the University of Bristol astrophysics servers, and should be persistently available to anyone.\n\n\n\n\n\n","category":"method"},{"location":"reference/#SpectralFitting.error_statistic-Tuple{AbstractDataset}","page":"Reference","title":"SpectralFitting.error_statistic","text":"error_statistic(::AbstractDataset)\n\nShould return an ErrorStatistics describing which error statistic this data uses.\n\nIf undefined for a derived type, returns ErrorStatistics.Unknown.\n\n\n\n\n\n","category":"method"},{"location":"reference/#SpectralFitting.finite_diff_kernel!-Tuple{Function, Any, Any}","page":"Reference","title":"SpectralFitting.finite_diff_kernel!","text":"finite_diff_kernel!(f::Function, flux, energy)\n\nCalculates the finite difference of the function f over the energy bin between the high and low bin edges, via\n\nc_i = f(E_itexthigh) - f(E_itextlow)\n\nsimilar to evaluating the limits of the integral between E_itexthigh and E_itextlow.\n\nThis utility function is primarily used for Additive models to ensure the flux per bin is normalised for the energy over the bin.\n\n\n\n\n\n","category":"method"},{"location":"reference/#SpectralFitting.folded_energy-Union{Tuple{ResponseMatrix{T}}, Tuple{T}} where T","page":"Reference","title":"SpectralFitting.folded_energy","text":"folded_energy(response::ResponseMatrix)\n\nGet the contiguously binned energy corresponding to the output (folded) domain of the response matrix. That is, the channel energies as used by the spectrum.\n\n\n\n\n\n","category":"method"},{"location":"reference/#SpectralFitting.get_tickslogscale-Union{Tuple{Tuple{T, T}}, Tuple{T}} where T<:AbstractFloat","page":"Reference","title":"SpectralFitting.get_tickslogscale","text":"get_tickslogscale(lims; skiplog=false)\n\nReturn a tuple (ticks, ticklabels) for the axis limit lims where multiples of 10 are major ticks with label and minor ticks have no label skiplog argument should be set to true if lims is already in log scale.\n\nModified from https://github.com/JuliaPlots/Plots.jl/issues/3318.\n\n\n\n\n\n","category":"method"},{"location":"reference/#SpectralFitting.implementation-Tuple{Type{<:AbstractSpectralModel}}","page":"Reference","title":"SpectralFitting.implementation","text":"implementation(model::AbstractSpectralModel)\nimplementation(::Type{<:AbstractSpectralModel})\n\nGet the AbstractSpectralModelImplementation for a given AbstractSpectralModel or model type.\n\nThis is used primarily to learn what optimizations we can do with a model, for example propagating auto-diff gradients through a model or arbitrary precision numbers.\n\n\n\n\n\n","category":"method"},{"location":"reference/#SpectralFitting.invoke!-Tuple{Any, Any, AbstractSpectralModel}","page":"Reference","title":"SpectralFitting.invoke!","text":"SpectralFitting.invoke!(output, domain, M::Type{<:AbstractSpectralModel}, params...)\n\nUsed to define the behaviour of models. Should calculate the output of the model and write in-place into output. The model parameters are passed in the model structure.\n\nwarning: Warning\nThis function should not be called directly. Use invokemodel instead. invoke! is only to define the model, not to use it. Users should always call models using invokemodel or invokemodel! to ensure normalisations and closures are accounted for.\n\nExample\n\nBase.@kwdef struct MyModel{T} <: AbstractSpectralModel{T,Multiplicative}\n p1::T = FitParam(1.0)\n p2::T = FitParam(2.0)\n p3::T = FitParam(3.0)\nend\n\nwould have the arguments passed to invoke! as\n\nfunction SpectralFitting.invoke!(output, domain, model::MyModel)\n # ...\nend\n\n\n\n\n\n","category":"method"},{"location":"reference/#SpectralFitting.invokemodel!-Tuple{Any, Any, AbstractSpectralModel{<:FitParam}}","page":"Reference","title":"SpectralFitting.invokemodel!","text":"invokemodel!(output, domain, model)\ninvokemodel!(output, domain, model, params::AbstractVector)\ninvokemodel!(output, domain, model, params::ParameterCache)\n\nIn-place variant of invokemodel, calculating the output of an AbstractSpectralModel given by model, optionally overriding the parameters using a ParameterCache or an AbstractVector.\n\nThe output may not necessarily be a single vector, and one should use allocate_model_output to allocate the output structure.\n\nExample\n\nmodel = PowerLaw()\ndomain = collect(range(0.1, 20.0, 100))\noutput = allocate_model_output(model, domain)\ninvokemodel!(output, domain, model)\n\n\n\n\n\n","category":"method"},{"location":"reference/#SpectralFitting.invokemodel-Tuple{Any, AbstractSpectralModel}","page":"Reference","title":"SpectralFitting.invokemodel","text":"invokemodel(domain, model::AbstractSpectralModel)\n\nInvoke the AbstractSpectralModel given by model over the domain domain.\n\nThis function will perform any normalisation or post-processing tasks that a specific model kind may require, e.g. multiplying by a normalisation constant for Additive models.\n\nnote: Note\n\n\ninvokemodel allocates the needed output arrays based on the element type of free_params to allow automatic differentation libraries to calculate parameter gradients.\n\nIn-place non-allocating variants are the invokemodel! functions.\n\nExample\n\nmodel = PowerLaw()\ndomain = collect(range(0.1, 20.0, 100))\n\ninvokemodel(domain, model)\n\n\n\n\n\n","category":"method"},{"location":"reference/#SpectralFitting.make_domain_variance-Tuple{AbstractLayout, AbstractDataset}","page":"Reference","title":"SpectralFitting.make_domain_variance","text":"make_domain_variance(layout::AbstractLayout, dataset::AbstractDataset)\n\nMake the variance vector associated with the domain.\n\n\n\n\n\n","category":"method"},{"location":"reference/#SpectralFitting.make_label-Tuple{AbstractDataset}","page":"Reference","title":"SpectralFitting.make_label","text":"make_label(d::AbstractDataset)\n\nReturn a string that gives a descriptive label for this dataset.\n\n\n\n\n\n","category":"method"},{"location":"reference/#SpectralFitting.make_model_domain-Tuple{AbstractLayout, AbstractDataset}","page":"Reference","title":"SpectralFitting.make_model_domain","text":"make_model_domain(layout::AbstractLayout, dataset::AbstractDataset)\n\nReturns the array used as the domain for the modelling. This is paired with make_domain_variance\n\n\n\n\n\n","category":"method"},{"location":"reference/#SpectralFitting.make_objective-Tuple{AbstractLayout, AbstractDataset}","page":"Reference","title":"SpectralFitting.make_objective","text":"make_objective(layout::AbstractLayout, dataset::AbstractDataset)\n\nReturns the array used as the target for model fitting. The array must correspond to the data AbstractLayout specified by the layout parameter.\n\nIn as far as it can be guarunteed, the memory in the returned array will not be mutated by any fitting procedures.\n\nDomain for this objective should be returned by make_model_domain.\n\n\n\n\n\n","category":"method"},{"location":"reference/#SpectralFitting.make_objective_variance-Tuple{AbstractLayout, AbstractDataset}","page":"Reference","title":"SpectralFitting.make_objective_variance","text":"make_objective_variance(layout::AbstractLayout, dataset::AbstractDataset)\n\nMake the variance vector associated with each objective point.\n\n\n\n\n\n","category":"method"},{"location":"reference/#SpectralFitting.make_output_domain-Tuple{AbstractLayout, AbstractDataset}","page":"Reference","title":"SpectralFitting.make_output_domain","text":"make_output_domain(layout::AbstractLayout, dataset::AbstractDataset)\n\nReturns the array used as the output domain. That is, in cases where the model input and output map to different domains, the input domain is said to be the model domain, the input domain is said to be the model domain. \n\nThe distinction is mainly used for the purposes of simulating data and for visualising data.\n\n\n\n\n\n","category":"method"},{"location":"reference/#SpectralFitting.make_surrogate_harness-Union{Tuple{T}, Tuple{M}, Tuple{Function, Any, M, T, T}} where {M<:AbstractSpectralModel, T<:(Tuple{Vararg{T, N}} where {N, T})}","page":"Reference","title":"SpectralFitting.make_surrogate_harness","text":"make_surrogate_harness(\n model::M,\n lowerbounds::T,\n upperbounds::T;\n optimization_samples = 200,\n seed_samples = 50,\n S::Type = RadialBasis,\n sample_type = SobolSample(),\n verbose = false,\n)\n\nCreates and optimizes a surrogate model of type S for model, using wrap_model_as_objective and optimize_accuracy! for optimization_samples iterations. Model is initially seeded with seed_samples points prior to optimization.\n\nwarning: Warning\nAdditive models integrate energies to calculate flux, which surrogate models are currently not capable of. Results for Additive models likely to be inaccurate. This will be patched in a future version.\n\n\n\n\n\n","category":"method"},{"location":"reference/#SpectralFitting.modelkind-Union{Tuple{Type{<:AbstractSpectralModel{T, K}}}, Tuple{K}, Tuple{T}} where {T, K}","page":"Reference","title":"SpectralFitting.modelkind","text":"modelkind(M::Type{<:AbstractSpectralModel})\nmodelkind(::AbstractSpectralModel)\n\nReturn the kind of model given by M: either Additive, Multiplicative, or Convolutional.\n\n\n\n\n\n","category":"method"},{"location":"reference/#SpectralFitting.numbertype-Union{Tuple{AbstractSpectralModel{T}}, Tuple{T}} where T<:Number","page":"Reference","title":"SpectralFitting.numbertype","text":"numbertype(::AbstractSpectralModel)\n\nGet the numerical type of the model. This goes through FitParam, so that the number type returned is as close to a primative as possible.\n\nSee also paramtype.\n\nExample\n\nnumbertype(PowerLaw()) == Float64\n\n\n\n\n\n","category":"method"},{"location":"reference/#SpectralFitting.objective_transformer-Tuple{AbstractLayout, AbstractDataset}","page":"Reference","title":"SpectralFitting.objective_transformer","text":"objective_transformer(layout::AbstractLayout, dataset::AbstractDataset)\n\nUsed to transform the output of the model onto the output domain. For spectral fitting, this is the method used to do response folding and bin masking.\n\nIf none provided, uses the _DEFAULT_TRANSFORMER\n\nfunction _DEFAULT_TRANSFORMER()\n function _transformer!!(domain, objective)\n objective\n end\n function _transformer!!(output, domain, objective)\n @. output = objective\n end\n _transformer!!\nend\n\n\n\n\n\n","category":"method"},{"location":"reference/#SpectralFitting.operation_symbol-Tuple{Type{<:AdditionOperator}}","page":"Reference","title":"SpectralFitting.operation_symbol","text":"operation_symbol(::AbstractCompositeOperator)\noperation_symbol(::Type{<:AbstractCompositeOperator})\n\nObtain the model symbol from a given AbstractCompositeOperator.\n\n\n\n\n\n","category":"method"},{"location":"reference/#SpectralFitting.optimize_accuracy!-Tuple{Surrogates.AbstractSurrogate, Function, Any, Any}","page":"Reference","title":"SpectralFitting.optimize_accuracy!","text":"optimize_accuracy!(\n surr::AbstractSurrogate,\n obj::Function,\n lb,\n ub;\n sample_type::SamplingAlgorithm = SobolSample(),\n maxiters = 200,\n N_truth = 5000,\n verbose = false,\n)\n\nImprove accuracy (faithfullness) of the surrogate model in recreating the objective function.\n\nSamples a new space of N_truth points between lb and ub, and calculates the objective function obj at each. Finds the point with largest MSE between surrogate and objective, and adds the point to the surrogate pool. Repeats maxiters times, adding maxiters points to surrogate model.\n\nOptionally print to stdout the MSE and iteration count with verbose = true.\n\nNote that upper- and lower-bounds should be in the form (E, params...), where E is a single energy and params are the model parameters.\n\n\n\n\n\n","category":"method"},{"location":"reference/#SpectralFitting.paramtype-Tuple{T} where T<:AbstractSpectralModel","page":"Reference","title":"SpectralFitting.paramtype","text":"paramtype(::AbstractSpectralModel)\n\nGet the parameter type of the model. This, unlike numbertype does not go through FitParam.\n\nExample\n\nparamtype(PowerLaw()) == FitParam{Float64}\n\n\n\n\n\n","category":"method"},{"location":"reference/#SpectralFitting.preferred_support-Tuple{Any}","page":"Reference","title":"SpectralFitting.preferred_support","text":"preferred_support(x)\n\nGet the preferred AbstractLayout of x. If multiple supports are available, the DEFAULT_SUPPORT_ORDERING is followed:\n\nDEFAULT_SUPPORT_ORDERING = (ContiguouslyBinned{Nothing}(nothing), OneToOne{Nothing}(nothing))\n\n\n\n\n\n","category":"method"},{"location":"reference/#SpectralFitting.preferred_units-Union{Tuple{T}, Tuple{T, SpectralFitting.AbstractStatistic}} where T<:AbstractDataset","page":"Reference","title":"SpectralFitting.preferred_units","text":"preferred_units(::Type{<:AbstractDataset}, s::AbstractStatistic)\npreferred_units(x, s::AbstractStatistic)\n\nGet the preferred units that a given dataset would use to fit the AbstractStatistic in. For example, for ChiSquared, the units of the model may be a rate, however for Cash the preferred units might be counts.\n\nReturning nothing from this function implies there is no unit preference.\n\nIf undefined for a derived type, returns nothing.\n\nSee also support_units.\n\n\n\n\n\n","category":"method"},{"location":"reference/#SpectralFitting.register_model_data-Tuple{Any, Vararg{String}}","page":"Reference","title":"SpectralFitting.register_model_data","text":"SpectralFitting.register_model_data(M::Type{<:AbstractSpectralModel}, model_data::ModelDataInfo...)\nSpectralFitting.register_model_data(M::Type{<:AbstractSpectralModel}, remote_and_local::Tuple{String,String}...)\nSpectralFitting.register_model_data(M::Type{<:AbstractSpectralModel}, filenames::String...)\nSpectralFitting.register_model_data(s::Symbol, filenames::String...)\n\nRegister filenames as model data associated with the model given by type M or symbol s. This function does not download any files, but rather adds the relevant filenames to a lookup which SpectralFitting.download_model_data consults when invoked, and consequently model data is only downloaded when needed.\n\nnote: Note\nIt is good practice to use this method immediately after defining a new model with @xspecmodel to register any required datafiles from the HEASoft source code, and therefore keep relevant information together.\n\nExample\n\n# by type\nregister_model_data(XS_Laor, \"ari.mod\")\n# by symbol\nregister_model_data(:XS_KyrLine, \"KBHline01.fits\")\n\n\n\n\n\n","category":"method"},{"location":"reference/#SpectralFitting.response_energy-Union{Tuple{ResponseMatrix{T}}, Tuple{T}} where T","page":"Reference","title":"SpectralFitting.response_energy","text":"response_energy(response::ResponseMatrix)\n\nGet the contiguously binned energy corresponding to the input domain of the response matrix. This is equivalent to the model domain.\n\n\n\n\n\n","category":"method"},{"location":"reference/#SpectralFitting.support_units-Tuple{T} where T<:AbstractLayout","page":"Reference","title":"SpectralFitting.support_units","text":"support_units(x)\n\nReturn the units of a particular layout. If this method returns nothing, assume the layout does not care about the units and handle that information appropriately (throw an error or set defaults).\n\n\n\n\n\n","category":"method"},{"location":"reference/#SpectralFitting.supports-Tuple{AbstractLayout, Any}","page":"Reference","title":"SpectralFitting.supports","text":"supports(x::Type)\n\nUsed to define whether a given type has support for a specific AbstractLayout. Should return a tuple of the supported layouts. This method should be implemented to express new support, not the query method. \n\nTo query, there is\n\nsupports(layout::AbstractLayout, x)::Bool\n\nExample\n\nsupports(::Type{typeof(x)}) = (OneToOne(),)\n@assert supports(ContiguouslyBinned(), x) == false\n\n\n\n\n\n","category":"method"},{"location":"reference/#SpectralFitting.with_units-Tuple{AbstractLayout, Any}","page":"Reference","title":"SpectralFitting.with_units","text":"with_units(::AbstractLayout, units)\n\nRemake the AbstractLayout with the desired units. This may be a no-op if the layout does not care about units, see support_units.\n\n\n\n\n\n","category":"method"},{"location":"reference/#SpectralFitting.wrap_model_as_objective-Tuple{AbstractSpectralModel, Any}","page":"Reference","title":"SpectralFitting.wrap_model_as_objective","text":"wrap_model_as_objective(model::AbstractSpectralModel; ΔE = 1e-1)\nwrap_model_as_objective(M::Type{<:AbstractSpectralModel}; ΔE = 1e-1)\n\nWrap a spectral model into an objective function for building/optimizing a surrogate model. Returns an anonymous function taking the tuple (E, params...) as the argument, and returning a single flux value.\n\n\n\n\n\n","category":"method"},{"location":"reference/#SpectralFitting.AbstractModelWrapper","page":"Reference","title":"SpectralFitting.AbstractModelWrapper","text":"abstract type AbstractModelWrapper{M,T,K} <: AbstractSpectralModel{T,K} end\n\nFirst field of the struct must be model.\n\n\n\n\n\n","category":"type"},{"location":"reference/#SpectralFitting.AbstractMultiDataset","page":"Reference","title":"SpectralFitting.AbstractMultiDataset","text":"Must support the same API, but may also have some query methods for specific internals.\n\n\n\n\n\n","category":"type"},{"location":"reference/#SpectralFitting.AbstractSpectralModelImplementation","page":"Reference","title":"SpectralFitting.AbstractSpectralModelImplementation","text":"abstract type AbstractSpectralModelImplementation end\n\nDetails about the implementation are represented by this abstract type, used in the trait pattern. Concrete types are\n\nJuliaImplementation(): means the model is implemented in the Julia programming language.\nXSPECImplementation(): means the model is vendored from the XSPEC model library.\n\n\n\n\n\n","category":"type"},{"location":"reference/#SpectralFitting.AbstractTableModel","page":"Reference","title":"SpectralFitting.AbstractTableModel","text":"abstract type AbstractTableModel{T,K} <: AbstractSpectralModel{T,K} end\n\nAbstract type representing table models, i.e. those models that interpolate or load data from a table. \n\nFirst field in the struct must be table. See PhotoelectricAbsorption for an example implementation.\n\n\n\n\n\n","category":"type"},{"location":"reference/#SpectralFitting.XS_CutOffPowerLaw","page":"Reference","title":"SpectralFitting.XS_CutOffPowerLaw","text":"XS_CutOffPowerLaw(K, Γ, Ecut, z)\n\nK: Normalisation.\nΓ: Photon index.\nEcut: Cut-off energy (keV).\nz: Redshift.\n\nExample\n\nusing SpectralFitting\nusing UnicodePlots\nenergy = 10 .^collect(range(-1.0, 2.0, 100))\nm = invokemodel(energy, XS_CutOffPowerLaw())\nlineplot(energy[1:end-1],m,xscale=:log10,yscale=:log10,xlim=(1e-1,1e2),ylim=(1e-6,1e0),xlabel=\"Energy (keV)\",ylabel=\"Flux\",title=\"XS_CutOffPowerLaw\",canvas=DotCanvas)\n\n XS_CutOffPowerLaw \n ┌────────────────────────────────────────┐ \n10⁰ │:.. │ \n │ '':... │ \n │ '':.. │ \n │ '''.. │ \n │ '':.. │ \n │ '':. │ \n │ ':. │ \nFlux │ '.. │ \n │ ':. │ \n │ '. │ \n │ : │ \n │ :. │ \n │ : │ \n │ : │ \n10⁻⁶ │ :│ \n └────────────────────────────────────────┘ \n 10⁻¹ 10² \n Energy (keV)\n\n\n\n\n\n","category":"type"},{"location":"reference/#SpectralFitting.XS_Jet","page":"Reference","title":"SpectralFitting.XS_Jet","text":"XS_Jet(K, mass, Dco, log_mdot, thetaobs, BulkG, phi, zdiss, B, logPrel, gmin_inj, gbreak, gmax, s1, s2, z)\n\nK: MUST BE FIXED AT UNITY as the jet spectrum normalisation is set by the relativisitic particle power.\nmass: Black hole mass in solar masses.\nDco: Comoving (proper) distance in Mpc.\nlog_mdot: log(L/L_Edd.\nthetaobs: Inclination angle (deg).\nBulkG: Bulk lorentz factor of the jet.\nphi: Angular size scale (radians) of the jet acceleration region as seen from the black hole.\nzdiss: Vertical distance from the black hole of the jet dissipation region (r_g).\nB: Magnetic field in the jet (Gauss).\nlogPrel: Log of the power injected in relativisitic particles (ergs/s).\ngmin_inj: Minimum lorentz factor of the injected electrons.\ngbreak: Lorentz factor of the break in injected electron distribution.\ngmax: Maximum lorentz factor.\ns1: Injected index of the electron distribution below the break.\ns2: Injected index of the electron distribution above the break.\nz: Cosmological redshift corresponding to the comoving distance used above.\n\nExample\n\nusing UnicodePlots\nenergy = 10 .^collect(range(-8.0, 8.0, 100))\nm = invokemodel(energy, XS_Jet())\nlineplot(energy[1:end-1],m,xscale=:log10,yscale=:log10,xlim=(1e-8,1e8),ylim=(1e-8,1e8),xlabel=\"Energy (keV)\",ylabel=\"Flux\",title=\"XS_Jet\",canvas=DotCanvas)\n\n XS_Jet \n ┌────────────────────────────────────────┐ \n10⁸ │ │ \n │ │ \n │ │ \n │ │ \n │ :':.. │ \n │ .' ''. │ \n │: ':. │ \nFlux │' ': │ \n │ '. │ \n │ : │ \n │ ''..... │ \n │ '''''.. │ \n │ ':.. │ \n │ ':. │ \n10⁻⁸ │ ''. │ \n └────────────────────────────────────────┘ \n 10⁻⁸ 10⁸ \n Energy (keV) \n\n\n\n\n\n","category":"type"},{"location":"reference/#SpectralFitting.Reflection._add_composite_info!-Tuple{SpectralFitting.Reflection.CompositeAggregation, Type{<:AbstractSpectralModel}, Union{Expr, Symbol}, Type}","page":"Reference","title":"SpectralFitting.Reflection._add_composite_info!","text":"Used exclusively to do recursive CompositeModel parsing.\n\n\n\n\n\n","category":"method"},{"location":"reference/#SpectralFitting.Reflection.add_objective_reduction!-Tuple{SpectralFitting.Reflection.CompositeAggregation, Symbol}","page":"Reference","title":"SpectralFitting.Reflection.add_objective_reduction!","text":"add_objective_reduction!(ra::CompositeAggregation, op::Symbol)\n\nReduces the current objective count and applies the reduction operation op to them. For example, if op is :+, and the objective count is 3, then after this function has been called the objective count will be 2 and the reduction expression\n\n@. flux2 = flux2 + flux3\n\nwill have been added to the CompositeAggregation.\n\n\n\n\n\n","category":"method"},{"location":"reference/#SpectralFitting.Reflection.assemble_composite_model_call-Tuple{Type{<:CompositeModel}, Any}","page":"Reference","title":"SpectralFitting.Reflection.assemble_composite_model_call","text":"assemble_composite_model_call(model::Type{<:CompositeModel}, parameters::Type{<:AbstractVector})\n\nAssemble the full composite model call, with objective unpacking via assemble_objective_unpack, closure and parameter assignments, model invocation, and objective reduction. Uses assemble_fast_call to put the final function body together.\n\n\n\n\n\n","category":"method"},{"location":"reference/#SpectralFitting.Reflection.assemble_objective_unpack-Tuple{Any}","page":"Reference","title":"SpectralFitting.Reflection.assemble_objective_unpack","text":"assemble_objective_unpack(N)\n\nAssembles the statements for unpacking the objective cache into a number of views. Assembles the part of the model call that looks like:\n\nobjective1 = view(objectives, :, 1), objective2 = ...\n\nfor N objectives slices.\n\n\n\n\n\n","category":"method"},{"location":"reference/#SpectralFitting.Reflection.get_info-Tuple{Type{<:AbstractSpectralModel}, Union{Expr, Symbol}}","page":"Reference","title":"SpectralFitting.Reflection.get_info","text":"get_info(model::Type{<:AbstractSpectralModel}, lens::Lens)\n\nReturns a ModelInfo struct for a given model.\n\n\n\n\n\n","category":"method"},{"location":"reference/#SpectralFitting.Reflection.make_constructor-Tuple{Type{<:AbstractSpectralModel}, Vector, Vector, Type}","page":"Reference","title":"SpectralFitting.Reflection.make_constructor","text":"make_constructor(model::Type{<:AbstractSpectralModel}, closures::Vector, params::Vector, T::Type)\n\nCreate a constructor expression for the model. Should return something similar to\n\n:(PowerLaw{T}(arg1, arg2, arg3)))\n\nunpacking the closures and params vectors in the appropriate places.\n\n\n\n\n\n","category":"method"},{"location":"reference/#SpectralFitting.Reflection.parameter_lenses-Tuple{Type{<:AbstractSpectralModel}, SpectralFitting.Reflection.ModelInfo}","page":"Reference","title":"SpectralFitting.Reflection.parameter_lenses","text":"parameter_lenses(::Type{<:AbstractSpectralModel}, info::ModelInfo)\n\nReturn a vector of lenses Lens that refer to each of the models parameters. The lenses should be relative to model.lens.\n\n\n\n\n\n","category":"method"},{"location":"reference/#SpectralFitting.Reflection.set_objective_count!-Tuple{SpectralFitting.Reflection.CompositeAggregation, Int64}","page":"Reference","title":"SpectralFitting.Reflection.set_objective_count!","text":"set_objective_count!(a::CompositeAggregation, o::Int)\n\nSet the objective count to a specific value. Will bump the maximum objective count if the new value exceeds the current maximum.\n\n\n\n\n\n","category":"method"},{"location":"reference/#SpectralFitting.Reflection.CompositeModelInfo","page":"Reference","title":"SpectralFitting.Reflection.CompositeModelInfo","text":"struct CompositeModelInfo\n \"The parameter symbols of the model with the respective lens to the actual parameter.\"\n parameter_symbols::Vector{Pair{Symbol,Lens}}\n \"Each model assigned to a unique symbol.\"\n models::Vector{Pair{Symbol,ModelInfo}}\n \"The expression representing the folding operations of this composite model.\"\n model_expression::Expr\n \"Constructor and objective folding expressions, used in generating the invocation call.\"\n expressions::Vector{Expr}\n \"The maximum number of objective caches this model will need.\"\n maximum_objective_cache_count::Int\n \"How many objective caches are currently active.\"\n objective_cache_count::Int\nend\n\nThe composite equivalent of ModelInfo, augmented to track the model symbol (a1, m3, etc.), and the model parameters (K_1, a_3, etc.)\n\n\n\n\n\n","category":"type"},{"location":"reference/#SpectralFitting.Reflection.ModelInfo","page":"Reference","title":"SpectralFitting.Reflection.ModelInfo","text":"struct ModelInfo\n \"All parameter symbols for the model.\"\n symbols::Vector{Symbol}\n \"Unique symbols generated for the parameter assignment when buildin the function call.\"\n generated_symbols::Vector{Symbol}\n \"Additional closure parameters that need to be handled when invoking the model.\"\n closure_symbols::Vector{Symbol}\n \"Unique closure generated symbols.\"\n generated_closure_symbols::Vector{Symbol}\n \"The lens that takes you to this model from some parent.\"\n lens::Lens\n \"The model type itself.\"\n model::Type\nend\n\nAll models are parsed into a ModelInfo struct relative to their parent (in the case of composite models).\n\nThe symbols field contains all of the model parameter symbols as they are in the structure, not as they have been generated. Recall when the invocation expressions are generated, we create anonymous paramter names to avoid conflicts. These are the generated_symbols instead.\n\n\n\n\n\n","category":"type"},{"location":"walkthrough/#Walkthrough","page":"Walkthrough","title":"Walkthrough","text":"","category":"section"},{"location":"walkthrough/","page":"Walkthrough","title":"Walkthrough","text":"warning: Warning\nThis walk through has not been fleshed out with the relevant astrophysical content yet (for example, whether a fit is good, what the different parameters mean, etc.), and so assumes some familarity with spectral fitting in general.It is also not yet complete, nor a faithful illustration of everything SpectralFitting.jl can do. It serves to illustrate similarities and differences in syntax between SpectralFitting.jl and XSPEC.","category":"page"},{"location":"walkthrough/","page":"Walkthrough","title":"Walkthrough","text":"This example walkthrough is the SpectralFitting.jl equivalent of the Walk through XSPEC from the XSPEC manual. We will use the same dataset, available for download from this link to the data files.","category":"page"},{"location":"walkthrough/#Overview","page":"Walkthrough","title":"Overview","text":"","category":"section"},{"location":"walkthrough/","page":"Walkthrough","title":"Walkthrough","text":"The first thing we want to do is load our datasets. Unlike in XSPEC, we have no requirement of being in the same directory as the data, or even that all of the response, ancillary, and spectral files be in the same place. For simplicity, we'll assume they are:","category":"page"},{"location":"walkthrough/","page":"Walkthrough","title":"Walkthrough","text":"note: Note\nBe sure to set DATADIR pointing to the directory where you keep the walkthrough data.","category":"page"},{"location":"walkthrough/","page":"Walkthrough","title":"Walkthrough","text":"using SpectralFitting, Plots\n\nDATADIR = \"...\"\nDATADIR = length(get(ENV, \"CI\", \"\")) > 0 ? @__DIR__() * \"/../../ex-datadir\" : \"/home/lilith/Developer/jl/datasets/xspec/walkthrough\" # hide\nspec1_path = joinpath(DATADIR, \"s54405.pha\")\ndata = OGIPDataset(spec1_path) ","category":"page"},{"location":"walkthrough/","page":"Walkthrough","title":"Walkthrough","text":"This will print a little card about our data, which shows us what else SpectralFitting.jl loaded. We can see the Primary Spectrum, the Response, but that the Background and Ancillary response files are missing. That's to be expected, since we don't have those files in the dataset. ","category":"page"},{"location":"walkthrough/","page":"Walkthrough","title":"Walkthrough","text":"We can check what paths it used by looking at","category":"page"},{"location":"walkthrough/","page":"Walkthrough","title":"Walkthrough","text":"data.paths","category":"page"},{"location":"walkthrough/","page":"Walkthrough","title":"Walkthrough","text":"We can load and alter any part of a dataset as we do our fitting. For example, if you have multiple different ancillary files at hand, switching them between fits is a one-liner.","category":"page"},{"location":"walkthrough/","page":"Walkthrough","title":"Walkthrough","text":"To visualize our data, we can use some of the Plots.jl recipes included in SpectralFitting.jl:","category":"page"},{"location":"walkthrough/","page":"Walkthrough","title":"Walkthrough","text":"plot(data, xlims = (0.5, 70), xscale = :log10)","category":"page"},{"location":"walkthrough/","page":"Walkthrough","title":"Walkthrough","text":"Note that the units are currently not divided by the energy bin widths. We can either do that manually, or use the normalize! to convert whatever units the data is currently in to the defacto standard counts s⁻¹ keV⁻¹ for fitting. Whilst we're at it, we see in the model card that there are 40 bad quality bins still present in our data. We can drop those as well, and plot the data on log-log axes:","category":"page"},{"location":"walkthrough/","page":"Walkthrough","title":"Walkthrough","text":"normalize!(data)\ndrop_bad_channels!(data)\nplot(data, ylims = (0.001, 2.0), yscale = :log10, xscale = :log10)","category":"page"},{"location":"walkthrough/","page":"Walkthrough","title":"Walkthrough","text":"Note that when there are no negative axes, the scale defaults to log on the plot unless otherwise specified.","category":"page"},{"location":"walkthrough/","page":"Walkthrough","title":"Walkthrough","text":"Next we want to specify a model to fit to this data. Models that are prefixed with XS_ are models that are linked from the XSPEC model library, provided via LibXSPEC_jll. For a full list of the models, see Models library.","category":"page"},{"location":"walkthrough/","page":"Walkthrough","title":"Walkthrough","text":"warning: Warning\nIt is advised to use the Julia implemented models. This allows various calculations to benefit from automatic differentiation, efficient multi-threading, GPU offloading, and various other useful things, see Why & how.","category":"page"},{"location":"walkthrough/","page":"Walkthrough","title":"Walkthrough","text":"We will start by fitting a photoelectric absorption model that acts on a power law model:","category":"page"},{"location":"walkthrough/","page":"Walkthrough","title":"Walkthrough","text":"note: Note\nTo see information about a model, use the ? in the Julia REPL:julia> ?PowerLaw\nXS_PowerLaw(K, a)\n\n • K: Normalisation.\n\n • a: Photon index.\n\nExample\n≡≡≡≡≡≡≡\n...","category":"page"},{"location":"walkthrough/","page":"Walkthrough","title":"Walkthrough","text":"model = PhotoelectricAbsorption() * PowerLaw()","category":"page"},{"location":"walkthrough/","page":"Walkthrough","title":"Walkthrough","text":"If we want to specify paramters of our model at instantiation, we can do that with","category":"page"},{"location":"walkthrough/","page":"Walkthrough","title":"Walkthrough","text":"model = PhotoelectricAbsorption() * PowerLaw(a = FitParam(3.0))","category":"page"},{"location":"walkthrough/","page":"Walkthrough","title":"Walkthrough","text":"SpectralFitting.jl adopts the SciML problem-solver abstraction, so to fit a model to data we specify a FittingProblem:","category":"page"},{"location":"walkthrough/","page":"Walkthrough","title":"Walkthrough","text":"prob = FittingProblem(model => data)","category":"page"},{"location":"walkthrough/","page":"Walkthrough","title":"Walkthrough","text":"SpectralFitting.jl makes a huge wealth of optimizers availble from Optimizations.jl, and others from further afield. For consistency with XSPEC, we'll use here a delayed-gratification least-squares algorithm from LsqFit.jl:","category":"page"},{"location":"walkthrough/","page":"Walkthrough","title":"Walkthrough","text":"result = fit(prob, LevenbergMarquadt())","category":"page"},{"location":"walkthrough/","page":"Walkthrough","title":"Walkthrough","text":"Here we can see the parameter vector, the estimated error on each parameter, and the measure of the fit statistic (here chi squared). We can overplot our result on our data easily:","category":"page"},{"location":"walkthrough/","page":"Walkthrough","title":"Walkthrough","text":"plot(data, \n ylims = (0.001, 2.0), \n xscale = :log10, \n yscale = :log10\n)\nplot!(result)","category":"page"},{"location":"walkthrough/","page":"Walkthrough","title":"Walkthrough","text":"Our model does not account for the high energy range well. We can ignore that range for now, and select everything from 0 to 15 keV and refit:","category":"page"},{"location":"walkthrough/","page":"Walkthrough","title":"Walkthrough","text":"mask_energies!(data, 0, 15)\nresult = fit(prob, LevenbergMarquadt())","category":"page"},{"location":"walkthrough/","page":"Walkthrough","title":"Walkthrough","text":"plot(data, \n ylims = (0.001, 2.0), \n xscale = :log10, \n yscale = :log10\n)\nplot!(result, label = \"PowerLaw\")","category":"page"},{"location":"walkthrough/","page":"Walkthrough","title":"Walkthrough","text":"The result is not yet baked into our model, and represents just the outcome of the fit. To update the parameters and errors in the model, we can use update_model!","category":"page"},{"location":"walkthrough/","page":"Walkthrough","title":"Walkthrough","text":"update_model!(model, result)","category":"page"},{"location":"walkthrough/","page":"Walkthrough","title":"Walkthrough","text":"note: Note\nSince fitting and updating a model is often done in tandem, SpectralFitting.jl has both a fit and fit! method, the latter automatically updates the model parameters after fit.","category":"page"},{"location":"walkthrough/","page":"Walkthrough","title":"Walkthrough","text":"To estimate the goodness of our fit, we can mimic the goodness command from XSPEC. This will use the simulate function to simulate spectra for a dataset (here determined by the result), and fit the model to the simulated dataset. The fit statistic for each fit is then appended to an array, which we can use to plot a histogram:","category":"page"},{"location":"walkthrough/","page":"Walkthrough","title":"Walkthrough","text":"spread = goodness(result; N = 1000, seed = 42, exposure_time = data.data.spectrum.exposure_time)\nhistogram(spread, ylims = (0, 300), label = \"Simulated\")\nvline!([result.χ2], label = \"Best fit\")","category":"page"},{"location":"walkthrough/","page":"Walkthrough","title":"Walkthrough","text":"Note we have set the random number generator seed with seed = 42 to allow our results to be strictly reproduced.","category":"page"},{"location":"walkthrough/","page":"Walkthrough","title":"Walkthrough","text":"The goodness command will log the percent of simulations with a fit statistic better than the result, but we can equivalently calculate that ourselves:","category":"page"},{"location":"walkthrough/","page":"Walkthrough","title":"Walkthrough","text":"count(<(result.χ2), spread) * 100 / length(spread)","category":"page"},{"location":"walkthrough/","page":"Walkthrough","title":"Walkthrough","text":"Next we want to calculate the flux in an energy range observed by the detector. We can do this with LogFlux or XS_CalculateFlux, as they are both equivalent implementations.","category":"page"},{"location":"walkthrough/","page":"Walkthrough","title":"Walkthrough","text":"We can modify our model by accessing properties from the model card and writing a new expression:","category":"page"},{"location":"walkthrough/","page":"Walkthrough","title":"Walkthrough","text":"calc_flux = XS_CalculateFlux(\n E_min = FitParam(0.2, frozen = true), \n E_max = FitParam(2.0, frozen = true),\n log10Flux = FitParam(-10.3, lower_limit = -100, upper_limit = 100),\n)\n\nflux_model = model.m1 * calc_flux(model.a1)","category":"page"},{"location":"walkthrough/","page":"Walkthrough","title":"Walkthrough","text":"Since we used the old model to define the new one, our best fit values are automatically copied into the new model. We can now freeze the normalization, as we are using the flux integrating model to scale the powerlaw component:","category":"page"},{"location":"walkthrough/","page":"Walkthrough","title":"Walkthrough","text":"flux_model.a1.K.frozen = true\nflux_model","category":"page"},{"location":"walkthrough/","page":"Walkthrough","title":"Walkthrough","text":"Looking at the data card, we see the fit domain does not include the full region that we want to integrate the flux over. We therefore need to extend the fitting domain:","category":"page"},{"location":"walkthrough/","page":"Walkthrough","title":"Walkthrough","text":"flux_problem = FittingProblem(flux_model => data)\n# TODO: domain extensions not fully implemented yet","category":"page"},{"location":"walkthrough/","page":"Walkthrough","title":"Walkthrough","text":"Now to fit we can repeat the above procedure, and even overplot the region of flux we integrated:","category":"page"},{"location":"walkthrough/","page":"Walkthrough","title":"Walkthrough","text":"flux_result = fit(flux_problem, LevenbergMarquadt())\n\nplot(data, \n ylims = (0.001, 2.0), \n xscale = :log10, \n yscale = :log10\n)\nplot!(flux_result)\nvspan!([flux_model.c1.E_min.value, flux_model.c1.E_max.value], alpha = 0.5)","category":"page"},{"location":"walkthrough/","page":"Walkthrough","title":"Walkthrough","text":"Let's try alternative models to see how they fit the data. First, an absorbed black body:","category":"page"},{"location":"walkthrough/","page":"Walkthrough","title":"Walkthrough","text":"model2 = PhotoelectricAbsorption() * XS_BlackBody()","category":"page"},{"location":"walkthrough/","page":"Walkthrough","title":"Walkthrough","text":"We fit in the same way as before:","category":"page"},{"location":"walkthrough/","page":"Walkthrough","title":"Walkthrough","text":"prob2 = FittingProblem(model2 => data)\nresult2 = fit!(prob2, LevenbergMarquadt())","category":"page"},{"location":"walkthrough/","page":"Walkthrough","title":"Walkthrough","text":"Let's overplot this result against our power law result:","category":"page"},{"location":"walkthrough/","page":"Walkthrough","title":"Walkthrough","text":"dp = plot(data, \n ylims = (0.001, 2.0), \n xscale = :log10, \n yscale = :log10,\n legend = :bottomleft,\n)\nplot!(dp, result, label = \"PowerLaw $(round(result.χ2))\")\nplot!(dp, result2, label = \"BlackBody $(round(result2.χ2))\")","category":"page"},{"location":"walkthrough/","page":"Walkthrough","title":"Walkthrough","text":"Or a bremsstrahlung model:","category":"page"},{"location":"walkthrough/","page":"Walkthrough","title":"Walkthrough","text":"model3 = PhotoelectricAbsorption() * XS_BremsStrahlung()\nprob3 = FittingProblem(model3 => data)\nresult3 = fit(prob3, LevenbergMarquadt())","category":"page"},{"location":"walkthrough/","page":"Walkthrough","title":"Walkthrough","text":"plot!(dp, result3, label = \"Brems $(round(result3.χ2))\")","category":"page"},{"location":"walkthrough/","page":"Walkthrough","title":"Walkthrough","text":"Let's take a look at the residuals of these three models. There are utility methods for this in SpectralFitting.jl, but we can easily just interact with the result directly:","category":"page"},{"location":"walkthrough/","page":"Walkthrough","title":"Walkthrough","text":"function calc_residuals(result)\n # select which result we want (only have one, but for generalisation to multi-model fits)\n r = result[1] \n y = invoke_result(r)\n @. (r.objective - y) / sqrt(r.variance)\nend\n\ndomain = SpectralFitting.plotting_domain(data)\n\nrp = hline([0], linestyle = :dash, legend = false)\nplot!(rp,domain, calc_residuals(result), seriestype = :stepmid)\nplot!(rp, domain, calc_residuals(result2), seriestype = :stepmid)\nplot!(rp, domain, calc_residuals(result3), seriestype = :stepmid)\nrp","category":"page"},{"location":"walkthrough/","page":"Walkthrough","title":"Walkthrough","text":"We can compose this figure with our previous one, and change to a linear x scale:","category":"page"},{"location":"walkthrough/","page":"Walkthrough","title":"Walkthrough","text":"plot(dp, rp, layout = grid(2, 1, heights = [0.7, 0.3]), link = :x, xscale = :linear)","category":"page"},{"location":"walkthrough/","page":"Walkthrough","title":"Walkthrough","text":"We can do all that plotting work in one go with the plotresult recipe:","category":"page"},{"location":"walkthrough/","page":"Walkthrough","title":"Walkthrough","text":"plotresult(\n data,\n [result, result2, result3],\n ylims = (0.001, 2.0), \n xscale = :log10, \n yscale = :log10,\n legend = :bottomleft,\n)","category":"page"},{"location":"walkthrough/","page":"Walkthrough","title":"Walkthrough","text":"Let's modify the black body model with a continuum component","category":"page"},{"location":"walkthrough/","page":"Walkthrough","title":"Walkthrough","text":"bbpl_model = model2.m1 * (PowerLaw() + model2.a1) |> deepcopy","category":"page"},{"location":"walkthrough/","page":"Walkthrough","title":"Walkthrough","text":"note: Note\nWe pipe the model to deepcopy to create a copy of all the model parameters. Not doing this means the parameters in bbpl_model will be aliased to the parameters in model2, and changing one with change the other.","category":"page"},{"location":"walkthrough/","page":"Walkthrough","title":"Walkthrough","text":"We'll freeze the hydrogen column density parameter to the galactic value and refit:","category":"page"},{"location":"walkthrough/","page":"Walkthrough","title":"Walkthrough","text":"bbpl_model.ηH_1.value = 4\nbbpl_model.ηH_1.frozen = true\nbbpl_model","category":"page"},{"location":"walkthrough/","page":"Walkthrough","title":"Walkthrough","text":"And fitting:","category":"page"},{"location":"walkthrough/","page":"Walkthrough","title":"Walkthrough","text":"bbpl_result = fit(\n FittingProblem(bbpl_model => data), \n LevenbergMarquadt()\n)","category":"page"},{"location":"walkthrough/","page":"Walkthrough","title":"Walkthrough","text":"Let's plot the result:","category":"page"},{"location":"walkthrough/","page":"Walkthrough","title":"Walkthrough","text":"plot(data, \n ylims = (0.001, 2.0), \n xscale = :log10, \n yscale = :log10,\n legend = :bottomleft,\n)\nplot!(bbpl_result)","category":"page"},{"location":"walkthrough/","page":"Walkthrough","title":"Walkthrough","text":"Update the model and fix the black body temperature to 2 keV:","category":"page"},{"location":"walkthrough/","page":"Walkthrough","title":"Walkthrough","text":"update_model!(bbpl_model, bbpl_result)\n\nbbpl_model.T_1.value = 2.0\nbbpl_model.T_1.frozen = true\nbbpl_model","category":"page"},{"location":"walkthrough/","page":"Walkthrough","title":"Walkthrough","text":"Fitting:","category":"page"},{"location":"walkthrough/","page":"Walkthrough","title":"Walkthrough","text":"bbpl_result2 = fit(\n FittingProblem(bbpl_model => data), \n LevenbergMarquadt()\n)","category":"page"},{"location":"walkthrough/","page":"Walkthrough","title":"Walkthrough","text":"Overplotting this new result:","category":"page"},{"location":"walkthrough/","page":"Walkthrough","title":"Walkthrough","text":"plot!(bbpl_result2)","category":"page"},{"location":"walkthrough/#MCMC","page":"Walkthrough","title":"MCMC","text":"","category":"section"},{"location":"walkthrough/","page":"Walkthrough","title":"Walkthrough","text":"We can use libraries like Pidgeons.jl or Turing.jl to perform Bayesian inference on our paramters. SpectralFitting.jl is designed with BYOO (Bring Your Own Optimizer) in mind, and so makes it relatively easy to get at the core fitting functions to be used with other packages.","category":"page"},{"location":"walkthrough/","page":"Walkthrough","title":"Walkthrough","text":"Let's use Turing.jl here, which means we'll also want to use StatsPlots.jl to plot our walker chains.","category":"page"},{"location":"walkthrough/","page":"Walkthrough","title":"Walkthrough","text":"using StatsPlots\nusing Turing","category":"page"},{"location":"walkthrough/","page":"Walkthrough","title":"Walkthrough","text":"Turing.jl provides enormous control over the definition of the model, and this is not control SpectralFitting.jl wants to take away from you. Although we will provide utility scripts to do the basics, here we'll show you everything step by step to give you an overview of what you can do.","category":"page"},{"location":"walkthrough/","page":"Walkthrough","title":"Walkthrough","text":"Let's go back to our first model:","category":"page"},{"location":"walkthrough/","page":"Walkthrough","title":"Walkthrough","text":"model","category":"page"},{"location":"walkthrough/","page":"Walkthrough","title":"Walkthrough","text":"This gave a pretty good fit but the errors on our paramters are not well defined, being estimated only from a convariance matrix in the least-squares solver. MCMC can give us better confidence regions, and even help us uncover dependencies between paramters. Here we'll take all of our parameters and convert them into a Turing.jl model with use of their macro:","category":"page"},{"location":"walkthrough/","page":"Walkthrough","title":"Walkthrough","text":"@model function mcmc_model(domain, objective, variance, f)\n K ~ Normal(20.0, 1.0)\n a ~ Normal(2.2, 0.3)\n ηH ~ truncated(Normal(0.5, 0.1); lower = 0)\n\n pred = f(domain, [K, a, ηH])\n return objective ~ MvNormal(pred, sqrt.(variance))\nend","category":"page"},{"location":"walkthrough/","page":"Walkthrough","title":"Walkthrough","text":"A few things to note here: we use the Turing.jl sampling syntax ~ to say that a variable is sampled from a certain type of prior distribution. There are no fixed criteria for what a distribution can be, and we encourage you to consult the Turing.jl documentation to learn how to define your own custom probability distributions. In this case, we will use Gaussians for all our parameters, and for the means and standard deviations use the best fit and estimated errors.","category":"page"},{"location":"walkthrough/","page":"Walkthrough","title":"Walkthrough","text":"At the moment we haven't explicitly used our model, but f in this case takes the roll of invoking our model, and folding through instrument responses. We call it in much the same way as invokemodel, despite it going the extra step to fold our model. To instantiate this, we can use the SpectralFitting.jl helper functions:","category":"page"},{"location":"walkthrough/","page":"Walkthrough","title":"Walkthrough","text":"config = FittingConfig(FittingProblem(model => data))\nmm = mcmc_model(\n make_model_domain(ContiguouslyBinned(), data),\n make_objective(ContiguouslyBinned(), data),\n make_objective_variance(ContiguouslyBinned(), data),\n # _f_objective returns a function used to evaluate and fold the model through the data\n SpectralFitting._f_objective(config),\n)\nnothing # hide","category":"page"},{"location":"walkthrough/","page":"Walkthrough","title":"Walkthrough","text":"That's it! We're now ready to sample our model. Since all our models are implemented in Julia, we can use gradient-boosted samplers with automatic differentiation, such as NUTS. We'll walk 5000 itterations, just as a small example:","category":"page"},{"location":"walkthrough/","page":"Walkthrough","title":"Walkthrough","text":"chain = sample(mm, NUTS(), 5_000)","category":"page"},{"location":"walkthrough/","page":"Walkthrough","title":"Walkthrough","text":"In the printout we see summary statistics about or model, in this case that it has converged well (rhat close to 1 for all parameters), better estimates of the standard deviation, and various quantiles. We can plot our chains to make sure the caterpillers are healthy and fuzzy, making use of StatsPlots.jl recipes:","category":"page"},{"location":"walkthrough/","page":"Walkthrough","title":"Walkthrough","text":"plot(chain)","category":"page"},{"location":"walkthrough/","page":"Walkthrough","title":"Walkthrough","text":"Corner plots are currently broken at time of writing.","category":"page"},{"location":"models/using-models/#Using-spectral-models","page":"Using models","title":"Using spectral models","text":"","category":"section"},{"location":"models/using-models/","page":"Using models","title":"Using models","text":"using SpectralFitting\nusing Plots","category":"page"},{"location":"models/using-models/","page":"Using models","title":"Using models","text":"In this page you'll find how to use the spectral model library and how to define your own models. Using the model library is as easy as invoking or composing models from the Model Index. For example:","category":"page"},{"location":"models/using-models/","page":"Using models","title":"Using models","text":"model = PowerLaw()","category":"page"},{"location":"models/using-models/","page":"Using models","title":"Using models","text":"In the output of the REPL we see the model name, and it's two parameters, with information about those parameters, such as the current value, the associated error (10% by defaul), the minimum and maximum values, and whether the parameter is frozen or not.","category":"page"},{"location":"models/using-models/","page":"Using models","title":"Using models","text":"note: Note\nSee FitParam for full details about fitable parameters.","category":"page"},{"location":"models/using-models/","page":"Using models","title":"Using models","text":"The parameters can be tweaked by accessing the fields","category":"page"},{"location":"models/using-models/","page":"Using models","title":"Using models","text":"model.K.value = 2.0\nmodel.K.frozen = true\nmodel","category":"page"},{"location":"models/using-models/","page":"Using models","title":"Using models","text":"We can invoke the model on a domain in the following way","category":"page"},{"location":"models/using-models/","page":"Using models","title":"Using models","text":"domain = collect(range(0.1, 10.0, 100))\ninvokemodel(domain, model)","category":"page"},{"location":"models/using-models/","page":"Using models","title":"Using models","text":"note: Note\nBy default, models are implemented to accept a single input vector with all of the low and high bin edges, and return a flux array with the flux in each energy bin. As such, it is here the case that:length(flux) == length(energy) - 1Models need not be defined as such, however. See AbstractLayout for more.","category":"page"},{"location":"models/using-models/","page":"Using models","title":"Using models","text":"Models can be composed together following the Model algebra. That means to expressive a photoelectric absorption component acting on the power law we can write","category":"page"},{"location":"models/using-models/","page":"Using models","title":"Using models","text":"model2 = PhotoelectricAbsorption() * model","category":"page"},{"location":"models/using-models/","page":"Using models","title":"Using models","text":"The parameters of this CompositeModel are are copied from the expression. This means we can modify the K_1 parameter in model2 without having to worry that we are changing model.K:","category":"page"},{"location":"models/using-models/","page":"Using models","title":"Using models","text":"model2.K_1.frozen = false\nmodel2","category":"page"},{"location":"models/using-models/","page":"Using models","title":"Using models","text":"model","category":"page"},{"location":"models/using-models/","page":"Using models","title":"Using models","text":"Composite models have the same methods as single models. This means we can invoke a model in the same way","category":"page"},{"location":"models/using-models/","page":"Using models","title":"Using models","text":"invokemodel(domain, model2)","category":"page"},{"location":"models/using-models/#Defining-new-models","page":"Using models","title":"Defining new models","text":"","category":"section"},{"location":"models/using-models/","page":"Using models","title":"Using models","text":"To define your own model, you need to tell the package what the model parameters are and how to invoke the model. This is all done by creating a struct which subtypes AbstractSpectralModel.","category":"page"},{"location":"models/using-models/","page":"Using models","title":"Using models","text":"Let's create a new Additive spectral model:","category":"page"},{"location":"models/using-models/","page":"Using models","title":"Using models","text":"Base.@kwdef struct MyModel{T} <: AbstractSpectralModel{T,Additive}\n K::T = FitParam(2.0)\n p::T = FitParam(3.0)\nend\n\n# implementing a dummy add operation this function can do anything it likes, but\n# must write the output into `output` and ideally should be thread safe\nfunction SpectralFitting.invoke!(output, input, model::MyModel)\n SpectralFitting.finite_diff_kernel!(output, input) do E\n E + model.p\n end\nend","category":"page"},{"location":"models/using-models/","page":"Using models","title":"Using models","text":"Here we used the utility method SpectralFitting.finite_diff_kernel! to ensure the additive model is appropriately scaled across the bin width.","category":"page"},{"location":"models/using-models/","page":"Using models","title":"Using models","text":"Note that Additive models do not need to use the normalization parameter K themselves. This is because when we use invokemodel these sorts of translations are automatically applied, for compatability with external models.","category":"page"},{"location":"models/using-models/","page":"Using models","title":"Using models","text":"Our model is now ready to use","category":"page"},{"location":"models/using-models/","page":"Using models","title":"Using models","text":"model = MyModel()","category":"page"},{"location":"models/using-models/","page":"Using models","title":"Using models","text":"domain = collect(range(0.1, 10.0, 100))\ninvokemodel(domain, model)","category":"page"},{"location":"models/using-models/","page":"Using models","title":"Using models","text":"note: Note\nTo add new XSPEC or foreign function models, see Wrapping new XSPEC models.","category":"page"},{"location":"models/using-models/#Model-abstraction","page":"Using models","title":"Model abstraction","text":"","category":"section"},{"location":"models/using-models/","page":"Using models","title":"Using models","text":"All spectral models are a sub-type of AbstractSpectralModel.","category":"page"},{"location":"models/using-models/","page":"Using models","title":"Using models","text":"AbstractSpectralModel\nSpectralFitting.invoke!\nmodelkind\nnumbertype\nimplementation","category":"page"},{"location":"models/using-models/#SpectralFitting.AbstractSpectralModel","page":"Using models","title":"SpectralFitting.AbstractSpectralModel","text":"abstract type AbstractSpectralModel{T,K<:AbstractSpectralModelKind} end\n\nSupertype of all spectral models, tracking the number type T and AbstractSpectralModelKind denoted K.\n\nImplementation\n\nSub-types must implement the following interface (see the function's documentation for examples):\n\nSpectralFitting.invoke!\n\nUsage\n\nThe available API for a spectral model is detailed below:\n\ninvokemodel / invokemodel!: the primary way to invoke a model.\nallocate_model_output: allocate the output matrix for the model.\n\nThe following query functions exist:\n\nmodelkind for obtaining K\nnumbertype for obtaining T\nimplementation used to assertain whether we can do things like automatic differentation through this model.\n\nModel reflection is supported by the following functions. These are intended for internal use and are not exported.\n\nparameter_named_tuple\nparameter_tuple\nremake_model_with_parameters\ndestructure_model\n\nThe parametric type parameter T is the number type of the model and K defines the AbstractSpectralModelKind.\n\n\n\n\n\n","category":"type"},{"location":"models/using-models/#SpectralFitting.invoke!","page":"Using models","title":"SpectralFitting.invoke!","text":"SpectralFitting.invoke!(output, domain, M::Type{<:AbstractSpectralModel}, params...)\n\nUsed to define the behaviour of models. Should calculate the output of the model and write in-place into output. The model parameters are passed in the model structure.\n\nwarning: Warning\nThis function should not be called directly. Use invokemodel instead. invoke! is only to define the model, not to use it. Users should always call models using invokemodel or invokemodel! to ensure normalisations and closures are accounted for.\n\nExample\n\nBase.@kwdef struct MyModel{T} <: AbstractSpectralModel{T,Multiplicative}\n p1::T = FitParam(1.0)\n p2::T = FitParam(2.0)\n p3::T = FitParam(3.0)\nend\n\nwould have the arguments passed to invoke! as\n\nfunction SpectralFitting.invoke!(output, domain, model::MyModel)\n # ...\nend\n\n\n\n\n\n","category":"function"},{"location":"models/using-models/#SpectralFitting.modelkind","page":"Using models","title":"SpectralFitting.modelkind","text":"modelkind(M::Type{<:AbstractSpectralModel})\nmodelkind(::AbstractSpectralModel)\n\nReturn the kind of model given by M: either Additive, Multiplicative, or Convolutional.\n\n\n\n\n\n","category":"function"},{"location":"models/using-models/#SpectralFitting.numbertype","page":"Using models","title":"SpectralFitting.numbertype","text":"numbertype(::AbstractSpectralModel)\n\nGet the numerical type of the model. This goes through FitParam, so that the number type returned is as close to a primative as possible.\n\nSee also paramtype.\n\nExample\n\nnumbertype(PowerLaw()) == Float64\n\n\n\n\n\n","category":"function"},{"location":"models/using-models/#SpectralFitting.implementation","page":"Using models","title":"SpectralFitting.implementation","text":"implementation(model::AbstractSpectralModel)\nimplementation(::Type{<:AbstractSpectralModel})\n\nGet the AbstractSpectralModelImplementation for a given AbstractSpectralModel or model type.\n\nThis is used primarily to learn what optimizations we can do with a model, for example propagating auto-diff gradients through a model or arbitrary precision numbers.\n\n\n\n\n\n","category":"function"},{"location":"models/using-models/#Model-methods","page":"Using models","title":"Model methods","text":"","category":"section"},{"location":"models/using-models/","page":"Using models","title":"Using models","text":"invokemodel\ninvokemodel!","category":"page"},{"location":"models/using-models/#SpectralFitting.invokemodel","page":"Using models","title":"SpectralFitting.invokemodel","text":"invokemodel(domain, model::AbstractSpectralModel)\n\nInvoke the AbstractSpectralModel given by model over the domain domain.\n\nThis function will perform any normalisation or post-processing tasks that a specific model kind may require, e.g. multiplying by a normalisation constant for Additive models.\n\nnote: Note\n\n\ninvokemodel allocates the needed output arrays based on the element type of free_params to allow automatic differentation libraries to calculate parameter gradients.\n\nIn-place non-allocating variants are the invokemodel! functions.\n\nExample\n\nmodel = PowerLaw()\ndomain = collect(range(0.1, 20.0, 100))\n\ninvokemodel(domain, model)\n\n\n\n\n\n","category":"function"},{"location":"models/using-models/#SpectralFitting.invokemodel!","page":"Using models","title":"SpectralFitting.invokemodel!","text":"invokemodel!(output, domain, model)\ninvokemodel!(output, domain, model, params::AbstractVector)\ninvokemodel!(output, domain, model, params::ParameterCache)\n\nIn-place variant of invokemodel, calculating the output of an AbstractSpectralModel given by model, optionally overriding the parameters using a ParameterCache or an AbstractVector.\n\nThe output may not necessarily be a single vector, and one should use allocate_model_output to allocate the output structure.\n\nExample\n\nmodel = PowerLaw()\ndomain = collect(range(0.1, 20.0, 100))\noutput = allocate_model_output(model, domain)\ninvokemodel!(output, domain, model)\n\n\n\n\n\n","category":"function"},{"location":"models/using-models/#Model-algebra","page":"Using models","title":"Model algebra","text":"","category":"section"},{"location":"models/using-models/","page":"Using models","title":"Using models","text":"Models exist as three different kinds, defined by an AbstractSpectralModelKind trait.","category":"page"},{"location":"models/using-models/","page":"Using models","title":"Using models","text":"AbstractSpectralModelKind\nAdditive\nMultiplicative\nConvolutional","category":"page"},{"location":"models/using-models/#SpectralFitting.AbstractSpectralModelKind","page":"Using models","title":"SpectralFitting.AbstractSpectralModelKind","text":"abstract type AbstractSpectralModelKind\n\nAbstract type of all model kinds. The algebra of models is as follows\n\nA + A = A\nM * M = M\nM * A = A\nC(A) = A\n\nwhere A is Additive, M is Multiplicative, and C is Convolutional. All other operations are prohibited, e.g. C(M) or M * C. To obtain M * C there must be an additive component, e.g. M * C(A).\n\n\n\n\n\n","category":"type"},{"location":"models/using-models/#SpectralFitting.Additive","page":"Using models","title":"SpectralFitting.Additive","text":"Additive <: AbstractSpectralModelKind\nAdditive()\n\nAdditive models are effectively the sources of photons, and are the principle building blocks of composite models. Every additive model has a normalisation parameter which re-scales the output by a constant factor K.\n\nnote: Note\nDefining custom additive models requires special care. See Defining new models.\n\n\n\n\n\n","category":"type"},{"location":"models/using-models/#SpectralFitting.Multiplicative","page":"Using models","title":"SpectralFitting.Multiplicative","text":"Multiplicative <: AbstractSpectralModelKind\nMultiplicative()\n\nMultiplicative models act on Additive models, by element-wise multiplying the output in each domain bin of the additive model by a different factor.\n\n\n\n\n\n","category":"type"},{"location":"models/using-models/#SpectralFitting.Convolutional","page":"Using models","title":"SpectralFitting.Convolutional","text":"Convolutional <: AbstractSpectralModelKind\nConvolutional()\n\nConvolutional models act on the output generated by Additive models, similar to Multiplicative models, however may convolve kernels through the output also.\n\n\n\n\n\n","category":"type"},{"location":"models/using-models/#Model-data-availability","page":"Using models","title":"Model data availability","text":"","category":"section"},{"location":"models/using-models/","page":"Using models","title":"Using models","text":"Many of the XSPEC implemented models use tabular data, such as FITS, and return results interpolated from these pre-calculated tables. In some cases, these table models have data files that are multiple gigabytes in size, and would be very unwieldy to ship indiscriminantly. SpectralFitting attempts to circumnavigate this bloat by downloading the model data on an ut opus basis.","category":"page"},{"location":"models/using-models/","page":"Using models","title":"Using models","text":"SpectralFitting.download_model_data\nSpectralFitting.download_all_model_data","category":"page"},{"location":"models/using-models/#SpectralFitting.download_model_data","page":"Using models","title":"SpectralFitting.download_model_data","text":"SpectralFitting.download_model_data(model::AbstractSpectralModel; kwargs...)\nSpectralFitting.download_model_data(M::Type{<:AbstractSpectralModel}; kwargs...)\nSpectralFitting.download_model_data(s::Symbol; kwargs...)\n\nDownloads the model data for a model specified either by model, type M, or symbol s. Datafiles associated with a specific model may be registered using SpectralFitting.register_model_data. The download is currently unconfigurable, but permits slight control via a number of keyword arguments:\n\nprogress::Bool = true\n\nDisplay a progress bar for the download.\n\nmodel_source_url::String = \"http://www.star.bris.ac.uk/fbaker/XSPEC-model-data\"\n\nThe source URL used to download the model data.\n\nAll standard XSPEC spectral model data is currently being hosted on the University of Bristol astrophysics servers, and should be persistently available to anyone.\n\n\n\n\n\n","category":"function"},{"location":"models/using-models/#SpectralFitting.download_all_model_data","page":"Using models","title":"SpectralFitting.download_all_model_data","text":"SpectralFitting.download_all_model_data()\n\nDownloads all model data for the models currently registered with SpectralFitting.register_model_data. Calls SpectralFitting.download_model_data to perform the download.\n\n\n\n\n\n","category":"function"},{"location":"models/using-models/","page":"Using models","title":"Using models","text":"Special care must be taken if new XSPEC models are wrapped to ensure the data is available. For more on this, see Wrapping new XSPEC models.","category":"page"},{"location":"models/using-models/","page":"Using models","title":"Using models","text":"Model data may also alternatively be copied in by-hand from a HEASoft XSPEC source directory. In this case, the location to copy the data to may be determined via joinpath(SpectralFitting.LibXSPEC_jll.artifact_dir, \"spectral\", \"modelData\").","category":"page"},{"location":"#SpectralFitting.jl-Documentation","page":"Home","title":"SpectralFitting.jl Documentation","text":"","category":"section"},{"location":"","page":"Home","title":"Home","text":"Fast and flexible spectral fitting in Julia.","category":"page"},{"location":"","page":"Home","title":"Home","text":"SpectralFitting.jl is a package for defining and using spectral models, with a number of utilities to make model composition easy and invocation fast. SpectralFitting wraps LibXSPEC_jll.jl to expose the library of models from HEASoft XSPEC, and provides helper functions for operating with spectral data from a number of different missions. The package natively uses LsqFit.jl to fit parameters using the Levenberg-Marquardt algorithm, but makes it easy to use Optim.jl for more specialized fitting algorithms, or Turing.jl for Bayesian inference and MCMC.","category":"page"},{"location":"","page":"Home","title":"Home","text":"SpectralFitting is designed to be extended, such that new models are simple to create, and new dataset processing pipelines for different missions are brief to define. Where performance is key, SpectralFitting helps you define fast and AD-compatible surrogates of spectral models using Surrogates.jl, and embed them in the model composition algebra.","category":"page"},{"location":"","page":"Home","title":"Home","text":"To get started, add the AstroRegistry from the University of Bristol and then install:","category":"page"},{"location":"","page":"Home","title":"Home","text":"julia>]\npkg> registry add https://github.com/astro-group-bristol/AstroRegistry\npkg> add SpectralFitting","category":"page"},{"location":"","page":"Home","title":"Home","text":"Then use","category":"page"},{"location":"","page":"Home","title":"Home","text":"using SpectralFitting\n# ....","category":"page"},{"location":"","page":"Home","title":"Home","text":"to get started. See Walkthrough for an example walkthrough the package.","category":"page"},{"location":"","page":"Home","title":"Home","text":"For more University of Bristol Astrophysics Group codes, see our GitHub organisation.","category":"page"}] +[{"location":"models/surrogate-models/#Surrogate-models","page":"Surrogate models","title":"Surrogate models","text":"","category":"section"},{"location":"models/surrogate-models/","page":"Surrogate models","title":"Surrogate models","text":"Surrogate models allow you to create fast or memory efficient approximations of model components, or assist in optimizing some objective function directly. SpectralFitting uses the Surrogates.jl library of models, that yields pure-Julia surrogate models. Consequently, surrogate models also permit use of automatic differentiation in fitting, and are therefore powerful tools for improving fitting performance.","category":"page"},{"location":"models/surrogate-models/#Surrogates-overview","page":"Surrogate models","title":"Surrogates overview","text":"","category":"section"},{"location":"models/surrogate-models/","page":"Surrogate models","title":"Surrogate models","text":"warning: Warning\nThe surrogate model optimization does not work well for most XSPEC models currently. This is being actively developed.","category":"page"},{"location":"models/surrogate-models/","page":"Surrogate models","title":"Surrogate models","text":"Any function may be wrapped as a surrogate model using the SurrogateSpectralModel type.","category":"page"},{"location":"models/surrogate-models/","page":"Surrogate models","title":"Surrogate models","text":"SurrogateSpectralModel","category":"page"},{"location":"models/surrogate-models/#SpectralFitting.SurrogateSpectralModel","page":"Surrogate models","title":"SpectralFitting.SurrogateSpectralModel","text":"SurrogateSpectralModel <: AbstractSpectralModel\nSurrogateSpectralModel(modelkind, surrogate, params, params_symbols)\n\nUsed to wrap a surrogate function into an AbstractSpectralModel.\n\nExample\n\nCreating a surrogate function using make_surrogate_harness:\n\n# build and optimize a surrogate model\nsurrogate = make_surrogate_harness(model, lower_bounds, upper_bounds)\n\n# create surrogate spectral model\nsm = SurrogateSpectralModel(\n Multiplicative(),\n surrogate,\n (FitParam(1.0),),\n (:ηH,)\n)\n\nThe lower_bounds and upper_bounds must be tuples in the form (E, params...), where E denotes the bounds on the energy range to train over.\n\n\n\n\n\n","category":"type"},{"location":"models/surrogate-models/","page":"Surrogate models","title":"Surrogate models","text":"To facilitate easy surrogate builds, SpectralFitting exports a number of utility functions.","category":"page"},{"location":"models/surrogate-models/","page":"Surrogate models","title":"Surrogate models","text":"make_surrogate_harness\noptimize_accuracy!","category":"page"},{"location":"models/surrogate-models/#SpectralFitting.make_surrogate_harness","page":"Surrogate models","title":"SpectralFitting.make_surrogate_harness","text":"make_surrogate_harness(\n model::M,\n lowerbounds::T,\n upperbounds::T;\n optimization_samples = 200,\n seed_samples = 50,\n S::Type = RadialBasis,\n sample_type = SobolSample(),\n verbose = false,\n)\n\nCreates and optimizes a surrogate model of type S for model, using wrap_model_as_objective and optimize_accuracy! for optimization_samples iterations. Model is initially seeded with seed_samples points prior to optimization.\n\nwarning: Warning\nAdditive models integrate energies to calculate flux, which surrogate models are currently not capable of. Results for Additive models likely to be inaccurate. This will be patched in a future version.\n\n\n\n\n\n","category":"function"},{"location":"models/surrogate-models/#SpectralFitting.optimize_accuracy!","page":"Surrogate models","title":"SpectralFitting.optimize_accuracy!","text":"optimize_accuracy!(\n surr::AbstractSurrogate,\n obj::Function,\n lb,\n ub;\n sample_type::SamplingAlgorithm = SobolSample(),\n maxiters = 200,\n N_truth = 5000,\n verbose = false,\n)\n\nImprove accuracy (faithfullness) of the surrogate model in recreating the objective function.\n\nSamples a new space of N_truth points between lb and ub, and calculates the objective function obj at each. Finds the point with largest MSE between surrogate and objective, and adds the point to the surrogate pool. Repeats maxiters times, adding maxiters points to surrogate model.\n\nOptionally print to stdout the MSE and iteration count with verbose = true.\n\nNote that upper- and lower-bounds should be in the form (E, params...), where E is a single energy and params are the model parameters.\n\n\n\n\n\n","category":"function"},{"location":"models/surrogate-models/#Creating-a-surrogate-for-XS_PhotoelectricAbsorption","page":"Surrogate models","title":"Creating a surrogate for XS_PhotoelectricAbsorption","text":"","category":"section"},{"location":"models/surrogate-models/","page":"Surrogate models","title":"Surrogate models","text":"Before we start, let us discuss a number of benefits the use of surrogate models may bring us:","category":"page"},{"location":"models/surrogate-models/","page":"Surrogate models","title":"Surrogate models","text":"SurrogateSpectralModel permit use of automatic differentiation.\nSurrogate models may be allocation-free depending on setup, whereas XSPEC wrappers will always have to allocate for type-conversions.\nSurrogate models may be considerably faster, especially for table models.\nSurrogate models are shareable (see Sharing surrogate models), and are tunable in size.","category":"page"},{"location":"models/surrogate-models/","page":"Surrogate models","title":"Surrogate models","text":"XS_PhotoelectricAbsorption is an XSPEC model that is wrapped by a thin C-wrapper into Julia. The implementation of this model is a number of Fortran routines from the late 90s, including a tabulation of ~3000 lines of data that has been copied directly into the Fortran source code.","category":"page"},{"location":"models/surrogate-models/","page":"Surrogate models","title":"Surrogate models","text":"The performance of this model represents its complexity.","category":"page"},{"location":"models/surrogate-models/","page":"Surrogate models","title":"Surrogate models","text":"using SpectralFitting \n\nenergy = collect(range(0.1, 20.0, 200))\nmodel = XS_PhotoelectricAbsorption()\n\nflux = similar(energy)[1:end-1]","category":"page"},{"location":"models/surrogate-models/","page":"Surrogate models","title":"Surrogate models","text":"Benchmarking with BenchmarkTools.jl:","category":"page"},{"location":"models/surrogate-models/","page":"Surrogate models","title":"Surrogate models","text":"using BenchmarkTools\n@benchmark invokemodel!($flux, $energy, $model)","category":"page"},{"location":"models/surrogate-models/","page":"Surrogate models","title":"Surrogate models","text":"The surrogate we'll construct will have to be tailored a little to the data we wish to fit, as we need to specify the parameter ranges our surrogate should learn. For example, we might be interested in energies between 01 and 20 keV (expressed in our domain), with equivalent hydrogen column etaH anywhere between 10^-3 and 30. We specify the parameter bounds using tuples:","category":"page"},{"location":"models/surrogate-models/","page":"Surrogate models","title":"Surrogate models","text":"lower_bounds = (1e-3,)\nupper_bounds = (30.0,)\nnothing # hide","category":"page"},{"location":"models/surrogate-models/","page":"Surrogate models","title":"Surrogate models","text":"note: Note\nThe first index is always the energy bounds, and the subsequent indices are the parameters in the same order they are defined in the model structure.","category":"page"},{"location":"models/surrogate-models/","page":"Surrogate models","title":"Surrogate models","text":"Next, we use make_surrogate_harness to build and optimize a surrogate function for our model. By default, the surrogate uses linear radial basis functions, and seeds the coefficients with a number of seed points. This function then improves the accuracy of the model using optimize_accuracy!, until a maximal number of iterations has been reached.","category":"page"},{"location":"models/surrogate-models/","page":"Surrogate models","title":"Surrogate models","text":"For illustration purposes, we'll omit the accuracy improving step, and perform this ourselves. We can do this by setting optimization_samples = 0 in the keyword arguments:","category":"page"},{"location":"models/surrogate-models/","page":"Surrogate models","title":"Surrogate models","text":"using Surrogates\n\nharness = make_surrogate_harness(\n (x, y) -> RadialBasis(x, y, lower_bounds, upper_bounds),\n energy,\n model,\n lower_bounds,\n upper_bounds;\n # default is 50, but to illustrate the training behaviour we'll set this low\n seed_samples = 2,\n)\n\n# number of points the surrogate has been trained on\nlength(harness.surrogate.x)","category":"page"},{"location":"models/surrogate-models/","page":"Surrogate models","title":"Surrogate models","text":"We can examine how well our surrogate reconstructs the model for a given test parameter:","category":"page"},{"location":"models/surrogate-models/","page":"Surrogate models","title":"Surrogate models","text":"using Plots\n# random test value\nηh_test = 22.9\n\nmodel.ηH.value = ηh_test\nf = invokemodel(energy, model)\n\nf̂ = harness.surrogate([ηh_test])\n\np = plot(energy[1:end-1], f, label=\"model\", legend=:bottomright, xlabel=\"E (keV)\") # hide\nplot!(energy[1:end-1], f̂, label=\"surr\") # hide\np # hide","category":"page"},{"location":"models/surrogate-models/","page":"Surrogate models","title":"Surrogate models","text":"Now we'll use optimize_accuracy! to improve the faithfulness of our surrogate. This requires making use of wrap_model_as_objective as a little wrapper around our model:","category":"page"},{"location":"models/surrogate-models/","page":"Surrogate models","title":"Surrogate models","text":"optimize_accuracy!(harness; maxiters=50)\n\nlength(harness.surrogate.x)","category":"page"},{"location":"models/surrogate-models/","page":"Surrogate models","title":"Surrogate models","text":"We can plot the surrogate model again and see the improvement.","category":"page"},{"location":"models/surrogate-models/","page":"Surrogate models","title":"Surrogate models","text":"new_f̂ = harness.surrogate([ηh_test])\nplot!(energy[1:end-1], new_f̂, label=\"surr+\") # hide\np # hide","category":"page"},{"location":"models/surrogate-models/","page":"Surrogate models","title":"Surrogate models","text":"Tight. We can also inspect the memory footprint of our model:","category":"page"},{"location":"models/surrogate-models/","page":"Surrogate models","title":"Surrogate models","text":"# in bytes\nBase.summarysize(harness) ","category":"page"},{"location":"models/surrogate-models/","page":"Surrogate models","title":"Surrogate models","text":"This may be reduced by lowering maxiters in optimize_accuracy! at the cost of decreasing faithfulness. However, compare this to the Fortran tabulated source file in the XSPEC source code, which is approximately 224 Kb. The surrogate model with all it's training data is of the same order.","category":"page"},{"location":"models/surrogate-models/#Using-a-surrogate-spectral-model","page":"Surrogate models","title":"Using a surrogate spectral model","text":"","category":"section"},{"location":"models/surrogate-models/","page":"Surrogate models","title":"Surrogate models","text":"Now that we have the surrogate model, we use SurrogateSpectralModel to wrap it into an AbstractSpectralModel. The constructor also needs to know the model kind, have a copy of the model parameters, and know which symbols to represent the parameters with.","category":"page"},{"location":"models/surrogate-models/","page":"Surrogate models","title":"Surrogate models","text":"sm = make_model(harness)","category":"page"},{"location":"models/surrogate-models/","page":"Surrogate models","title":"Surrogate models","text":"We can now use the familiar API and attempt to benchmark the performance:","category":"page"},{"location":"models/surrogate-models/","page":"Surrogate models","title":"Surrogate models","text":"@benchmark invokemodel!($flux, $energy, $sm)","category":"page"},{"location":"models/surrogate-models/","page":"Surrogate models","title":"Surrogate models","text":"Comparing this to the initial benchmark of XS_PhotoelectricAbsorption, we see about a significant speedup, with no allocations, and this surrogate model is now automatic differentiation ready.","category":"page"},{"location":"models/surrogate-models/#Evaluating-the-model","page":"Surrogate models","title":"Evaluating the model","text":"","category":"section"},{"location":"models/surrogate-models/","page":"Surrogate models","title":"Surrogate models","text":"p_range = collect(range(1.0, 30.0))\n\nfluxes_vecs = map(p_range) do p\n model.ηH.value = p\n f = invokemodel(energy, model)\nend\nfluxes_mat = reduce(hcat, fluxes_vecs)\n\nsurface(p_range, energy[1:end-1], fluxes_mat, xlabel = \"ηH\", ylabel = \"E\", zlabel = \"f\", title = \"Model\")","category":"page"},{"location":"models/surrogate-models/","page":"Surrogate models","title":"Surrogate models","text":"s_fluxes_vecs = map(p_range) do p\n sm.params[1].value = p\n display(sm)\n f = invokemodel(energy, sm)\nend\ns_fluxes_mat = reduce(hcat, s_fluxes_vecs)\n\nsurface(p_range, energy[1:end-1], s_fluxes_mat, xlabel = \"ηH\", ylabel = \"E\", zlabel = \"f\", title = \"Surrogate\")","category":"page"},{"location":"models/surrogate-models/#Sharing-surrogate-models","page":"Surrogate models","title":"Sharing surrogate models","text":"","category":"section"},{"location":"models/surrogate-models/","page":"Surrogate models","title":"Surrogate models","text":"To export and import surrogate models, JLD2.jl is recommended.","category":"page"},{"location":"models/models/#Model-index","page":"Model index","title":"Model index","text":"","category":"section"},{"location":"models/models/","page":"Model index","title":"Model index","text":"Models wrapped from XSPEC implementations are prefixed with XS_*, whereas pure-Julia models are simply named, e.g. XS_PowerLaw in XSPEC vs PowerLaw in Julia.","category":"page"},{"location":"models/models/","page":"Model index","title":"Model index","text":"The available models are","category":"page"},{"location":"models/models/","page":"Model index","title":"Model index","text":"Pages = [\"models.md\"]\nOrder = [:type]","category":"page"},{"location":"models/models/#Julia-models","page":"Model index","title":"Julia models","text":"","category":"section"},{"location":"models/models/","page":"Model index","title":"Model index","text":"PowerLaw\nBlackBody","category":"page"},{"location":"models/models/#SpectralFitting.PowerLaw","page":"Model index","title":"SpectralFitting.PowerLaw","text":"XS_PowerLaw(K, a)\n\nK: Normalisation.\na: Photon index.\n\nExample\n\nenergy = collect(range(0.1, 20.0, 100))\ninvokemodel(energy, PowerLaw())\n\n PowerLaw\n ┌────────────────────────────────────────┐\n 0.5 │ │\n │: │\n │: │\n │: │\n │: │\n │: │\n │: │\n │ : │\n │ : │\n │ : │\n │ :. │\n │ ':.. │\n │ ''':...... │\n │ ''''''''''''''........│\n 0 │ │\n └────────────────────────────────────────┘\n 0 20\n E (keV)\n\n\n\n\n\n","category":"type"},{"location":"models/models/#SpectralFitting.BlackBody","page":"Model index","title":"SpectralFitting.BlackBody","text":"XS_BlackBody(K, T)\n\nK: Normalisation.\nkT: Temperature (keV).\n\nExample\n\nenergy = collect(range(0.1, 20.0, 100))\ninvokemodel(energy, BlackBody())\n\n BlackBody\n ┌────────────────────────────────────────┐\n 0.2 │ │\n │ │\n │ │\n │ │\n │ │\n │ │\n │ .:''':.. │\n │ :' '':. │\n │ .' ':. │\n │ .: '.. │\n │ : ':. │\n │ .' ':.. │\n │ : ''... │\n │: '''.... │\n 0 │: '''│\n └────────────────────────────────────────┘\n 0 20\n E (keV)\n\n\n\n\n\n","category":"type"},{"location":"models/models/#XSPEC-models","page":"Model index","title":"XSPEC models","text":"","category":"section"},{"location":"models/models/","page":"Model index","title":"Model index","text":"XSPEC models frequently have tabular data dependencies, without which the models fail to invoke (see Model data availability). If the data files are known but not present, the XSPEC models will throw an error with instructions for downloading the missing data. If the data files are unknown, Julia may crash catastrophically. If this is the case, often a single line will be printed with the LibXSPEC error, specifying the name of the missing source file. This can be registered as a data dependency of a model using SpectralFitting.register_model_data.","category":"page"},{"location":"models/models/","page":"Model index","title":"Model index","text":"The first time any XSPEC model is invoked, SpectralFitting checks to see whether requisite data is needed, and whether the data is downloaded. Subsequent calls will hit a lookup cache instead to avoid run-time costs of performing this check.","category":"page"},{"location":"models/models/","page":"Model index","title":"Model index","text":"XS_PowerLaw\nXS_BlackBody\nXS_BremsStrahlung\nXS_Gaussian\nXS_Laor\nXS_DiskLine\nXS_PhotoelectricAbsorption\nXS_WarmAbsorption\nXS_CalculateFlux\nXS_KerrDisk\nXS_KyrLine","category":"page"},{"location":"models/models/#SpectralFitting.XS_PowerLaw","page":"Model index","title":"SpectralFitting.XS_PowerLaw","text":"XS_PowerLaw(K, a)\n\nK: Normalisation.\na: Photon index.\n\nExample\n\nenergy = collect(range(0.1, 20.0, 100))\ninvokemodel(energy, XS_PowerLaw())\n\n XS_PowerLaw\n ┌────────────────────────────────────────┐\n 0.5 │ │\n │: │\n │: │\n │: │\n │: │\n │: │\n │: │\n │ : │\n │ : │\n │ : │\n │ :. │\n │ ':.. │\n │ ''':...... │\n │ ''''''''''''''........│\n 0 │ │\n └────────────────────────────────────────┘\n 0 20\n E (keV)\n\n\n\n\n\n","category":"type"},{"location":"models/models/#SpectralFitting.XS_BlackBody","page":"Model index","title":"SpectralFitting.XS_BlackBody","text":"XS_BlackBody(K, T)\n\nK: Normalisation.\nT: Temperature (keV).\n\nExample\n\nenergy = collect(range(0.1, 20.0, 100))\ninvokemodel(energy, XS_BlackBody())\n\n XS_BlackBody\n ┌────────────────────────────────────────┐\n 0.2 │ │\n │ │\n │ │\n │ │\n │ │\n │ │\n │ .:''':.. │\n │ .: ''. │\n │ .' ':. │\n │ : ''.. │\n │ : ':. │\n │ : '':. │\n │.: ''.. │\n │: '':.... │\n 0 │' '''│\n └────────────────────────────────────────┘\n 0 20\n E (keV)\n\n\n\n\n\n","category":"type"},{"location":"models/models/#SpectralFitting.XS_BremsStrahlung","page":"Model index","title":"SpectralFitting.XS_BremsStrahlung","text":"XS_BremsStrahlung(K, T)\n\nK: Normalisation.\nT: Plasma temperature (keV).\n\nExample\n\nenergy = collect(range(0.1, 20.0, 100))\ninvokemodel(energy, XS_BremsStrahlung())\n\n XS_BremsStrahlung\n ┌────────────────────────────────────────┐\n 2 │ │\n │. │\n │: │\n │: │\n │: │\n │: │\n │: │\n │: │\n │: │\n │: │\n │: │\n │: │\n │'. │\n │ : │\n 0 │ ':....................................│\n └────────────────────────────────────────┘\n 0 20\n E (keV)\n\n\n\n\n\n","category":"type"},{"location":"models/models/#SpectralFitting.XS_Gaussian","page":"Model index","title":"SpectralFitting.XS_Gaussian","text":"XS_Gaussian(K, E, σ)\n\nK: Normalisation\nE: Line wavelength in Angstrom.\nσ: Line width in Angstrom.\n\nExample\n\nenergy = collect(range(4.0, 8.0, 100))\ninvokemodel(energy, XS_Gaussian())\n\n XS_Gaussian \n ┌────────────────────────────────────────┐ \n 0.09 │ │ \n │ . │ \n │ : : │ \n │ : : │ \n │ : '. │ \n │ .' : │ \n │ : : │ \n │ : : │ \n │ : '. │ \n │ : : │ \n │ : : │ \n │ : : │ \n │ .' : │ \n │ : : │ \n 0 │.......: :......................│ \n └────────────────────────────────────────┘ \n 0 20 \n E (keV) \n\n\n\n\n\n","category":"type"},{"location":"models/models/#SpectralFitting.XS_Laor","page":"Model index","title":"SpectralFitting.XS_Laor","text":"XS_Laor(K, lineE, a, inner_r, outer_r, incl)\n\nK: Normalisation.\nlineE: Rest frame line energy (keV).\na: Power law dependence of emissitivy. Scales R⁻ᵅ.\ninner_r: Inner radius of the accretion disk (GM/c).\nouter_r: Outer radius of the accretion disk (GM/c).\nθ: Disk inclination angle to line of sight (degrees, 0 is pole on).\n\nExample\n\nenergy = collect(range(0.1, 10.0, 100))\ninvokemodel(energy, XS_Laor())\n\n XS_Laor\n ┌────────────────────────────────────────┐\n 0.06 │ │\n │ │\n │ :: │\n │ :: │\n │ : : │\n │ : : │\n │ : : │\n │ :' : │\n │ .: : │\n │ :' : │\n │ .' : │\n │ .:' : │\n │ ..' : │\n │ .:' : │\n 0 │.......:'' :............│\n └────────────────────────────────────────┘\n 0 10\n E (keV)\n\n\n\n\n\n","category":"type"},{"location":"models/models/#SpectralFitting.XS_DiskLine","page":"Model index","title":"SpectralFitting.XS_DiskLine","text":"XS_DiskLine(K, lineE, β, inner_r, outer_r, incl)\n\nK: Normalisation.\nlineE: Rest frame line energy (keV).\nβ: Power law dependence of emissitivy. If < 10, scales Rᵅ.\ninner_r: Inner radius of the accretion disk (GM/c).\nouter_r: Outer radius of the accretion disk (GM/c).\nθ: Disk inclination angle to line of sight (degrees, 0 is pole on).\n\nExample\n\nenergy = collect(range(4.0, 8.0, 100))\ninvokemodel(energy, XS_DiskLine())\n\n XS_DiskLine\n ┌────────────────────────────────────────┐\n 0.09 │ │\n │ . │\n │ : │\n │ :: │\n │ . :: │\n │ : :: │\n │ :'': │\n │ .' : │\n │ : : │\n │ : : │\n │ .' : │\n │ : : │\n │ .: '. │\n │ .:' : │\n 0 │...............:''' :.........│\n └────────────────────────────────────────┘\n 4 8\n E (keV)\n\n\n\n\n\n","category":"type"},{"location":"models/models/#SpectralFitting.XS_PhotoelectricAbsorption","page":"Model index","title":"SpectralFitting.XS_PhotoelectricAbsorption","text":"XS_PhotoelectricAbsorption(ηH)\n\nηH: Equivalent hydrogen column (units of 10²² atoms per cm⁻²).\n\nExample\n\nenergy = collect(range(0.1, 20.0, 100))\ninvokemodel(energy, XS_PhotoelectricAbsorption())\n\n XS_PhotoelectricAbsorption\n ┌────────────────────────────────────────┐\n 1 │ ...''''''''''''''''''''''''''''''│\n │ .' │\n │ : │\n │ :' │\n │ : │\n │ : │\n │ : │\n │ : │\n │ : │\n │ : │\n │ : │\n │ : │\n │ : │\n │ : │\n 0 │.: │\n └────────────────────────────────────────┘\n 0 20\n E (keV)\n\n\n\n\n\n","category":"type"},{"location":"models/models/#SpectralFitting.XS_WarmAbsorption","page":"Model index","title":"SpectralFitting.XS_WarmAbsorption","text":"XS_WarmAbsorption(ηH, Ew)\n\nηH: Equivalent hydrogen column (units of 10²² atoms per cm⁻²).\nEw: Window energy (keV).\n\nExample\n\nenergy = collect(range(0.1, 20.0, 100))\ninvokemodel(energy, XS_WarmAbsorption())\n\n XS_WarmAbsorption\n ┌────────────────────────────────────────┐\n 1 │': ...''':'''''''''''''''''''''''''│\n │ : .:' │\n │ : .' │\n │ : .: │\n │ : : │\n │ : : │\n │ : : │\n │ : : │\n │ : : │\n │ : : │\n │ :: │\n │ :: │\n │ : │\n │ : │\n 0.2 │ : │\n └────────────────────────────────────────┘\n 0 20\n E (keV)\n\n\n\n\n\n","category":"type"},{"location":"models/models/#SpectralFitting.XS_CalculateFlux","page":"Model index","title":"SpectralFitting.XS_CalculateFlux","text":"XS_CalculateFlux(E_min, E_max, lg10Flux)\n\nE_min: Minimum energy.\nE_max: Maximum energy.\nlog10Flux: log (base 10) flux in erg / cm^2 / s\n\n\n\n\n\n","category":"type"},{"location":"models/models/#SpectralFitting.XS_KerrDisk","page":"Model index","title":"SpectralFitting.XS_KerrDisk","text":"XS_KerrDisk(K, lineE, index1, index2, break_r, a, θ, inner_r, outer_r)\n\nK: Normalisation.\nlineE: Rest frame line energy (keV).\nindex1: Emissivity index for inner disk.\nindex2: Emissivity index for outer disk.\nbreak_r: Break radius seperating inner and outer disk (gᵣ).\na: Dimensionless black hole spin.\nθ: Disk inclination angle to line of sight (degrees).\ninner_r: Inner radius of the disk in units of rₘₛ.\nouter_r: Outer radius of the disk in units of rₘₛ.\nz: Redshift.\n\nExample\n\nenergy = collect(range(0.1, 20.0, 100))\ninvokemodel(energy, XS_KerrDisk())\n\n XS_KerrDisk\n ┌────────────────────────────────────────┐\n 0.05 │ │\n │ │\n │ . │\n │ .: │\n │ ::: │\n │ .:' '. │\n │ .: : │\n │ ..' : │\n │ :' : │\n │ .' : │\n │ .:' : │\n │ .:'' : │\n │ .::' : │\n │ ..:' : │\n 0 │.........:''' :......│\n └────────────────────────────────────────┘\n 0 8\n E (keV)\n\n\n\n\n\n","category":"type"},{"location":"models/models/#SpectralFitting.XS_KyrLine","page":"Model index","title":"SpectralFitting.XS_KyrLine","text":"XS_KyrLine(K, a, θ_obs, inner_r, ms_flag, outer_r, lineE, α, β, break_r, z, limb)\n\nK: Normalisation.\na: Dimensionless black hole spin.\nθ: Observer inclination (0 is on pole, degrees).\ninner_r: Inner radius of the disk in units of GM/c²\nms_flag: 0: integrate from rᵢₙ. 1: integrate from rₘₛ.\nouter_r: Outer radius of the disk in units of GM/c²\nlineE: Rest frame line energy (keV).\nα\nβ\nbreak_r: Break radius seperating inner and outer disk (GM/c²).\nz: Overall Doppler shift.\nlimb: 0: isotropic emission, 1: Laor's limb darkening, 2: Haard's limb brightening.\n\nExample\n\nenergy = collect(range(0.1, 20.0, 100))\ninvokemodel(energy, XS_KyrLine())\n\n XS_KyrLine\n ┌────────────────────────────────────────┐\n 0.05 │ │\n │ │\n │ : │\n │ :. │\n │ :.': │\n │ :' : │\n │ : : │\n │ .' : │\n │ .:' : │\n │ .' : │\n │ .: : │\n │ .' : │\n │ .:' : │\n │ ..:' : │\n 0 │.........:''' :......│\n └────────────────────────────────────────┘\n 0 8\n E (keV)\n\n\n\n\n\n","category":"type"},{"location":"models/models/#Wrapping-new-XSPEC-models","page":"Model index","title":"Wrapping new XSPEC models","text":"","category":"section"},{"location":"models/models/","page":"Model index","title":"Model index","text":"SpectralFitting exports a helpful macro to facilitate wrapping additional XSPEC models.","category":"page"},{"location":"models/models/","page":"Model index","title":"Model index","text":"@xspecmodel\nSpectralFitting.register_model_data","category":"page"},{"location":"models/models/#SpectralFitting.@xspecmodel","page":"Model index","title":"SpectralFitting.@xspecmodel","text":"@xspecmodel [type=Float64] [ff_call_site] model\n\nUsed to wrap additional XSPEC models, generating the needed AbstractSpectralModel implementation.\n\nThe type keyword specifies the underlying type to coerce input and output arrays to, as different implementations may have incompatible number of bits. The ff_call_site is the foreign fuction call site, which is the first argument to ccall, and follows the same conventions. The model is a struct, which must subtype AbstractSpectralModel.\n\nIf the callsite is not specified, the user must implement _unsafe_ffi_invoke!.\n\nExamples\n\n@xspecmodel :C_powerlaw struct XS_PowerLaw{T} <: AbstractSpectralModel{T, Additive}\n \"Normalisation.\"\n K::T\n \"Photon index.\"\n a::T\nend\n\n# constructor has default values\nfunction XS_PowerLaw(; K = FitParam(1.0), a = FitParam(1.0))\n XS_PowerLaw{typeof(K)}(K, a)\nend\n\nWe define a new structure XS_PowerLaw with two parameters, but since the model is Additive, only a single parameter (a) is passed to the XSPEC function. The function we bind to this model is :C_powerlaw from the XSPEC C wrappers.\n\nThe macro will then generate the following functions\n\nimplementation \ninvoke! \n_safe_ffi_invoke! \n\nIf a callsite was specified, it will also generate:\n\n_unsafe_ffi_invoke! \n\n\n\n\n\n","category":"macro"},{"location":"models/models/#SpectralFitting.register_model_data","page":"Model index","title":"SpectralFitting.register_model_data","text":"SpectralFitting.register_model_data(M::Type{<:AbstractSpectralModel}, model_data::ModelDataInfo...)\nSpectralFitting.register_model_data(M::Type{<:AbstractSpectralModel}, remote_and_local::Tuple{String,String}...)\nSpectralFitting.register_model_data(M::Type{<:AbstractSpectralModel}, filenames::String...)\nSpectralFitting.register_model_data(s::Symbol, filenames::String...)\n\nRegister filenames as model data associated with the model given by type M or symbol s. This function does not download any files, but rather adds the relevant filenames to a lookup which SpectralFitting.download_model_data consults when invoked, and consequently model data is only downloaded when needed.\n\nnote: Note\nIt is good practice to use this method immediately after defining a new model with @xspecmodel to register any required datafiles from the HEASoft source code, and therefore keep relevant information together.\n\nExample\n\n# by type\nregister_model_data(XS_Laor, \"ari.mod\")\n# by symbol\nregister_model_data(:XS_KyrLine, \"KBHline01.fits\")\n\n\n\n\n\n","category":"function"},{"location":"models/models/#Generating-model-fingerprints","page":"Model index","title":"Generating model fingerprints","text":"","category":"section"},{"location":"models/models/","page":"Model index","title":"Model index","text":"To generate the unicode plot to add as a fingerprint, we use a simple function:","category":"page"},{"location":"models/models/","page":"Model index","title":"Model index","text":"using SpectralFitting, UnicodePlots\n\nfunction plotmodel(energy, model)\n flux = invokemodel(energy, model)\n lineplot(\n energy[1:end-1], \n flux, \n title=String(Base.typename(typeof(model)).name), \n xlabel=\"E (keV)\", \n canvas=DotCanvas\n )\nend\n\n# e.g. for XS_PowerLaw()\nenergy = collect(range(0.1, 20.0, 100))\nplotmodel(energy, XS_PowerLaw())","category":"page"},{"location":"examples/examples/#Spectral-fitting-examples","page":"Diverse examples","title":"Spectral fitting examples","text":"","category":"section"},{"location":"examples/examples/","page":"Diverse examples","title":"Diverse examples","text":"using SpectralFitting\nusing Plots\nENV[\"GKSwstype\"]=\"nul\"\nPlots.default(show=false)","category":"page"},{"location":"examples/examples/","page":"Diverse examples","title":"Diverse examples","text":"Below are a number of examples illustrating how this package may be used.","category":"page"},{"location":"examples/examples/#Using-the-model-library","page":"Diverse examples","title":"Using the model library","text":"","category":"section"},{"location":"examples/examples/","page":"Diverse examples","title":"Diverse examples","text":"The model library details a model algebra (see AbstractSpectralModelKind) for composing models together. An example use of this may be to construct a complex model from a series of simpler models, and invoke the models on a given energy grid:","category":"page"},{"location":"examples/examples/","page":"Diverse examples","title":"Diverse examples","text":"using SpectralFitting\nusing Plots \n\nmodel = PhotoelectricAbsorption() * (PowerLaw() + BlackBody()) \n\n# define energy grid\nenergy = collect(range(0.1, 12.0, 100))\n\nflux = invokemodel(energy, model)\n\nplot(energy[1:end-1], flux)","category":"page"},{"location":"examples/examples/","page":"Diverse examples","title":"Diverse examples","text":"Note this energy grid may be arbitrarily spaced, but, like XSPEC, assumes the bins are contiguous, i.e. that the high energy limit of one bin is the low energy limit of the next.","category":"page"},{"location":"examples/examples/","page":"Diverse examples","title":"Diverse examples","text":"The full model library of available models is listed in Model index.","category":"page"},{"location":"parameters/#Parameters","page":"Parameters","title":"Parameters","text":"","category":"section"},{"location":"transitioning-from-xspec/#Transitioning-from-XSPEC","page":"Transitioning from XSPEC","title":"Transitioning from XSPEC","text":"","category":"section"},{"location":"examples/optimizers/#Optimizer-galore","page":"Optimizer galore","title":"Optimizer galore","text":"","category":"section"},{"location":"examples/optimizers/","page":"Optimizer galore","title":"Optimizer galore","text":"Let's fit a spectrum:","category":"page"},{"location":"examples/optimizers/","page":"Optimizer galore","title":"Optimizer galore","text":"using SpectralFitting, Plots\n\nDATADIR = \"...\"\nDATADIR = length(get(ENV, \"CI\", \"\")) > 0 ? @__DIR__() * \"/../../ex-datadir\" : \"/home/lilith/Developer/jl/datasets/xspec/walkthrough\" # hide\nspec1_path = joinpath(DATADIR, \"s54405.pha\")\ndata = OGIPDataset(spec1_path) \nnormalize!(data)\n\nmask_energies!(data, 1, 15)\n\n# a plotting utility\nmy_plot(data) = plot(\n data, \n xscale = :log10, \n yscale = :log10,\n ylims = (1e-3, 1.3)\n)\n\nmy_plot(data)","category":"page"},{"location":"examples/sherpa-example/#A-quick-guide-to-modelling-and-fitting-in-SpectralFitting.jl","page":"A quick guide","title":"A quick guide to modelling and fitting in SpectralFitting.jl","text":"","category":"section"},{"location":"examples/sherpa-example/","page":"A quick guide","title":"A quick guide","text":"This is SpectralFitting.jl version of A quick guide to modeling and fitting in Sherpa.","category":"page"},{"location":"examples/sherpa-example/","page":"A quick guide","title":"A quick guide","text":"using SpectralFitting, Plots\nusing Random\nRandom.seed!(0)\n\nx = collect(range(-5, 5, 200))\n\nA_true = 3.0\npos_true = 1.3\nsigma_true = 0.8\nerr_true = 0.2\n\ny = @. A_true * exp(-(x - pos_true)^2 / (2 * sigma_true^2))\n\ny_noisy = y .+ (0.2 * randn(length(y)))\n\nscatter(x, y_noisy)","category":"page"},{"location":"examples/sherpa-example/","page":"A quick guide","title":"A quick guide","text":"To make this into a fittable dataset, we observe that our layout is injective (i.e. length(x) == length(y)). This is subtly different from how the majority of spectral models are implemented, which usually assume some kind of binning (length(x) == length(y) + 1). Fortunately, SpectralFitting.jl can track this for us, and do various conversion to make the models work correctly for the data. We need only tell the package what our AbstractLayout is:","category":"page"},{"location":"examples/sherpa-example/","page":"A quick guide","title":"A quick guide","text":"data = InjectiveData(x, y_noisy; name = \"example\")","category":"page"},{"location":"examples/sherpa-example/","page":"A quick guide","title":"A quick guide","text":"The data prints the data card, which provides us some high level information about our data at a glance. We can plot the data trivially using one of the Plots.jl recipes","category":"page"},{"location":"examples/sherpa-example/","page":"A quick guide","title":"A quick guide","text":"plot(data, markersize = 3)","category":"page"},{"location":"examples/sherpa-example/","page":"A quick guide","title":"A quick guide","text":"Next we want to specify a model to fit to this data. Models that are prefixed with XS_ are models that are linked from the XSPEC model library, provided via LibXSPEC_jll. For a full list of the models, see Models index.","category":"page"},{"location":"examples/sherpa-example/","page":"A quick guide","title":"A quick guide","text":"warning: Warning\nIt is advised to use the Julia implemented models. This allows various calculations to benefit from automatic differentiation, efficient multi-threading, GPU offloading, and various other useful things, see Why & how.","category":"page"},{"location":"examples/sherpa-example/","page":"A quick guide","title":"A quick guide","text":"model = GaussianLine(μ = FitParam(0.0))","category":"page"},{"location":"examples/sherpa-example/","page":"A quick guide","title":"A quick guide","text":"We can plot our model over the same domain range quite easily too:","category":"page"},{"location":"examples/sherpa-example/","page":"A quick guide","title":"A quick guide","text":"plot(data.domain[1:end-1], invokemodel(data.domain, model))","category":"page"},{"location":"examples/sherpa-example/","page":"A quick guide","title":"A quick guide","text":"Note that we've had to adjust the domain here. As stated before, most models are implemented for binned data, and therefore return one fewer bin than given.","category":"page"},{"location":"examples/sherpa-example/","page":"A quick guide","title":"A quick guide","text":"SpectralFitting.jl adopts the SciML problem-solver abstraction, so to fit a model to data we specify a FittingProblem:","category":"page"},{"location":"examples/sherpa-example/","page":"A quick guide","title":"A quick guide","text":"prob = FittingProblem(model => data)","category":"page"},{"location":"examples/sherpa-example/","page":"A quick guide","title":"A quick guide","text":"We fit problem then by calling fit:","category":"page"},{"location":"examples/sherpa-example/","page":"A quick guide","title":"A quick guide","text":"result = fit(prob, LevenbergMarquadt())","category":"page"},{"location":"examples/sherpa-example/","page":"A quick guide","title":"A quick guide","text":"The result card tells us a little bit about how successful the fit was. We further inspect the fit by overplotting result on the data:","category":"page"},{"location":"examples/sherpa-example/","page":"A quick guide","title":"A quick guide","text":"plot(data, markersize = 3)\nplot!(result)","category":"page"},{"location":"examples/sherpa-example/","page":"A quick guide","title":"A quick guide","text":"We can create a contour plot of the fit statistic by evaluating the result everywhere on the grid and measuring the statistic:","category":"page"},{"location":"examples/sherpa-example/","page":"A quick guide","title":"A quick guide","text":"amps = range(50, 200, 50)\ndevs = range(0.5, 1.2, 50)\n\nstats = [\n measure(ChiSquared(), result, [a, result.u[2], d])\n for d in devs, a in amps\n]\n\n# 1, 2, and 3 sigma contours\nlevels = [2.3, 4.61, 9.21]\ncontour(\n amps, \n devs, \n stats .- result.χ2, \n levels = levels, \n xlabel = \"K\", \n ylabel = \"σ\"\n)\nscatter!([result.u[1]], [result.u[3]])","category":"page"},{"location":"examples/sherpa-example/#Simultaneous-fits","page":"A quick guide","title":"Simultaneous fits","text":"","category":"section"},{"location":"models/composite-models/#Composite-models","page":"Composite models","title":"Composite models","text":"","category":"section"},{"location":"models/composite-models/","page":"Composite models","title":"Composite models","text":"The model algebra defined by the AbstractSpectralModelKind yields instances of CompositeModel, nested to various degrees. These composite models are designed to make as much information about the spectral model available at compile-time, such that rich and optimized generated functions may be assembled purely from the Julia types (see Why & how).","category":"page"},{"location":"models/composite-models/","page":"Composite models","title":"Composite models","text":"CompositeModel\nAbstractCompositeOperator\nAdditionOperator\nMultiplicationOperator\nConvolutionOperator\noperation_symbol","category":"page"},{"location":"models/composite-models/#SpectralFitting.CompositeModel","page":"Composite models","title":"SpectralFitting.CompositeModel","text":"CompositeModel{M1,M2,O} <: AbstractSpectralModel\nCompositeModel(left_model, right_model, op::AbstractCompositeOperator)\n\nType resulting from operations combining any number of AbstractSpectralModel via the model algebra defined from AbstractSpectralModelKind.\n\nEach operation binary operation in the model algebra is encoded in the parametric types of the CompositeModel, where the operation is given by an AbstractCompositeOperator. Composite models adopt the model kind of the right model, i.e. M2, and obey the model algebra accordingly.\n\nComposite models very rarely need to be constructed directly, and are instead obtained by regular model operations.\n\nExample\n\nmodel = PhotoelectricAbsorption() * (PowerLaw() + BlackBody())\ntypeof(model) <: CompositeModel # true\n\n\n\n\n\n","category":"type"},{"location":"models/composite-models/#SpectralFitting.AbstractCompositeOperator","page":"Composite models","title":"SpectralFitting.AbstractCompositeOperator","text":"abstract type AbstractCompositeOperator\n\nSuperype of all composition operators. Used to implement the model algebra of AbstractSpectralModelKind through a trait system.\n\nThe Julia symbol corresponding to a given AbstractCompositeOperator may be obtained through operation_symbol.\n\n\n\n\n\n","category":"type"},{"location":"models/composite-models/#SpectralFitting.AdditionOperator","page":"Composite models","title":"SpectralFitting.AdditionOperator","text":"AdditionOperator <: AbstractCompositeOperator\nAdditionOperator()\n\nCorresponds to the :(+) symbol.\n\n\n\n\n\n","category":"type"},{"location":"models/composite-models/#SpectralFitting.MultiplicationOperator","page":"Composite models","title":"SpectralFitting.MultiplicationOperator","text":"MultiplicationOperator <: AbstractCompositeOperator\nMultiplicationOperator()\n\nCorresponds to the :(*) symbol.\n\n\n\n\n\n","category":"type"},{"location":"models/composite-models/#SpectralFitting.ConvolutionOperator","page":"Composite models","title":"SpectralFitting.ConvolutionOperator","text":"ConvolutionOperator <: AbstractCompositeOperator\nConvolutionOperator()\n\nHas no corresponding symbol, since it invokes a function call C(A).\n\n\n\n\n\n","category":"type"},{"location":"models/composite-models/#SpectralFitting.operation_symbol","page":"Composite models","title":"SpectralFitting.operation_symbol","text":"operation_symbol(::AbstractCompositeOperator)\noperation_symbol(::Type{<:AbstractCompositeOperator})\n\nObtain the model symbol from a given AbstractCompositeOperator.\n\n\n\n\n\n","category":"function"},{"location":"datasets/datasets/#Datasets","page":"Using datasets","title":"Datasets","text":"","category":"section"},{"location":"datasets/datasets/","page":"Using datasets","title":"Using datasets","text":"SpectralFitting.jl supports a wide variety of datasets, and makes it easy to wrap your own.","category":"page"},{"location":"datasets/datasets/","page":"Using datasets","title":"Using datasets","text":"For spectral fitting specifics, the main dataset type is","category":"page"},{"location":"datasets/datasets/","page":"Using datasets","title":"Using datasets","text":"SpectralData","category":"page"},{"location":"datasets/datasets/#SpectralFitting.SpectralData","page":"Using datasets","title":"SpectralFitting.SpectralData","text":"SpectralData{T} <: AbstractDataset\n\nA general spectral data structure, minimally with a Spectrum and ResponseMatrix. Optionally also includes the AncillaryResponse and a background Spectrum.\n\nMethods\n\nThe following methods are made available through the SpectralData:\n\nregroup!\nrestrict_domain!\nmask_energies!\ndrop_channels!\ndrop_bad_channels!\ndrop_negative_channels!\nnormalize!\nobjective_units\nspectrum_energy\nbin_widths\nsubtract_background!\nset_domain!\nerror_statistic\n\nConstructors\n\nThe available constructors are:\n\nSpectralData(paths::SpectralDataPaths; kwargs...)\n\nUsing SpectralDataPaths.\n\nIf the spectrum and repsonse matrix have already been loaded seperately, use\n\nSpectralData(\n spectrum::Spectrum,\n response::ResponseMatrix;\n # try to match the domains of the response matrix to the data\n match_domains = true,\n background = nothing,\n ancillary = nothing,\n)\n\n\n\n\n\n","category":"type"},{"location":"datasets/datasets/#Dataset-abstraction","page":"Using datasets","title":"Dataset abstraction","text":"","category":"section"},{"location":"datasets/datasets/","page":"Using datasets","title":"Using datasets","text":"Datasets must define a small API to make fitting possible. The picture to have in mind when considering the different domains is as follows: the model is trying to predict the objective. It does so by taking in input domain and maps it to some output domain.","category":"page"},{"location":"datasets/datasets/","page":"Using datasets","title":"Using datasets","text":"That means make_output_domain and make_objective_domain correspond to the (XY) values of the data that the model is trying to fit, whilst the model is evaluated on the make_model_domain, which need not be the same as the output domain.","category":"page"},{"location":"datasets/datasets/","page":"Using datasets","title":"Using datasets","text":"In other cases, the objective_transformer acts to transform the output of the model onto the output domain. ","category":"page"},{"location":"datasets/datasets/","page":"Using datasets","title":"Using datasets","text":"Mathematically, expressing the output domain X, the model domain D, the model output M(D) and objective S, along with the transformer as T, then the relationship between the different domains is","category":"page"},{"location":"datasets/datasets/","page":"Using datasets","title":"Using datasets","text":"hatS = T times M(D)","category":"page"},{"location":"datasets/datasets/","page":"Using datasets","title":"Using datasets","text":"Both hatS and S are defined over X. The various fitting operations try to find model paramters that make hatS and S as close as possible.","category":"page"},{"location":"datasets/datasets/","page":"Using datasets","title":"Using datasets","text":"AbstractDataset\nmake_objective_variance\nmake_objective\nmake_domain_variance\nmake_model_domain\nmake_output_domain","category":"page"},{"location":"datasets/datasets/#SpectralFitting.AbstractDataset","page":"Using datasets","title":"SpectralFitting.AbstractDataset","text":"abstract type AbstractDataset\n\nAbstract type for use in fitting routines. High level representation of some underlying data structures. \n\nFitting data is considered to have an objective and a domain. As the domain may be, for example, energy bins (high and low), or fourier frequencies (single value), the purpose of this abstraction is to provide some facility for translating between these representations for the models to fit with. This is done by checking that the AbstractLayout of the model and data are compatible, or at least have compatible translations.\n\nMust implement a minimal set of accessor methods. These are paired with objective and domain parlance. Note that these functions are prefixed with make_* and not get_* to represent that there may be allocations or work going into the translation. Usage of these functions should be sparse in the interest of performance.\n\nThe arrays returned by the make_* functions must correspond to the AbstractLayout specified by the caller.\n\nmake_objective_variance\nmake_objective\nmake_domain_variance\nmake_model_domain\nmake_ouput_domain\n\nAdditionally there is an objective transformer that transforms the output of the model onto the output domain:\n\nobjective_transformer\n\nFinally, to make all of the fitting for different statistical regimes work efficiently, datasets should inform which units are preferred to fit. They may also give the error statistics they prefer, and a label name primarily used to disambiguate:\n\npreferred_units\nerror_statistic\nmake_label\n\n\n\n\n\n","category":"type"},{"location":"datasets/datasets/#SpectralFitting.make_objective_variance","page":"Using datasets","title":"SpectralFitting.make_objective_variance","text":"make_objective_variance(layout::AbstractLayout, dataset::AbstractDataset)\n\nMake the variance vector associated with each objective point.\n\n\n\n\n\n","category":"function"},{"location":"datasets/datasets/#SpectralFitting.make_objective","page":"Using datasets","title":"SpectralFitting.make_objective","text":"make_objective(layout::AbstractLayout, dataset::AbstractDataset)\n\nReturns the array used as the target for model fitting. The array must correspond to the data AbstractLayout specified by the layout parameter.\n\nIn as far as it can be guarunteed, the memory in the returned array will not be mutated by any fitting procedures.\n\nDomain for this objective should be returned by make_model_domain.\n\n\n\n\n\n","category":"function"},{"location":"datasets/datasets/#SpectralFitting.make_domain_variance","page":"Using datasets","title":"SpectralFitting.make_domain_variance","text":"make_domain_variance(layout::AbstractLayout, dataset::AbstractDataset)\n\nMake the variance vector associated with the domain.\n\n\n\n\n\n","category":"function"},{"location":"datasets/datasets/#SpectralFitting.make_model_domain","page":"Using datasets","title":"SpectralFitting.make_model_domain","text":"make_model_domain(layout::AbstractLayout, dataset::AbstractDataset)\n\nReturns the array used as the domain for the modelling. This is paired with make_domain_variance\n\n\n\n\n\n","category":"function"},{"location":"datasets/datasets/#SpectralFitting.make_output_domain","page":"Using datasets","title":"SpectralFitting.make_output_domain","text":"make_output_domain(layout::AbstractLayout, dataset::AbstractDataset)\n\nReturns the array used as the output domain. That is, in cases where the model input and output map to different domains, the input domain is said to be the model domain, the input domain is said to be the model domain. \n\nThe distinction is mainly used for the purposes of simulating data and for visualising data.\n\n\n\n\n\n","category":"function"},{"location":"datasets/datasets/#Underlying-data-layouts","page":"Using datasets","title":"Underlying data layouts","text":"","category":"section"},{"location":"datasets/datasets/","page":"Using datasets","title":"Using datasets","text":"AbstractLayout\nOneToOne\nContiguouslyBinned\ncommon_support\npreferred_support\nsupports","category":"page"},{"location":"datasets/datasets/#SpectralFitting.AbstractLayout","page":"Using datasets","title":"SpectralFitting.AbstractLayout","text":"abstract type AbstractLayout end\n\nThe data layout primarily concerns the relationship between the objective and the domain. It is used to work out whether a model and a dataset are fittable, and if not, whether a translation in the output of the model to the domain of the model is possible.\n\nThe following methods may be used to interrogate support:\n\npreferred_support for inferring the preferred support of a model when multiple supports are possible.\ncommon_support to obtain the common support of two structures\n\nThe following method is also used to define the support of a model or dataset:\n\nsupports\n\nFor cases where unit information needs to be propagated, an AbstractLayout can also be used to ensure the units are compatible. To query the units of a layout, use\n\nsupport_units\n\n\n\n\n\n","category":"type"},{"location":"datasets/datasets/#SpectralFitting.OneToOne","page":"Using datasets","title":"SpectralFitting.OneToOne","text":"struct OneToOne <: AbstractLayout end\n\nIndicates there is a one-to-one (injective) correspondence between each input value and each output value. That is to say\n\nlength(objective) == length(domain)\n\n\n\n\n\n","category":"type"},{"location":"datasets/datasets/#SpectralFitting.ContiguouslyBinned","page":"Using datasets","title":"SpectralFitting.ContiguouslyBinned","text":"struct ContiguouslyBinned <: AbstractLayout end\n\nContiguously binned data layout means that the domain describes high and low bins, with the objective being the value in that bin. This means\n\nlength(objective) + 1== length(domain)\n\nNote that the contiguous qualifer is to mean there is no gaps in the bins, and that\n\nDelta E_i = E_i+1 - E_i\n\n\n\n\n\n","category":"type"},{"location":"datasets/datasets/#SpectralFitting.common_support","page":"Using datasets","title":"SpectralFitting.common_support","text":"common_support(x, y)\n\nFind the common AbstractLayout of x and y, following the ordering of preferred_support.\n\n\n\n\n\n","category":"function"},{"location":"datasets/datasets/#SpectralFitting.preferred_support","page":"Using datasets","title":"SpectralFitting.preferred_support","text":"preferred_support(x)\n\nGet the preferred AbstractLayout of x. If multiple supports are available, the DEFAULT_SUPPORT_ORDERING is followed:\n\nDEFAULT_SUPPORT_ORDERING = (ContiguouslyBinned{Nothing}(nothing), OneToOne{Nothing}(nothing))\n\n\n\n\n\n","category":"function"},{"location":"datasets/datasets/#SpectralFitting.supports","page":"Using datasets","title":"SpectralFitting.supports","text":"supports(x::Type)\n\nUsed to define whether a given type has support for a specific AbstractLayout. Should return a tuple of the supported layouts. This method should be implemented to express new support, not the query method. \n\nTo query, there is\n\nsupports(layout::AbstractLayout, x)::Bool\n\nExample\n\nsupports(::Type{typeof(x)}) = (OneToOne(),)\n@assert supports(ContiguouslyBinned(), x) == false\n\n\n\n\n\n","category":"function"},{"location":"why-and-how/#Why-and-how","page":"Why & How","title":"Why & how","text":"","category":"section"},{"location":"why-and-how/","page":"Why & How","title":"Why & How","text":"SpectralFitting.jl is a package for fitting models to spectral data, similar to XSPEC, Sherpa or ISIS.","category":"page"},{"location":"why-and-how/","page":"Why & How","title":"Why & How","text":"The rationale for this package is to provide a unanimous interface for different model libraries, and to leverage advancements in the computional methods that are available in Julia, including the rich statistics ecosystem, with automatic-differentiation and speed.","category":"page"},{"location":"why-and-how/","page":"Why & How","title":"Why & How","text":"Longer term ambitions include","category":"page"},{"location":"why-and-how/","page":"Why & How","title":"Why & How","text":"Multi-wavelength fits\nRadiative transfer embedded into the package\nSpectral and timing fits","category":"page"},{"location":"why-and-how/","page":"Why & How","title":"Why & How","text":"SpectralFitting aims to provide highly optimised and flexible fitting algorithms, along with a library of spectral models, for use in any field of Astronomy that concerns itself with spectral data.","category":"page"},{"location":"why-and-how/#Rewriting-model-calls-during-invocation","page":"Why & How","title":"Rewriting model calls during invocation","text":"","category":"section"},{"location":"why-and-how/","page":"Why & How","title":"Why & How","text":"using SpectralFitting","category":"page"},{"location":"why-and-how/","page":"Why & How","title":"Why & How","text":"SpectralFitting.jl tries to optimise model invocation through source-rewriting. For compatibility with the XSPEC model library, this is achieved through aggressive pre-allocation and shuffling of output vectors. All XSPEC models output the result of their calculation through side effects into a flux array passed as an argument, and therefore each model invocation requires its own output before addition or multiplication of fluxes may occur.","category":"page"},{"location":"why-and-how/","page":"Why & How","title":"Why & How","text":"Principally, combining several models together would look like this:","category":"page"},{"location":"why-and-how/","page":"Why & How","title":"Why & How","text":"energy = collect(range(0.1, 20.0, 100))\n\nflux1 = invokemodel(energy, XS_PowerLaw())\nflux2 = invokemodel(energy, XS_PowerLaw(a=FitParam(3.0)))\nflux3 = invokemodel(energy, XS_BlackBody())\nflux4 = invokemodel(energy, XS_PhotoelectricAbsorption())\n\ntotal_flux = @. flux4 * (flux1 + flux2 + flux3)\nsum(total_flux)","category":"page"},{"location":"why-and-how/","page":"Why & How","title":"Why & How","text":"But these operations could also be performed in a different order:","category":"page"},{"location":"why-and-how/","page":"Why & How","title":"Why & How","text":"flux1 = invokemodel(energy, XS_PowerLaw())\nflux2 = invokemodel(energy, XS_PowerLaw(a=FitParam(3.0)))\ntotal_flux = @. flux1 + flux2\n\nflux3 = invokemodel(energy, XS_BlackBody())\n@. total_flux = total_flux + flux3\n\nflux4 = invokemodel(energy, XS_PhotoelectricAbsorption())\n@. total_flux = total_flux * flux4\nsum(total_flux)","category":"page"},{"location":"why-and-how/","page":"Why & How","title":"Why & How","text":"Doing so would allow us to only pre-allocate 2 flux arrays, instead of 4 when using the in-place variants:","category":"page"},{"location":"why-and-how/","page":"Why & How","title":"Why & How","text":"fluxes = zeros(Float64, (length(energy) - 1, 2))\nflux1, flux2 = eachcol(fluxes)\n\ninvokemodel!(flux1, energy, XS_PowerLaw())\ninvokemodel!(flux2, energy, XS_PowerLaw(a=FitParam(3.0)))\n@. flux1 = flux1 + flux2\n\ninvokemodel!(flux2, energy, XS_BlackBody())\n@. flux1 = flux1 + flux2\n\ninvokemodel!(flux2, energy, XS_PhotoelectricAbsorption())\n@. flux1 = flux1 * flux2\nsum(flux1)","category":"page"},{"location":"why-and-how/","page":"Why & How","title":"Why & How","text":"It is precisely this re-writing that SpectralFitting performs via @generated functions. We can inspect the code used to generate the invocation body after defining a CompositeModel:","category":"page"},{"location":"why-and-how/","page":"Why & How","title":"Why & How","text":"model = XS_PhotoelectricAbsorption() * (\n XS_PowerLaw() + XS_PowerLaw(a=FitParam(3.0)) + XS_BlackBody()\n)\n\nparams = get_value.(SpectralFitting.parameter_tuple(model))\nSpectralFitting.Reflection.assemble_composite_model_call(typeof(model), typeof(params))","category":"page"},{"location":"why-and-how/","page":"Why & How","title":"Why & How","text":"This generated function also takes care of some other things for us, such as unpacking parameters (optionally unpacking frozen parameters separately), and ensuring any closure are passed to invokemodel if a model needs them (e.g., SurrogateSpectralModel).","category":"page"},{"location":"why-and-how/","page":"Why & How","title":"Why & How","text":"This is achieved by moving as much information as possible about the model and its construction to its type, such that all of the invocation and parameter unpacking may be inferred at compile time.","category":"page"},{"location":"why-and-how/","page":"Why & How","title":"Why & How","text":"note: Note\nWith the addition of more pure-Julia models, non-allocating methods without aggressive pre-allocation are possible, and will be added in the future. Such methods may allow models to add or multiply in-place on the total flux array, instead of relying on later broadcasts.","category":"page"},{"location":"fitting/#Fitting-spectral-models","page":"Fitting spectral models","title":"Fitting spectral models","text":"","category":"section"},{"location":"reference/#API-reference","page":"Reference","title":"API reference","text":"","category":"section"},{"location":"reference/#Utility-functions-for-defining-new-models","page":"Reference","title":"Utility functions for defining new models","text":"","category":"section"},{"location":"reference/","page":"Reference","title":"Reference","text":"SpectralFitting.finite_diff_kernel!\nwrap_model_as_objective","category":"page"},{"location":"reference/#SpectralFitting.finite_diff_kernel!","page":"Reference","title":"SpectralFitting.finite_diff_kernel!","text":"finite_diff_kernel!(f::Function, flux, energy)\n\nCalculates the finite difference of the function f over the energy bin between the high and low bin edges, via\n\nc_i = f(E_itexthigh) - f(E_itextlow)\n\nsimilar to evaluating the limits of the integral between E_itexthigh and E_itextlow.\n\nThis utility function is primarily used for Additive models to ensure the flux per bin is normalised for the energy over the bin.\n\n\n\n\n\n","category":"function"},{"location":"reference/#SpectralFitting.wrap_model_as_objective","page":"Reference","title":"SpectralFitting.wrap_model_as_objective","text":"wrap_model_as_objective(model::AbstractSpectralModel; ΔE = 1e-1)\nwrap_model_as_objective(M::Type{<:AbstractSpectralModel}; ΔE = 1e-1)\n\nWrap a spectral model into an objective function for building/optimizing a surrogate model. Returns an anonymous function taking the tuple (E, params...) as the argument, and returning a single flux value.\n\n\n\n\n\n","category":"function"},{"location":"reference/#General-reference","page":"Reference","title":"General reference","text":"","category":"section"},{"location":"reference/","page":"Reference","title":"Reference","text":"Modules = [SpectralFitting, SpectralFitting.Reflection]\nOrder = [:function, :type]","category":"page"},{"location":"reference/#Base.copy-Tuple{AbstractTableModel}","page":"Reference","title":"Base.copy","text":"Base.copy(m::AbstractTableModel)\n\nCreate a copy of an AbstractTableModel. This will copy all fields except the table field, which is assumed to be a constant set of values that can be shared by multiple copies.\n\nWhen this is not the case, the user should redefine Base.copy for their particular table model to copy the table as needed.\n\n\n\n\n\n","category":"method"},{"location":"reference/#Base.copy-Tuple{AsConvolution}","page":"Reference","title":"Base.copy","text":"Base.copy(m::AsConvolution)\n\nCreates a copy of an AsConvolution wrapped model. Will make a deepcopy of the cache to elimiate possible thread contention, but does not copy the domain.\n\n\n\n\n\n","category":"method"},{"location":"reference/#SpectralFitting._accumulated_indices-Tuple{Any}","page":"Reference","title":"SpectralFitting._accumulated_indices","text":"_accumulated_indices(items)\n\nitems is a tuple or vector of lengths n1, n2, ...\n\nReturns a tuple or array with same length as items, which gives the index boundaries of an array with size n1 + n2 + ....\n\n\n\n\n\n","category":"method"},{"location":"reference/#SpectralFitting._bind_pairs!-Tuple{FittingProblem, Vararg{Pair{Int64, Symbol}}}","page":"Reference","title":"SpectralFitting._bind_pairs!","text":"Bind the symbols of last(pair) in all models indexes by first(pair).\n\n\n\n\n\n","category":"method"},{"location":"reference/#SpectralFitting._safe_ffi_invoke!-Tuple{Any, Any, Any, Type{<:AbstractSpectralModel}}","page":"Reference","title":"SpectralFitting._safe_ffi_invoke!","text":"function _safe_ffi_invoke!(output, input, params, ModelType::Type{<:AbstractSpectralModel})\n\nWrapper to do a foreign function call to an XSPEC model.\n\n\n\n\n\n","category":"method"},{"location":"reference/#SpectralFitting._subtract_background!-NTuple{9, Any}","page":"Reference","title":"SpectralFitting._subtract_background!","text":"Does the background subtraction and returns units of counts. That means we have multiplied through by a factor t_D relative to the reference equation (2.3) in the XSPEC manual.\n\n\n\n\n\n","category":"method"},{"location":"reference/#SpectralFitting._unsafe_ffi_invoke!-Tuple{Any, Any, Any, Any, Type{<:AbstractSpectralModel}}","page":"Reference","title":"SpectralFitting._unsafe_ffi_invoke!","text":"function _unsafe_ffi_invoke!(\n output,\n error_vec,\n input,\n params,\n ModelType::Type{<:AbstractSpectralModel},\n)\n\nWrapper to do a foreign function call to an XSPEC model.\n\nSee also _safe_ffi_invoke!.\n\n\n\n\n\n","category":"method"},{"location":"reference/#SpectralFitting.allocate_model_output-Union{Tuple{T2}, Tuple{T1}, Tuple{AbstractSpectralModel{T1}, AbstractVector{T2}}} where {T1, T2}","page":"Reference","title":"SpectralFitting.allocate_model_output","text":"allocate_model_output(model::AbstractSpectralModel, domain::AbstractVector)\n\nAllocate the output space for the AbstractSpectralModel for a given domain. The output type will be the promoted element type of the model and domain.\n\nUses construct_objective_cache to construct the appropriate layout.\n\n\n\n\n\n","category":"method"},{"location":"reference/#SpectralFitting.common_support-Tuple{Any, Any}","page":"Reference","title":"SpectralFitting.common_support","text":"common_support(x, y)\n\nFind the common AbstractLayout of x and y, following the ordering of preferred_support.\n\n\n\n\n\n","category":"method"},{"location":"reference/#SpectralFitting.count_error-Tuple{Any, Any}","page":"Reference","title":"SpectralFitting.count_error","text":"count_error(k, σ)\n\nGives the error on k mean photon counts to a significance of σ standard deviations about the mean.\n\nDerived from likelihood of binomial distributions being the beta function.\n\n\n\n\n\n","category":"method"},{"location":"reference/#SpectralFitting.download_all_model_data-Tuple{}","page":"Reference","title":"SpectralFitting.download_all_model_data","text":"SpectralFitting.download_all_model_data()\n\nDownloads all model data for the models currently registered with SpectralFitting.register_model_data. Calls SpectralFitting.download_model_data to perform the download.\n\n\n\n\n\n","category":"method"},{"location":"reference/#SpectralFitting.download_model_data-Tuple{Type{<:AbstractSpectralModel}}","page":"Reference","title":"SpectralFitting.download_model_data","text":"SpectralFitting.download_model_data(model::AbstractSpectralModel; kwargs...)\nSpectralFitting.download_model_data(M::Type{<:AbstractSpectralModel}; kwargs...)\nSpectralFitting.download_model_data(s::Symbol; kwargs...)\n\nDownloads the model data for a model specified either by model, type M, or symbol s. Datafiles associated with a specific model may be registered using SpectralFitting.register_model_data. The download is currently unconfigurable, but permits slight control via a number of keyword arguments:\n\nprogress::Bool = true\n\nDisplay a progress bar for the download.\n\nmodel_source_url::String = \"http://www.star.bris.ac.uk/fbaker/XSPEC-model-data\"\n\nThe source URL used to download the model data.\n\nAll standard XSPEC spectral model data is currently being hosted on the University of Bristol astrophysics servers, and should be persistently available to anyone.\n\n\n\n\n\n","category":"method"},{"location":"reference/#SpectralFitting.error_statistic-Tuple{AbstractDataset}","page":"Reference","title":"SpectralFitting.error_statistic","text":"error_statistic(::AbstractDataset)\n\nShould return an ErrorStatistics describing which error statistic this data uses.\n\nIf undefined for a derived type, returns ErrorStatistics.Unknown.\n\n\n\n\n\n","category":"method"},{"location":"reference/#SpectralFitting.finite_diff_kernel!-Tuple{Function, Any, Any}","page":"Reference","title":"SpectralFitting.finite_diff_kernel!","text":"finite_diff_kernel!(f::Function, flux, energy)\n\nCalculates the finite difference of the function f over the energy bin between the high and low bin edges, via\n\nc_i = f(E_itexthigh) - f(E_itextlow)\n\nsimilar to evaluating the limits of the integral between E_itexthigh and E_itextlow.\n\nThis utility function is primarily used for Additive models to ensure the flux per bin is normalised for the energy over the bin.\n\n\n\n\n\n","category":"method"},{"location":"reference/#SpectralFitting.folded_energy-Union{Tuple{ResponseMatrix{T}}, Tuple{T}} where T","page":"Reference","title":"SpectralFitting.folded_energy","text":"folded_energy(response::ResponseMatrix)\n\nGet the contiguously binned energy corresponding to the output (folded) domain of the response matrix. That is, the channel energies as used by the spectrum.\n\n\n\n\n\n","category":"method"},{"location":"reference/#SpectralFitting.get_tickslogscale-Union{Tuple{Tuple{T, T}}, Tuple{T}} where T<:AbstractFloat","page":"Reference","title":"SpectralFitting.get_tickslogscale","text":"get_tickslogscale(lims; skiplog=false)\n\nReturn a tuple (ticks, ticklabels) for the axis limit lims where multiples of 10 are major ticks with label and minor ticks have no label skiplog argument should be set to true if lims is already in log scale.\n\nModified from https://github.com/JuliaPlots/Plots.jl/issues/3318.\n\n\n\n\n\n","category":"method"},{"location":"reference/#SpectralFitting.implementation-Tuple{Type{<:AbstractSpectralModel}}","page":"Reference","title":"SpectralFitting.implementation","text":"implementation(model::AbstractSpectralModel)\nimplementation(::Type{<:AbstractSpectralModel})\n\nGet the AbstractSpectralModelImplementation for a given AbstractSpectralModel or model type.\n\nThis is used primarily to learn what optimizations we can do with a model, for example propagating auto-diff gradients through a model or arbitrary precision numbers.\n\n\n\n\n\n","category":"method"},{"location":"reference/#SpectralFitting.invoke!-Tuple{Any, Any, AbstractSpectralModel}","page":"Reference","title":"SpectralFitting.invoke!","text":"SpectralFitting.invoke!(output, domain, M::Type{<:AbstractSpectralModel}, params...)\n\nUsed to define the behaviour of models. Should calculate the output of the model and write in-place into output. The model parameters are passed in the model structure.\n\nwarning: Warning\nThis function should not be called directly. Use invokemodel instead. invoke! is only to define the model, not to use it. Users should always call models using invokemodel or invokemodel! to ensure normalisations and closures are accounted for.\n\nExample\n\nBase.@kwdef struct MyModel{T} <: AbstractSpectralModel{T,Multiplicative}\n p1::T = FitParam(1.0)\n p2::T = FitParam(2.0)\n p3::T = FitParam(3.0)\nend\n\nwould have the arguments passed to invoke! as\n\nfunction SpectralFitting.invoke!(output, domain, model::MyModel)\n # ...\nend\n\n\n\n\n\n","category":"method"},{"location":"reference/#SpectralFitting.invokemodel!-Tuple{Any, Any, AbstractSpectralModel{<:FitParam}}","page":"Reference","title":"SpectralFitting.invokemodel!","text":"invokemodel!(output, domain, model)\ninvokemodel!(output, domain, model, params::AbstractVector)\ninvokemodel!(output, domain, model, params::ParameterCache)\n\nIn-place variant of invokemodel, calculating the output of an AbstractSpectralModel given by model, optionally overriding the parameters using a ParameterCache or an AbstractVector.\n\nThe output may not necessarily be a single vector, and one should use allocate_model_output to allocate the output structure.\n\nExample\n\nmodel = PowerLaw()\ndomain = collect(range(0.1, 20.0, 100))\noutput = allocate_model_output(model, domain)\ninvokemodel!(output, domain, model)\n\n\n\n\n\n","category":"method"},{"location":"reference/#SpectralFitting.invokemodel-Tuple{Any, AbstractSpectralModel}","page":"Reference","title":"SpectralFitting.invokemodel","text":"invokemodel(domain, model::AbstractSpectralModel)\n\nInvoke the AbstractSpectralModel given by model over the domain domain.\n\nThis function will perform any normalisation or post-processing tasks that a specific model kind may require, e.g. multiplying by a normalisation constant for Additive models.\n\nnote: Note\n\n\ninvokemodel allocates the needed output arrays based on the element type of free_params to allow automatic differentation libraries to calculate parameter gradients.\n\nIn-place non-allocating variants are the invokemodel! functions.\n\nExample\n\nmodel = PowerLaw()\ndomain = collect(range(0.1, 20.0, 100))\n\ninvokemodel(domain, model)\n\n\n\n\n\n","category":"method"},{"location":"reference/#SpectralFitting.make_domain_variance-Tuple{AbstractLayout, AbstractDataset}","page":"Reference","title":"SpectralFitting.make_domain_variance","text":"make_domain_variance(layout::AbstractLayout, dataset::AbstractDataset)\n\nMake the variance vector associated with the domain.\n\n\n\n\n\n","category":"method"},{"location":"reference/#SpectralFitting.make_label-Tuple{AbstractDataset}","page":"Reference","title":"SpectralFitting.make_label","text":"make_label(d::AbstractDataset)\n\nReturn a string that gives a descriptive label for this dataset.\n\n\n\n\n\n","category":"method"},{"location":"reference/#SpectralFitting.make_model_domain-Tuple{AbstractLayout, AbstractDataset}","page":"Reference","title":"SpectralFitting.make_model_domain","text":"make_model_domain(layout::AbstractLayout, dataset::AbstractDataset)\n\nReturns the array used as the domain for the modelling. This is paired with make_domain_variance\n\n\n\n\n\n","category":"method"},{"location":"reference/#SpectralFitting.make_objective-Tuple{AbstractLayout, AbstractDataset}","page":"Reference","title":"SpectralFitting.make_objective","text":"make_objective(layout::AbstractLayout, dataset::AbstractDataset)\n\nReturns the array used as the target for model fitting. The array must correspond to the data AbstractLayout specified by the layout parameter.\n\nIn as far as it can be guarunteed, the memory in the returned array will not be mutated by any fitting procedures.\n\nDomain for this objective should be returned by make_model_domain.\n\n\n\n\n\n","category":"method"},{"location":"reference/#SpectralFitting.make_objective_variance-Tuple{AbstractLayout, AbstractDataset}","page":"Reference","title":"SpectralFitting.make_objective_variance","text":"make_objective_variance(layout::AbstractLayout, dataset::AbstractDataset)\n\nMake the variance vector associated with each objective point.\n\n\n\n\n\n","category":"method"},{"location":"reference/#SpectralFitting.make_output_domain-Tuple{AbstractLayout, AbstractDataset}","page":"Reference","title":"SpectralFitting.make_output_domain","text":"make_output_domain(layout::AbstractLayout, dataset::AbstractDataset)\n\nReturns the array used as the output domain. That is, in cases where the model input and output map to different domains, the input domain is said to be the model domain, the input domain is said to be the model domain. \n\nThe distinction is mainly used for the purposes of simulating data and for visualising data.\n\n\n\n\n\n","category":"method"},{"location":"reference/#SpectralFitting.make_surrogate_harness-Union{Tuple{T}, Tuple{M}, Tuple{Function, Any, M, T, T}} where {M<:AbstractSpectralModel, T<:(Tuple{Vararg{T, N}} where {N, T})}","page":"Reference","title":"SpectralFitting.make_surrogate_harness","text":"make_surrogate_harness(\n model::M,\n lowerbounds::T,\n upperbounds::T;\n optimization_samples = 200,\n seed_samples = 50,\n S::Type = RadialBasis,\n sample_type = SobolSample(),\n verbose = false,\n)\n\nCreates and optimizes a surrogate model of type S for model, using wrap_model_as_objective and optimize_accuracy! for optimization_samples iterations. Model is initially seeded with seed_samples points prior to optimization.\n\nwarning: Warning\nAdditive models integrate energies to calculate flux, which surrogate models are currently not capable of. Results for Additive models likely to be inaccurate. This will be patched in a future version.\n\n\n\n\n\n","category":"method"},{"location":"reference/#SpectralFitting.modelkind-Union{Tuple{Type{<:AbstractSpectralModel{T, K}}}, Tuple{K}, Tuple{T}} where {T, K}","page":"Reference","title":"SpectralFitting.modelkind","text":"modelkind(M::Type{<:AbstractSpectralModel})\nmodelkind(::AbstractSpectralModel)\n\nReturn the kind of model given by M: either Additive, Multiplicative, or Convolutional.\n\n\n\n\n\n","category":"method"},{"location":"reference/#SpectralFitting.numbertype-Union{Tuple{AbstractSpectralModel{T}}, Tuple{T}} where T<:Number","page":"Reference","title":"SpectralFitting.numbertype","text":"numbertype(::AbstractSpectralModel)\n\nGet the numerical type of the model. This goes through FitParam, so that the number type returned is as close to a primative as possible.\n\nSee also paramtype.\n\nExample\n\nnumbertype(PowerLaw()) == Float64\n\n\n\n\n\n","category":"method"},{"location":"reference/#SpectralFitting.objective_transformer-Tuple{AbstractLayout, AbstractDataset}","page":"Reference","title":"SpectralFitting.objective_transformer","text":"objective_transformer(layout::AbstractLayout, dataset::AbstractDataset)\n\nUsed to transform the output of the model onto the output domain. For spectral fitting, this is the method used to do response folding and bin masking.\n\nIf none provided, uses the _DEFAULT_TRANSFORMER\n\nfunction _DEFAULT_TRANSFORMER()\n function _transformer!!(domain, objective)\n objective\n end\n function _transformer!!(output, domain, objective)\n @. output = objective\n end\n _transformer!!\nend\n\n\n\n\n\n","category":"method"},{"location":"reference/#SpectralFitting.operation_symbol-Tuple{Type{<:AdditionOperator}}","page":"Reference","title":"SpectralFitting.operation_symbol","text":"operation_symbol(::AbstractCompositeOperator)\noperation_symbol(::Type{<:AbstractCompositeOperator})\n\nObtain the model symbol from a given AbstractCompositeOperator.\n\n\n\n\n\n","category":"method"},{"location":"reference/#SpectralFitting.optimize_accuracy!-Tuple{Surrogates.AbstractSurrogate, Function, Any, Any}","page":"Reference","title":"SpectralFitting.optimize_accuracy!","text":"optimize_accuracy!(\n surr::AbstractSurrogate,\n obj::Function,\n lb,\n ub;\n sample_type::SamplingAlgorithm = SobolSample(),\n maxiters = 200,\n N_truth = 5000,\n verbose = false,\n)\n\nImprove accuracy (faithfullness) of the surrogate model in recreating the objective function.\n\nSamples a new space of N_truth points between lb and ub, and calculates the objective function obj at each. Finds the point with largest MSE between surrogate and objective, and adds the point to the surrogate pool. Repeats maxiters times, adding maxiters points to surrogate model.\n\nOptionally print to stdout the MSE and iteration count with verbose = true.\n\nNote that upper- and lower-bounds should be in the form (E, params...), where E is a single energy and params are the model parameters.\n\n\n\n\n\n","category":"method"},{"location":"reference/#SpectralFitting.paramtype-Tuple{T} where T<:AbstractSpectralModel","page":"Reference","title":"SpectralFitting.paramtype","text":"paramtype(::AbstractSpectralModel)\n\nGet the parameter type of the model. This, unlike numbertype does not go through FitParam.\n\nExample\n\nparamtype(PowerLaw()) == FitParam{Float64}\n\n\n\n\n\n","category":"method"},{"location":"reference/#SpectralFitting.preferred_support-Tuple{Any}","page":"Reference","title":"SpectralFitting.preferred_support","text":"preferred_support(x)\n\nGet the preferred AbstractLayout of x. If multiple supports are available, the DEFAULT_SUPPORT_ORDERING is followed:\n\nDEFAULT_SUPPORT_ORDERING = (ContiguouslyBinned{Nothing}(nothing), OneToOne{Nothing}(nothing))\n\n\n\n\n\n","category":"method"},{"location":"reference/#SpectralFitting.preferred_units-Union{Tuple{T}, Tuple{T, SpectralFitting.AbstractStatistic}} where T<:AbstractDataset","page":"Reference","title":"SpectralFitting.preferred_units","text":"preferred_units(::Type{<:AbstractDataset}, s::AbstractStatistic)\npreferred_units(x, s::AbstractStatistic)\n\nGet the preferred units that a given dataset would use to fit the AbstractStatistic in. For example, for ChiSquared, the units of the model may be a rate, however for Cash the preferred units might be counts.\n\nReturning nothing from this function implies there is no unit preference.\n\nIf undefined for a derived type, returns nothing.\n\nSee also support_units.\n\n\n\n\n\n","category":"method"},{"location":"reference/#SpectralFitting.register_model_data-Tuple{Any, Vararg{String}}","page":"Reference","title":"SpectralFitting.register_model_data","text":"SpectralFitting.register_model_data(M::Type{<:AbstractSpectralModel}, model_data::ModelDataInfo...)\nSpectralFitting.register_model_data(M::Type{<:AbstractSpectralModel}, remote_and_local::Tuple{String,String}...)\nSpectralFitting.register_model_data(M::Type{<:AbstractSpectralModel}, filenames::String...)\nSpectralFitting.register_model_data(s::Symbol, filenames::String...)\n\nRegister filenames as model data associated with the model given by type M or symbol s. This function does not download any files, but rather adds the relevant filenames to a lookup which SpectralFitting.download_model_data consults when invoked, and consequently model data is only downloaded when needed.\n\nnote: Note\nIt is good practice to use this method immediately after defining a new model with @xspecmodel to register any required datafiles from the HEASoft source code, and therefore keep relevant information together.\n\nExample\n\n# by type\nregister_model_data(XS_Laor, \"ari.mod\")\n# by symbol\nregister_model_data(:XS_KyrLine, \"KBHline01.fits\")\n\n\n\n\n\n","category":"method"},{"location":"reference/#SpectralFitting.response_energy-Union{Tuple{ResponseMatrix{T}}, Tuple{T}} where T","page":"Reference","title":"SpectralFitting.response_energy","text":"response_energy(response::ResponseMatrix)\n\nGet the contiguously binned energy corresponding to the input domain of the response matrix. This is equivalent to the model domain.\n\n\n\n\n\n","category":"method"},{"location":"reference/#SpectralFitting.support_units-Tuple{T} where T<:AbstractLayout","page":"Reference","title":"SpectralFitting.support_units","text":"support_units(x)\n\nReturn the units of a particular layout. If this method returns nothing, assume the layout does not care about the units and handle that information appropriately (throw an error or set defaults).\n\n\n\n\n\n","category":"method"},{"location":"reference/#SpectralFitting.supports-Tuple{AbstractLayout, Any}","page":"Reference","title":"SpectralFitting.supports","text":"supports(x::Type)\n\nUsed to define whether a given type has support for a specific AbstractLayout. Should return a tuple of the supported layouts. This method should be implemented to express new support, not the query method. \n\nTo query, there is\n\nsupports(layout::AbstractLayout, x)::Bool\n\nExample\n\nsupports(::Type{typeof(x)}) = (OneToOne(),)\n@assert supports(ContiguouslyBinned(), x) == false\n\n\n\n\n\n","category":"method"},{"location":"reference/#SpectralFitting.with_units-Tuple{AbstractLayout, Any}","page":"Reference","title":"SpectralFitting.with_units","text":"with_units(::AbstractLayout, units)\n\nRemake the AbstractLayout with the desired units. This may be a no-op if the layout does not care about units, see support_units.\n\n\n\n\n\n","category":"method"},{"location":"reference/#SpectralFitting.wrap_model_as_objective-Tuple{AbstractSpectralModel, Any}","page":"Reference","title":"SpectralFitting.wrap_model_as_objective","text":"wrap_model_as_objective(model::AbstractSpectralModel; ΔE = 1e-1)\nwrap_model_as_objective(M::Type{<:AbstractSpectralModel}; ΔE = 1e-1)\n\nWrap a spectral model into an objective function for building/optimizing a surrogate model. Returns an anonymous function taking the tuple (E, params...) as the argument, and returning a single flux value.\n\n\n\n\n\n","category":"method"},{"location":"reference/#SpectralFitting.AbstractModelWrapper","page":"Reference","title":"SpectralFitting.AbstractModelWrapper","text":"abstract type AbstractModelWrapper{M,T,K} <: AbstractSpectralModel{T,K} end\n\nFirst field of the struct must be model.\n\n\n\n\n\n","category":"type"},{"location":"reference/#SpectralFitting.AbstractMultiDataset","page":"Reference","title":"SpectralFitting.AbstractMultiDataset","text":"Must support the same API, but may also have some query methods for specific internals.\n\n\n\n\n\n","category":"type"},{"location":"reference/#SpectralFitting.AbstractSpectralModelImplementation","page":"Reference","title":"SpectralFitting.AbstractSpectralModelImplementation","text":"abstract type AbstractSpectralModelImplementation end\n\nDetails about the implementation are represented by this abstract type, used in the trait pattern. Concrete types are\n\nJuliaImplementation(): means the model is implemented in the Julia programming language.\nXSPECImplementation(): means the model is vendored from the XSPEC model library.\n\n\n\n\n\n","category":"type"},{"location":"reference/#SpectralFitting.AbstractTableModel","page":"Reference","title":"SpectralFitting.AbstractTableModel","text":"abstract type AbstractTableModel{T,K} <: AbstractSpectralModel{T,K} end\n\nAbstract type representing table models, i.e. those models that interpolate or load data from a table. \n\nFirst field in the struct must be table. See PhotoelectricAbsorption for an example implementation.\n\n\n\n\n\n","category":"type"},{"location":"reference/#SpectralFitting.XS_CutOffPowerLaw","page":"Reference","title":"SpectralFitting.XS_CutOffPowerLaw","text":"XS_CutOffPowerLaw(K, Γ, Ecut, z)\n\nK: Normalisation.\nΓ: Photon index.\nEcut: Cut-off energy (keV).\nz: Redshift.\n\nExample\n\nusing SpectralFitting\nusing UnicodePlots\nenergy = 10 .^collect(range(-1.0, 2.0, 100))\nm = invokemodel(energy, XS_CutOffPowerLaw())\nlineplot(energy[1:end-1],m,xscale=:log10,yscale=:log10,xlim=(1e-1,1e2),ylim=(1e-6,1e0),xlabel=\"Energy (keV)\",ylabel=\"Flux\",title=\"XS_CutOffPowerLaw\",canvas=DotCanvas)\n\n XS_CutOffPowerLaw \n ┌────────────────────────────────────────┐ \n10⁰ │:.. │ \n │ '':... │ \n │ '':.. │ \n │ '''.. │ \n │ '':.. │ \n │ '':. │ \n │ ':. │ \nFlux │ '.. │ \n │ ':. │ \n │ '. │ \n │ : │ \n │ :. │ \n │ : │ \n │ : │ \n10⁻⁶ │ :│ \n └────────────────────────────────────────┘ \n 10⁻¹ 10² \n Energy (keV)\n\n\n\n\n\n","category":"type"},{"location":"reference/#SpectralFitting.XS_Jet","page":"Reference","title":"SpectralFitting.XS_Jet","text":"XS_Jet(K, mass, Dco, log_mdot, thetaobs, BulkG, phi, zdiss, B, logPrel, gmin_inj, gbreak, gmax, s1, s2, z)\n\nK: MUST BE FIXED AT UNITY as the jet spectrum normalisation is set by the relativisitic particle power.\nmass: Black hole mass in solar masses.\nDco: Comoving (proper) distance in Mpc.\nlog_mdot: log(L/L_Edd.\nthetaobs: Inclination angle (deg).\nBulkG: Bulk lorentz factor of the jet.\nphi: Angular size scale (radians) of the jet acceleration region as seen from the black hole.\nzdiss: Vertical distance from the black hole of the jet dissipation region (r_g).\nB: Magnetic field in the jet (Gauss).\nlogPrel: Log of the power injected in relativisitic particles (ergs/s).\ngmin_inj: Minimum lorentz factor of the injected electrons.\ngbreak: Lorentz factor of the break in injected electron distribution.\ngmax: Maximum lorentz factor.\ns1: Injected index of the electron distribution below the break.\ns2: Injected index of the electron distribution above the break.\nz: Cosmological redshift corresponding to the comoving distance used above.\n\nExample\n\nusing UnicodePlots\nenergy = 10 .^collect(range(-8.0, 8.0, 100))\nm = invokemodel(energy, XS_Jet())\nlineplot(energy[1:end-1],m,xscale=:log10,yscale=:log10,xlim=(1e-8,1e8),ylim=(1e-8,1e8),xlabel=\"Energy (keV)\",ylabel=\"Flux\",title=\"XS_Jet\",canvas=DotCanvas)\n\n XS_Jet \n ┌────────────────────────────────────────┐ \n10⁸ │ │ \n │ │ \n │ │ \n │ │ \n │ :':.. │ \n │ .' ''. │ \n │: ':. │ \nFlux │' ': │ \n │ '. │ \n │ : │ \n │ ''..... │ \n │ '''''.. │ \n │ ':.. │ \n │ ':. │ \n10⁻⁸ │ ''. │ \n └────────────────────────────────────────┘ \n 10⁻⁸ 10⁸ \n Energy (keV) \n\n\n\n\n\n","category":"type"},{"location":"reference/#SpectralFitting.Reflection._add_composite_info!-Tuple{SpectralFitting.Reflection.CompositeAggregation, Type{<:AbstractSpectralModel}, Union{Expr, Symbol}, Type}","page":"Reference","title":"SpectralFitting.Reflection._add_composite_info!","text":"Used exclusively to do recursive CompositeModel parsing.\n\n\n\n\n\n","category":"method"},{"location":"reference/#SpectralFitting.Reflection.add_objective_reduction!-Tuple{SpectralFitting.Reflection.CompositeAggregation, Symbol}","page":"Reference","title":"SpectralFitting.Reflection.add_objective_reduction!","text":"add_objective_reduction!(ra::CompositeAggregation, op::Symbol)\n\nReduces the current objective count and applies the reduction operation op to them. For example, if op is :+, and the objective count is 3, then after this function has been called the objective count will be 2 and the reduction expression\n\n@. flux2 = flux2 + flux3\n\nwill have been added to the CompositeAggregation.\n\n\n\n\n\n","category":"method"},{"location":"reference/#SpectralFitting.Reflection.assemble_composite_model_call-Tuple{Type{<:CompositeModel}, Any}","page":"Reference","title":"SpectralFitting.Reflection.assemble_composite_model_call","text":"assemble_composite_model_call(model::Type{<:CompositeModel}, parameters::Type{<:AbstractVector})\n\nAssemble the full composite model call, with objective unpacking via assemble_objective_unpack, closure and parameter assignments, model invocation, and objective reduction. Uses assemble_fast_call to put the final function body together.\n\n\n\n\n\n","category":"method"},{"location":"reference/#SpectralFitting.Reflection.assemble_objective_unpack-Tuple{Any}","page":"Reference","title":"SpectralFitting.Reflection.assemble_objective_unpack","text":"assemble_objective_unpack(N)\n\nAssembles the statements for unpacking the objective cache into a number of views. Assembles the part of the model call that looks like:\n\nobjective1 = view(objectives, :, 1), objective2 = ...\n\nfor N objectives slices.\n\n\n\n\n\n","category":"method"},{"location":"reference/#SpectralFitting.Reflection.get_info-Tuple{Type{<:AbstractSpectralModel}, Union{Expr, Symbol}}","page":"Reference","title":"SpectralFitting.Reflection.get_info","text":"get_info(model::Type{<:AbstractSpectralModel}, lens::Lens)\n\nReturns a ModelInfo struct for a given model.\n\n\n\n\n\n","category":"method"},{"location":"reference/#SpectralFitting.Reflection.make_constructor-Tuple{Type{<:AbstractSpectralModel}, Vector, Vector, Type}","page":"Reference","title":"SpectralFitting.Reflection.make_constructor","text":"make_constructor(model::Type{<:AbstractSpectralModel}, closures::Vector, params::Vector, T::Type)\n\nCreate a constructor expression for the model. Should return something similar to\n\n:(PowerLaw{T}(arg1, arg2, arg3)))\n\nunpacking the closures and params vectors in the appropriate places.\n\n\n\n\n\n","category":"method"},{"location":"reference/#SpectralFitting.Reflection.parameter_lenses-Tuple{Type{<:AbstractSpectralModel}, SpectralFitting.Reflection.ModelInfo}","page":"Reference","title":"SpectralFitting.Reflection.parameter_lenses","text":"parameter_lenses(::Type{<:AbstractSpectralModel}, info::ModelInfo)\n\nReturn a vector of lenses Lens that refer to each of the models parameters. The lenses should be relative to model.lens.\n\n\n\n\n\n","category":"method"},{"location":"reference/#SpectralFitting.Reflection.set_objective_count!-Tuple{SpectralFitting.Reflection.CompositeAggregation, Int64}","page":"Reference","title":"SpectralFitting.Reflection.set_objective_count!","text":"set_objective_count!(a::CompositeAggregation, o::Int)\n\nSet the objective count to a specific value. Will bump the maximum objective count if the new value exceeds the current maximum.\n\n\n\n\n\n","category":"method"},{"location":"reference/#SpectralFitting.Reflection.CompositeModelInfo","page":"Reference","title":"SpectralFitting.Reflection.CompositeModelInfo","text":"struct CompositeModelInfo\n \"The parameter symbols of the model with the respective lens to the actual parameter.\"\n parameter_symbols::Vector{Pair{Symbol,Lens}}\n \"Each model assigned to a unique symbol.\"\n models::Vector{Pair{Symbol,ModelInfo}}\n \"The expression representing the folding operations of this composite model.\"\n model_expression::Expr\n \"Constructor and objective folding expressions, used in generating the invocation call.\"\n expressions::Vector{Expr}\n \"The maximum number of objective caches this model will need.\"\n maximum_objective_cache_count::Int\n \"How many objective caches are currently active.\"\n objective_cache_count::Int\nend\n\nThe composite equivalent of ModelInfo, augmented to track the model symbol (a1, m3, etc.), and the model parameters (K_1, a_3, etc.)\n\n\n\n\n\n","category":"type"},{"location":"reference/#SpectralFitting.Reflection.ModelInfo","page":"Reference","title":"SpectralFitting.Reflection.ModelInfo","text":"struct ModelInfo\n \"All parameter symbols for the model.\"\n symbols::Vector{Symbol}\n \"Unique symbols generated for the parameter assignment when buildin the function call.\"\n generated_symbols::Vector{Symbol}\n \"Additional closure parameters that need to be handled when invoking the model.\"\n closure_symbols::Vector{Symbol}\n \"Unique closure generated symbols.\"\n generated_closure_symbols::Vector{Symbol}\n \"The lens that takes you to this model from some parent.\"\n lens::Lens\n \"The model type itself.\"\n model::Type\nend\n\nAll models are parsed into a ModelInfo struct relative to their parent (in the case of composite models).\n\nThe symbols field contains all of the model parameter symbols as they are in the structure, not as they have been generated. Recall when the invocation expressions are generated, we create anonymous paramter names to avoid conflicts. These are the generated_symbols instead.\n\n\n\n\n\n","category":"type"},{"location":"walkthrough/#Walkthrough","page":"Walkthrough","title":"Walkthrough","text":"","category":"section"},{"location":"walkthrough/","page":"Walkthrough","title":"Walkthrough","text":"warning: Warning\nThis walk through has not been fleshed out with the relevant astrophysical content yet (for example, whether a fit is good, what the different parameters mean, etc.), and so assumes some familarity with spectral fitting in general.It is also not yet complete, nor a faithful illustration of everything SpectralFitting.jl can do. It serves to illustrate similarities and differences in syntax between SpectralFitting.jl and XSPEC.","category":"page"},{"location":"walkthrough/","page":"Walkthrough","title":"Walkthrough","text":"This example walkthrough is the SpectralFitting.jl equivalent of the Walk through XSPEC from the XSPEC manual. We will use the same dataset, available for download from this link to the data files.","category":"page"},{"location":"walkthrough/#Overview","page":"Walkthrough","title":"Overview","text":"","category":"section"},{"location":"walkthrough/","page":"Walkthrough","title":"Walkthrough","text":"The first thing we want to do is load our datasets. Unlike in XSPEC, we have no requirement of being in the same directory as the data, or even that all of the response, ancillary, and spectral files be in the same place. For simplicity, we'll assume they are:","category":"page"},{"location":"walkthrough/","page":"Walkthrough","title":"Walkthrough","text":"note: Note\nBe sure to set DATADIR pointing to the directory where you keep the walkthrough data.","category":"page"},{"location":"walkthrough/","page":"Walkthrough","title":"Walkthrough","text":"using SpectralFitting, Plots\n\nDATADIR = \"...\"\nDATADIR = length(get(ENV, \"CI\", \"\")) > 0 ? @__DIR__() * \"/../../ex-datadir\" : \"/home/lilith/Developer/jl/datasets/xspec/walkthrough\" # hide\nspec1_path = joinpath(DATADIR, \"s54405.pha\")\ndata = OGIPDataset(spec1_path) ","category":"page"},{"location":"walkthrough/","page":"Walkthrough","title":"Walkthrough","text":"This will print a little card about our data, which shows us what else SpectralFitting.jl loaded. We can see the Primary Spectrum, the Response, but that the Background and Ancillary response files are missing. That's to be expected, since we don't have those files in the dataset. ","category":"page"},{"location":"walkthrough/","page":"Walkthrough","title":"Walkthrough","text":"We can check what paths it used by looking at","category":"page"},{"location":"walkthrough/","page":"Walkthrough","title":"Walkthrough","text":"data.paths","category":"page"},{"location":"walkthrough/","page":"Walkthrough","title":"Walkthrough","text":"We can load and alter any part of a dataset as we do our fitting. For example, if you have multiple different ancillary files at hand, switching them between fits is a one-liner.","category":"page"},{"location":"walkthrough/","page":"Walkthrough","title":"Walkthrough","text":"To visualize our data, we can use some of the Plots.jl recipes included in SpectralFitting.jl:","category":"page"},{"location":"walkthrough/","page":"Walkthrough","title":"Walkthrough","text":"plot(data, xlims = (0.5, 70), xscale = :log10)","category":"page"},{"location":"walkthrough/","page":"Walkthrough","title":"Walkthrough","text":"Note that the units are currently not divided by the energy bin widths. We can either do that manually, or use the normalize! to convert whatever units the data is currently in to the defacto standard counts s⁻¹ keV⁻¹ for fitting. Whilst we're at it, we see in the model card that there are 40 bad quality bins still present in our data. We can drop those as well, and plot the data on log-log axes:","category":"page"},{"location":"walkthrough/","page":"Walkthrough","title":"Walkthrough","text":"normalize!(data)\ndrop_bad_channels!(data)\nplot(data, ylims = (0.001, 2.0), yscale = :log10, xscale = :log10)","category":"page"},{"location":"walkthrough/","page":"Walkthrough","title":"Walkthrough","text":"Note that when there are no negative axes, the scale defaults to log on the plot unless otherwise specified.","category":"page"},{"location":"walkthrough/","page":"Walkthrough","title":"Walkthrough","text":"Next we want to specify a model to fit to this data. Models that are prefixed with XS_ are models that are linked from the XSPEC model library, provided via LibXSPEC_jll. For a full list of the models, see Models library.","category":"page"},{"location":"walkthrough/","page":"Walkthrough","title":"Walkthrough","text":"warning: Warning\nIt is advised to use the Julia implemented models. This allows various calculations to benefit from automatic differentiation, efficient multi-threading, GPU offloading, and various other useful things, see Why & how.","category":"page"},{"location":"walkthrough/","page":"Walkthrough","title":"Walkthrough","text":"We will start by fitting a photoelectric absorption model that acts on a power law model:","category":"page"},{"location":"walkthrough/","page":"Walkthrough","title":"Walkthrough","text":"note: Note\nTo see information about a model, use the ? in the Julia REPL:julia> ?PowerLaw\nXS_PowerLaw(K, a)\n\n • K: Normalisation.\n\n • a: Photon index.\n\nExample\n≡≡≡≡≡≡≡\n...","category":"page"},{"location":"walkthrough/","page":"Walkthrough","title":"Walkthrough","text":"model = PhotoelectricAbsorption() * PowerLaw()","category":"page"},{"location":"walkthrough/","page":"Walkthrough","title":"Walkthrough","text":"If we want to specify paramters of our model at instantiation, we can do that with","category":"page"},{"location":"walkthrough/","page":"Walkthrough","title":"Walkthrough","text":"model = PhotoelectricAbsorption() * PowerLaw(a = FitParam(3.0))","category":"page"},{"location":"walkthrough/","page":"Walkthrough","title":"Walkthrough","text":"SpectralFitting.jl adopts the SciML problem-solver abstraction, so to fit a model to data we specify a FittingProblem:","category":"page"},{"location":"walkthrough/","page":"Walkthrough","title":"Walkthrough","text":"prob = FittingProblem(model => data)","category":"page"},{"location":"walkthrough/","page":"Walkthrough","title":"Walkthrough","text":"SpectralFitting.jl makes a huge wealth of optimizers availble from Optimizations.jl, and others from further afield. For consistency with XSPEC, we'll use here a delayed-gratification least-squares algorithm from LsqFit.jl:","category":"page"},{"location":"walkthrough/","page":"Walkthrough","title":"Walkthrough","text":"result = fit(prob, LevenbergMarquadt())","category":"page"},{"location":"walkthrough/","page":"Walkthrough","title":"Walkthrough","text":"Here we can see the parameter vector, the estimated error on each parameter, and the measure of the fit statistic (here chi squared). We can overplot our result on our data easily:","category":"page"},{"location":"walkthrough/","page":"Walkthrough","title":"Walkthrough","text":"plot(data, \n ylims = (0.001, 2.0), \n xscale = :log10, \n yscale = :log10\n)\nplot!(result)","category":"page"},{"location":"walkthrough/","page":"Walkthrough","title":"Walkthrough","text":"Our model does not account for the high energy range well. We can ignore that range for now, and select everything from 0 to 15 keV and refit:","category":"page"},{"location":"walkthrough/","page":"Walkthrough","title":"Walkthrough","text":"mask_energies!(data, 0, 15)\nresult = fit(prob, LevenbergMarquadt())","category":"page"},{"location":"walkthrough/","page":"Walkthrough","title":"Walkthrough","text":"plot(data, \n ylims = (0.001, 2.0), \n xscale = :log10, \n yscale = :log10\n)\nplot!(result, label = \"PowerLaw\")","category":"page"},{"location":"walkthrough/","page":"Walkthrough","title":"Walkthrough","text":"The result is not yet baked into our model, and represents just the outcome of the fit. To update the parameters and errors in the model, we can use update_model!","category":"page"},{"location":"walkthrough/","page":"Walkthrough","title":"Walkthrough","text":"update_model!(model, result)","category":"page"},{"location":"walkthrough/","page":"Walkthrough","title":"Walkthrough","text":"note: Note\nSince fitting and updating a model is often done in tandem, SpectralFitting.jl has both a fit and fit! method, the latter automatically updates the model parameters after fit.","category":"page"},{"location":"walkthrough/","page":"Walkthrough","title":"Walkthrough","text":"To estimate the goodness of our fit, we can mimic the goodness command from XSPEC. This will use the simulate function to simulate spectra for a dataset (here determined by the result), and fit the model to the simulated dataset. The fit statistic for each fit is then appended to an array, which we can use to plot a histogram:","category":"page"},{"location":"walkthrough/","page":"Walkthrough","title":"Walkthrough","text":"spread = goodness(result; N = 1000, seed = 42, exposure_time = data.data.spectrum.exposure_time)\nhistogram(spread, ylims = (0, 300), label = \"Simulated\")\nvline!([result.χ2], label = \"Best fit\")","category":"page"},{"location":"walkthrough/","page":"Walkthrough","title":"Walkthrough","text":"Note we have set the random number generator seed with seed = 42 to allow our results to be strictly reproduced.","category":"page"},{"location":"walkthrough/","page":"Walkthrough","title":"Walkthrough","text":"The goodness command will log the percent of simulations with a fit statistic better than the result, but we can equivalently calculate that ourselves:","category":"page"},{"location":"walkthrough/","page":"Walkthrough","title":"Walkthrough","text":"count(<(result.χ2), spread) * 100 / length(spread)","category":"page"},{"location":"walkthrough/","page":"Walkthrough","title":"Walkthrough","text":"Next we want to calculate the flux in an energy range observed by the detector. We can do this with LogFlux or XS_CalculateFlux, as they are both equivalent implementations.","category":"page"},{"location":"walkthrough/","page":"Walkthrough","title":"Walkthrough","text":"We can modify our model by accessing properties from the model card and writing a new expression:","category":"page"},{"location":"walkthrough/","page":"Walkthrough","title":"Walkthrough","text":"calc_flux = XS_CalculateFlux(\n E_min = FitParam(0.2, frozen = true), \n E_max = FitParam(2.0, frozen = true),\n log10Flux = FitParam(-10.3, lower_limit = -100, upper_limit = 100),\n)\n\nflux_model = model.m1 * calc_flux(model.a1)","category":"page"},{"location":"walkthrough/","page":"Walkthrough","title":"Walkthrough","text":"Since we used the old model to define the new one, our best fit values are automatically copied into the new model. We can now freeze the normalization, as we are using the flux integrating model to scale the powerlaw component:","category":"page"},{"location":"walkthrough/","page":"Walkthrough","title":"Walkthrough","text":"flux_model.a1.K.frozen = true\nflux_model","category":"page"},{"location":"walkthrough/","page":"Walkthrough","title":"Walkthrough","text":"Looking at the data card, we see the fit domain does not include the full region that we want to integrate the flux over. We therefore need to extend the fitting domain:","category":"page"},{"location":"walkthrough/","page":"Walkthrough","title":"Walkthrough","text":"flux_problem = FittingProblem(flux_model => data)\n# TODO: domain extensions not fully implemented yet","category":"page"},{"location":"walkthrough/","page":"Walkthrough","title":"Walkthrough","text":"Now to fit we can repeat the above procedure, and even overplot the region of flux we integrated:","category":"page"},{"location":"walkthrough/","page":"Walkthrough","title":"Walkthrough","text":"flux_result = fit(flux_problem, LevenbergMarquadt())\n\nplot(data, \n ylims = (0.001, 2.0), \n xscale = :log10, \n yscale = :log10\n)\nplot!(flux_result)\nvspan!([flux_model.c1.E_min.value, flux_model.c1.E_max.value], alpha = 0.5)","category":"page"},{"location":"walkthrough/","page":"Walkthrough","title":"Walkthrough","text":"Let's try alternative models to see how they fit the data. First, an absorbed black body:","category":"page"},{"location":"walkthrough/","page":"Walkthrough","title":"Walkthrough","text":"model2 = PhotoelectricAbsorption() * XS_BlackBody()","category":"page"},{"location":"walkthrough/","page":"Walkthrough","title":"Walkthrough","text":"We fit in the same way as before:","category":"page"},{"location":"walkthrough/","page":"Walkthrough","title":"Walkthrough","text":"prob2 = FittingProblem(model2 => data)\nresult2 = fit!(prob2, LevenbergMarquadt())","category":"page"},{"location":"walkthrough/","page":"Walkthrough","title":"Walkthrough","text":"Let's overplot this result against our power law result:","category":"page"},{"location":"walkthrough/","page":"Walkthrough","title":"Walkthrough","text":"dp = plot(data, \n ylims = (0.001, 2.0), \n xscale = :log10, \n yscale = :log10,\n legend = :bottomleft,\n)\nplot!(dp, result, label = \"PowerLaw $(round(result.χ2))\")\nplot!(dp, result2, label = \"BlackBody $(round(result2.χ2))\")","category":"page"},{"location":"walkthrough/","page":"Walkthrough","title":"Walkthrough","text":"Or a bremsstrahlung model:","category":"page"},{"location":"walkthrough/","page":"Walkthrough","title":"Walkthrough","text":"model3 = PhotoelectricAbsorption() * XS_BremsStrahlung()\nprob3 = FittingProblem(model3 => data)\nresult3 = fit(prob3, LevenbergMarquadt())","category":"page"},{"location":"walkthrough/","page":"Walkthrough","title":"Walkthrough","text":"plot!(dp, result3, label = \"Brems $(round(result3.χ2))\")","category":"page"},{"location":"walkthrough/","page":"Walkthrough","title":"Walkthrough","text":"Let's take a look at the residuals of these three models. There are utility methods for this in SpectralFitting.jl, but we can easily just interact with the result directly:","category":"page"},{"location":"walkthrough/","page":"Walkthrough","title":"Walkthrough","text":"function calc_residuals(result)\n # select which result we want (only have one, but for generalisation to multi-model fits)\n r = result[1] \n y = invoke_result(r)\n @. (r.objective - y) / sqrt(r.variance)\nend\n\ndomain = SpectralFitting.plotting_domain(data)\n\nrp = hline([0], linestyle = :dash, legend = false)\nplot!(rp,domain, calc_residuals(result), seriestype = :stepmid)\nplot!(rp, domain, calc_residuals(result2), seriestype = :stepmid)\nplot!(rp, domain, calc_residuals(result3), seriestype = :stepmid)\nrp","category":"page"},{"location":"walkthrough/","page":"Walkthrough","title":"Walkthrough","text":"We can compose this figure with our previous one, and change to a linear x scale:","category":"page"},{"location":"walkthrough/","page":"Walkthrough","title":"Walkthrough","text":"plot(dp, rp, layout = grid(2, 1, heights = [0.7, 0.3]), link = :x, xscale = :linear)","category":"page"},{"location":"walkthrough/","page":"Walkthrough","title":"Walkthrough","text":"We can do all that plotting work in one go with the plotresult recipe:","category":"page"},{"location":"walkthrough/","page":"Walkthrough","title":"Walkthrough","text":"plotresult(\n data,\n [result, result2, result3],\n ylims = (0.001, 2.0), \n xscale = :log10, \n yscale = :log10,\n legend = :bottomleft,\n)","category":"page"},{"location":"walkthrough/","page":"Walkthrough","title":"Walkthrough","text":"Let's modify the black body model with a continuum component","category":"page"},{"location":"walkthrough/","page":"Walkthrough","title":"Walkthrough","text":"bbpl_model = model2.m1 * (PowerLaw() + model2.a1) |> deepcopy","category":"page"},{"location":"walkthrough/","page":"Walkthrough","title":"Walkthrough","text":"note: Note\nWe pipe the model to deepcopy to create a copy of all the model parameters. Not doing this means the parameters in bbpl_model will be aliased to the parameters in model2, and changing one with change the other.","category":"page"},{"location":"walkthrough/","page":"Walkthrough","title":"Walkthrough","text":"We'll freeze the hydrogen column density parameter to the galactic value and refit:","category":"page"},{"location":"walkthrough/","page":"Walkthrough","title":"Walkthrough","text":"bbpl_model.ηH_1.value = 4\nbbpl_model.ηH_1.frozen = true\nbbpl_model","category":"page"},{"location":"walkthrough/","page":"Walkthrough","title":"Walkthrough","text":"And fitting:","category":"page"},{"location":"walkthrough/","page":"Walkthrough","title":"Walkthrough","text":"bbpl_result = fit(\n FittingProblem(bbpl_model => data), \n LevenbergMarquadt()\n)","category":"page"},{"location":"walkthrough/","page":"Walkthrough","title":"Walkthrough","text":"Let's plot the result:","category":"page"},{"location":"walkthrough/","page":"Walkthrough","title":"Walkthrough","text":"plot(data, \n ylims = (0.001, 2.0), \n xscale = :log10, \n yscale = :log10,\n legend = :bottomleft,\n)\nplot!(bbpl_result)","category":"page"},{"location":"walkthrough/","page":"Walkthrough","title":"Walkthrough","text":"Update the model and fix the black body temperature to 2 keV:","category":"page"},{"location":"walkthrough/","page":"Walkthrough","title":"Walkthrough","text":"update_model!(bbpl_model, bbpl_result)\n\nbbpl_model.T_1.value = 2.0\nbbpl_model.T_1.frozen = true\nbbpl_model","category":"page"},{"location":"walkthrough/","page":"Walkthrough","title":"Walkthrough","text":"Fitting:","category":"page"},{"location":"walkthrough/","page":"Walkthrough","title":"Walkthrough","text":"bbpl_result2 = fit(\n FittingProblem(bbpl_model => data), \n LevenbergMarquadt()\n)","category":"page"},{"location":"walkthrough/","page":"Walkthrough","title":"Walkthrough","text":"Overplotting this new result:","category":"page"},{"location":"walkthrough/","page":"Walkthrough","title":"Walkthrough","text":"plot!(bbpl_result2)","category":"page"},{"location":"walkthrough/#MCMC","page":"Walkthrough","title":"MCMC","text":"","category":"section"},{"location":"walkthrough/","page":"Walkthrough","title":"Walkthrough","text":"We can use libraries like Pidgeons.jl or Turing.jl to perform Bayesian inference on our paramters. SpectralFitting.jl is designed with BYOO (Bring Your Own Optimizer) in mind, and so makes it relatively easy to get at the core fitting functions to be used with other packages.","category":"page"},{"location":"walkthrough/","page":"Walkthrough","title":"Walkthrough","text":"Let's use Turing.jl here, which means we'll also want to use StatsPlots.jl to plot our walker chains.","category":"page"},{"location":"walkthrough/","page":"Walkthrough","title":"Walkthrough","text":"using StatsPlots\nusing Turing","category":"page"},{"location":"walkthrough/","page":"Walkthrough","title":"Walkthrough","text":"Turing.jl provides enormous control over the definition of the model, and this is not control SpectralFitting.jl wants to take away from you. Although we will provide utility scripts to do the basics, here we'll show you everything step by step to give you an overview of what you can do.","category":"page"},{"location":"walkthrough/","page":"Walkthrough","title":"Walkthrough","text":"Let's go back to our first model:","category":"page"},{"location":"walkthrough/","page":"Walkthrough","title":"Walkthrough","text":"model","category":"page"},{"location":"walkthrough/","page":"Walkthrough","title":"Walkthrough","text":"This gave a pretty good fit but the errors on our paramters are not well defined, being estimated only from a convariance matrix in the least-squares solver. MCMC can give us better confidence regions, and even help us uncover dependencies between paramters. Here we'll take all of our parameters and convert them into a Turing.jl model with use of their macro:","category":"page"},{"location":"walkthrough/","page":"Walkthrough","title":"Walkthrough","text":"@model function mcmc_model(domain, objective, variance, f)\n K ~ Normal(20.0, 1.0)\n a ~ Normal(2.2, 0.3)\n ηH ~ truncated(Normal(0.5, 0.1); lower = 0)\n\n pred = f(domain, [K, a, ηH])\n return objective ~ MvNormal(pred, sqrt.(variance))\nend","category":"page"},{"location":"walkthrough/","page":"Walkthrough","title":"Walkthrough","text":"A few things to note here: we use the Turing.jl sampling syntax ~ to say that a variable is sampled from a certain type of prior distribution. There are no fixed criteria for what a distribution can be, and we encourage you to consult the Turing.jl documentation to learn how to define your own custom probability distributions. In this case, we will use Gaussians for all our parameters, and for the means and standard deviations use the best fit and estimated errors.","category":"page"},{"location":"walkthrough/","page":"Walkthrough","title":"Walkthrough","text":"At the moment we haven't explicitly used our model, but f in this case takes the roll of invoking our model, and folding through instrument responses. We call it in much the same way as invokemodel, despite it going the extra step to fold our model. To instantiate this, we can use the SpectralFitting.jl helper functions:","category":"page"},{"location":"walkthrough/","page":"Walkthrough","title":"Walkthrough","text":"config = FittingConfig(FittingProblem(model => data))\nmm = mcmc_model(\n make_model_domain(ContiguouslyBinned(), data),\n make_objective(ContiguouslyBinned(), data),\n make_objective_variance(ContiguouslyBinned(), data),\n # _f_objective returns a function used to evaluate and fold the model through the data\n SpectralFitting._f_objective(config),\n)\nnothing # hide","category":"page"},{"location":"walkthrough/","page":"Walkthrough","title":"Walkthrough","text":"That's it! We're now ready to sample our model. Since all our models are implemented in Julia, we can use gradient-boosted samplers with automatic differentiation, such as NUTS. We'll walk 5000 itterations, just as a small example:","category":"page"},{"location":"walkthrough/","page":"Walkthrough","title":"Walkthrough","text":"chain = sample(mm, NUTS(), 5_000)","category":"page"},{"location":"walkthrough/","page":"Walkthrough","title":"Walkthrough","text":"In the printout we see summary statistics about or model, in this case that it has converged well (rhat close to 1 for all parameters), better estimates of the standard deviation, and various quantiles. We can plot our chains to make sure the caterpillers are healthy and fuzzy, making use of StatsPlots.jl recipes:","category":"page"},{"location":"walkthrough/","page":"Walkthrough","title":"Walkthrough","text":"plot(chain)","category":"page"},{"location":"walkthrough/","page":"Walkthrough","title":"Walkthrough","text":"Corner plots are currently broken at time of writing.","category":"page"},{"location":"models/using-models/#Using-spectral-models","page":"Using models","title":"Using spectral models","text":"","category":"section"},{"location":"models/using-models/","page":"Using models","title":"Using models","text":"using SpectralFitting\nusing Plots","category":"page"},{"location":"models/using-models/","page":"Using models","title":"Using models","text":"In this page you'll find how to use the spectral model library and how to define your own models. Using the model library is as easy as invoking or composing models from the Model Index. For example:","category":"page"},{"location":"models/using-models/","page":"Using models","title":"Using models","text":"model = PowerLaw()","category":"page"},{"location":"models/using-models/","page":"Using models","title":"Using models","text":"In the output of the REPL we see the model name, and it's two parameters, with information about those parameters, such as the current value, the associated error (10% by defaul), the minimum and maximum values, and whether the parameter is frozen or not.","category":"page"},{"location":"models/using-models/","page":"Using models","title":"Using models","text":"note: Note\nSee FitParam for full details about fitable parameters.","category":"page"},{"location":"models/using-models/","page":"Using models","title":"Using models","text":"The parameters can be tweaked by accessing the fields","category":"page"},{"location":"models/using-models/","page":"Using models","title":"Using models","text":"model.K.value = 2.0\nmodel.K.frozen = true\nmodel","category":"page"},{"location":"models/using-models/","page":"Using models","title":"Using models","text":"We can invoke the model on a domain in the following way","category":"page"},{"location":"models/using-models/","page":"Using models","title":"Using models","text":"domain = collect(range(0.1, 10.0, 100))\ninvokemodel(domain, model)","category":"page"},{"location":"models/using-models/","page":"Using models","title":"Using models","text":"note: Note\nBy default, models are implemented to accept a single input vector with all of the low and high bin edges, and return a flux array with the flux in each energy bin. As such, it is here the case that:length(flux) == length(energy) - 1Models need not be defined as such, however. See AbstractLayout for more.","category":"page"},{"location":"models/using-models/","page":"Using models","title":"Using models","text":"Models can be composed together following the Model algebra. That means to expressive a photoelectric absorption component acting on the power law we can write","category":"page"},{"location":"models/using-models/","page":"Using models","title":"Using models","text":"model2 = PhotoelectricAbsorption() * model","category":"page"},{"location":"models/using-models/","page":"Using models","title":"Using models","text":"The parameters of this CompositeModel are are copied from the expression. This means we can modify the K_1 parameter in model2 without having to worry that we are changing model.K:","category":"page"},{"location":"models/using-models/","page":"Using models","title":"Using models","text":"model2.K_1.frozen = false\nmodel2","category":"page"},{"location":"models/using-models/","page":"Using models","title":"Using models","text":"model","category":"page"},{"location":"models/using-models/","page":"Using models","title":"Using models","text":"Composite models have the same methods as single models. This means we can invoke a model in the same way","category":"page"},{"location":"models/using-models/","page":"Using models","title":"Using models","text":"invokemodel(domain, model2)","category":"page"},{"location":"models/using-models/#Defining-new-models","page":"Using models","title":"Defining new models","text":"","category":"section"},{"location":"models/using-models/","page":"Using models","title":"Using models","text":"To define your own model, you need to tell the package what the model parameters are and how to invoke the model. This is all done by creating a struct which subtypes AbstractSpectralModel.","category":"page"},{"location":"models/using-models/","page":"Using models","title":"Using models","text":"Let's create a new Additive spectral model:","category":"page"},{"location":"models/using-models/","page":"Using models","title":"Using models","text":"Base.@kwdef struct MyModel{T} <: AbstractSpectralModel{T,Additive}\n K::T = FitParam(2.0)\n p::T = FitParam(3.0)\nend\n\n# implementing a dummy add operation this function can do anything it likes, but\n# must write the output into `output` and ideally should be thread safe\nfunction SpectralFitting.invoke!(output, input, model::MyModel)\n SpectralFitting.finite_diff_kernel!(output, input) do E\n E + model.p\n end\nend","category":"page"},{"location":"models/using-models/","page":"Using models","title":"Using models","text":"Here we used the utility method SpectralFitting.finite_diff_kernel! to ensure the additive model is appropriately scaled across the bin width.","category":"page"},{"location":"models/using-models/","page":"Using models","title":"Using models","text":"Note that Additive models do not need to use the normalization parameter K themselves. This is because when we use invokemodel these sorts of translations are automatically applied, for compatability with external models.","category":"page"},{"location":"models/using-models/","page":"Using models","title":"Using models","text":"Our model is now ready to use","category":"page"},{"location":"models/using-models/","page":"Using models","title":"Using models","text":"model = MyModel()","category":"page"},{"location":"models/using-models/","page":"Using models","title":"Using models","text":"domain = collect(range(0.1, 10.0, 100))\ninvokemodel(domain, model)","category":"page"},{"location":"models/using-models/","page":"Using models","title":"Using models","text":"note: Note\nTo add new XSPEC or foreign function models, see Wrapping new XSPEC models.","category":"page"},{"location":"models/using-models/#Model-abstraction","page":"Using models","title":"Model abstraction","text":"","category":"section"},{"location":"models/using-models/","page":"Using models","title":"Using models","text":"All spectral models are a sub-type of AbstractSpectralModel.","category":"page"},{"location":"models/using-models/","page":"Using models","title":"Using models","text":"AbstractSpectralModel\nSpectralFitting.invoke!\nmodelkind\nnumbertype\nimplementation","category":"page"},{"location":"models/using-models/#SpectralFitting.AbstractSpectralModel","page":"Using models","title":"SpectralFitting.AbstractSpectralModel","text":"abstract type AbstractSpectralModel{T,K<:AbstractSpectralModelKind} end\n\nSupertype of all spectral models, tracking the number type T and AbstractSpectralModelKind denoted K.\n\nImplementation\n\nSub-types must implement the following interface (see the function's documentation for examples):\n\nSpectralFitting.invoke!\n\nUsage\n\nThe available API for a spectral model is detailed below:\n\ninvokemodel / invokemodel!: the primary way to invoke a model.\nallocate_model_output: allocate the output matrix for the model.\n\nThe following query functions exist:\n\nmodelkind for obtaining K\nnumbertype for obtaining T\nimplementation used to assertain whether we can do things like automatic differentation through this model.\n\nModel reflection is supported by the following functions. These are intended for internal use and are not exported.\n\nparameter_named_tuple\nparameter_tuple\nremake_model_with_parameters\ndestructure_model\n\nThe parametric type parameter T is the number type of the model and K defines the AbstractSpectralModelKind.\n\n\n\n\n\n","category":"type"},{"location":"models/using-models/#SpectralFitting.invoke!","page":"Using models","title":"SpectralFitting.invoke!","text":"SpectralFitting.invoke!(output, domain, M::Type{<:AbstractSpectralModel}, params...)\n\nUsed to define the behaviour of models. Should calculate the output of the model and write in-place into output. The model parameters are passed in the model structure.\n\nwarning: Warning\nThis function should not be called directly. Use invokemodel instead. invoke! is only to define the model, not to use it. Users should always call models using invokemodel or invokemodel! to ensure normalisations and closures are accounted for.\n\nExample\n\nBase.@kwdef struct MyModel{T} <: AbstractSpectralModel{T,Multiplicative}\n p1::T = FitParam(1.0)\n p2::T = FitParam(2.0)\n p3::T = FitParam(3.0)\nend\n\nwould have the arguments passed to invoke! as\n\nfunction SpectralFitting.invoke!(output, domain, model::MyModel)\n # ...\nend\n\n\n\n\n\n","category":"function"},{"location":"models/using-models/#SpectralFitting.modelkind","page":"Using models","title":"SpectralFitting.modelkind","text":"modelkind(M::Type{<:AbstractSpectralModel})\nmodelkind(::AbstractSpectralModel)\n\nReturn the kind of model given by M: either Additive, Multiplicative, or Convolutional.\n\n\n\n\n\n","category":"function"},{"location":"models/using-models/#SpectralFitting.numbertype","page":"Using models","title":"SpectralFitting.numbertype","text":"numbertype(::AbstractSpectralModel)\n\nGet the numerical type of the model. This goes through FitParam, so that the number type returned is as close to a primative as possible.\n\nSee also paramtype.\n\nExample\n\nnumbertype(PowerLaw()) == Float64\n\n\n\n\n\n","category":"function"},{"location":"models/using-models/#SpectralFitting.implementation","page":"Using models","title":"SpectralFitting.implementation","text":"implementation(model::AbstractSpectralModel)\nimplementation(::Type{<:AbstractSpectralModel})\n\nGet the AbstractSpectralModelImplementation for a given AbstractSpectralModel or model type.\n\nThis is used primarily to learn what optimizations we can do with a model, for example propagating auto-diff gradients through a model or arbitrary precision numbers.\n\n\n\n\n\n","category":"function"},{"location":"models/using-models/#Model-methods","page":"Using models","title":"Model methods","text":"","category":"section"},{"location":"models/using-models/","page":"Using models","title":"Using models","text":"invokemodel\ninvokemodel!","category":"page"},{"location":"models/using-models/#SpectralFitting.invokemodel","page":"Using models","title":"SpectralFitting.invokemodel","text":"invokemodel(domain, model::AbstractSpectralModel)\n\nInvoke the AbstractSpectralModel given by model over the domain domain.\n\nThis function will perform any normalisation or post-processing tasks that a specific model kind may require, e.g. multiplying by a normalisation constant for Additive models.\n\nnote: Note\n\n\ninvokemodel allocates the needed output arrays based on the element type of free_params to allow automatic differentation libraries to calculate parameter gradients.\n\nIn-place non-allocating variants are the invokemodel! functions.\n\nExample\n\nmodel = PowerLaw()\ndomain = collect(range(0.1, 20.0, 100))\n\ninvokemodel(domain, model)\n\n\n\n\n\n","category":"function"},{"location":"models/using-models/#SpectralFitting.invokemodel!","page":"Using models","title":"SpectralFitting.invokemodel!","text":"invokemodel!(output, domain, model)\ninvokemodel!(output, domain, model, params::AbstractVector)\ninvokemodel!(output, domain, model, params::ParameterCache)\n\nIn-place variant of invokemodel, calculating the output of an AbstractSpectralModel given by model, optionally overriding the parameters using a ParameterCache or an AbstractVector.\n\nThe output may not necessarily be a single vector, and one should use allocate_model_output to allocate the output structure.\n\nExample\n\nmodel = PowerLaw()\ndomain = collect(range(0.1, 20.0, 100))\noutput = allocate_model_output(model, domain)\ninvokemodel!(output, domain, model)\n\n\n\n\n\n","category":"function"},{"location":"models/using-models/#Model-algebra","page":"Using models","title":"Model algebra","text":"","category":"section"},{"location":"models/using-models/","page":"Using models","title":"Using models","text":"Models exist as three different kinds, defined by an AbstractSpectralModelKind trait.","category":"page"},{"location":"models/using-models/","page":"Using models","title":"Using models","text":"AbstractSpectralModelKind\nAdditive\nMultiplicative\nConvolutional","category":"page"},{"location":"models/using-models/#SpectralFitting.AbstractSpectralModelKind","page":"Using models","title":"SpectralFitting.AbstractSpectralModelKind","text":"abstract type AbstractSpectralModelKind\n\nAbstract type of all model kinds. The algebra of models is as follows\n\nA + A = A\nM * M = M\nM * A = A\nC(A) = A\n\nwhere A is Additive, M is Multiplicative, and C is Convolutional. All other operations are prohibited, e.g. C(M) or M * C. To obtain M * C there must be an additive component, e.g. M * C(A).\n\n\n\n\n\n","category":"type"},{"location":"models/using-models/#SpectralFitting.Additive","page":"Using models","title":"SpectralFitting.Additive","text":"Additive <: AbstractSpectralModelKind\nAdditive()\n\nAdditive models are effectively the sources of photons, and are the principle building blocks of composite models. Every additive model has a normalisation parameter which re-scales the output by a constant factor K.\n\nnote: Note\nDefining custom additive models requires special care. See Defining new models.\n\n\n\n\n\n","category":"type"},{"location":"models/using-models/#SpectralFitting.Multiplicative","page":"Using models","title":"SpectralFitting.Multiplicative","text":"Multiplicative <: AbstractSpectralModelKind\nMultiplicative()\n\nMultiplicative models act on Additive models, by element-wise multiplying the output in each domain bin of the additive model by a different factor.\n\n\n\n\n\n","category":"type"},{"location":"models/using-models/#SpectralFitting.Convolutional","page":"Using models","title":"SpectralFitting.Convolutional","text":"Convolutional <: AbstractSpectralModelKind\nConvolutional()\n\nConvolutional models act on the output generated by Additive models, similar to Multiplicative models, however may convolve kernels through the output also.\n\n\n\n\n\n","category":"type"},{"location":"models/using-models/#Model-data-availability","page":"Using models","title":"Model data availability","text":"","category":"section"},{"location":"models/using-models/","page":"Using models","title":"Using models","text":"Many of the XSPEC implemented models use tabular data, such as FITS, and return results interpolated from these pre-calculated tables. In some cases, these table models have data files that are multiple gigabytes in size, and would be very unwieldy to ship indiscriminantly. SpectralFitting attempts to circumnavigate this bloat by downloading the model data on an ut opus basis.","category":"page"},{"location":"models/using-models/","page":"Using models","title":"Using models","text":"SpectralFitting.download_model_data\nSpectralFitting.download_all_model_data","category":"page"},{"location":"models/using-models/#SpectralFitting.download_model_data","page":"Using models","title":"SpectralFitting.download_model_data","text":"SpectralFitting.download_model_data(model::AbstractSpectralModel; kwargs...)\nSpectralFitting.download_model_data(M::Type{<:AbstractSpectralModel}; kwargs...)\nSpectralFitting.download_model_data(s::Symbol; kwargs...)\n\nDownloads the model data for a model specified either by model, type M, or symbol s. Datafiles associated with a specific model may be registered using SpectralFitting.register_model_data. The download is currently unconfigurable, but permits slight control via a number of keyword arguments:\n\nprogress::Bool = true\n\nDisplay a progress bar for the download.\n\nmodel_source_url::String = \"http://www.star.bris.ac.uk/fbaker/XSPEC-model-data\"\n\nThe source URL used to download the model data.\n\nAll standard XSPEC spectral model data is currently being hosted on the University of Bristol astrophysics servers, and should be persistently available to anyone.\n\n\n\n\n\n","category":"function"},{"location":"models/using-models/#SpectralFitting.download_all_model_data","page":"Using models","title":"SpectralFitting.download_all_model_data","text":"SpectralFitting.download_all_model_data()\n\nDownloads all model data for the models currently registered with SpectralFitting.register_model_data. Calls SpectralFitting.download_model_data to perform the download.\n\n\n\n\n\n","category":"function"},{"location":"models/using-models/","page":"Using models","title":"Using models","text":"Special care must be taken if new XSPEC models are wrapped to ensure the data is available. For more on this, see Wrapping new XSPEC models.","category":"page"},{"location":"models/using-models/","page":"Using models","title":"Using models","text":"Model data may also alternatively be copied in by-hand from a HEASoft XSPEC source directory. In this case, the location to copy the data to may be determined via joinpath(SpectralFitting.LibXSPEC_jll.artifact_dir, \"spectral\", \"modelData\").","category":"page"},{"location":"#SpectralFitting.jl-Documentation","page":"Home","title":"SpectralFitting.jl Documentation","text":"","category":"section"},{"location":"","page":"Home","title":"Home","text":"Fast and flexible spectral fitting in Julia.","category":"page"},{"location":"","page":"Home","title":"Home","text":"SpectralFitting.jl is a package for defining and using spectral models, with a number of utilities to make model composition easy and invocation fast. SpectralFitting wraps LibXSPEC_jll.jl to expose the library of models from HEASoft XSPEC, and provides helper functions for operating with spectral data from a number of different missions. The package natively uses LsqFit.jl to fit parameters using the Levenberg-Marquardt algorithm, but makes it easy to use Optim.jl for more specialized fitting algorithms, or Turing.jl for Bayesian inference and MCMC.","category":"page"},{"location":"","page":"Home","title":"Home","text":"SpectralFitting is designed to be extended, such that new models are simple to create, and new dataset processing pipelines for different missions are brief to define. Where performance is key, SpectralFitting helps you define fast and AD-compatible surrogates of spectral models using Surrogates.jl, and embed them in the model composition algebra.","category":"page"},{"location":"","page":"Home","title":"Home","text":"To get started, add the AstroRegistry from the University of Bristol and then install:","category":"page"},{"location":"","page":"Home","title":"Home","text":"julia>]\npkg> registry add https://github.com/astro-group-bristol/AstroRegistry\npkg> add SpectralFitting","category":"page"},{"location":"","page":"Home","title":"Home","text":"Then use","category":"page"},{"location":"","page":"Home","title":"Home","text":"using SpectralFitting\n# ....","category":"page"},{"location":"","page":"Home","title":"Home","text":"to get started. See Walkthrough for an example walkthrough the package.","category":"page"},{"location":"","page":"Home","title":"Home","text":"For more University of Bristol Astrophysics Group codes, see our GitHub organisation.","category":"page"}] } diff --git a/dev/transitioning-from-xspec/index.html b/dev/transitioning-from-xspec/index.html index e70466e5..55dd2a0a 100644 --- a/dev/transitioning-from-xspec/index.html +++ b/dev/transitioning-from-xspec/index.html @@ -1,2 +1,2 @@ -Transitioning from XSPEC · SpectralFitting.jl
+Transitioning from XSPEC · SpectralFitting.jl
diff --git a/dev/walkthrough/ad18999d.svg b/dev/walkthrough/1835cdbf.svg similarity index 81% rename from dev/walkthrough/ad18999d.svg rename to dev/walkthrough/1835cdbf.svg index 5b881021..160fec42 100644 --- a/dev/walkthrough/ad18999d.svg +++ b/dev/walkthrough/1835cdbf.svg @@ -1,81 +1,81 @@ - + - + - + - + - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/dev/walkthrough/5f36110c.svg b/dev/walkthrough/22376bb2.svg similarity index 73% rename from dev/walkthrough/5f36110c.svg rename to dev/walkthrough/22376bb2.svg index 560ded36..2d25e365 100644 --- a/dev/walkthrough/5f36110c.svg +++ b/dev/walkthrough/22376bb2.svg @@ -1,401 +1,401 @@ - + - + - + - + - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/dev/walkthrough/e35c532a.svg b/dev/walkthrough/29a69dfd.svg similarity index 64% rename from dev/walkthrough/e35c532a.svg rename to dev/walkthrough/29a69dfd.svg index 12a35c3c..c5515add 100644 --- a/dev/walkthrough/e35c532a.svg +++ b/dev/walkthrough/29a69dfd.svg @@ -1,976 +1,976 @@ - + - + - + - + - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/dev/walkthrough/e9da2912.svg b/dev/walkthrough/2c8c3c6e.svg similarity index 52% rename from dev/walkthrough/e9da2912.svg rename to dev/walkthrough/2c8c3c6e.svg index f6019622..c42c1d42 100644 --- a/dev/walkthrough/e9da2912.svg +++ b/dev/walkthrough/2c8c3c6e.svg @@ -1,397 +1,399 @@ - + - + - + - + - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/dev/walkthrough/bdd08721.svg b/dev/walkthrough/3d287d54.svg similarity index 83% rename from dev/walkthrough/bdd08721.svg rename to dev/walkthrough/3d287d54.svg index ec381011..79856966 100644 --- a/dev/walkthrough/bdd08721.svg +++ b/dev/walkthrough/3d287d54.svg @@ -1,54 +1,54 @@ - + - + - + - + - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/dev/walkthrough/947c06f6.svg b/dev/walkthrough/543dc105.svg similarity index 67% rename from dev/walkthrough/947c06f6.svg rename to dev/walkthrough/543dc105.svg index 6a30efb0..93b04c32 100644 --- a/dev/walkthrough/947c06f6.svg +++ b/dev/walkthrough/543dc105.svg @@ -1,527 +1,527 @@ - + - + - + - + - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/dev/walkthrough/4fdfde46.svg b/dev/walkthrough/62e0bb86.svg similarity index 77% rename from dev/walkthrough/4fdfde46.svg rename to dev/walkthrough/62e0bb86.svg index 91186c3d..38d00446 100644 --- a/dev/walkthrough/4fdfde46.svg +++ b/dev/walkthrough/62e0bb86.svg @@ -390,10 +390,8 @@ - - - - - - - + + + + + diff --git a/dev/walkthrough/fac20b1b.svg b/dev/walkthrough/63f94449.svg similarity index 71% rename from dev/walkthrough/fac20b1b.svg rename to dev/walkthrough/63f94449.svg index 9eb7624f..4c53a133 100644 --- a/dev/walkthrough/fac20b1b.svg +++ b/dev/walkthrough/63f94449.svg @@ -1,399 +1,399 @@ - + - + - + - + - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/dev/walkthrough/898bda8f.svg b/dev/walkthrough/813a997c.svg similarity index 77% rename from dev/walkthrough/898bda8f.svg rename to dev/walkthrough/813a997c.svg index a6996ea1..db4092cb 100644 --- a/dev/walkthrough/898bda8f.svg +++ b/dev/walkthrough/813a997c.svg @@ -1,373 +1,373 @@ - + - + - + - + - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/dev/walkthrough/892c476e.svg b/dev/walkthrough/9f9250ae.svg similarity index 70% rename from dev/walkthrough/892c476e.svg rename to dev/walkthrough/9f9250ae.svg index bd359cd6..4f37d0cd 100644 --- a/dev/walkthrough/892c476e.svg +++ b/dev/walkthrough/9f9250ae.svg @@ -1,397 +1,397 @@ - + - + - + - + - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/dev/walkthrough/2eb36cc9.svg b/dev/walkthrough/a867f9bc.svg similarity index 93% rename from dev/walkthrough/2eb36cc9.svg rename to dev/walkthrough/a867f9bc.svg index 38da424c..bad7aa2b 100644 --- a/dev/walkthrough/2eb36cc9.svg +++ b/dev/walkthrough/a867f9bc.svg @@ -1,204 +1,204 @@ - + - + - + - + - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + - + - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + - + - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/dev/walkthrough/129a8dd5.svg b/dev/walkthrough/c90fd845.svg similarity index 69% rename from dev/walkthrough/129a8dd5.svg rename to dev/walkthrough/c90fd845.svg index 7a72630f..4a28da76 100644 --- a/dev/walkthrough/129a8dd5.svg +++ b/dev/walkthrough/c90fd845.svg @@ -1,529 +1,529 @@ - + - + - + - + - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/dev/walkthrough/23d37588.svg b/dev/walkthrough/cd7c9177.svg similarity index 70% rename from dev/walkthrough/23d37588.svg rename to dev/walkthrough/cd7c9177.svg index 2e813fe9..50599fdc 100644 --- a/dev/walkthrough/23d37588.svg +++ b/dev/walkthrough/cd7c9177.svg @@ -1,401 +1,401 @@ - + - + - + - + - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/dev/walkthrough/784a04b8.svg b/dev/walkthrough/e07e5e40.svg similarity index 74% rename from dev/walkthrough/784a04b8.svg rename to dev/walkthrough/e07e5e40.svg index cacf7215..ac7c86af 100644 --- a/dev/walkthrough/784a04b8.svg +++ b/dev/walkthrough/e07e5e40.svg @@ -1,521 +1,521 @@ - + - + - + - + - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/dev/walkthrough/index.html b/dev/walkthrough/index.html index cf421728..dca81833 100644 --- a/dev/walkthrough/index.html +++ b/dev/walkthrough/index.html @@ -31,9 +31,9 @@ . Response : /home/runner/work/SpectralFitting.jl/SpectralFitting.jl/docs/build/../../ex-datadir/s54405.rsp . Background : nothing . Ancillary : nothing -

We can load and alter any part of a dataset as we do our fitting. For example, if you have multiple different ancillary files at hand, switching them between fits is a one-liner.

To visualize our data, we can use some of the Plots.jl recipes included in SpectralFitting.jl:

plot(data, xlims = (0.5, 70), xscale = :log10)
Example block output

Note that the units are currently not divided by the energy bin widths. We can either do that manually, or use the normalize! to convert whatever units the data is currently in to the defacto standard counts s⁻¹ keV⁻¹ for fitting. Whilst we're at it, we see in the model card that there are 40 bad quality bins still present in our data. We can drop those as well, and plot the data on log-log axes:

normalize!(data)
+

We can load and alter any part of a dataset as we do our fitting. For example, if you have multiple different ancillary files at hand, switching them between fits is a one-liner.

To visualize our data, we can use some of the Plots.jl recipes included in SpectralFitting.jl:

plot(data, xlims = (0.5, 70), xscale = :log10)
Example block output

Note that the units are currently not divided by the energy bin widths. We can either do that manually, or use the normalize! to convert whatever units the data is currently in to the defacto standard counts s⁻¹ keV⁻¹ for fitting. Whilst we're at it, we see in the model card that there are 40 bad quality bins still present in our data. We can drop those as well, and plot the data on log-log axes:

normalize!(data)
 drop_bad_channels!(data)
-plot(data, ylims = (0.001, 2.0), yscale = :log10, xscale = :log10)
Example block output

Note that when there are no negative axes, the scale defaults to log on the plot unless otherwise specified.

Next we want to specify a model to fit to this data. Models that are prefixed with XS_ are models that are linked from the XSPEC model library, provided via LibXSPEC_jll. For a full list of the models, see Models library.

Warning

It is advised to use the Julia implemented models. This allows various calculations to benefit from automatic differentiation, efficient multi-threading, GPU offloading, and various other useful things, see Why & how.

We will start by fitting a photoelectric absorption model that acts on a power law model:

Note

To see information about a model, use the ? in the Julia REPL:

julia> ?PowerLaw
+plot(data, ylims = (0.001, 2.0), yscale = :log10, xscale = :log10)
Example block output

Note that when there are no negative axes, the scale defaults to log on the plot unless otherwise specified.

Next we want to specify a model to fit to this data. Models that are prefixed with XS_ are models that are linked from the XSPEC model library, provided via LibXSPEC_jll. For a full list of the models, see Models library.

Warning

It is advised to use the Julia implemented models. This allows various calculations to benefit from automatic differentiation, efficient multi-threading, GPU offloading, and various other useful things, see Why & how.

We will start by fitting a photoelectric absorption model that acts on a power law model:

Note

To see information about a model, use the ? in the Julia REPL:

julia> ?PowerLaw
 XS_PowerLaw(K, a)
 
     •  K: Normalisation.
@@ -73,7 +73,7 @@
     xscale = :log10,
     yscale = :log10
 )
-plot!(result)
Example block output

Our model does not account for the high energy range well. We can ignore that range for now, and select everything from 0 to 15 keV and refit:

mask_energies!(data, 0, 15)
+plot!(result)
Example block output

Our model does not account for the high energy range well. We can ignore that range for now, and select everything from 0 to 15 keV and refit:

mask_energies!(data, 0, 15)
 result = fit(prob, LevenbergMarquadt())
┌ FittingResult:
 │   Model: CompositeModel[PhotoelectricAbsorption * PowerLaw]
 │   . u     : [21.584, 2.2307, 0.53199]
@@ -84,7 +84,7 @@
     xscale = :log10,
     yscale = :log10
 )
-plot!(result, label = "PowerLaw")
Example block output

The result is not yet baked into our model, and represents just the outcome of the fit. To update the parameters and errors in the model, we can use update_model!

update_model!(model, result)
┌ CompositeModel with 2 model components:
+plot!(result, label = "PowerLaw")
Example block output

The result is not yet baked into our model, and represents just the outcome of the fit. To update the parameters and errors in the model, we can use update_model!

update_model!(model, result)
┌ CompositeModel with 2 model components:
 │      m1 * a1
 │ Model key and parameters:
 │    a1 => PowerLaw
@@ -94,7 +94,7 @@
 │      ηH_1 ->  0.532 ± 0.1  ∈ [ 0, Inf ]   FREE
Note

Since fitting and updating a model is often done in tandem, SpectralFitting.jl has both a fit and fit! method, the latter automatically updates the model parameters after fit.

To estimate the goodness of our fit, we can mimic the goodness command from XSPEC. This will use the simulate function to simulate spectra for a dataset (here determined by the result), and fit the model to the simulated dataset. The fit statistic for each fit is then appended to an array, which we can use to plot a histogram:

spread = goodness(result; N = 1000, seed = 42, exposure_time = data.data.spectrum.exposure_time)
 histogram(spread, ylims = (0, 300), label = "Simulated")
-vline!([result.χ2], label = "Best fit")
Example block output

Note we have set the random number generator seed with seed = 42 to allow our results to be strictly reproduced.

The goodness command will log the percent of simulations with a fit statistic better than the result, but we can equivalently calculate that ourselves:

count(<(result.χ2), spread) * 100 / length(spread)
56.9

Next we want to calculate the flux in an energy range observed by the detector. We can do this with LogFlux or XS_CalculateFlux, as they are both equivalent implementations.

We can modify our model by accessing properties from the model card and writing a new expression:

calc_flux = XS_CalculateFlux(
+vline!([result.χ2], label = "Best fit")
Example block output

Note we have set the random number generator seed with seed = 42 to allow our results to be strictly reproduced.

The goodness command will log the percent of simulations with a fit statistic better than the result, but we can equivalently calculate that ourselves:

count(<(result.χ2), spread) * 100 / length(spread)
56.9

Next we want to calculate the flux in an energy range observed by the detector. We can do this with LogFlux or XS_CalculateFlux, as they are both equivalent implementations.

We can modify our model by accessing properties from the model card and writing a new expression:

calc_flux = XS_CalculateFlux(
     E_min = FitParam(0.2, frozen = true),
     E_max = FitParam(2.0, frozen = true),
     log10Flux = FitParam(-10.3, lower_limit = -100, upper_limit = 100),
@@ -139,7 +139,7 @@
     yscale = :log10
 )
 plot!(flux_result)
-vspan!([flux_model.c1.E_min.value, flux_model.c1.E_max.value], alpha = 0.5)
Example block output

Let's try alternative models to see how they fit the data. First, an absorbed black body:

model2 = PhotoelectricAbsorption() * XS_BlackBody()
┌ CompositeModel with 2 model components:
+vspan!([flux_model.c1.E_min.value, flux_model.c1.E_max.value], alpha = 0.5)
Example block output

Let's try alternative models to see how they fit the data. First, an absorbed black body:

model2 = PhotoelectricAbsorption() * XS_BlackBody()
┌ CompositeModel with 2 model components:
 │      m1 * a1
 │ Model key and parameters:
 │    a1 => XS_BlackBody
@@ -160,14 +160,14 @@
     legend = :bottomleft,
 )
 plot!(dp, result, label = "PowerLaw $(round(result.χ2))")
-plot!(dp, result2, label = "BlackBody $(round(result2.χ2))")
Example block output

Or a bremsstrahlung model:

model3 = PhotoelectricAbsorption() * XS_BremsStrahlung()
+plot!(dp, result2, label = "BlackBody $(round(result2.χ2))")
Example block output

Or a bremsstrahlung model:

model3 = PhotoelectricAbsorption() * XS_BremsStrahlung()
 prob3 = FittingProblem(model3 => data)
 result3 = fit(prob3, LevenbergMarquadt())
┌ FittingResult:
 │   Model: CompositeModel[PhotoelectricAbsorption * XS_BremsStrahlung]
 │   . u     : [13.868, 5.3034, 0.0]
 │   . σᵤ    : [1.3178, 0.70705, 0.19664]
 │   . χ²    : 40.027 
-└ 
plot!(dp, result3, label = "Brems $(round(result3.χ2))")
Example block output

Let's take a look at the residuals of these three models. There are utility methods for this in SpectralFitting.jl, but we can easily just interact with the result directly:

function calc_residuals(result)
+└ 
plot!(dp, result3, label = "Brems $(round(result3.χ2))")
Example block output

Let's take a look at the residuals of these three models. There are utility methods for this in SpectralFitting.jl, but we can easily just interact with the result directly:

function calc_residuals(result)
     # select which result we want (only have one, but for generalisation to multi-model fits)
     r = result[1]
     y = invoke_result(r)
@@ -180,14 +180,14 @@
 plot!(rp,domain, calc_residuals(result), seriestype = :stepmid)
 plot!(rp, domain, calc_residuals(result2), seriestype = :stepmid)
 plot!(rp, domain, calc_residuals(result3), seriestype = :stepmid)
-rp
Example block output

We can compose this figure with our previous one, and change to a linear x scale:

plot(dp, rp, layout = grid(2, 1, heights = [0.7, 0.3]), link = :x, xscale = :linear)
Example block output

We can do all that plotting work in one go with the plotresult recipe:

plotresult(
+rp
Example block output

We can compose this figure with our previous one, and change to a linear x scale:

plot(dp, rp, layout = grid(2, 1, heights = [0.7, 0.3]), link = :x, xscale = :linear)
Example block output

We can do all that plotting work in one go with the plotresult recipe:

plotresult(
     data,
     [result, result2, result3],
     ylims = (0.001, 2.0),
     xscale = :log10,
     yscale = :log10,
     legend = :bottomleft,
-)
Example block output

Let's modify the black body model with a continuum component

bbpl_model = model2.m1 * (PowerLaw() + model2.a1) |> deepcopy
┌ CompositeModel with 3 model components:
+)
Example block output

Let's modify the black body model with a continuum component

bbpl_model = model2.m1 * (PowerLaw() + model2.a1) |> deepcopy
┌ CompositeModel with 3 model components:
 │      m1 * (a2 + a1)
 │ Model key and parameters:
 │    a1 => XS_BlackBody
@@ -225,7 +225,7 @@
     yscale = :log10,
     legend = :bottomleft,
 )
-plot!(bbpl_result)
Example block output

Update the model and fix the black body temperature to 2 keV:

update_model!(bbpl_model, bbpl_result)
+plot!(bbpl_result)
Example block output

Update the model and fix the black body temperature to 2 keV:

update_model!(bbpl_model, bbpl_result)
 
 bbpl_model.T_1.value = 2.0
 bbpl_model.T_1.frozen = true
@@ -248,7 +248,7 @@
 │   . u     : [0.38234, 573.17, 4.8351]
 │   . σᵤ    : [0.034796, 83.183, 0.16012]
 │   . χ²    : 71.708 
-└ 

Overplotting this new result:

plot!(bbpl_result2)
Example block output

MCMC

We can use libraries like Pidgeons.jl or Turing.jl to perform Bayesian inference on our paramters. SpectralFitting.jl is designed with BYOO (Bring Your Own Optimizer) in mind, and so makes it relatively easy to get at the core fitting functions to be used with other packages.

Let's use Turing.jl here, which means we'll also want to use StatsPlots.jl to plot our walker chains.

using StatsPlots
+└ 

Overplotting this new result:

plot!(bbpl_result2)
Example block output

MCMC

We can use libraries like Pidgeons.jl or Turing.jl to perform Bayesian inference on our paramters. SpectralFitting.jl is designed with BYOO (Bring Your Own Optimizer) in mind, and so makes it relatively easy to get at the core fitting functions to be used with other packages.

Let's use Turing.jl here, which means we'll also want to use StatsPlots.jl to plot our walker chains.

using StatsPlots
 using Turing

Turing.jl provides enormous control over the definition of the model, and this is not control SpectralFitting.jl wants to take away from you. Although we will provide utility scripts to do the basics, here we'll show you everything step by step to give you an overview of what you can do.

Let's go back to our first model:

model
┌ CompositeModel with 2 model components:
 │      m1 * a1
 │ Model key and parameters:
@@ -276,8 +276,8 @@
 Iterations        = 1001:1:6000
 Number of chains  = 1
 Samples per chain = 5000
-Wall duration     = 14.14 seconds
-Compute duration  = 14.14 seconds
+Wall duration     = 14.23 seconds
+Compute duration  = 14.23 seconds
 parameters        = K, a, ηH
 internals         = lp, n_steps, is_accept, acceptance_rate, log_density, hamiltonian_energy, hamiltonian_energy_error, max_hamiltonian_energy_error, tree_depth, numerical_error, step_size, nom_step_size
 
@@ -297,4 +297,4 @@
            K   18.6898   19.6935   20.2662   20.8458   21.9474
            a    2.1239    2.1650    2.1877    2.2106    2.2530
           ηH    0.3310    0.4201    0.4708    0.5198    0.6214
-

In the printout we see summary statistics about or model, in this case that it has converged well (rhat close to 1 for all parameters), better estimates of the standard deviation, and various quantiles. We can plot our chains to make sure the caterpillers are healthy and fuzzy, making use of StatsPlots.jl recipes:

plot(chain)
Example block output

Corner plots are currently broken at time of writing.

+

In the printout we see summary statistics about or model, in this case that it has converged well (rhat close to 1 for all parameters), better estimates of the standard deviation, and various quantiles. We can plot our chains to make sure the caterpillers are healthy and fuzzy, making use of StatsPlots.jl recipes:

plot(chain)
Example block output

Corner plots are currently broken at time of writing.

diff --git a/dev/why-and-how/index.html b/dev/why-and-how/index.html index 954fc81d..701a56fe 100644 --- a/dev/why-and-how/index.html +++ b/dev/why-and-how/index.html @@ -59,4 +59,4 @@ return objective1 end end -end

This generated function also takes care of some other things for us, such as unpacking parameters (optionally unpacking frozen parameters separately), and ensuring any closure are passed to invokemodel if a model needs them (e.g., SurrogateSpectralModel).

This is achieved by moving as much information as possible about the model and its construction to its type, such that all of the invocation and parameter unpacking may be inferred at compile time.

Note

With the addition of more pure-Julia models, non-allocating methods without aggressive pre-allocation are possible, and will be added in the future. Such methods may allow models to add or multiply in-place on the total flux array, instead of relying on later broadcasts.

+end

This generated function also takes care of some other things for us, such as unpacking parameters (optionally unpacking frozen parameters separately), and ensuring any closure are passed to invokemodel if a model needs them (e.g., SurrogateSpectralModel).

This is achieved by moving as much information as possible about the model and its construction to its type, such that all of the invocation and parameter unpacking may be inferred at compile time.

Note

With the addition of more pure-Julia models, non-allocating methods without aggressive pre-allocation are possible, and will be added in the future. Such methods may allow models to add or multiply in-place on the total flux array, instead of relying on later broadcasts.