diff --git a/.nojekyll b/.nojekyll new file mode 100644 index 00000000..e69de29b diff --git a/core.html b/core.html new file mode 100644 index 00000000..edaf5e38 --- /dev/null +++ b/core.html @@ -0,0 +1,811 @@ + + + + + + + + + +hierarchicalforecast - Core + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ + +
+ +
+ + +
+ + + +
+ +
+
+

Core

+
+ + + +
+ + + + +
+ + +
+ + +

HierarchicalForecast contains pure Python implementations of hierarchical reconciliation methods as well as a core.HierarchicalReconciliation wrapper class that enables easy interaction with these methods through pandas DataFrames containing the hierarchical time series and the base predictions.

+

The core.HierarchicalReconciliation reconciliation class operates with the hierarchical time series pd.DataFrame Y_df, the base predictions pd.DataFrame Y_hat_df, the aggregation constraints matrix S. For more information on the creation of aggregation constraints matrix see the utils aggregation method.

+
+

core.HierarchicalReconciliation

+
+

source

+
+

init

+
+
 init (reconcilers:List[Callable])
+
+

Hierarchical Reconciliation Class.

+

The core.HierarchicalReconciliation class allows you to efficiently fit multiple HierarchicaForecast methods for a collection of time series and base predictions stored in pandas DataFrames. The Y_df dataframe identifies series and datestamps with the unique_id and ds columns while the y column denotes the target time series variable. The Y_h dataframe stores the base predictions, example (AutoARIMA, ETS, etc.).

+

Parameters:
reconcilers: A list of instantiated classes of the reconciliation methods module .

+

References:
Rob J. Hyndman and George Athanasopoulos (2018). ā€œForecasting principles and practice, Hierarchical and Grouped Seriesā€.

+
+

source

+
+
+

HierarchicalReconciliation

+
+
 HierarchicalReconciliation (reconcilers:List[Callable])
+
+

Hierarchical Reconciliation Class.

+

The core.HierarchicalReconciliation class allows you to efficiently fit multiple HierarchicaForecast methods for a collection of time series and base predictions stored in pandas DataFrames. The Y_df dataframe identifies series and datestamps with the unique_id and ds columns while the y column denotes the target time series variable. The Y_h dataframe stores the base predictions, example (AutoARIMA, ETS, etc.).

+

Parameters:
reconcilers: A list of instantiated classes of the reconciliation methods module .

+

References:
Rob J. Hyndman and George Athanasopoulos (2018). ā€œForecasting principles and practice, Hierarchical and Grouped Seriesā€.

+
+

source

+
+
+

reconcile

+
+
 reconcile (Y_hat_df:pandas.core.frame.DataFrame,
+            S:pandas.core.frame.DataFrame, tags:Dict[str,numpy.ndarray],
+            Y_df:Optional[pandas.core.frame.DataFrame]=None,
+            level:Optional[List[int]]=None,
+            intervals_method:str='normality', num_samples:int=-1,
+            seed:int=0, sort_df:bool=True, is_balanced:bool=False)
+
+

Hierarchical Reconciliation Method.

+

The reconcile method is analogous to SKLearn fit_predict method, it applies different reconciliation techniques instantiated in the reconcilers list.

+

Most reconciliation methods can be described by the following convenient linear algebra notation:

+

\[\tilde{\mathbf{y}}_{[a,b],\tau} = \mathbf{S}_{[a,b][b]} \mathbf{P}_{[b][a,b]} \hat{\mathbf{y}}_{[a,b],\tau}\]

+

where \(a, b\) represent the aggregate and bottom levels, \(\mathbf{S}_{[a,b][b]}\) contains the hierarchical aggregation constraints, and \(\mathbf{P}_{[b][a,b]}\) varies across reconciliation methods. The reconciled predictions are \(\tilde{\mathbf{y}}_{[a,b],\tau}\), and the base predictions \(\hat{\mathbf{y}}_{[a,b],\tau}\).

+

Parameters:
Y_hat_df: pd.DataFrame, base forecasts with columns ds and models to reconcile indexed by unique_id.
Y_df: pd.DataFrame, training set of base time series with columns ['ds', 'y'] indexed by unique_id.
If a class of self.reconciles receives y_hat_insample, Y_df must include them as columns.
S: pd.DataFrame with summing matrix of size (base, bottom), see aggregate method.
tags: Each key is a level and its value contains tags associated to that level.
level: positive float list [0,100), confidence levels for prediction intervals.
intervals_method: str, method used to calculate prediction intervals, one of normality, bootstrap, permbu.
num_samples: int=-1, if positive return that many probabilistic coherent samples. seed: int=0, random seed for numpy generatorā€™s replicability.
sort_df : bool (default=True), if True, sort df by [unique_id,ds].
is_balanced: bool=False, wether Y_df is balanced, set it to True to speed things up if Y_df is balanced.

+

Returns:
Y_tilde_df: pd.DataFrame, with reconciled predictions.

+
+

source

+
+
+

bootstrap_reconcile

+
+
 bootstrap_reconcile (Y_hat_df:pandas.core.frame.DataFrame,
+                      S_df:pandas.core.frame.DataFrame,
+                      tags:Dict[str,numpy.ndarray],
+                      Y_df:Optional[pandas.core.frame.DataFrame]=None,
+                      level:Optional[List[int]]=None,
+                      intervals_method:str='normality',
+                      num_samples:int=-1, num_seeds:int=1,
+                      sort_df:bool=True)
+
+

Bootstraped Hierarchical Reconciliation Method.

+

Applies N times, based on different random seeds, the reconcile method for the different reconciliation techniques instantiated in the reconcilers list.

+

Parameters:
Y_hat_df: pd.DataFrame, base forecasts with columns ds and models to reconcile indexed by unique_id.
Y_df: pd.DataFrame, training set of base time series with columns ['ds', 'y'] indexed by unique_id.
If a class of self.reconciles receives y_hat_insample, Y_df must include them as columns.
S: pd.DataFrame with summing matrix of size (base, bottom), see aggregate method.
tags: Each key is a level and its value contains tags associated to that level.
level: positive float list [0,100), confidence levels for prediction intervals.
intervals_method: str, method used to calculate prediction intervals, one of normality, bootstrap, permbu.
num_samples: int=-1, if positive return that many probabilistic coherent samples. num_seeds: int=1, random seed for numpy generatorā€™s replicability.
sort_df : bool (default=True), if True, sort df by [unique_id,ds].

+

Returns:
Y_bootstrap_df: pd.DataFrame, with bootstraped reconciled predictions.

+
+
+
+

Example

+
+
import numpy as np
+import pandas as pd
+
+from statsforecast.core import StatsForecast
+from statsforecast.models import ETS, Naive
+
+from hierarchicalforecast.utils import aggregate
+from hierarchicalforecast.core import HierarchicalReconciliation
+from hierarchicalforecast.methods import BottomUp, MinTrace
+
+# Load TourismSmall dataset
+df = pd.read_csv('https://raw.githubusercontent.com/Nixtla/transfer-learning-time-series/main/datasets/tourism.csv')
+df = df.rename({'Trips': 'y', 'Quarter': 'ds'}, axis=1)
+df.insert(0, 'Country', 'Australia')
+
+# Create hierarchical seires based on geographic levels and purpose
+# And Convert quarterly ds string to pd.datetime format
+hierarchy_levels = [['Country'],
+                    ['Country', 'State'], 
+                    ['Country', 'Purpose'], 
+                    ['Country', 'State', 'Region'], 
+                    ['Country', 'State', 'Purpose'], 
+                    ['Country', 'State', 'Region', 'Purpose']]
+
+Y_df, S_df, tags = aggregate(df=df, spec=hierarchy_levels)
+qs = Y_df['ds'].str.replace(r'(\d+) (Q\d)', r'\1-\2', regex=True)
+Y_df['ds'] = pd.PeriodIndex(qs, freq='Q').to_timestamp()
+Y_df = Y_df.reset_index()
+
+# Split train/test sets
+Y_test_df  = Y_df.groupby('unique_id').tail(4)
+Y_train_df = Y_df.drop(Y_test_df.index)
+
+# Compute base auto-ETS predictions
+# Careful identifying correct data freq, this data quarterly 'Q'
+fcst = StatsForecast(df=Y_train_df,
+                     #models=[ETS(season_length=12), Naive()],
+                     models=[Naive()],
+                     freq='Q', n_jobs=-1)
+Y_hat_df = fcst.forecast(h=4, fitted=True)
+Y_fitted_df = fcst.forecast_fitted_values()
+
+# Reconcile the base predictions
+Y_train_df = Y_train_df.reset_index().set_index('unique_id')
+Y_hat_df = Y_hat_df.reset_index().set_index('unique_id')
+reconcilers = [BottomUp(),
+               MinTrace(method='mint_shrink')]
+hrec = HierarchicalReconciliation(reconcilers=reconcilers)
+Y_rec_df = hrec.reconcile(Y_hat_df=Y_hat_df, 
+                          Y_df=Y_fitted_df,
+                          S=S_df, tags=tags)
+Y_rec_df.groupby('unique_id').head(2)
+
+ + +
+ +

If you find the code useful, please ā­ us on Github

+ +
+ + + + \ No newline at end of file diff --git a/evaluation.html b/evaluation.html new file mode 100644 index 00000000..907962bc --- /dev/null +++ b/evaluation.html @@ -0,0 +1,862 @@ + + + + + + + + + +hierarchicalforecast - Hierarchical Evaluation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ + +
+ +
+ + +
+ + + +
+ +
+
+

Hierarchical Evaluation

+
+ + + +
+ + + + +
+ + +
+ + +

To assist the evaluation of hierarchical forecasting systems, we make available accuracy metrics along with the HierarchicalEvaluation module that facilitates the measurement of predictionā€™s accuracy through the hierarchy levels.

+

The available metrics include point and probabilistic multivariate scoring rules that were used in previous hierarchical forecasting studies.

+
+

Accuracy Measurements

+
+

Relative Mean Squared Error

+
+

source

+
+

rel_mse

+
+
 rel_mse (y, y_hat, y_train, mask=None)
+
+

Relative Mean Squared Error

+

Computes Relative mean squared error (RelMSE), as proposed by Hyndman & Koehler (2006) as an alternative to percentage errors, to avoid measure unstability.

+

\[ \mathrm{RelMSE}(\mathbf{y}, \mathbf{\hat{y}}, \mathbf{\hat{y}}^{naive1}) = +\frac{\mathrm{MSE}(\mathbf{y}, \mathbf{\hat{y}})}{\mathrm{MSE}(\mathbf{y}, \mathbf{\hat{y}}^{naive1})} \]

+

Parameters:
y: numpy array, Actual values of size (n_series, horizon).
y_hat: numpy array, Predicted values (n_series, horizon).
mask: numpy array, Specifies date stamps per serie to consider in loss.

+

Returns:
loss: float.

+

References:
- Hyndman, R. J and Koehler, A. B. (2006). ā€œAnother look at measures of forecast accuracyā€, International Journal of Forecasting, Volume 22, Issue 4.
- Kin G. Olivares, O. Nganba Meetei, Ruijun Ma, Rohan Reddy, Mengfei Cao, Lee Dicker. ā€œProbabilistic Hierarchical Forecasting with Deep Poisson Mixtures. Submitted to the International Journal Forecasting, Working paper available at arxiv.

+
+
+
+

Mean Squared Scaled Error

+
+

source

+
+

msse

+
+
 msse (y, y_hat, y_train, mask=None)
+
+

Mean Squared Scaled Error

+

Computes Mean squared scaled error (MSSE), as proposed by Hyndman & Koehler (2006) as an alternative to percentage errors, to avoid measure unstability.

+

\[ \mathrm{MSSE}(\mathbf{y}, \mathbf{\hat{y}}, \mathbf{y}^{in-sample}) = +\frac{\frac{1}{h} \sum^{t+h}_{\tau=t+1} (y_{\tau} - \hat{y}_{\tau})^2}{\frac{1}{t-1} \sum^{t}_{\tau=2} (y_{\tau} - y_{\tau-1})^2},\]

+

where \(n\) (\(n=\)n) is the size of the training data, and \(h\) is the forecasting horizon (\(h=\)horizon).

+

Parameters:
y: numpy array, Actual values of size (n_series, horizon).
y_hat: numpy array, Predicted values (n_series, horizon).
y_train: numpy array, Predicted values (n_series, n).
mask: numpy array, Specifies date stamps per serie to consider in loss.

+

Returns:
loss: float.

+

References:
- Hyndman, R. J and Koehler, A. B. (2006). ā€œAnother look at measures of forecast accuracyā€, International Journal of Forecasting, Volume 22, Issue 4.

+
+
+
+

Scaled CRPS

+
+

source

+
+

scaled_crps

+
+
 scaled_crps (y, y_hat, quantiles)
+
+

Scaled Continues Ranked Probability Score

+

Calculates a scaled variation of the CRPS, as proposed by Rangapuram (2021), to measure the accuracy of predicted quantiles y_hat compared to the observation y.

+

This metric averages percentual weighted absolute deviations as defined by the quantile losses.

+

\[ \mathrm{sCRPS}(\hat{F}_{\tau}, \mathbf{y}_{\tau}) = \frac{2}{N} \sum_{i} +\int^{1}_{0} +\frac{\mathrm{QL}(\hat{F}_{i,\tau}, y_{i,\tau})_{q}}{\sum_{i} | y_{i,\tau} |} dq \]

+

where \(\hat{F}_{\tau}\) is the an estimated multivariate distribution, and \(y_{i,\tau}\) are its realizations.

+

Parameters:
y: numpy array, Actual values of size (n_series, horizon).
y_hat: numpy array, Predicted quantiles of size (n_series, horizon, n_quantiles).
quantiles: numpy array,(n_quantiles). Quantiles to estimate from the distribution of y.

+

Returns:
loss: float.

+

References:
- Gneiting, Tilmann. (2011). ā€œQuantiles as optimal point forecastsā€. International Journal of Forecasting.
- Spyros Makridakis, Evangelos Spiliotis, Vassilios Assimakopoulos, Zhi Chen, Anil Gaba, Ilia Tsetlin, Robert L. Winkler. (2022). ā€œThe M5 uncertainty competition: Results, findings and conclusionsā€. International Journal of Forecasting.
- Syama Sundar Rangapuram, Lucien D Werner, Konstantinos Benidis, Pedro Mercado, Jan Gasthaus, Tim Januschowski. (2021). ā€œEnd-to-End Learning of Coherent Probabilistic Forecasts for Hierarchical Time Seriesā€. Proceedings of the 38th International Conference on Machine Learning (ICML).

+
+
+
+

Energy Score

+
+

source

+
+

energy_score

+
+
 energy_score (y, y_sample1, y_sample2, beta=2)
+
+

Energy Score

+

Calculates Gneitingā€™s Energy Score sample approximation for y and independent multivariate samples y_sample1 and y_sample2. The Energy Score generalizes the CRPS (beta=1) in the multivariate setting.

+

\[ \mathrm{ES}(\mathbf{y}_{\tau}, \mathbf{\hat{y}}_{\tau}, \mathbf{\hat{y}}_{\tau}') += \frac{1}{2} \mathbb{E}_{\hat{P}} \left[ ||\mathbf{\hat{y}}_{\tau} - \mathbf{\hat{y}}_{\tau}'||^{\beta} \right] +- \mathbb{E}_{\hat{P}} \left[ ||\mathbf{y}_{\tau} - \mathbf{\hat{y}}_{\tau}||^{\beta} \right] +\quad \beta \in (0,2]\]

+

where \(\mathbf{\hat{y}}_{\tau}, \mathbf{\hat{y}}_{\tau}'\) are independent samples drawn from \(\hat{P}\).

+

Parameters:
y: numpy array, Actual values of size (n_series, horizon).
y_sample1: numpy array, predictive distribution sample of size (n_series, horizon, n_samples).
y_sample2: numpy array, predictive distribution sample of size (n_series, horizon, n_samples).
beta: float in (0,2], defines the energy scoreā€™s power for the euclidean metric.

+

Returns:
score: float.

+

References:
- Gneiting, Tilmann, and Adrian E. Raftery. (2007). ā€œStrictly proper scoring rules, prediction and estimationā€. Journal of the American Statistical Association.
- Anastasios Panagiotelis, Puwasala Gamakumara, George Athanasopoulos, Rob J. Hyndman. (2022). ā€œProbabilistic forecast reconciliation: Properties, evaluation and score optimisationā€. European Journal of Operational Research.

+
+

source

+
+
+

log_score

+
+
 log_score (y, y_hat, cov, allow_singular=True)
+
+

Log Score.

+

One of the simplest multivariate probability scoring rules, it evaluates the negative density at the value of the realisation.

+

\[ \mathrm{LS}(\mathbf{y}_{\tau}, \mathbf{P}(\theta_{\tau})) += - \log(f(\mathbf{y}_{\tau}, \theta_{\tau}))\]

+

where \(f\) is the density, \(\mathbf{P}(\theta_{\tau})\) is a parametric distribution and \(f(\mathbf{y}_{\tau}, \theta_{\tau})\) represents its density. For the moment we only support multivariate normal log score.

+

\[f(\mathbf{y}_{\tau}, \theta_{\tau}) = +(2\pi )^{-k/2}\det({\boldsymbol{\Sigma }})^{-1/2} +\,\exp \left( +-{\frac {1}{2}}(\mathbf{y}_{\tau} -\hat{\mathbf{y}}_{\tau})^{\!{\mathsf{T}}} +{\boldsymbol{\Sigma }}^{-1} +(\mathbf{y}_{\tau} -\hat{\mathbf{y}}_{\tau}) +\right)\]

+

Parameters:
y: numpy array, Actual values of size (n_series, horizon).
y_hat: numpy array, Predicted values (n_series, horizon).
cov: numpy matrix, Predicted values covariance (n_series, n_series, horizon).
allow_singular: bool=True, if true allows singular covariance.

+

Returns:
score: float.

+
+
x = np.linspace(0, 5, 10, endpoint=False)
+y = multivariate_normal.pdf(x, mean=2.5, cov=0.5)
+y
+
+
+
+
+
+

Hierarchical Evaluation

+
+

source

+
+

HierarchicalEvaluation

+
+
 HierarchicalEvaluation (evaluators:List[Callable])
+
+

Hierarchical Evaluation Class.

+

You can use your own metrics to evaluate the performance of each level in the structure. The metrics receive y and y_hat as arguments and they are numpy arrays of size (series, horizon). Consider, for example, the function rmse that calculates the root mean squared error.

+

This class facilitates measurements across the hierarchy, defined by the tags list. See also the aggregate method.

+

Parameters:
evaluators: functions with arguments y, y_hat (numpy arrays).

+

References:

+
+

source

+
+
+

HierarchicalEvaluation.evaluate

+
+
 HierarchicalEvaluation.evaluate (Y_hat_df:pandas.core.frame.DataFrame,
+                                  Y_test_df:pandas.core.frame.DataFrame,
+                                  tags:Dict[str,numpy.ndarray], Y_df:Optio
+                                  nal[pandas.core.frame.DataFrame]=None,
+                                  benchmark:Optional[str]=None)
+
+

Hierarchical Evaluation Method.

+

Parameters:
Y_hat_df: pd.DataFrame, Forecasts indexed by 'unique_id' with column 'ds' and models to evaluate.
Y_test_df: pd.DataFrame, True values with columns ['ds', 'y'].
tags: np.array, each str key is a level and its value contains tags associated to that level.
Y_df: pd.DataFrame, Training set of base time series with columns ['ds', 'y'] indexed by unique_id.
benchmark: str, If passed, evaluators are scaled by the error of this benchark.

+

Returns:
evaluation: pd.DataFrame with accuracy measurements across hierarchical levels.

+
+
+
+

Example

+
+
+

References

+ + + +
+ +

If you find the code useful, please ā­ us on Github

+ +
+ + + + \ No newline at end of file diff --git a/examples/AustralianDomesticTourism-Bootstraped-Intervals_files/figure-html/cell-11-output-1.png b/examples/AustralianDomesticTourism-Bootstraped-Intervals_files/figure-html/cell-11-output-1.png new file mode 100644 index 00000000..67e3d30d Binary files /dev/null and b/examples/AustralianDomesticTourism-Bootstraped-Intervals_files/figure-html/cell-11-output-1.png differ diff --git a/examples/AustralianDomesticTourism-Bootstraped-Intervals_files/figure-html/cell-12-output-1.png b/examples/AustralianDomesticTourism-Bootstraped-Intervals_files/figure-html/cell-12-output-1.png new file mode 100644 index 00000000..a79719f9 Binary files /dev/null and b/examples/AustralianDomesticTourism-Bootstraped-Intervals_files/figure-html/cell-12-output-1.png differ diff --git a/examples/AustralianDomesticTourism-Bootstraped-Intervals_files/figure-html/cell-20-output-1.png b/examples/AustralianDomesticTourism-Bootstraped-Intervals_files/figure-html/cell-20-output-1.png new file mode 100644 index 00000000..d333e10d Binary files /dev/null and b/examples/AustralianDomesticTourism-Bootstraped-Intervals_files/figure-html/cell-20-output-1.png differ diff --git a/examples/AustralianDomesticTourism-Bootstraped-Intervals_files/figure-html/cell-21-output-1.png b/examples/AustralianDomesticTourism-Bootstraped-Intervals_files/figure-html/cell-21-output-1.png new file mode 100644 index 00000000..73dffde2 Binary files /dev/null and b/examples/AustralianDomesticTourism-Bootstraped-Intervals_files/figure-html/cell-21-output-1.png differ diff --git a/examples/AustralianDomesticTourism-Bootstraped-Intervals_files/figure-html/cell-22-output-1.png b/examples/AustralianDomesticTourism-Bootstraped-Intervals_files/figure-html/cell-22-output-1.png new file mode 100644 index 00000000..292f8538 Binary files /dev/null and b/examples/AustralianDomesticTourism-Bootstraped-Intervals_files/figure-html/cell-22-output-1.png differ diff --git a/examples/AustralianDomesticTourism-Bootstraped-Intervals_files/figure-html/cell-23-output-1.png b/examples/AustralianDomesticTourism-Bootstraped-Intervals_files/figure-html/cell-23-output-1.png new file mode 100644 index 00000000..21f2d6de Binary files /dev/null and b/examples/AustralianDomesticTourism-Bootstraped-Intervals_files/figure-html/cell-23-output-1.png differ diff --git a/examples/AustralianDomesticTourism-Intervals_files/figure-html/cell-11-output-1.png b/examples/AustralianDomesticTourism-Intervals_files/figure-html/cell-11-output-1.png new file mode 100644 index 00000000..67e3d30d Binary files /dev/null and b/examples/AustralianDomesticTourism-Intervals_files/figure-html/cell-11-output-1.png differ diff --git a/examples/AustralianDomesticTourism-Intervals_files/figure-html/cell-12-output-1.png b/examples/AustralianDomesticTourism-Intervals_files/figure-html/cell-12-output-1.png new file mode 100644 index 00000000..a79719f9 Binary files /dev/null and b/examples/AustralianDomesticTourism-Intervals_files/figure-html/cell-12-output-1.png differ diff --git a/examples/AustralianDomesticTourism-Intervals_files/figure-html/cell-20-output-1.png b/examples/AustralianDomesticTourism-Intervals_files/figure-html/cell-20-output-1.png new file mode 100644 index 00000000..7df65727 Binary files /dev/null and b/examples/AustralianDomesticTourism-Intervals_files/figure-html/cell-20-output-1.png differ diff --git a/examples/AustralianDomesticTourism-Intervals_files/figure-html/cell-21-output-1.png b/examples/AustralianDomesticTourism-Intervals_files/figure-html/cell-21-output-1.png new file mode 100644 index 00000000..0d68892a Binary files /dev/null and b/examples/AustralianDomesticTourism-Intervals_files/figure-html/cell-21-output-1.png differ diff --git a/examples/AustralianDomesticTourism-Intervals_files/figure-html/cell-22-output-1.png b/examples/AustralianDomesticTourism-Intervals_files/figure-html/cell-22-output-1.png new file mode 100644 index 00000000..27e2b0e6 Binary files /dev/null and b/examples/AustralianDomesticTourism-Intervals_files/figure-html/cell-22-output-1.png differ diff --git a/examples/AustralianDomesticTourism-Intervals_files/figure-html/cell-23-output-1.png b/examples/AustralianDomesticTourism-Intervals_files/figure-html/cell-23-output-1.png new file mode 100644 index 00000000..e8c33ae8 Binary files /dev/null and b/examples/AustralianDomesticTourism-Intervals_files/figure-html/cell-23-output-1.png differ diff --git a/examples/AustralianDomesticTourism-Permbu-Intervals_files/figure-html/cell-11-output-1.png b/examples/AustralianDomesticTourism-Permbu-Intervals_files/figure-html/cell-11-output-1.png new file mode 100644 index 00000000..52a7471c Binary files /dev/null and b/examples/AustralianDomesticTourism-Permbu-Intervals_files/figure-html/cell-11-output-1.png differ diff --git a/examples/AustralianDomesticTourism-Permbu-Intervals_files/figure-html/cell-12-output-1.png b/examples/AustralianDomesticTourism-Permbu-Intervals_files/figure-html/cell-12-output-1.png new file mode 100644 index 00000000..628eb0b1 Binary files /dev/null and b/examples/AustralianDomesticTourism-Permbu-Intervals_files/figure-html/cell-12-output-1.png differ diff --git a/examples/AustralianDomesticTourism-Permbu-Intervals_files/figure-html/cell-20-output-1.png b/examples/AustralianDomesticTourism-Permbu-Intervals_files/figure-html/cell-20-output-1.png new file mode 100644 index 00000000..eabac5d3 Binary files /dev/null and b/examples/AustralianDomesticTourism-Permbu-Intervals_files/figure-html/cell-20-output-1.png differ diff --git a/examples/AustralianDomesticTourism-Permbu-Intervals_files/figure-html/cell-21-output-1.png b/examples/AustralianDomesticTourism-Permbu-Intervals_files/figure-html/cell-21-output-1.png new file mode 100644 index 00000000..7b6dae0b Binary files /dev/null and b/examples/AustralianDomesticTourism-Permbu-Intervals_files/figure-html/cell-21-output-1.png differ diff --git a/examples/AustralianDomesticTourism-Permbu-Intervals_files/figure-html/cell-22-output-1.png b/examples/AustralianDomesticTourism-Permbu-Intervals_files/figure-html/cell-22-output-1.png new file mode 100644 index 00000000..4e335ee0 Binary files /dev/null and b/examples/AustralianDomesticTourism-Permbu-Intervals_files/figure-html/cell-22-output-1.png differ diff --git a/examples/MLFrameworksExample_files/figure-html/cell-15-output-1.png b/examples/MLFrameworksExample_files/figure-html/cell-15-output-1.png new file mode 100644 index 00000000..b6b58987 Binary files /dev/null and b/examples/MLFrameworksExample_files/figure-html/cell-15-output-1.png differ diff --git a/examples/MLFrameworksExample_files/figure-html/cell-16-output-1.png b/examples/MLFrameworksExample_files/figure-html/cell-16-output-1.png new file mode 100644 index 00000000..d1a34c56 Binary files /dev/null and b/examples/MLFrameworksExample_files/figure-html/cell-16-output-1.png differ diff --git a/examples/MLFrameworksExample_files/figure-html/cell-6-output-1.png b/examples/MLFrameworksExample_files/figure-html/cell-6-output-1.png new file mode 100644 index 00000000..dbd2c691 Binary files /dev/null and b/examples/MLFrameworksExample_files/figure-html/cell-6-output-1.png differ diff --git a/examples/TourismLarge-Evaluation_files/figure-html/cell-13-output-1.png b/examples/TourismLarge-Evaluation_files/figure-html/cell-13-output-1.png new file mode 100644 index 00000000..859d5f27 Binary files /dev/null and b/examples/TourismLarge-Evaluation_files/figure-html/cell-13-output-1.png differ diff --git a/examples/TourismLarge-Evaluation_files/figure-html/cell-7-output-1.png b/examples/TourismLarge-Evaluation_files/figure-html/cell-7-output-1.png new file mode 100644 index 00000000..15bc8d4b Binary files /dev/null and b/examples/TourismLarge-Evaluation_files/figure-html/cell-7-output-1.png differ diff --git a/examples/australiandomestictourism-bootstraped-intervals.html b/examples/australiandomestictourism-bootstraped-intervals.html new file mode 100644 index 00000000..038540de --- /dev/null +++ b/examples/australiandomestictourism-bootstraped-intervals.html @@ -0,0 +1,1249 @@ + + + + + + + + + +hierarchicalforecast - Bootstrap + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ + +
+ +
+ + +
+ + + +
+ +
+
+

Bootstrap

+
+ + + +
+ + + + +
+ + +
+ + +

Open In Colab

+

In many cases, only the time series at the lowest level of the hierarchies (bottom time series) are available. HierarchicalForecast has tools to create time series for all hierarchies and also allows you to calculate prediction intervals for all hierarchies. In this notebook we will see how to do it.

+
+
!pip install hierarchicalforecast statsforecast
+
+
+
import numpy as np
+import pandas as pd
+import matplotlib.pyplot as plt
+
+# compute base forecast no coherent
+from statsforecast.models import ETS, Naive
+from statsforecast.core import StatsForecast
+
+#obtain hierarchical reconciliation methods and evaluation
+from hierarchicalforecast.methods import BottomUp, MinTrace
+from hierarchicalforecast.utils import aggregate, HierarchicalPlot
+from hierarchicalforecast.core import HierarchicalReconciliation
+from hierarchicalforecast.evaluation import HierarchicalEvaluation
+
+
/Users/fedex/miniconda3/envs/hierarchicalforecast/lib/python3.10/site-packages/statsforecast/core.py:21: TqdmWarning: IProgress not found. Please update jupyter and ipywidgets. See https://ipywidgets.readthedocs.io/en/stable/user_install.html
+  from tqdm.autonotebook import tqdm
+
+
+
+

Aggregate bottom time series

+

In this example we will use the Tourism dataset from the Forecasting: Principles and Practice book. The dataset only contains the time series at the lowest level, so we need to create the time series for all hierarchies.

+
+
Y_df = pd.read_csv('https://raw.githubusercontent.com/Nixtla/transfer-learning-time-series/main/datasets/tourism.csv')
+Y_df = Y_df.rename({'Trips': 'y', 'Quarter': 'ds'}, axis=1)
+Y_df.insert(0, 'Country', 'Australia')
+Y_df = Y_df[['Country', 'Region', 'State', 'Purpose', 'ds', 'y']]
+Y_df['ds'] = Y_df['ds'].str.replace(r'(\d+) (Q\d)', r'\1-\2', regex=True)
+Y_df['ds'] = pd.to_datetime(Y_df['ds'])
+Y_df.head()
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
CountryRegionStatePurposedsy
0AustraliaAdelaideSouth AustraliaBusiness1998-01-01135.077690
1AustraliaAdelaideSouth AustraliaBusiness1998-04-01109.987316
2AustraliaAdelaideSouth AustraliaBusiness1998-07-01166.034687
3AustraliaAdelaideSouth AustraliaBusiness1998-10-01127.160464
4AustraliaAdelaideSouth AustraliaBusiness1999-01-01137.448533
+ +
+
+
+

The dataset can be grouped in the following non-strictly hierarchical structure.

+
+
spec = [
+    ['Country'],
+    ['Country', 'State'], 
+    ['Country', 'Purpose'], 
+    ['Country', 'State', 'Region'], 
+    ['Country', 'State', 'Purpose'], 
+    ['Country', 'State', 'Region', 'Purpose']
+]
+
+

Using the aggregate function from HierarchicalForecast we can generate: 1. Y_df: the hierarchical structured series \(\mathbf{y}_{[a,b]\tau}\) 2. S_df: the aggregation constraings dataframe with \(S_{[a,b]}\) 3. tags: a list with the ā€˜unique_idsā€™ conforming each aggregation level.

+
+
Y_df, S_df, tags = aggregate(df=Y_df, spec=spec)
+Y_df = Y_df.reset_index()
+
+
/Users/fedex/miniconda3/envs/hierarchicalforecast/lib/python3.10/site-packages/sklearn/preprocessing/_encoders.py:828: FutureWarning: `sparse` was renamed to `sparse_output` in version 1.2 and will be removed in 1.4. `sparse_output` is ignored unless you leave `sparse` to its default value.
+  warnings.warn(
+
+
+
+
Y_df.head()
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
unique_iddsy
0Australia1998-01-0123182.197269
1Australia1998-04-0120323.380067
2Australia1998-07-0119826.640511
3Australia1998-10-0120830.129891
4Australia1999-01-0122087.353380
+ +
+
+
+
+
S_df.iloc[:5, :5]
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Australia/ACT/Canberra/BusinessAustralia/ACT/Canberra/HolidayAustralia/ACT/Canberra/OtherAustralia/ACT/Canberra/VisitingAustralia/New South Wales/Blue Mountains/Business
Australia1.01.01.01.01.0
Australia/ACT1.01.01.01.00.0
Australia/New South Wales0.00.00.00.01.0
Australia/Northern Territory0.00.00.00.00.0
Australia/Queensland0.00.00.00.00.0
+ +
+
+
+
+
tags['Country/Purpose']
+
+
array(['Australia/Business', 'Australia/Holiday', 'Australia/Other',
+       'Australia/Visiting'], dtype=object)
+
+
+

We can visualize the S_df dataframe and Y_df using the HierarchicalPlot class as follows.

+
+
hplot = HierarchicalPlot(S=S_df, tags=tags)
+
+
+
hplot.plot_summing_matrix()
+
+

+
+
+
+
hplot.plot_hierarchically_linked_series(
+    bottom_series='Australia/ACT/Canberra/Holiday',
+    Y_df=Y_df.set_index('unique_id')
+)
+
+

+
+
+
+

Split Train/Test sets

+

We use the final two years (8 quarters) as test set.

+
+
Y_test_df = Y_df.groupby('unique_id').tail(8)
+Y_train_df = Y_df.drop(Y_test_df.index)
+
+
+
Y_test_df = Y_test_df.set_index('unique_id')
+Y_train_df = Y_train_df.set_index('unique_id')
+
+
+
Y_train_df.groupby('unique_id').size()
+
+
unique_id
+Australia                                                72
+Australia/ACT                                            72
+Australia/ACT/Business                                   72
+Australia/ACT/Canberra                                   72
+Australia/ACT/Canberra/Business                          72
+                                                         ..
+Australia/Western Australia/Experience Perth/Other       72
+Australia/Western Australia/Experience Perth/Visiting    72
+Australia/Western Australia/Holiday                      72
+Australia/Western Australia/Other                        72
+Australia/Western Australia/Visiting                     72
+Length: 425, dtype: int64
+
+
+
+
+
+

Computing Base Forecasts

+

The following cell computes the base forecasts for each time series in Y_df using the AutoETS and model. Observe that Y_hat_df contains the forecasts but they are not coherent. Since we are computing prediction intervals using bootstrapping, we only need the fitted values of the models.

+
+
fcst = StatsForecast(df=Y_train_df, 
+                     models=[ETS(season_length=4, model='ZAA')],
+                     freq='QS', n_jobs=-1)
+Y_hat_df = fcst.forecast(h=8, fitted=True)
+Y_fitted_df = fcst.forecast_fitted_values()
+
+
/Users/fedex/miniconda3/envs/hierarchicalforecast/lib/python3.10/site-packages/statsforecast/models.py:526: FutureWarning: `ETS` will be deprecated in future versions of `StatsForecast`. Please use `AutoETS` instead.
+  ETS._warn()
+
+
+
+
+

Reconcile Base Forecasts

+

The following cell makes the previous forecasts coherent using the HierarchicalReconciliation class. Since the hierarchy structure is not strict, we canā€™t use methods such as TopDown or MiddleOut. In this example we use BottomUp and MinTrace. If you want to calculate prediction intervals, you have to use the level argument as follows and set intervals_method='bootstrap'.

+
+
reconcilers = [
+    BottomUp(),
+    MinTrace(method='mint_shrink'),
+    MinTrace(method='ols')
+]
+hrec = HierarchicalReconciliation(reconcilers=reconcilers)
+Y_rec_df = hrec.reconcile(Y_hat_df=Y_hat_df, Y_df=Y_fitted_df, S=S_df, 
+                          tags=tags, level=[80, 90], 
+                          intervals_method='bootstrap')
+
+

The dataframe Y_rec_df contains the reconciled forecasts.

+
+
Y_rec_df.head()
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
dsETSETS/BottomUpETS/BottomUp-lo-90ETS/BottomUp-lo-80ETS/BottomUp-hi-80ETS/BottomUp-hi-90ETS/MinTrace_method-mint_shrinkETS/MinTrace_method-mint_shrink-lo-90ETS/MinTrace_method-mint_shrink-lo-80ETS/MinTrace_method-mint_shrink-hi-80ETS/MinTrace_method-mint_shrink-hi-90ETS/MinTrace_method-olsETS/MinTrace_method-ols-lo-90ETS/MinTrace_method-ols-lo-80ETS/MinTrace_method-ols-hi-80ETS/MinTrace_method-ols-hi-90
unique_id
Australia2016-01-0126080.87890624487.34960923244.12099623333.69472725381.79296925426.33398425532.52355924428.91170124709.21063826365.60693426476.25550126034.11424124914.13637525100.46293827102.73502227176.416922
Australia2016-04-0124587.01171923069.74414121826.51943421912.96289123946.60625024281.44726624118.55717723199.96862623295.24425225108.47041025489.38360624567.46099523484.05056823640.63842325709.76367825809.249492
Australia2016-07-0124147.30859422689.77734421297.13671921530.43828123701.17382824155.82031223731.25138722627.63966922818.72918224821.48845825246.86743224150.13489823030.15683423155.02555625359.99237625404.841402
Australia2016-10-0124794.04101623429.75781222037.12304722276.45312524241.41796924441.16015624486.54934423385.92723223600.70452525353.55562525481.47855724831.58451623725.92446423836.47517425900.20525425977.265089
Australia2017-01-0126284.00000024940.04296923696.72275423904.38281225814.94140625974.16992226041.86748824972.07785825158.98671026918.10474727135.58084526348.20333525254.65932425487.50229127410.87303527477.334507
+ +
+
+
+
+
+

Plot Predictions

+

Then we can plot the probabilist forecasts using the following function.

+
+
plot_df = pd.concat([Y_df.set_index(['unique_id', 'ds']), 
+                     Y_rec_df.set_index('ds', append=True)], axis=1)
+plot_df = plot_df.reset_index('ds')
+
+
+

Plot single time series

+
+
hplot.plot_series(
+    series='Australia',
+    Y_df=plot_df, 
+    models=['y', 'ETS', 'ETS/MinTrace_method-ols', 'ETS/MinTrace_method-mint_shrink'],
+    level=[80]
+)
+
+

+
+
+
+
# Since we are plotting a bottom time series
+# the probabilistic and mean forecasts
+# differ due to bootstrapping
+hplot.plot_series(
+    series='Australia/Western Australia/Experience Perth/Visiting',
+    Y_df=plot_df, 
+    models=['y', 'ETS', 'ETS/BottomUp'],
+    level=[80]
+)
+
+

+
+
+
+
+

Plot hierarchichally linked time series

+
+
hplot.plot_hierarchically_linked_series(
+    bottom_series='Australia/Western Australia/Experience Perth/Visiting',
+    Y_df=plot_df, 
+    models=['y', 'ETS', 'ETS/MinTrace_method-ols', 'ETS/BottomUp'],
+    level=[80]
+)
+
+

+
+
+
+
# ACT only has Canberra
+hplot.plot_hierarchically_linked_series(
+    bottom_series='Australia/ACT/Canberra/Other',
+    Y_df=plot_df, 
+    models=['y', 'ETS/MinTrace_method-mint_shrink'],
+    level=[80, 90]
+)
+
+

+
+
+
+
+

References

+ + + +
+
+ +

If you find the code useful, please ā­ us on Github

+ +
+ + + + \ No newline at end of file diff --git a/examples/australiandomestictourism-intervals.html b/examples/australiandomestictourism-intervals.html new file mode 100644 index 00000000..e441fcb7 --- /dev/null +++ b/examples/australiandomestictourism-intervals.html @@ -0,0 +1,1272 @@ + + + + + + + + + +hierarchicalforecast - Normality + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ + +
+ +
+ + +
+ + + +
+ +
+
+

Normality

+
+ + + +
+ + + + +
+ + +
+ + +

Open In Colab

+

In many cases, only the time series at the lowest level of the hierarchies (bottom time series) are available. HierarchicalForecast has tools to create time series for all hierarchies and also allows you to calculate prediction intervals for all hierarchies. In this notebook we will see how to do it.

+
+
!pip install hierarchicalforecast statsforecast
+
+
+
import numpy as np
+import pandas as pd
+import matplotlib.pyplot as plt
+
+# compute base forecast no coherent
+from statsforecast.models import AutoARIMA
+from statsforecast.core import StatsForecast
+
+#obtain hierarchical reconciliation methods and evaluation
+from hierarchicalforecast.methods import BottomUp, MinTrace
+from hierarchicalforecast.utils import aggregate, HierarchicalPlot
+from hierarchicalforecast.core import HierarchicalReconciliation
+from hierarchicalforecast.evaluation import HierarchicalEvaluation
+
+
/Users/fedex/miniconda3/envs/hierarchicalforecast/lib/python3.10/site-packages/statsforecast/core.py:21: TqdmWarning: IProgress not found. Please update jupyter and ipywidgets. See https://ipywidgets.readthedocs.io/en/stable/user_install.html
+  from tqdm.autonotebook import tqdm
+
+
+
+

Aggregate bottom time series

+

In this example we will use the Tourism dataset from the Forecasting: Principles and Practice book. The dataset only contains the time series at the lowest level, so we need to create the time series for all hierarchies.

+
+
Y_df = pd.read_csv('https://raw.githubusercontent.com/Nixtla/transfer-learning-time-series/main/datasets/tourism.csv')
+Y_df = Y_df.rename({'Trips': 'y', 'Quarter': 'ds'}, axis=1)
+Y_df.insert(0, 'Country', 'Australia')
+Y_df = Y_df[['Country', 'Region', 'State', 'Purpose', 'ds', 'y']]
+Y_df['ds'] = Y_df['ds'].str.replace(r'(\d+) (Q\d)', r'\1-\2', regex=True)
+Y_df['ds'] = pd.to_datetime(Y_df['ds'])
+Y_df.head()
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
CountryRegionStatePurposedsy
0AustraliaAdelaideSouth AustraliaBusiness1998-01-01135.077690
1AustraliaAdelaideSouth AustraliaBusiness1998-04-01109.987316
2AustraliaAdelaideSouth AustraliaBusiness1998-07-01166.034687
3AustraliaAdelaideSouth AustraliaBusiness1998-10-01127.160464
4AustraliaAdelaideSouth AustraliaBusiness1999-01-01137.448533
+ +
+
+
+

The dataset can be grouped in the following non-strictly hierarchical structure.

+
+
spec = [
+    ['Country'],
+    ['Country', 'State'], 
+    ['Country', 'Purpose'], 
+    ['Country', 'State', 'Region'], 
+    ['Country', 'State', 'Purpose'], 
+    ['Country', 'State', 'Region', 'Purpose']
+]
+
+

Using the aggregate function from HierarchicalForecast we can generate: 1. Y_df: the hierarchical structured series \(\mathbf{y}_{[a,b]\tau}\) 2. S_df: the aggregation constraings dataframe with \(S_{[a,b]}\) 3. tags: a list with the ā€˜unique_idsā€™ conforming each aggregation level.

+
+
Y_df, S_df, tags = aggregate(df=Y_df, spec=spec)
+Y_df = Y_df.reset_index()
+
+
/Users/fedex/miniconda3/envs/hierarchicalforecast/lib/python3.10/site-packages/sklearn/preprocessing/_encoders.py:828: FutureWarning: `sparse` was renamed to `sparse_output` in version 1.2 and will be removed in 1.4. `sparse_output` is ignored unless you leave `sparse` to its default value.
+  warnings.warn(
+
+
+
+
Y_df.head()
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
unique_iddsy
0Australia1998-01-0123182.197269
1Australia1998-04-0120323.380067
2Australia1998-07-0119826.640511
3Australia1998-10-0120830.129891
4Australia1999-01-0122087.353380
+ +
+
+
+
+
S_df.iloc[:5, :5]
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Australia/ACT/Canberra/BusinessAustralia/ACT/Canberra/HolidayAustralia/ACT/Canberra/OtherAustralia/ACT/Canberra/VisitingAustralia/New South Wales/Blue Mountains/Business
Australia1.01.01.01.01.0
Australia/ACT1.01.01.01.00.0
Australia/New South Wales0.00.00.00.01.0
Australia/Northern Territory0.00.00.00.00.0
Australia/Queensland0.00.00.00.00.0
+ +
+
+
+
+
tags['Country/Purpose']
+
+
array(['Australia/Business', 'Australia/Holiday', 'Australia/Other',
+       'Australia/Visiting'], dtype=object)
+
+
+

We can visualize the S matrix and the data using the HierarchicalPlot class as follows.

+
+
hplot = HierarchicalPlot(S=S_df, tags=tags)
+
+
+
hplot.plot_summing_matrix()
+
+

+
+
+
+
hplot.plot_hierarchically_linked_series(
+    bottom_series='Australia/ACT/Canberra/Holiday',
+    Y_df=Y_df.set_index('unique_id')
+)
+
+

+
+
+
+

Split Train/Test sets

+

We use the final two years (8 quarters) as test set.

+
+
Y_test_df = Y_df.groupby('unique_id').tail(8)
+Y_train_df = Y_df.drop(Y_test_df.index)
+
+
+
Y_test_df = Y_test_df.set_index('unique_id')
+Y_train_df = Y_train_df.set_index('unique_id')
+
+
+
Y_train_df.groupby('unique_id').size()
+
+
unique_id
+Australia                                                72
+Australia/ACT                                            72
+Australia/ACT/Business                                   72
+Australia/ACT/Canberra                                   72
+Australia/ACT/Canberra/Business                          72
+                                                         ..
+Australia/Western Australia/Experience Perth/Other       72
+Australia/Western Australia/Experience Perth/Visiting    72
+Australia/Western Australia/Holiday                      72
+Australia/Western Australia/Other                        72
+Australia/Western Australia/Visiting                     72
+Length: 425, dtype: int64
+
+
+
+
+
+

Computing base forecasts

+

The following cell computes the base forecasts for each time series in Y_df using the AutoARIMA and model. Observe that Y_hat_df contains the forecasts but they are not coherent. To reconcile the prediction intervals we need to calculate the uncoherent intervals using the level argument of StatsForecast.

+
+
fcst = StatsForecast(df=Y_train_df,
+                     models=[AutoARIMA(season_length=4)], 
+                     freq='QS', n_jobs=-1)
+Y_hat_df = fcst.forecast(h=8, fitted=True, level=[80, 90])
+Y_fitted_df = fcst.forecast_fitted_values()
+
+
+
+

Reconcile forecasts

+

The following cell makes the previous forecasts coherent using the HierarchicalReconciliation class. Since the hierarchy structure is not strict, we canā€™t use methods such as TopDown or MiddleOut. In this example we use BottomUp and MinTrace. If you want to calculate prediction intervals, you have to use the level argument as follows.

+
+
reconcilers = [
+    BottomUp(),
+    MinTrace(method='mint_shrink'),
+    MinTrace(method='ols')
+]
+hrec = HierarchicalReconciliation(reconcilers=reconcilers)
+Y_rec_df = hrec.reconcile(Y_hat_df=Y_hat_df, Y_df=Y_fitted_df, 
+                          S=S_df, tags=tags, level=[80, 90])
+
+

The dataframe Y_rec_df contains the reconciled forecasts.

+
+
Y_rec_df.head()
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
dsAutoARIMAAutoARIMA-lo-90AutoARIMA-lo-80AutoARIMA-hi-80AutoARIMA-hi-90AutoARIMA/BottomUpAutoARIMA/BottomUp-lo-90AutoARIMA/BottomUp-lo-80AutoARIMA/BottomUp-hi-80...AutoARIMA/MinTrace_method-mint_shrinkAutoARIMA/MinTrace_method-mint_shrink-lo-90AutoARIMA/MinTrace_method-mint_shrink-lo-80AutoARIMA/MinTrace_method-mint_shrink-hi-80AutoARIMA/MinTrace_method-mint_shrink-hi-90AutoARIMA/MinTrace_method-olsAutoARIMA/MinTrace_method-ols-lo-90AutoARIMA/MinTrace_method-ols-lo-80AutoARIMA/MinTrace_method-ols-hi-80AutoARIMA/MinTrace_method-ols-hi-90
unique_id
Australia2016-01-0126212.55468824694.22460925029.58007827395.52734427730.88476624368.09960923674.07644123827.36670624908.832513...25205.74939724453.41711524619.58622925791.91256525958.08167926059.04751224978.60836425217.24708726900.84793727139.486661
Australia2016-04-0125033.66796923324.06640623701.66992226365.66601626743.26953122395.92187521629.48207821798.76714622993.076604...23720.83319022915.77223323093.58763224348.07874824525.89414824769.46425723554.94655123823.19947025715.72904525983.981963
Australia2016-07-0124507.02734422625.50000023041.07617225972.97851626388.55468822004.16992221182.94507421364.33062422644.009219...23167.12369122316.29807422504.22160423830.02577724017.94930824205.85534422870.66108623165.56807325246.14261625541.049603
Australia2016-10-0125598.92968823559.91992224010.28125027187.57812527637.93750022325.05664121456.89297721648.64599623001.467285...23982.25191323087.31371523284.98047824679.52334824877.19011125271.86133623825.78231124145.18063426398.54203826717.940362
Australia2017-01-0126982.57812524651.53515625166.39648428798.75781229313.61914123258.00195322296.17871422508.61850824007.385398...25002.24361524016.74719524234.41573125770.07149825987.74003426611.14373624959.63664725324.40827227897.87920128262.650825
+ +

5 rows Ɨ 21 columns

+
+
+
+
+
+

Plot forecasts

+

Then we can plot the probabilistic forecasts using the following function.

+
+
plot_df = pd.concat([Y_df.set_index(['unique_id', 'ds']), 
+                     Y_rec_df.set_index('ds', append=True)], axis=1)
+plot_df = plot_df.reset_index('ds')
+
+
+

Plot single time series

+
+
hplot.plot_series(
+    series='Australia',
+    Y_df=plot_df, 
+    models=['y', 'AutoARIMA', 'AutoARIMA/MinTrace_method-ols'],
+    level=[80]
+)
+
+

+
+
+
+
# Since we are plotting a bottom time series
+# the probabilistic and mean forecasts
+# are the same
+hplot.plot_series(
+    series='Australia/Western Australia/Experience Perth/Visiting',
+    Y_df=plot_df, 
+    models=['y', 'AutoARIMA', 'AutoARIMA/BottomUp'],
+    level=[80]
+)
+
+

+
+
+
+
+

Plot hierarchichally linked time series

+
+
hplot.plot_hierarchically_linked_series(
+    bottom_series='Australia/Western Australia/Experience Perth/Visiting',
+    Y_df=plot_df, 
+    models=['y', 'AutoARIMA', 'AutoARIMA/MinTrace_method-ols', 'AutoARIMA/BottomUp'],
+    level=[80]
+)
+
+

+
+
+
+
# ACT only has Canberra
+hplot.plot_hierarchically_linked_series(
+    bottom_series='Australia/ACT/Canberra/Other',
+    Y_df=plot_df, 
+    models=['y', 'AutoARIMA/MinTrace_method-mint_shrink'],
+    level=[80, 90]
+)
+
+

+
+
+
+
+

References

+ + + +
+
+ +

If you find the code useful, please ā­ us on Github

+ +
+ + + + \ No newline at end of file diff --git a/examples/australiandomestictourism-permbu-intervals.html b/examples/australiandomestictourism-permbu-intervals.html new file mode 100644 index 00000000..378c04b2 --- /dev/null +++ b/examples/australiandomestictourism-permbu-intervals.html @@ -0,0 +1,1273 @@ + + + + + + + + + +hierarchicalforecast - PERMBU + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ + +
+ +
+ + +
+ + + +
+ +
+
+

PERMBU

+
+ + + +
+ + + + +
+ + +
+ + +

Open In Colab

+

In many cases, only the time series at the lowest level of the hierarchies (bottom time series) are available. HierarchicalForecast has tools to create time series for all hierarchies and also allows you to calculate prediction intervals for all hierarchies. In this notebook we will see how to do it.

+
+
!pip install hierarchicalforecast statsforecast
+
+
+
import numpy as np
+import pandas as pd
+import matplotlib.pyplot as plt
+
+# compute base forecast no coherent
+from statsforecast.models import AutoARIMA
+from statsforecast.core import StatsForecast
+
+#obtain hierarchical reconciliation methods and evaluation
+from hierarchicalforecast.methods import BottomUp, MinTrace
+from hierarchicalforecast.utils import aggregate, HierarchicalPlot
+from hierarchicalforecast.core import HierarchicalReconciliation
+from hierarchicalforecast.evaluation import HierarchicalEvaluation
+
+
/Users/fedex/miniconda3/envs/hierarchicalforecast/lib/python3.10/site-packages/statsforecast/core.py:21: TqdmWarning: IProgress not found. Please update jupyter and ipywidgets. See https://ipywidgets.readthedocs.io/en/stable/user_install.html
+  from tqdm.autonotebook import tqdm
+
+
+
+

Aggregate bottom time series

+

In this example we will use the Tourism dataset from the Forecasting: Principles and Practice book. The dataset only contains the time series at the lowest level, so we need to create the time series for all hierarchies.

+
+
Y_df = pd.read_csv('https://raw.githubusercontent.com/Nixtla/transfer-learning-time-series/main/datasets/tourism.csv')
+Y_df = Y_df.rename({'Trips': 'y', 'Quarter': 'ds'}, axis=1)
+Y_df.insert(0, 'Country', 'Australia')
+Y_df = Y_df[['Country', 'Region', 'State', 'Purpose', 'ds', 'y']]
+Y_df['ds'] = Y_df['ds'].str.replace(r'(\d+) (Q\d)', r'\1-\2', regex=True)
+Y_df['ds'] = pd.to_datetime(Y_df['ds'])
+Y_df.head()
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
CountryRegionStatePurposedsy
0AustraliaAdelaideSouth AustraliaBusiness1998-01-01135.077690
1AustraliaAdelaideSouth AustraliaBusiness1998-04-01109.987316
2AustraliaAdelaideSouth AustraliaBusiness1998-07-01166.034687
3AustraliaAdelaideSouth AustraliaBusiness1998-10-01127.160464
4AustraliaAdelaideSouth AustraliaBusiness1999-01-01137.448533
+ +
+
+
+

The dataset can be grouped in the following strictly hierarchical structure.

+
+
spec = [
+    ['Country'],
+    ['Country', 'State'], 
+    ['Country', 'State', 'Region']
+]
+
+

Using the aggregate function from HierarchicalForecast we can get the full set of time series.

+
+
Y_df, S_df, tags = aggregate(df=Y_df, spec=spec)
+Y_df = Y_df.reset_index()
+
+
/Users/fedex/miniconda3/envs/hierarchicalforecast/lib/python3.10/site-packages/sklearn/preprocessing/_encoders.py:828: FutureWarning: `sparse` was renamed to `sparse_output` in version 1.2 and will be removed in 1.4. `sparse_output` is ignored unless you leave `sparse` to its default value.
+  warnings.warn(
+
+
+
+
Y_df.head()
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
unique_iddsy
0Australia1998-01-0123182.197269
1Australia1998-04-0120323.380067
2Australia1998-07-0119826.640511
3Australia1998-10-0120830.129891
4Australia1999-01-0122087.353380
+ +
+
+
+
+
S_df.iloc[:5, :5]
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Australia/ACT/CanberraAustralia/New South Wales/Blue MountainsAustralia/New South Wales/Capital CountryAustralia/New South Wales/Central CoastAustralia/New South Wales/Central NSW
Australia1.01.01.01.01.0
Australia/ACT1.00.00.00.00.0
Australia/New South Wales0.01.01.01.01.0
Australia/Northern Territory0.00.00.00.00.0
Australia/Queensland0.00.00.00.00.0
+ +
+
+
+
+
tags['Country/State']
+
+
array(['Australia/ACT', 'Australia/New South Wales',
+       'Australia/Northern Territory', 'Australia/Queensland',
+       'Australia/South Australia', 'Australia/Tasmania',
+       'Australia/Victoria', 'Australia/Western Australia'], dtype=object)
+
+
+

We can visualize the S matrix and the data using the HierarchicalPlot class as follows.

+
+
hplot = HierarchicalPlot(S=S_df, tags=tags)
+
+
+
hplot.plot_summing_matrix()
+
+

+
+
+
+
hplot.plot_hierarchically_linked_series(
+    bottom_series='Australia/ACT/Canberra',
+    Y_df=Y_df.set_index('unique_id')
+)
+
+

+
+
+
+

Split Train/Test sets

+

We use the final two years (8 quarters) as test set.

+
+
Y_test_df = Y_df.groupby('unique_id').tail(8)
+Y_train_df = Y_df.drop(Y_test_df.index)
+
+
+
Y_test_df = Y_test_df.set_index('unique_id')
+Y_train_df = Y_train_df.set_index('unique_id')
+
+
+
Y_train_df.groupby('unique_id').size()
+
+
unique_id
+Australia                                                 72
+Australia/ACT                                             72
+Australia/ACT/Canberra                                    72
+Australia/New South Wales                                 72
+Australia/New South Wales/Blue Mountains                  72
+                                                          ..
+Australia/Western Australia/Australia's Coral Coast       72
+Australia/Western Australia/Australia's Golden Outback    72
+Australia/Western Australia/Australia's North West        72
+Australia/Western Australia/Australia's South West        72
+Australia/Western Australia/Experience Perth              72
+Length: 85, dtype: int64
+
+
+
+
+
+

Computing base forecasts

+

The following cell computes the base forecasts for each time series in Y_df using the AutoARIMA and model. Observe that Y_hat_df contains the forecasts but they are not coherent. To reconcile the prediction intervals we need to calculate the uncoherent intervals using the level argument of StatsForecast.

+
+
fcst = StatsForecast(df=Y_train_df,
+                     models=[AutoARIMA(season_length=4)], 
+                     freq='QS', n_jobs=-1)
+Y_hat_df = fcst.forecast(h=8, fitted=True, level=[80, 90])
+Y_fitted_df = fcst.forecast_fitted_values()
+
+
+
+

Reconcile forecasts and compute prediction intervals using PERMBU

+

The following cell makes the previous forecasts coherent using the HierarchicalReconciliation class. In this example we use BottomUp and MinTrace. If you want to calculate prediction intervals, you have to use the level argument as follows and also intervals_method='permbu'.

+
+
reconcilers = [
+    BottomUp(),
+    MinTrace(method='mint_shrink'),
+    MinTrace(method='ols')
+]
+hrec = HierarchicalReconciliation(reconcilers=reconcilers)
+Y_rec_df = hrec.reconcile(Y_hat_df=Y_hat_df, Y_df=Y_fitted_df,
+                          S=S_df, tags=tags,
+                          level=[80, 90], intervals_method='permbu')
+
+
/Users/fedex/miniconda3/envs/hierarchicalforecast/lib/python3.10/site-packages/sklearn/preprocessing/_encoders.py:828: FutureWarning: `sparse` was renamed to `sparse_output` in version 1.2 and will be removed in 1.4. `sparse_output` is ignored unless you leave `sparse` to its default value.
+  warnings.warn(
+/Users/fedex/miniconda3/envs/hierarchicalforecast/lib/python3.10/site-packages/sklearn/preprocessing/_encoders.py:828: FutureWarning: `sparse` was renamed to `sparse_output` in version 1.2 and will be removed in 1.4. `sparse_output` is ignored unless you leave `sparse` to its default value.
+  warnings.warn(
+/Users/fedex/miniconda3/envs/hierarchicalforecast/lib/python3.10/site-packages/sklearn/preprocessing/_encoders.py:828: FutureWarning: `sparse` was renamed to `sparse_output` in version 1.2 and will be removed in 1.4. `sparse_output` is ignored unless you leave `sparse` to its default value.
+  warnings.warn(
+/Users/fedex/miniconda3/envs/hierarchicalforecast/lib/python3.10/site-packages/sklearn/preprocessing/_encoders.py:828: FutureWarning: `sparse` was renamed to `sparse_output` in version 1.2 and will be removed in 1.4. `sparse_output` is ignored unless you leave `sparse` to its default value.
+  warnings.warn(
+/Users/fedex/miniconda3/envs/hierarchicalforecast/lib/python3.10/site-packages/sklearn/preprocessing/_encoders.py:828: FutureWarning: `sparse` was renamed to `sparse_output` in version 1.2 and will be removed in 1.4. `sparse_output` is ignored unless you leave `sparse` to its default value.
+  warnings.warn(
+/Users/fedex/miniconda3/envs/hierarchicalforecast/lib/python3.10/site-packages/sklearn/preprocessing/_encoders.py:828: FutureWarning: `sparse` was renamed to `sparse_output` in version 1.2 and will be removed in 1.4. `sparse_output` is ignored unless you leave `sparse` to its default value.
+  warnings.warn(
+
+
+

The dataframe Y_rec_df contains the reconciled forecasts.

+
+
Y_rec_df.head()
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
dsAutoARIMAAutoARIMA-lo-90AutoARIMA-lo-80AutoARIMA-hi-80AutoARIMA-hi-90AutoARIMA/BottomUpAutoARIMA/BottomUp-lo-90AutoARIMA/BottomUp-lo-80AutoARIMA/BottomUp-hi-80...AutoARIMA/MinTrace_method-mint_shrinkAutoARIMA/MinTrace_method-mint_shrink-lo-90AutoARIMA/MinTrace_method-mint_shrink-lo-80AutoARIMA/MinTrace_method-mint_shrink-hi-80AutoARIMA/MinTrace_method-mint_shrink-hi-90AutoARIMA/MinTrace_method-olsAutoARIMA/MinTrace_method-ols-lo-90AutoARIMA/MinTrace_method-ols-lo-80AutoARIMA/MinTrace_method-ols-hi-80AutoARIMA/MinTrace_method-ols-hi-90
unique_id
Australia2016-01-0126212.55468824694.22460925029.58007827395.52734427730.88476624865.63671924106.80251024373.96204325423.566450...25395.41192824733.04663324824.27468125939.34500725998.69246026133.75895325516.48451825600.64492626662.92320426855.585562
Australia2016-04-0125033.66796923324.06640623701.66992226365.66601626743.26953123247.09765622696.59793022821.25635723830.632567...23986.27254023289.81143223525.63078524563.99864524739.82623824934.26039924402.57090424481.96856025567.08556525696.312229
Australia2016-07-0124507.02734422625.50000023041.07617225972.97851626388.55468822658.20703121816.98890622011.90503523158.311619...23345.82118422688.60557422780.60257423934.24460924033.90622024374.02656923539.67372423797.83665124893.46309025098.321828
Australia2016-10-0125598.92968823559.91992224010.28125027187.57812527637.93750023330.80468822567.94829922694.44970823850.068162...24275.42342023392.04044723626.16566224828.20781324958.92600225477.95191324793.43699324911.27154726006.24416126152.362329
Australia2017-01-0126982.57812524651.53515625166.39648428798.75781229313.61914124497.00195323578.41850323731.65743725114.564017...25485.43387924549.96962524802.81969126073.79287226284.82638626842.56474126037.72556126248.17183127436.22276127668.067666
+ +

5 rows Ɨ 21 columns

+
+
+
+
+
+

Plot forecasts

+

Then we can plot the probabilist forecasts using the following function.

+
+
plot_df = pd.concat([Y_df.set_index(['unique_id', 'ds']), 
+                     Y_rec_df.set_index('ds', append=True)], axis=1)
+plot_df = plot_df.reset_index('ds')
+
+
+

Plot single time series

+
+
hplot.plot_series(
+    series='Australia',
+    Y_df=plot_df, 
+    models=['y', 'AutoARIMA', 
+            'AutoARIMA/MinTrace_method-ols',
+            'AutoARIMA/BottomUp'
+           ],
+    level=[80]
+)
+
+

+
+
+
+
+

Plot hierarchichally linked time series

+
+
hplot.plot_hierarchically_linked_series(
+    bottom_series='Australia/Western Australia/Experience Perth',
+    Y_df=plot_df, 
+    models=['y', 'AutoARIMA', 'AutoARIMA/MinTrace_method-ols', 'AutoARIMA/BottomUp'],
+    level=[80]
+)
+
+

+
+
+
+
# ACT only has Canberra
+hplot.plot_hierarchically_linked_series(
+    bottom_series='Australia/ACT/Canberra',
+    Y_df=plot_df, 
+    models=['y', 'AutoARIMA/MinTrace_method-mint_shrink'],
+    level=[80, 90]
+)
+
+

+
+
+
+
+

References

+ + + +
+
+ +

If you find the code useful, please ā­ us on Github

+ +
+ + + + \ No newline at end of file diff --git a/examples/australiandomestictourism.html b/examples/australiandomestictourism.html new file mode 100644 index 00000000..a8670447 --- /dev/null +++ b/examples/australiandomestictourism.html @@ -0,0 +1,1294 @@ + + + + + + + + + + +hierarchicalforecast - Geographical Aggregation (Tourism) + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ + +
+ +
+ + +
+ + + +
+ +
+
+

Geographical Aggregation (Tourism)

+
+ +
+
+ Geographical Hierarchical Forecasting on Australian Tourism Data +
+
+ + +
+ + + + +
+ + +
+ + +

In many applications, a set of time series is hierarchically organized. Examples include the presence of geographic levels, products, or categories that define different types of aggregations. In such scenarios, forecasters are often required to provide predictions for all disaggregate and aggregate series. A natural desire is for those predictions to be ā€œcoherentā€, that is, for the bottom series to add up precisely to the forecasts of the aggregated series.

+

In this notebook we present an example on how to use HierarchicalForecast to produce coherent forecasts between geographical levels. We will use the classic Australian Domestic Tourism (Tourism) dataset, which contains monthly time series of the number of visitors to each state of Australia.

+

We will first load the Tourism data and produce base forecasts using an ETS model from StatsForecast, and then reconciliate the forecasts with several reconciliation algorithms from HierarchicalForecast. Finally, we show the performance is comparable with the results reported by the Forecasting: Principles and Practice which uses the R package fable.

+

You can run these experiments using CPU or GPU with Google Colab.

+

Open In Colab

+
+
!pip install hierarchicalforecast
+!pip install -U statsforecast numba
+
+
+

1. Load and Process Data

+

In this example we will use the Tourism dataset from the Forecasting: Principles and Practice book.

+

The dataset only contains the time series at the lowest level, so we need to create the time series for all hierarchies.

+
+
import numpy as np
+import pandas as pd
+
+
+
Y_df = pd.read_csv('https://raw.githubusercontent.com/Nixtla/transfer-learning-time-series/main/datasets/tourism.csv')
+Y_df = Y_df.rename({'Trips': 'y', 'Quarter': 'ds'}, axis=1)
+Y_df.insert(0, 'Country', 'Australia')
+Y_df = Y_df[['Country', 'Region', 'State', 'Purpose', 'ds', 'y']]
+Y_df['ds'] = Y_df['ds'].str.replace(r'(\d+) (Q\d)', r'\1-\2', regex=True)
+Y_df['ds'] = pd.to_datetime(Y_df['ds'])
+Y_df.head()
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
CountryRegionStatePurposedsy
0AustraliaAdelaideSouth AustraliaBusiness1998-01-01135.077690
1AustraliaAdelaideSouth AustraliaBusiness1998-04-01109.987316
2AustraliaAdelaideSouth AustraliaBusiness1998-07-01166.034687
3AustraliaAdelaideSouth AustraliaBusiness1998-10-01127.160464
4AustraliaAdelaideSouth AustraliaBusiness1999-01-01137.448533
+ +
+
+
+

The dataset can be grouped in the following non-strictly hierarchical structure.

+
+
spec = [
+    ['Country'],
+    ['Country', 'State'], 
+    ['Country', 'Purpose'], 
+    ['Country', 'State', 'Region'], 
+    ['Country', 'State', 'Purpose'], 
+    ['Country', 'State', 'Region', 'Purpose']
+]
+
+

Using the aggregate function from HierarchicalForecast we can get the full set of time series.

+
+
from hierarchicalforecast.utils import aggregate
+
+
+
Y_df, S_df, tags = aggregate(Y_df, spec)
+Y_df = Y_df.reset_index()
+
+
+
Y_df.head()
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
unique_iddsy
0Australia1998-01-0123182.197269
1Australia1998-04-0120323.380067
2Australia1998-07-0119826.640511
3Australia1998-10-0120830.129891
4Australia1999-01-0122087.353380
+ +
+
+
+
+
S_df.iloc[:5, :5]
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Australia/ACT/Canberra/BusinessAustralia/ACT/Canberra/HolidayAustralia/ACT/Canberra/OtherAustralia/ACT/Canberra/VisitingAustralia/New South Wales/Blue Mountains/Business
Australia1.01.01.01.01.0
Australia/ACT1.01.01.01.00.0
Australia/New South Wales0.00.00.00.01.0
Australia/Northern Territory0.00.00.00.00.0
Australia/Queensland0.00.00.00.00.0
+ +
+
+
+
+
tags['Country/Purpose']
+
+
array(['Australia/Business', 'Australia/Holiday', 'Australia/Other',
+       'Australia/Visiting'], dtype=object)
+
+
+
+

Split Train/Test sets

+

We use the final two years (8 quarters) as test set.

+
+
Y_test_df = Y_df.groupby('unique_id').tail(8)
+Y_train_df = Y_df.drop(Y_test_df.index)
+
+
+
Y_test_df = Y_test_df.set_index('unique_id')
+Y_train_df = Y_train_df.set_index('unique_id')
+
+
+
Y_train_df.groupby('unique_id').size()
+
+
unique_id
+Australia                                                72
+Australia/ACT                                            72
+Australia/ACT/Business                                   72
+Australia/ACT/Canberra                                   72
+Australia/ACT/Canberra/Business                          72
+                                                         ..
+Australia/Western Australia/Experience Perth/Other       72
+Australia/Western Australia/Experience Perth/Visiting    72
+Australia/Western Australia/Holiday                      72
+Australia/Western Australia/Other                        72
+Australia/Western Australia/Visiting                     72
+Length: 425, dtype: int64
+
+
+
+
+
+

2. Computing base forecasts

+

The following cell computes the base forecasts for each time series in Y_df using the ETS model. Observe that Y_hat_df contains the forecasts but they are not coherent.

+
+
from statsforecast.models import ETS
+from statsforecast.core import StatsForecast
+
+
+
fcst = StatsForecast(df=Y_train_df, 
+                     models=[ETS(season_length=4, model='ZZA')], 
+                     freq='QS', n_jobs=-1)
+Y_hat_df = fcst.forecast(h=8, fitted=True)
+Y_fitted_df = fcst.forecast_fitted_values()
+
+
+
+

3. Reconcile forecasts

+

The following cell makes the previous forecasts coherent using the HierarchicalReconciliation class. Since the hierarchy structure is not strict, we canā€™t use methods such as TopDown or MiddleOut. In this example we use BottomUp and MinTrace.

+
+
from hierarchicalforecast.methods import BottomUp, MinTrace
+from hierarchicalforecast.core import HierarchicalReconciliation
+
+
+
reconcilers = [
+    BottomUp(),
+    MinTrace(method='mint_shrink'),
+    MinTrace(method='ols')
+]
+hrec = HierarchicalReconciliation(reconcilers=reconcilers)
+Y_rec_df = hrec.reconcile(Y_hat_df=Y_hat_df, Y_df=Y_fitted_df, S=S_df, tags=tags)
+
+

The dataframe Y_rec_df contains the reconciled forecasts.

+
+
Y_rec_df.head()
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
dsETSETS/BottomUpETS/MinTrace_method-mint_shrinkETS/MinTrace_method-ols
unique_id
Australia2016-01-0125990.06835924379.67968825438.88835125894.418893
Australia2016-04-0124458.49023422902.66406223925.18854124357.230480
Australia2016-07-0123974.05664122412.98437523440.31033823865.929521
Australia2016-10-0124563.45507823127.63867224101.00183324470.783968
Australia2017-01-0125990.06835924516.17578125556.66761625901.382401
+ +
+
+
+
+
+

4. Evaluation

+

The HierarchicalForecast package includes the HierarchicalEvaluation class to evaluate the different hierarchies and also is capable of compute scaled metrics compared to a benchmark model.

+
+
from hierarchicalforecast.evaluation import HierarchicalEvaluation
+
+
+
def rmse(y, y_hat):
+    return np.mean(np.sqrt(np.mean((y-y_hat)**2, axis=1)))
+
+def mase(y, y_hat, y_insample, seasonality=4):
+    errors = np.mean(np.abs(y - y_hat), axis=1)
+    scale = np.mean(np.abs(y_insample[:, seasonality:] - y_insample[:, :-seasonality]), axis=1)
+    return np.mean(errors / scale)
+
+eval_tags = {}
+eval_tags['Total'] = tags['Country']
+eval_tags['Purpose'] = tags['Country/Purpose']
+eval_tags['State'] = tags['Country/State']
+eval_tags['Regions'] = tags['Country/State/Region']
+eval_tags['Bottom'] = tags['Country/State/Region/Purpose']
+eval_tags['All'] = np.concatenate(list(tags.values()))
+
+evaluator = HierarchicalEvaluation(evaluators=[rmse, mase])
+evaluation = evaluator.evaluate(
+        Y_hat_df=Y_rec_df, Y_test_df=Y_test_df,
+        tags=eval_tags, Y_df=Y_train_df
+)
+evaluation = evaluation.drop('Overall')
+evaluation.columns = ['Base', 'BottomUp', 'MinTrace(mint_shrink)', 'MinTrace(ols)']
+evaluation = evaluation.applymap('{:.2f}'.format)
+
+
/var/folders/rp/97y9_3ns23v01hdn0rp9ndw40000gp/T/ipykernel_46857/2768439279.py:22: PerformanceWarning: dropping on a non-lexsorted multi-index without a level parameter may impact performance.
+  evaluation = evaluation.drop('Overall')
+
+
+
+

RMSE

+

The following table shows the performance measured using RMSE across levels for each reconciliation method.

+
+
evaluation.query('metric == "rmse"')
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
BaseBottomUpMinTrace(mint_shrink)MinTrace(ols)
levelmetric
Totalrmse1743.293028.932102.471818.94
Purposermse534.75791.28574.84515.53
Statermse308.15413.43315.89287.34
Regionsrmse51.6555.1446.4846.29
Bottomrmse19.3719.3717.7818.19
Allrmse45.1954.9544.5942.71
+ +
+
+
+
+
+

MASE

+

The following table shows the performance measured using MASE across levels for each reconciliation method.

+
+
evaluation.query('metric == "mase"')
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
BaseBottomUpMinTrace(mint_shrink)MinTrace(ols)
levelmetric
Totalmase1.593.162.051.67
Purposemase1.322.281.481.25
Statemase1.391.901.391.25
Regionsmase1.121.191.010.99
Bottommase0.980.980.941.01
Allmase1.031.080.981.02
+ +
+
+
+
+
+

Comparison fable

+

Observe that we can recover the results reported by the Forecasting: Principles and Practice. The original results were calculated using the R package fable.

+
+
+

+
Fableā€™s reconciliation results
+
+
+
+
+

References

+ + + +
+
+ +

If you find the code useful, please ā­ us on Github

+ +
+ + + + \ No newline at end of file diff --git a/examples/australianprisonpopulation.html b/examples/australianprisonpopulation.html new file mode 100644 index 00000000..d52f89f9 --- /dev/null +++ b/examples/australianprisonpopulation.html @@ -0,0 +1,1193 @@ + + + + + + + + + + +hierarchicalforecast - Geographical Aggregation (Prison Population) + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ + +
+ +
+ + +
+ + + +
+ +
+
+

Geographical Aggregation (Prison Population)

+
+ +
+
+ Geographical Hierarchical Forecasting on Australian Prison Population Data +
+
+ + +
+ + + + +
+ + +
+ + +

In many applications, a set of time series is hierarchically organized. Examples include the presence of geographic levels, products, or categories that define different types of aggregations. In such scenarios, forecasters are often required to provide predictions for all disaggregate and aggregate series. A natural desire is for those predictions to be ā€œcoherentā€, that is, for the bottom series to add up precisely to the forecasts of the aggregated series.

+

In this notebook we present an example on how to use HierarchicalForecast to produce coherent forecasts between geographical levels. We will use the Australian Prison Population dataset.

+

We will first load the dataset and produce base forecasts using an ETS model from StatsForecast, and then reconciliate the forecasts with several reconciliation algorithms from HierarchicalForecast. Finally, we show the performance is comparable with the results reported by the Forecasting: Principles and Practice which uses the R package fable.

+

You can run these experiments using CPU or GPU with Google Colab.

+

Open In Colab

+
+
!pip install hierarchicalforecast
+!pip install -U statsforecast numba
+
+
+

1. Load and Process Data

+

The dataset only contains the time series at the lowest level, so we need to create the time series for all hierarchies.

+
+
import numpy as np
+import pandas as pd
+
+
+
Y_df = pd.read_csv('https://OTexts.com/fpp3/extrafiles/prison_population.csv')
+Y_df = Y_df.rename({'Count': 'y', 'Date': 'ds'}, axis=1)
+Y_df.insert(0, 'Country', 'Australia')
+Y_df = Y_df[['Country', 'State', 'Gender', 'Legal', 'Indigenous', 'ds', 'y']]
+Y_df['ds'] = pd.to_datetime(Y_df['ds'])
+Y_df.head()
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
CountryStateGenderLegalIndigenousdsy
0AustraliaACTFemaleRemandedATSI2005-03-010
1AustraliaACTFemaleRemandedNon-ATSI2005-03-012
2AustraliaACTFemaleSentencedATSI2005-03-010
3AustraliaACTFemaleSentencedNon-ATSI2005-03-015
4AustraliaACTMaleRemandedATSI2005-03-017
+ +
+
+
+

The dataset can be grouped in the following grouped structure.

+
+
hiers = [
+    ['Country'],
+    ['Country', 'State'], 
+    ['Country', 'Gender'], 
+    ['Country', 'Legal'], 
+    ['Country', 'State', 'Gender', 'Legal']
+]
+
+

Using the aggregate function from HierarchicalForecast we can get the full set of time series.

+
+
from hierarchicalforecast.utils import aggregate
+
+
+
Y_df, S_df, tags = aggregate(Y_df, hiers)
+Y_df['y'] = Y_df['y']/1e3
+Y_df = Y_df.reset_index()
+
+
+
Y_df.head()
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
unique_iddsy
0Australia2005-03-0124.296
1Australia2005-06-0124.643
2Australia2005-09-0124.511
3Australia2005-12-0124.393
4Australia2006-03-0124.524
+ +
+
+
+
+
S_df.iloc[:5, :5]
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Australia/ACT/Female/RemandedAustralia/ACT/Female/SentencedAustralia/ACT/Male/RemandedAustralia/ACT/Male/SentencedAustralia/NSW/Female/Remanded
Australia1.01.01.01.01.0
Australia/ACT1.01.01.01.00.0
Australia/NSW0.00.00.00.01.0
Australia/NT0.00.00.00.00.0
Australia/QLD0.00.00.00.00.0
+ +
+
+
+
+
tags
+
+
{'Country': array(['Australia'], dtype=object),
+ 'Country/State': array(['Australia/ACT', 'Australia/NSW', 'Australia/NT', 'Australia/QLD',
+        'Australia/SA', 'Australia/TAS', 'Australia/VIC', 'Australia/WA'],
+       dtype=object),
+ 'Country/Gender': array(['Australia/Female', 'Australia/Male'], dtype=object),
+ 'Country/Legal': array(['Australia/Remanded', 'Australia/Sentenced'], dtype=object),
+ 'Country/State/Gender/Legal': ['Australia/ACT/Female/Remanded',
+  'Australia/ACT/Female/Sentenced',
+  'Australia/ACT/Male/Remanded',
+  'Australia/ACT/Male/Sentenced',
+  'Australia/NSW/Female/Remanded',
+  'Australia/NSW/Female/Sentenced',
+  'Australia/NSW/Male/Remanded',
+  'Australia/NSW/Male/Sentenced',
+  'Australia/NT/Female/Remanded',
+  'Australia/NT/Female/Sentenced',
+  'Australia/NT/Male/Remanded',
+  'Australia/NT/Male/Sentenced',
+  'Australia/QLD/Female/Remanded',
+  'Australia/QLD/Female/Sentenced',
+  'Australia/QLD/Male/Remanded',
+  'Australia/QLD/Male/Sentenced',
+  'Australia/SA/Female/Remanded',
+  'Australia/SA/Female/Sentenced',
+  'Australia/SA/Male/Remanded',
+  'Australia/SA/Male/Sentenced',
+  'Australia/TAS/Female/Remanded',
+  'Australia/TAS/Female/Sentenced',
+  'Australia/TAS/Male/Remanded',
+  'Australia/TAS/Male/Sentenced',
+  'Australia/VIC/Female/Remanded',
+  'Australia/VIC/Female/Sentenced',
+  'Australia/VIC/Male/Remanded',
+  'Australia/VIC/Male/Sentenced',
+  'Australia/WA/Female/Remanded',
+  'Australia/WA/Female/Sentenced',
+  'Australia/WA/Male/Remanded',
+  'Australia/WA/Male/Sentenced']}
+
+
+
+

Split Train/Test sets

+

We use the final two years (8 quarters) as test set.

+
+
Y_test_df = Y_df.groupby('unique_id').tail(8)
+Y_train_df = Y_df.drop(Y_test_df.index)
+
+
+
Y_test_df = Y_test_df.set_index('unique_id')
+Y_train_df = Y_train_df.set_index('unique_id')
+
+
+
+
+

2. Computing base forecasts

+

The following cell computes the base forecasts for each time series in Y_df using the ETS model. Observe that Y_hat_df contains the forecasts but they are not coherent.

+
+
from statsforecast.models import ETS
+from statsforecast.core import StatsForecast
+
+
+
fcst = StatsForecast(df=Y_train_df,
+                     models=[ETS(season_length=4, model='ZMZ')], 
+                     freq='QS', n_jobs=-1)
+Y_hat_df = fcst.forecast(h=8, fitted=True)
+Y_fitted_df = fcst.forecast_fitted_values()
+
+
+
+

3. Reconcile forecasts

+

The following cell makes the previous forecasts coherent using the HierarchicalReconciliation class. Since the hierarchy structure is not strict, we canā€™t use methods such as TopDown or MiddleOut. In this example we use BottomUp and MinTrace.

+
+
from hierarchicalforecast.methods import BottomUp, MinTrace
+from hierarchicalforecast.core import HierarchicalReconciliation
+
+
+
reconcilers = [
+    BottomUp(),
+    MinTrace(method='mint_shrink')
+]
+hrec = HierarchicalReconciliation(reconcilers=reconcilers)
+Y_rec_df = hrec.reconcile(Y_hat_df=Y_hat_df, Y_df=Y_fitted_df, S=S_df, tags=tags)
+
+

The dataframe Y_rec_df contains the reconciled forecasts.

+
+
Y_rec_df.head()
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
dsETSETS/BottomUpETS/MinTrace_method-mint_shrink
unique_id
Australia2015-01-0134.79949634.93389134.927244
Australia2015-04-0135.19263835.47356035.440861
Australia2015-07-0135.18821735.68736335.476427
Australia2015-10-0135.88862636.01068535.946153
Australia2016-01-0136.04543736.40010136.244707
+ +
+
+
+
+
+

4. Evaluation

+

The HierarchicalForecast package includes the HierarchicalEvaluation class to evaluate the different hierarchies and also is capable of compute scaled metrics compared to a benchmark model.

+
+
from hierarchicalforecast.evaluation import HierarchicalEvaluation
+
+
+
def mase(y, y_hat, y_insample, seasonality=4):
+    errors = np.mean(np.abs(y - y_hat), axis=1)
+    scale = np.mean(np.abs(y_insample[:, seasonality:] - y_insample[:, :-seasonality]), axis=1)
+    return np.mean(errors / scale)
+
+eval_tags = {}
+eval_tags['Total'] = tags['Country']
+eval_tags['State'] = tags['Country/State']
+eval_tags['Legal status'] = tags['Country/Legal']
+eval_tags['Gender'] = tags['Country/Gender']
+eval_tags['Bottom'] = tags['Country/State/Gender/Legal']
+eval_tags['All series'] = np.concatenate(list(tags.values()))
+
+evaluator = HierarchicalEvaluation(evaluators=[mase])
+evaluation = evaluator.evaluate(
+    Y_hat_df=Y_rec_df, Y_test_df=Y_test_df,
+    tags=eval_tags,
+    Y_df=Y_train_df
+)
+evaluation = evaluation.reset_index().drop(columns='metric').drop(0).set_index('level')
+evaluation.columns = ['Base', 'BottomUp', 'MinTrace(mint_shrink)']
+evaluation.applymap('{:.2f}'.format)
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
BaseBottomUpMinTrace(mint_shrink)
level
Total1.361.021.16
State1.541.571.61
Legal status2.402.502.40
Gender1.080.810.95
Bottom2.172.172.16
All series2.002.002.00
+ +
+
+
+
+

Fable Comparison

+

Observe that we can recover the results reported by the Forecasting: Principles and Practice book. The original results were calculated using the R package fable.

+
+
+

+
Fableā€™s reconciliation results
+
+
+
+
+

References

+ + + +
+
+ +

If you find the code useful, please ā­ us on Github

+ +
+ + + + \ No newline at end of file diff --git a/examples/hierarchicalforecast-gluonts.html b/examples/hierarchicalforecast-gluonts.html new file mode 100644 index 00000000..7626f719 --- /dev/null +++ b/examples/hierarchicalforecast-gluonts.html @@ -0,0 +1,1468 @@ + + + + + + + + + +hierarchicalforecast - GluonTS + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ + +
+ +
+ + +
+ + + +
+ +
+
+

GluonTS

+
+ + + +
+ + + + +
+ + +
+ + +

This example notebook demonstrates the compatibility of HierarchicalForecastā€™s reconciliation methods with popular machine-learning libraries, specifically GluonTS.

+

The notebook utilizes the GluonTS DeepAREstimator to create base forecasts for the TourismLarge Hierarchical Dataset. We make the base forecasts compatible with HierarchicalForecastā€™s reconciliation functions via the samples_to_quantiles_df utility function that transforms GluonTSā€™ output forecasts into a compatible data frame format. After that, we use HierarchicalForecast to reconcile the base predictions.

+

References
- David Salinas, Valentin Flunkert, Jan Gasthaus, Tim Januschowski (2020). ā€œDeepAR: Probabilistic forecasting with autoregressive recurrent networksā€. International Journal of Forecasting.
- Alexander Alexandrov et. al (2020). ā€œGluonTS: Probabilistic and Neural Time Series Modeling in Pythonā€. Journal of Machine Learning Research.

+

You can run these experiments using CPU or GPU with Google Colab.

+

Open In Colab

+
+

1. Installing packages

+
+
!pip install mxnet-cu112
+
+
+
import mxnet as mx
+
+assert mx.context.num_gpus()>0
+
+
+
!pip install gluonts
+!pip install datasetsforecast
+!pip install git+https://github.com/Nixtla/hierarchicalforecast.git
+
+
+
import numpy as np
+import pandas as pd
+
+from datasetsforecast.hierarchical import HierarchicalData
+
+from gluonts.mx.trainer import Trainer
+from gluonts.dataset.pandas import PandasDataset
+from gluonts.mx.model.deepar import DeepAREstimator
+
+from hierarchicalforecast.methods import BottomUp, MinTrace
+from hierarchicalforecast.core import HierarchicalReconciliation
+from hierarchicalforecast.evaluation import scaled_crps
+from hierarchicalforecast.utils import samples_to_quantiles_df
+
+
/usr/local/lib/python3.10/dist-packages/gluonts/json.py:101: UserWarning: Using `json`-module for json-handling. Consider installing one of `orjson`, `ujson` to speed up serialization and deserialization.
+  warnings.warn(
+
+
+
+
+

2. Load hierarchical dataset

+

This detailed Australian Tourism Dataset comes from the National Visitor Survey, managed by the Tourism Research Australia, it is composed of 555 monthly series from 1998 to 2016, it is organized geographically, and purpose of travel. The natural geographical hierarchy comprises seven states, divided further in 27 zones and 76 regions. The purpose of travel categories are holiday, visiting friends and relatives (VFR), business and other. The MinT (Wickramasuriya et al., 2019), among other hierarchical forecasting studies has used the dataset it in the past. The dataset can be accessed in the MinT reconciliation webpage, although other sources are available.

+ ++++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Geographical DivisionNumber of series per divisionNumber of series per purposeTotal
Australia145
States72835
Zones27108135
Regions76304380
Total111444555
+
+
dataset = 'TourismLarge'
+Y_df, S_df, tags = HierarchicalData.load(directory = "./data", group=dataset)
+Y_df['ds'] = pd.to_datetime(Y_df['ds'])
+
+
+
def sort_hier_df(Y_df, S_df):
+    # sorts unique_id lexicographically
+    Y_df.unique_id = Y_df.unique_id.astype('category')
+    Y_df.unique_id = Y_df.unique_id.cat.set_categories(S_df.index)
+    Y_df = Y_df.sort_values(by=['unique_id', 'ds'])
+    return Y_df
+
+Y_df = sort_hier_df(Y_df, S_df)
+
+
+
horizon = 12
+
+Y_test_df = Y_df.groupby('unique_id').tail(horizon)
+Y_train_df = Y_df.drop(Y_test_df.index)
+Y_train_df
+
+ +
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
unique_iddsy
0TotalAll1998-01-0145151.071280
1TotalAll1998-02-0117294.699551
2TotalAll1998-03-0120725.114184
3TotalAll1998-04-0125388.612353
4TotalAll1998-05-0120330.035211
............
126523GBDOth2015-08-0117.683774
126524GBDOth2015-09-010.000000
126525GBDOth2015-10-010.000000
126526GBDOth2015-11-010.000000
126527GBDOth2015-12-010.000000
+ + +

119880 rows Ɨ 3 columns

+
+ + + + + +
+
+ +
+
+
+
ds = PandasDataset.from_long_dataframe(Y_train_df, target="y", item_id="unique_id")
+
+
+
+

3. Fit and Predict Model

+
+
estimator = DeepAREstimator(
+    freq="M",
+    prediction_length=horizon,
+    trainer=Trainer(ctx = mx.context.gpu(),
+                    epochs=20),
+)
+predictor = estimator.train(ds)
+
+forecast_it = predictor.predict(ds, num_samples=1000)
+
+forecasts = list(forecast_it)
+forecasts = np.array([arr.samples for arr in forecasts])
+forecasts.shape
+
+
100%|ā–ˆā–ˆā–ˆā–ˆā–ˆā–ˆā–ˆā–ˆā–ˆā–ˆ| 50/50 [00:11<00:00,  4.39it/s, epoch=1/20, avg_epoch_loss=5.35]
+100%|ā–ˆā–ˆā–ˆā–ˆā–ˆā–ˆā–ˆā–ˆā–ˆā–ˆ| 50/50 [00:05<00:00,  8.75it/s, epoch=2/20, avg_epoch_loss=5.22]
+100%|ā–ˆā–ˆā–ˆā–ˆā–ˆā–ˆā–ˆā–ˆā–ˆā–ˆ| 50/50 [00:03<00:00, 14.41it/s, epoch=3/20, avg_epoch_loss=5.17]
+100%|ā–ˆā–ˆā–ˆā–ˆā–ˆā–ˆā–ˆā–ˆā–ˆā–ˆ| 50/50 [00:02<00:00, 20.76it/s, epoch=4/20, avg_epoch_loss=5.02]
+100%|ā–ˆā–ˆā–ˆā–ˆā–ˆā–ˆā–ˆā–ˆā–ˆā–ˆ| 50/50 [00:02<00:00, 19.27it/s, epoch=5/20, avg_epoch_loss=5.05]
+100%|ā–ˆā–ˆā–ˆā–ˆā–ˆā–ˆā–ˆā–ˆā–ˆā–ˆ| 50/50 [00:04<00:00, 11.52it/s, epoch=6/20, avg_epoch_loss=5.12]
+100%|ā–ˆā–ˆā–ˆā–ˆā–ˆā–ˆā–ˆā–ˆā–ˆā–ˆ| 50/50 [00:03<00:00, 16.59it/s, epoch=7/20, avg_epoch_loss=4.97]
+100%|ā–ˆā–ˆā–ˆā–ˆā–ˆā–ˆā–ˆā–ˆā–ˆā–ˆ| 50/50 [00:03<00:00, 16.27it/s, epoch=8/20, avg_epoch_loss=4.97]
+100%|ā–ˆā–ˆā–ˆā–ˆā–ˆā–ˆā–ˆā–ˆā–ˆā–ˆ| 50/50 [00:02<00:00, 19.96it/s, epoch=9/20, avg_epoch_loss=5.11]
+100%|ā–ˆā–ˆā–ˆā–ˆā–ˆā–ˆā–ˆā–ˆā–ˆā–ˆ| 50/50 [00:04<00:00, 11.36it/s, epoch=10/20, avg_epoch_loss=4.97]
+100%|ā–ˆā–ˆā–ˆā–ˆā–ˆā–ˆā–ˆā–ˆā–ˆā–ˆ| 50/50 [00:03<00:00, 16.62it/s, epoch=11/20, avg_epoch_loss=5.05]
+100%|ā–ˆā–ˆā–ˆā–ˆā–ˆā–ˆā–ˆā–ˆā–ˆā–ˆ| 50/50 [00:02<00:00, 17.76it/s, epoch=12/20, avg_epoch_loss=5.04]
+100%|ā–ˆā–ˆā–ˆā–ˆā–ˆā–ˆā–ˆā–ˆā–ˆā–ˆ| 50/50 [00:02<00:00, 21.56it/s, epoch=13/20, avg_epoch_loss=4.99]
+100%|ā–ˆā–ˆā–ˆā–ˆā–ˆā–ˆā–ˆā–ˆā–ˆā–ˆ| 50/50 [00:02<00:00, 20.64it/s, epoch=14/20, avg_epoch_loss=5.03]
+100%|ā–ˆā–ˆā–ˆā–ˆā–ˆā–ˆā–ˆā–ˆā–ˆā–ˆ| 50/50 [00:03<00:00, 13.22it/s, epoch=15/20, avg_epoch_loss=4.97]
+100%|ā–ˆā–ˆā–ˆā–ˆā–ˆā–ˆā–ˆā–ˆā–ˆā–ˆ| 50/50 [00:02<00:00, 17.79it/s, epoch=16/20, avg_epoch_loss=4.95]
+100%|ā–ˆā–ˆā–ˆā–ˆā–ˆā–ˆā–ˆā–ˆā–ˆā–ˆ| 50/50 [00:02<00:00, 18.29it/s, epoch=17/20, avg_epoch_loss=5.02]
+100%|ā–ˆā–ˆā–ˆā–ˆā–ˆā–ˆā–ˆā–ˆā–ˆā–ˆ| 50/50 [00:02<00:00, 17.73it/s, epoch=18/20, avg_epoch_loss=5.02]
+100%|ā–ˆā–ˆā–ˆā–ˆā–ˆā–ˆā–ˆā–ˆā–ˆā–ˆ| 50/50 [00:02<00:00, 19.10it/s, epoch=19/20, avg_epoch_loss=5.02]
+100%|ā–ˆā–ˆā–ˆā–ˆā–ˆā–ˆā–ˆā–ˆā–ˆā–ˆ| 50/50 [00:03<00:00, 13.29it/s, epoch=20/20, avg_epoch_loss=5]
+
+
+
(555, 1000, 12)
+
+
+
+
+

4. Reconciliation

+
+
level = np.arange(1, 100, 2)
+
+#transform the output of DeepAREstimator to a form that is compatible with HierarchicalForecast
+quantiles, forecast_df = samples_to_quantiles_df(samples=forecasts, 
+                               unique_ids=S_df.index, 
+                               dates=Y_test_df['ds'].unique(), 
+                               level=level,
+                               model_name='DeepAREstimator')
+
+#reconcile forecasts
+reconcilers = [
+    BottomUp(),
+    MinTrace('ols')
+]
+hrec = HierarchicalReconciliation(reconcilers=reconcilers)
+
+forecast_rec = hrec.reconcile(Y_hat_df=forecast_df, S=S_df, tags=tags, level=level)
+
+
+
forecast_rec
+
+ +
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
dsDeepAREstimatorDeepAREstimator-medianDeepAREstimator-lo-99DeepAREstimator-lo-97DeepAREstimator-lo-95DeepAREstimator-lo-93DeepAREstimator-lo-91DeepAREstimator-lo-89DeepAREstimator-lo-87...DeepAREstimator/MinTrace_method-ols-hi-81DeepAREstimator/MinTrace_method-ols-hi-83DeepAREstimator/MinTrace_method-ols-hi-85DeepAREstimator/MinTrace_method-ols-hi-87DeepAREstimator/MinTrace_method-ols-hi-89DeepAREstimator/MinTrace_method-ols-hi-91DeepAREstimator/MinTrace_method-ols-hi-93DeepAREstimator/MinTrace_method-ols-hi-95DeepAREstimator/MinTrace_method-ols-hi-97DeepAREstimator/MinTrace_method-ols-hi-99
unique_id
TotalAll2016-01-0143165.92968843002.05859427712.29797930371.24351632741.45874033305.42949234446.46595735164.38041035732.592422...48703.13204648956.75248049233.84383649540.74321949886.82621850286.87792850766.39457751375.71757752240.50636653910.351214
TotalAll2016-02-0120326.79687520469.21093813156.55087915086.48825715738.45703116134.38634316696.16001016828.67643617139.442129...22902.63524423019.41268423146.99711823288.30641123447.65747823631.85799323852.64748524133.20524224531.39011825300.256426
TotalAll2016-03-0124362.20312524237.25097717340.83719718470.07158219132.18061519658.16894519974.22335920339.48358420519.382959...26759.16663426873.89633826999.24353027138.07491227294.63169927475.60218927692.52005527968.15812728359.36068229114.744632
TotalAll2016-04-0129131.66210929236.00878919923.62374021814.11224622685.98750023350.11341823721.05696324168.28620124513.198066...32277.20937032427.58438632591.87563232773.84046432979.03779633216.23391333500.54587733861.82179834374.56687135364.640665
TotalAll2016-05-0122587.77929722638.54101614453.28594716236.98586917163.25180717894.04675818559.20445318789.05306619055.381455...25400.97671625532.98457525677.20890225836.94812226017.08217026225.30659626474.89202926792.04086027242.15804528111.301901
..................................................................
GBDOth2016-08-01-0.300811-0.316894-2.994549-2.208182-2.005075-1.725068-1.620723-1.501304-1.355108...27.15159528.29314129.54033030.92168532.47940534.28003936.43834439.18090843.07332450.589300
GBDOth2016-09-01-0.089410-0.079164-2.981229-2.356738-1.812428-1.499515-1.365453-1.199702-1.120727...24.91208026.03504427.26193228.62080130.15316531.92448934.04766236.74558440.57464047.968273
GBDOth2016-10-01-0.196041-0.207104-2.829650-2.270969-1.674091-1.289834-1.153728-1.078916-1.029915...25.42395826.55097327.78228729.14605930.68395232.46166634.59249937.30015441.14302548.563331
GBDOth2016-11-01-0.315826-0.274183-2.461571-1.829249-1.535889-1.329642-1.260961-1.134465-1.007276...25.12596026.25799127.49478428.86462530.40936132.19498634.33530137.05500540.91497748.368305
GBDOth2016-12-01-0.291579-0.268462-3.987842-2.078746-1.619226-1.385310-1.253607-1.156472-1.092625...26.21609827.31053528.50625529.83060431.32404133.05036635.11960337.74898741.48077248.686579
+ + +

6660 rows Ɨ 305 columns

+
+ + + + + +
+
+ +
+
+
+
+

5. Evaluation

+

To evaluate we use a scaled variation of the CRPS, as proposed by Rangapuram (2021), to measure the accuracy of predicted quantiles y_hat compared to the observation y.

+

\[ \mathrm{sCRPS}(\hat{F}_{\tau}, \mathbf{y}_{\tau}) = \frac{2}{N} \sum_{i} +\int^{1}_{0} +\frac{\mathrm{QL}(\hat{F}_{i,\tau}, y_{i,\tau})_{q}}{\sum_{i} | y_{i,\tau} |} dq \]

+

As you can see, HierarchicalForecast results improve on the results of specialized algorithms like HierE2E.

+
+
rec_model_names = ['DeepAREstimator/MinTrace_method-ols', 'DeepAREstimator/BottomUp']
+
+quantiles = np.array(quantiles[1:]) #remove first quantile (median)
+n_quantiles = len(quantiles)
+n_series = len(S_df)
+
+for name in rec_model_names:
+    quantile_columns = [col for col in forecast_rec.columns if (name+'-') in col]
+    y_rec  = forecast_rec[quantile_columns].values 
+    y_test = Y_test_df['y'].values
+
+    y_rec  = y_rec.reshape(n_series, horizon, n_quantiles)
+    y_test = y_test.reshape(n_series, horizon)
+    scrps  = scaled_crps(y=y_test, y_hat=y_rec, quantiles=quantiles)
+    print("{:<40} {:.5f}".format(name+":", scrps))
+
+
DeepAREstimator/MinTrace_method-ols:     0.12632
+DeepAREstimator/BottomUp:                0.13933
+
+
+ + +
+ +

If you find the code useful, please ā­ us on Github

+ +
+ + + + \ No newline at end of file diff --git a/examples/imgs/AustralianDomesticTourism-results-fable.png b/examples/imgs/AustralianDomesticTourism-results-fable.png new file mode 100644 index 00000000..15f437d0 Binary files /dev/null and b/examples/imgs/AustralianDomesticTourism-results-fable.png differ diff --git a/examples/imgs/AustralianPrisonPopulation-results-fable.png b/examples/imgs/AustralianPrisonPopulation-results-fable.png new file mode 100644 index 00000000..fc4f988c Binary files /dev/null and b/examples/imgs/AustralianPrisonPopulation-results-fable.png differ diff --git a/examples/imgs/hierarchical_motivation1.png b/examples/imgs/hierarchical_motivation1.png new file mode 100644 index 00000000..1504f766 Binary files /dev/null and b/examples/imgs/hierarchical_motivation1.png differ diff --git a/examples/imgs/hierarchical_motivation2.png b/examples/imgs/hierarchical_motivation2.png new file mode 100644 index 00000000..1be4525d Binary files /dev/null and b/examples/imgs/hierarchical_motivation2.png differ diff --git a/examples/index.html b/examples/index.html new file mode 100644 index 00000000..b4cc4a91 --- /dev/null +++ b/examples/index.html @@ -0,0 +1,748 @@ + + + + + + + + + +hierarchicalforecast - Tutorials + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ + +
+ +
+ + +
+ + + +
+ +
+
+

Tutorials

+
+ + + +
+ + + + +
+ + +
+ +

Click through to any of these tutorials to get started with HierarchicalForecastā€™s features.

+ + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+Title +
+Bootstrap +
+Geographical Aggregation (Prison Population) +
+Geographical Aggregation (Tourism) +
+GluonTS +
+Install +
+Introduction +
+Neural/MLForecast +
+Non-Negative MinTrace +
+Normality +
+PERMBU +
+Probabilistic Forecast Evaluation +
+Reconciliation Quick Start +
+
+No matching items +
+

If you find the code useful, please ā­ us on Github

+ +
+ + + + \ No newline at end of file diff --git a/examples/installation.html b/examples/installation.html new file mode 100644 index 00000000..f3219460 --- /dev/null +++ b/examples/installation.html @@ -0,0 +1,710 @@ + + + + + + + + + + +hierarchicalforecast - Install + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ + +
+ +
+ + +
+ + + +
+ +
+
+

Install

+
+ +
+
+ Install HierachicalForecast with pip or conda +
+
+ + +
+ + + + +
+ + +
+ + +

You can install the released version of HierachicalForecast from the Python package index with:

+
pip install hierarchicalforecast
+

or

+
conda install -c conda-forge hierarchicalforecast
+
+
+
+ +
+
+Tip +
+
+
+

We recommend installing your libraries inside a python virtual or conda environment.

+
+
+
+

User our env (optional)

+

If you donā€™t have a Conda environment and need tools like Numba, Pandas, NumPy, Jupyter, StatsModels, and Nbdev you can use ours by following these steps:

+
    +
  1. Clone the HierachicalForecast repo:
  2. +
+
$ git clone https://github.com/Nixtla/hierachicalforecast.git && cd hierachicalforecast
+
    +
  1. Create the environment using the environment.yml file:
  2. +
+
$ conda env create -f environment.yml
+
    +
  1. Activate the environment:
  2. +
+
$ conda activate statsforecast
+ + +
+ +

If you find the code useful, please ā­ us on Github

+ +
+ + + + \ No newline at end of file diff --git a/examples/introduction.html b/examples/introduction.html new file mode 100644 index 00000000..eaa7f6c5 --- /dev/null +++ b/examples/introduction.html @@ -0,0 +1,1248 @@ + + + + + + + + + + +hierarchicalforecast - Introduction + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ + +
+ +
+ + +
+ + + +
+ +
+
+

Introduction

+
+ +
+
+ Introduction to Hierarchial Forecasting using HierarchialForecast +
+
+ + +
+ + + + +
+ + +
+ + +

You can run these experiments using CPU or GPU with Google Colab.

+

Open In Colab

+
+

1. Hierarchical Series

+

In many applications, a set of time series is hierarchically organized. Examples include the presence of geographic levels, products, or categories that define different types of aggregations.

+

In such scenarios, forecasters are often required to provide predictions for all disaggregate and aggregate series. A natural desire is for those predictions to be ā€œcoherentā€, that is, for the bottom series to add up precisely to the forecasts of the aggregated series.

+
+
+

+
Figure 1. A two level time series hierarchical structure, with four bottom level variables.
+
+
+

Figure 1. shows a simple hierarchical structure where we have four bottom-level series, two middle-level series, and the top level representing the total aggregation. Its hierarchical aggregations or coherency constraints are:

+

\[\begin{align} + y_{\mathrm{Total},\tau} = y_{\beta_{1},\tau}+y_{\beta_{2},\tau}+y_{\beta_{3},\tau}+y_{\beta_{4},\tau} + \qquad \qquad \qquad \qquad \qquad \\ + \mathbf{y}_{[a],\tau}=\left[y_{\mathrm{Total},\tau},\; y_{\beta_{1},\tau}+y_{\beta_{2},\tau},\;y_{\beta_{3},\tau}+y_{\beta_{4},\tau}\right]^{\intercal} + \qquad + \mathbf{y}_{[b],\tau}=\left[ y_{\beta_{1},\tau},\; y_{\beta_{2},\tau},\; y_{\beta_{3},\tau},\; y_{\beta_{4},\tau} \right]^{\intercal} +\end{align}\]

+

Luckily these constraints can be compactly expressed with the following matrices:

+

\[\begin{align} +\mathbf{S}_{[a,b][b]} += +\begin{bmatrix} +\mathbf{A}_{\mathrm{[a][b]}} \\ + \\ + \\ +\mathbf{I}_{\mathrm{[b][b]}} \\ + \\ +\end{bmatrix} += +\begin{bmatrix} +1 & 1 & 1 & 1 \\ +1 & 1 & 0 & 0 \\ +0 & 0 & 1 & 1 \\ +1 & 0 & 0 & 0 \\ +0 & 1 & 0 & 0 \\ +0 & 0 & 1 & 0 \\ +0 & 0 & 0 & 1 \\ +\end{bmatrix} +\end{align}\]

+

where \(\mathbf{A}_{[a,b][b]}\) aggregates the bottom series to the upper levels, and \(\mathbf{I}_{\mathrm{[b][b]}}\) is an identity matrix. The representation of the hierarchical series is then:

+

\[\begin{align} +\mathbf{y}_{[a,b],\tau} = \mathbf{S}_{[a,b][b]} \mathbf{y}_{[b],\tau} +\end{align}\]

+

To visualize an example, in Figure 2. One can think of the hierarchical time series structure levels to represent different geographical aggregations. For example, in Figure 2. the top level is the total aggregation of series within a country, the middle level being its states and the bottom level its regions.

+
+
+

+
Figure 2. A hierarchy can be composed of geographic levels. In this example the top level corresponds to country aggregation, middle level to states, and bottom level to regions.
+
+
+
+
+

2. Hierarchical Forecast

+

To achieve ā€œcoherencyā€, most statistical solutions to the hierarchical forecasting challenge implement a two-stage reconciliation process.
+1. First, we obtain a set of the base forecast \(\mathbf{\hat{y}}_{[a,b],\tau}\) 2. Later, we reconcile them into coherent forecasts \(\mathbf{\tilde{y}}_{[a,b],\tau}\).

+

Most hierarchical reconciliation methods can be expressed by the following transformations:

+

\[\begin{align} +\tilde{\mathbf{y}}_{[a,b],\tau} = \mathbf{S}_{[a,b][b]} \mathbf{P}_{[b][a,b]} \hat{\mathbf{y}}_{[a,b],\tau} +\end{align}\]

+

The HierarchicalForecast library offers a Python collection of reconciliation methods, datasets, evaluation and visualization tools for the task. Among its available reconciliation methods we have BottomUp, TopDown, MiddleOut, MinTrace, ERM. Among its probabilistic coherent methods we have Normality, Bootstrap, PERMBU.

+
+
+

3. Minimal Example

+
+
!pip install hierarchicalforecast
+!pip install -U numba statsforecast datasetsforecast
+
+
+

Wrangling Data

+
+
import numpy as np
+import pandas as pd
+
+

We are going to creat a synthetic data set to illustrate a hierarchical time series structure like the one in Figure 1.

+

We will create a two level structure with four bottom series where aggregations of the series are self evident.

+
+
# Create Figure 1. synthetic bottom data
+ds = pd.date_range(start='2000-01-01', end='2000-08-01', freq='MS')
+y_base = np.arange(1,9)
+r1 = y_base * (10**1)
+r2 = y_base * (10**1)
+r3 = y_base * (10**2)
+r4 = y_base * (10**2)
+
+ys = np.concatenate([r1, r2, r3, r4])
+ds = np.tile(ds, 4)
+unique_ids = ['r1'] * 8 + ['r2'] * 8 + ['r3'] * 8 + ['r4'] * 8
+top_level = 'Australia'
+middle_level = ['State1'] * 16 + ['State2'] * 16
+bottom_level = unique_ids
+
+bottom_df = dict(ds=ds,
+                 top_level=top_level, 
+                 middle_level=middle_level, 
+                 bottom_level=bottom_level,
+                 y=ys)
+bottom_df = pd.DataFrame(bottom_df)
+bottom_df.groupby('bottom_level').head(2)
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
dstop_levelmiddle_levelbottom_levely
02000-01-01AustraliaState1r110
12000-02-01AustraliaState1r120
82000-01-01AustraliaState1r210
92000-02-01AustraliaState1r220
162000-01-01AustraliaState2r3100
172000-02-01AustraliaState2r3200
242000-01-01AustraliaState2r4100
252000-02-01AustraliaState2r4200
+ +
+
+
+

The previously introduced hierarchical series \(\mathbf{y}_{[a,b]\tau}\) is captured within the Y_hier_df dataframe.

+

The aggregation constraints matrix \(\mathbf{S}_{[a][b]}\) is captured within the S_df dataframe.

+

Finally the tags contains a list within Y_hier_df composing each hierarchical level, for example the tags['top_level'] contains Australiaā€™s aggregated series index.

+
+
from hierarchicalforecast.utils import aggregate
+
+
+
# Create hierarchical structure and constraints
+hierarchy_levels = [['top_level'],
+                    ['top_level', 'middle_level'],
+                    ['top_level', 'middle_level', 'bottom_level']]
+Y_hier_df, S_df, tags = aggregate(df=bottom_df, spec=hierarchy_levels)
+Y_hier_df = Y_hier_df.reset_index()
+print('S_df.shape', S_df.shape)
+print('Y_hier_df.shape', Y_hier_df.shape)
+print("tags['top_level']", tags['top_level'])
+
+
S_df.shape (7, 4)
+Y_hier_df.shape (56, 3)
+tags['top_level'] ['Australia']
+
+
+
/Users/cchallu/opt/anaconda3/envs/hierarchicalforecast/lib/python3.10/site-packages/sklearn/preprocessing/_encoders.py:828: FutureWarning: `sparse` was renamed to `sparse_output` in version 1.2 and will be removed in 1.4. `sparse_output` is ignored unless you leave `sparse` to its default value.
+  warnings.warn(
+
+
+
+
Y_hier_df.groupby('unique_id').head(2)
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
unique_iddsy
0Australia2000-01-01220.0
1Australia2000-02-01440.0
8Australia/State12000-01-0120.0
9Australia/State12000-02-0140.0
16Australia/State22000-01-01200.0
17Australia/State22000-02-01400.0
24Australia/State1/r12000-01-0110.0
25Australia/State1/r12000-02-0120.0
32Australia/State1/r22000-01-0110.0
33Australia/State1/r22000-02-0120.0
40Australia/State2/r32000-01-01100.0
41Australia/State2/r32000-02-01200.0
48Australia/State2/r42000-01-01100.0
49Australia/State2/r42000-02-01200.0
+ +
+
+
+
+
S_df
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Australia/State1/r1Australia/State1/r2Australia/State2/r3Australia/State2/r4
Australia1.01.01.01.0
Australia/State11.01.00.00.0
Australia/State20.00.01.01.0
Australia/State1/r11.00.00.00.0
Australia/State1/r20.01.00.00.0
Australia/State2/r30.00.01.00.0
Australia/State2/r40.00.00.01.0
+ +
+
+
+
+
+

Base Predictions

+

Next, we compute the base forecast for each time series using the naive model. Observe that Y_hat_df contains the forecasts but they are not coherent.

+
+
from statsforecast.models import Naive
+from statsforecast.core import StatsForecast
+
+
+
# Split train/test sets
+Y_test_df  = Y_hier_df.groupby('unique_id').tail(4)
+Y_train_df = Y_hier_df.drop(Y_test_df.index)
+
+# Compute base Naive predictions
+# Careful identifying correct data freq, this data quarterly 'Q'
+fcst = StatsForecast(df=Y_train_df,
+                     models=[Naive()],
+                     freq='Q', n_jobs=-1)
+Y_hat_df = fcst.forecast(h=4, fitted=True)
+Y_fitted_df = fcst.forecast_fitted_values()
+
+
+
+

Reconciliation

+
+
from hierarchicalforecast.methods import BottomUp
+from hierarchicalforecast.core import HierarchicalReconciliation
+
+
+
# You can select a reconciler from our collection
+reconcilers = [BottomUp()] # MinTrace(method='mint_shrink')
+hrec = HierarchicalReconciliation(reconcilers=reconcilers)
+
+Y_rec_df = hrec.reconcile(Y_hat_df=Y_hat_df, 
+                          Y_df=Y_fitted_df,
+                          S=S_df, tags=tags)
+Y_rec_df.groupby('unique_id').head(2)
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
dsNaiveNaive/BottomUp
unique_id
Australia2000-06-30880.0880.0
Australia2000-09-30880.0880.0
Australia/State12000-06-3080.080.0
Australia/State12000-09-3080.080.0
Australia/State22000-06-30800.0800.0
Australia/State22000-09-30800.0800.0
Australia/State1/r12000-06-3040.040.0
Australia/State1/r12000-09-3040.040.0
Australia/State1/r22000-06-3040.040.0
Australia/State1/r22000-09-3040.040.0
Australia/State2/r32000-06-30400.0400.0
Australia/State2/r32000-09-30400.0400.0
Australia/State2/r42000-06-30400.0400.0
Australia/State2/r42000-09-30400.0400.0
+ +
+
+
+
+
+
+

References

+ + + +
+ +

If you find the code useful, please ā­ us on Github

+ +
+ + + + \ No newline at end of file diff --git a/examples/mlframeworksexample.html b/examples/mlframeworksexample.html new file mode 100644 index 00000000..71359b5c --- /dev/null +++ b/examples/mlframeworksexample.html @@ -0,0 +1,1991 @@ + + + + + + + + + +hierarchicalforecast - Neural/MLForecast + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ + +
+ +
+ + +
+ + + +
+ +
+
+

Neural/MLForecast

+
+ + + +
+ + + + +
+ + +
+ + +

This example notebook demonstrates the compatibility of HierarchicalForecastā€™s reconciliation methods with popular machine-learning libraries, specifically NeuralForecast and MLForecast.

+

The notebook utilizes NBEATS and XGBRegressor models to create base forecasts for the TourismLarge Hierarchical Dataset. After that, we use HierarchicalForecast to reconcile the base predictions.

+

References
- Boris N. Oreshkin, Dmitri Carpov, Nicolas Chapados, Yoshua Bengio (2019). ā€œN-BEATS: Neural basis expansion analysis for interpretable time series forecastingā€. url: https://arxiv.org/abs/1905.10437
- Tianqi Chen and Carlos Guestrin. ā€œXGBoost: A Scalable Tree Boosting Systemā€. In: Proceedings of the 22nd ACM SIGKDD International Conference on Knowledge Discovery and Data Mining. KDD ā€™16. San Francisco, California, USA: Association for Computing Machinery, 2016, pp. 785ā€“794. isbn: 9781450342322. doi: 10.1145/2939672.2939785. url: https://doi.org/10.1145/2939672.2939785 (cit. on p. 26).

+

You can run these experiments using CPU or GPU with Google Colab.

+

Open In Colab

+
+

1. Installing packages

+
+
!pip install datasetsforecast mlforecast 
+!pip install git+https://github.com/Nixtla/neuralforecast.git
+!pip install git+https://github.com/Nixtla/hierarchicalforecast.git
+
+
+
import numpy as np
+import pandas as pd
+
+from datasetsforecast.hierarchical import HierarchicalData
+
+from neuralforecast import NeuralForecast
+from neuralforecast.models import NBEATS
+from neuralforecast.losses.pytorch import GMM
+
+from mlforecast import MLForecast
+from window_ops.expanding import expanding_mean
+from mlforecast.utils import PredictionIntervals
+from mlforecast.target_transforms import Differences
+import xgboost as xgb
+
+#obtain hierarchical reconciliation methods and evaluation
+from hierarchicalforecast.methods import BottomUp, MinTrace
+from hierarchicalforecast.utils import HierarchicalPlot
+from hierarchicalforecast.core import HierarchicalReconciliation
+from hierarchicalforecast.evaluation import scaled_crps
+
+
+
+

2. Load hierarchical dataset

+

This detailed Australian Tourism Dataset comes from the National Visitor Survey, managed by the Tourism Research Australia, it is composed of 555 monthly series from 1998 to 2016, it is organized geographically, and purpose of travel. The natural geographical hierarchy comprises seven states, divided further in 27 zones and 76 regions. The purpose of travel categories are holiday, visiting friends and relatives (VFR), business and other. The MinT (Wickramasuriya et al., 2019), among other hierarchical forecasting studies has used the dataset it in the past. The dataset can be accessed in the MinT reconciliation webpage, although other sources are available.

+ ++++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Geographical DivisionNumber of series per divisionNumber of series per purposeTotal
Australia145
States72835
Zones27108135
Regions76304380
Total111444555
+
+
Y_df, S_df, tags = HierarchicalData.load('./data', 'TourismLarge')
+Y_df['ds'] = pd.to_datetime(Y_df['ds'])
+
+
+
Y_df.head()
+
+ +
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
unique_iddsy
0TotalAll1998-01-0145151.071280
1TotalAll1998-02-0117294.699551
2TotalAll1998-03-0120725.114184
3TotalAll1998-04-0125388.612353
4TotalAll1998-05-0120330.035211
+ + +
+ + + + + +
+
+ +
+
+

Visualize the aggregation matrix.

+
+
hplot = HierarchicalPlot(S=S_df, tags=tags)
+hplot.plot_summing_matrix()
+
+

+
+
+

Split the dataframe in train/test splits.

+
+
def sort_hier_df(Y_df, S_df):
+    # sorts unique_id lexicographically
+    Y_df.unique_id = Y_df.unique_id.astype('category')
+    Y_df.unique_id = Y_df.unique_id.cat.set_categories(S_df.index)
+    Y_df = Y_df.sort_values(by=['unique_id', 'ds'])
+    return Y_df
+
+Y_df = sort_hier_df(Y_df, S_df)
+
+
+
horizon = 12
+Y_test_df = Y_df.groupby('unique_id').tail(horizon)
+Y_train_df = Y_df.drop(Y_test_df.index)
+
+
+
+

3. Fit and Predict Models

+

HierarchicalForecast is compatible with many different ML models. Here, we show two examples:
1. NBEATS, a MLP-based deep neural architecture.
2. XGBRegressor, a tree-based architecture.

+
+
level = np.arange(0, 100, 2)
+qs = [[50-lv/2, 50+lv/2] for lv in level]
+quantiles = np.sort(np.concatenate(qs)/100)
+
+#fit/predict NBEATS from NeuralForecast
+nbeats = NBEATS(h=horizon,
+              input_size=2*horizon,
+              loss=GMM(n_components=10, quantiles=quantiles),
+              scaler_type='robust',
+              max_steps=2000)
+nf = NeuralForecast(models=[nbeats], freq='MS')
+nf.fit(df=Y_train_df)
+Y_hat_nf = nf.predict()
+
+#fit/predict XGBRegressor from MLForecast
+mf = MLForecast(models=[xgb.XGBRegressor()], 
+                freq='MS',
+                lags=[1,2,12,24],
+                date_features=['month'],
+                )
+mf.fit(Y_train_df, prediction_intervals=PredictionIntervals(n_windows=10, window_size=horizon)) 
+Y_hat_mf = mf.predict(horizon, level=level).set_index('unique_id')
+
+
INFO:lightning_fabric.utilities.seed:Global seed set to 1
+
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+
+
Y_hat_nf
+
+ +
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
dsNBEATSNBEATS-lo-98.0NBEATS-lo-96.0NBEATS-lo-94.0NBEATS-lo-92.0NBEATS-lo-90.0NBEATS-lo-88.0NBEATS-lo-86.0NBEATS-lo-84.0...NBEATS-hi-80.0NBEATS-hi-82.0NBEATS-hi-84.0NBEATS-hi-86.0NBEATS-hi-88.0NBEATS-hi-90.0NBEATS-hi-92.0NBEATS-hi-94.0NBEATS-hi-96.0NBEATS-hi-98.0
unique_id
TotalAll2016-01-0144304.03906224825.77148426974.60742227405.91406227881.26953128640.23828129469.51367230213.27734431009.929688...51838.82812552150.52343852404.88671952564.65234452951.23828153216.83984453689.35156254015.07421954545.88281255752.621094
TotalAll2016-02-0120877.98437517909.36523418334.90234418577.35546918653.08593818755.07226618839.82421918965.94726619074.134766...22756.22070322892.50976623029.40234423133.94140623221.66601623385.62890623587.02148423862.34375024243.56054724526.462891
TotalAll2016-03-0123444.97265618971.35546919329.70507819472.61914119756.50390619843.70312520075.36328120126.68945320259.271484...26024.24218826116.67773426196.49804726342.33984426535.79882826758.47656226934.58203127097.13085927441.99609427704.375000
TotalAll2016-04-0128927.13281224030.25781224540.77929724732.56640624988.00195325160.74414125304.65820325456.00195325567.078125...31568.96679731698.85546931856.85156232097.91601632211.32031232345.98828132510.90234432724.63867233078.03125033525.035156
TotalAll2016-05-0122716.43359419728.51171919910.92578120089.44335920214.95507820269.90625020355.70898420441.34960920491.029297...24937.33593825114.39648425270.27929725446.76562525676.28710926028.42773426440.01171927477.54101628452.41992229793.591797
..................................................................
GBDOth2016-08-014.731373-30.691290-8.694043-2.576124-2.196553-2.069076-1.913422-1.854156-1.767804...9.25202810.94821112.03194414.39676018.52352343.28771658.20753169.75492981.399673116.701561
GBDOth2016-09-015.685491-32.813366-11.985416-2.978264-2.413029-2.120405-1.788605-1.673310-1.550562...12.78784014.33054215.56358116.99604029.90103945.08659760.72438075.46257892.432518125.217796
GBDOth2016-10-014.760162-51.105358-27.034277-8.493114-2.859874-2.140030-1.905673-1.764797-1.621011...10.93060411.96060513.87651614.83936418.54010032.25114448.57326165.30146083.327026113.249001
GBDOth2016-11-016.491304-31.302568-6.776994-2.816422-2.196187-2.002094-1.806302-1.613474-1.538146...14.44944215.16187717.71551922.24718539.64864352.63457967.81211175.64786585.764038116.143196
GBDOth2016-12-016.683663-37.663929-13.461041-2.384047-2.037058-1.877487-1.620457-1.436237-1.304141...15.08643816.03809018.20685224.43112235.07840744.13880562.43591377.259911104.585594123.915787
+ + +

6660 rows Ɨ 102 columns

+
+ + + + + +
+
+ +
+
+
+
Y_hat_mf
+
+ +
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
dsXGBRegressorXGBRegressor-lo-98XGBRegressor-lo-96XGBRegressor-lo-94XGBRegressor-lo-92XGBRegressor-lo-90XGBRegressor-lo-88XGBRegressor-lo-86XGBRegressor-lo-84...XGBRegressor-hi-80XGBRegressor-hi-82XGBRegressor-hi-84XGBRegressor-hi-86XGBRegressor-hi-88XGBRegressor-hi-90XGBRegressor-hi-92XGBRegressor-hi-94XGBRegressor-hi-96XGBRegressor-hi-98
unique_id
TotalAll2016-01-0143891.53125039048.05388639398.91136839749.76885040100.62633240451.48381540561.80468140586.21961340610.634545...47123.59809047148.01302247172.42795547196.84288747221.25781947331.57868547682.43616848033.29365048384.15113248735.008614
TotalAll2016-02-0120715.65625018476.75688418482.49253718488.22819018493.96384218499.69949518539.21269218590.78929718642.365902...22685.79338822737.36999322788.94659822840.52320322892.09980822931.61300522937.34865822943.08431022948.81996322954.555616
TotalAll2016-03-0123008.89648417292.31222717323.85964117355.40705517386.95446917418.50188317582.39686917793.55884418004.720819...27590.74820027801.91017528013.07215028224.23412528435.39610028599.29108528630.83850028662.38591428693.93332828725.480742
TotalAll2016-04-0127731.05078122333.04714422537.51014522741.97314522946.43614523150.89914623233.88116423273.47711823313.073071...32069.83658432109.43253832149.02849132188.62444532228.22039832311.20241732515.66541732720.12841732924.59141833129.054418
TotalAll2016-05-0124898.52929721768.00467721859.56461521951.12455222042.68449022134.24442822222.83540722310.36604222397.896678...27224.10064427311.63128027399.16191627486.69255127574.22318727662.81416627754.37410427845.93404127937.49397928029.053917
..................................................................
GBDOth2016-08-018.842277-2.504086-1.296329-0.0885711.1191872.3269442.7942742.9971653.200056...14.07871514.28160614.48449714.68738814.89027915.35760916.56536617.77312418.98088220.188639
GBDOth2016-09-014.991811-1.879816-1.879816-1.879816-1.879816-1.879816-1.635940-1.304965-0.973990...10.29566210.62663710.95761211.28858711.61956111.86343811.86343811.86343811.86343811.863438
GBDOth2016-10-018.6477152.3395812.3395812.3395812.3395812.3395812.3395812.3395812.339581...14.95584814.95584814.95584814.95584814.95584814.95584814.95584814.95584814.95584814.955848
GBDOth2016-11-015.1803460.4510950.4510950.4510950.4510950.4510950.4510950.4510950.451095...9.9095979.9095979.9095979.9095979.9095979.9095979.9095979.9095979.9095979.909597
GBDOth2016-12-016.0526221.7913511.7913511.7913511.7913511.7913511.7913511.7913511.791351...10.31389210.31389210.31389210.31389210.31389210.31389210.31389210.31389210.31389210.313892
+ + +

6660 rows Ɨ 102 columns

+
+ + + + + +
+
+ +
+
+
+
+

4. Reconcile Predictions

+

With minimal parsing, we can reconcile the raw output predictions with different HierarchicalForecast reconciliation methods.

+
+
+
+ +
+
+Reconciliation Methods Availability +
+
+
+
+
+

The following reconciliation methods require access to insample predictions:
- ERM(method='closed'), ERM(method='reg_bu')
- TopDown(method='average_proportions'), TopDown(method='proportion_averages')
- MiddleOut(top_down_method='average_proportions'), MiddleOut(top_down_method='proportion_averages')
- MinTrace(method='wls_var'), MinTrace(method='mint_cov'), MinTrace(method='mint_shrink')

+

You can obtain NeuralForecastā€™s insample predictions via the NeuralForecast.predict_insample method.

+

We are working on making MLForecastā€™s insample predictions available.

+
+
+
+
+
reconcilers = [
+    BottomUp(),
+    MinTrace('ols')
+]
+hrec = HierarchicalReconciliation(reconcilers=reconcilers)
+
+Y_rec_nf = hrec.reconcile(Y_hat_df=Y_hat_nf, Y_df = Y_train_df, S=S_df, tags=tags, level=level)
+Y_rec_mf = hrec.reconcile(Y_hat_df=Y_hat_mf, Y_df = Y_train_df, S=S_df, tags=tags, level=level)
+
+
+
+

5. Evaluation

+

To evaluate we use a scaled variation of the CRPS, as proposed by Rangapuram (2021), to measure the accuracy of predicted quantiles y_hat compared to the observation y.

+

\[ \mathrm{sCRPS}(\hat{F}_{\tau}, \mathbf{y}_{\tau}) = \frac{2}{N} \sum_{i} +\int^{1}_{0} +\frac{\mathrm{QL}(\hat{F}_{i,\tau}, y_{i,\tau})_{q}}{\sum_{i} | y_{i,\tau} |} dq \]

+
+
rec_model_names_nf = ['NBEATS/BottomUp', 'NBEATS/MinTrace_method-ols']
+rec_model_names_mf = ['XGBRegressor/BottomUp', 'XGBRegressor/MinTrace_method-ols']
+
+n_quantiles = len(quantiles)
+n_series = len(S_df)
+
+for name in rec_model_names_nf:
+    quantile_columns = [col for col in Y_rec_nf.columns if (name+'-lo') in col or (name+'-hi') in col]
+    y_rec  = Y_rec_nf[quantile_columns].values 
+    y_test = Y_test_df['y'].values
+
+    y_rec  = y_rec.reshape(n_series, horizon, n_quantiles)
+    y_test = y_test.reshape(n_series, horizon)
+    scrps  = scaled_crps(y=y_test, y_hat=y_rec, quantiles=quantiles)
+    print("{:<40} {:.5f}".format(name+":", scrps))
+
+for name in rec_model_names_mf:
+    quantile_columns = [col for col in Y_rec_mf.columns if (name+'-lo') in col or (name+'-hi') in col]
+    y_rec  = Y_rec_mf[quantile_columns].values 
+    y_test = Y_test_df['y'].values
+
+    y_rec  = y_rec.reshape(n_series, horizon, n_quantiles)
+    y_test = y_test.reshape(n_series, horizon)
+    scrps  = scaled_crps(y=y_test, y_hat=y_rec, quantiles=quantiles)
+    print("{:<40} {:.5f}".format(name+":", scrps))
+
+
NBEATS/BottomUp:                         0.12853
+NBEATS/MinTrace_method-ols:              0.12945
+XGBRegressor/BottomUp:                   0.13202
+XGBRegressor/MinTrace_method-ols:        0.13417
+
+
+
+
+

6. Visualizations

+
+
plot_nf = pd.concat([Y_df.set_index(['unique_id', 'ds']), 
+                     Y_rec_nf.set_index('ds', append=True)], axis=1)
+plot_nf = plot_nf.reset_index('ds')
+
+plot_mf = pd.concat([Y_df.set_index(['unique_id', 'ds']), 
+                     Y_rec_mf.set_index('ds', append=True)], axis=1)
+plot_mf = plot_mf.reset_index('ds')
+
+
+
hplot.plot_series(
+    series='TotalVis',
+    Y_df=plot_nf, 
+    models=['y', 'NBEATS', 'NBEATS/BottomUp', 'NBEATS/MinTrace_method-ols'],
+    level=[80]
+)
+
+

+
+
+
+
hplot.plot_series(
+    series='TotalVis',
+    Y_df=plot_mf, 
+    models=['y', 'XGBRegressor', 'XGBRegressor/BottomUp', 'XGBRegressor/MinTrace_method-ols'],
+    level=[80]
+)
+
+

+
+
+ + +
+ +

If you find the code useful, please ā­ us on Github

+ + +
+ + + + \ No newline at end of file diff --git a/examples/nonnegativereconciliation.html b/examples/nonnegativereconciliation.html new file mode 100644 index 00000000..000e35e7 --- /dev/null +++ b/examples/nonnegativereconciliation.html @@ -0,0 +1,1303 @@ + + + + + + + + + +hierarchicalforecast - Non-Negative MinTrace + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ + +
+ +
+ + +
+ + + +
+ +
+
+

Non-Negative MinTrace

+
+ + + +
+ + + + +
+ + +
+ + +

Large collections of time series organized into structures at different aggregation levels often require their forecasts to follow their aggregation constraints and to be nonnegative, which poses the challenge of creating novel algorithms capable of coherent forecasts.

+

The HierarchicalForecast package provides a wide collection of Python implementations of hierarchical forecasting algorithms that follow nonnegative hierarchical reconciliation.

+

In this notebook, we will show how to use the HierarchicalForecast package to perform nonnegative reconciliation of forecasts on Wiki2 dataset.

+

You can run these experiments using CPU or GPU with Google Colab.

+

Open In Colab

+
+
!pip install hierarchicalforecast statsforecast datasetsforecast
+
+
+

1. Load Data

+

In this example we will use the Wiki2 dataset. The following cell gets the time series for the different levels in the hierarchy, the summing dataframe S_df which recovers the full dataset from the bottom level hierarchy and the indices of each hierarchy denoted by tags.

+
+
import numpy as np
+import pandas as pd
+
+from datasetsforecast.hierarchical import HierarchicalData
+
+
+
Y_df, S_df, tags = HierarchicalData.load('./data', 'Wiki2')
+Y_df['ds'] = pd.to_datetime(Y_df['ds'])
+
+
+
Y_df.head()
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
unique_iddsy
0Total2016-01-01156508
1Total2016-01-02129902
2Total2016-01-03138203
3Total2016-01-04115017
4Total2016-01-05126042
+ +
+
+
+
+
S_df.iloc[:5, :5]
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
de_AAC_AAG_001de_AAC_AAG_010de_AAC_AAG_014de_AAC_AAG_045de_AAC_AAG_063
Total11111
de11111
en00000
fr00000
ja00000
+ +
+
+
+
+
tags
+
+
{'Views': array(['Total'], dtype=object),
+ 'Views/Country': array(['de', 'en', 'fr', 'ja', 'ru', 'zh'], dtype=object),
+ 'Views/Country/Access': array(['de_AAC', 'de_DES', 'de_MOB', 'en_AAC', 'en_DES', 'en_MOB',
+        'fr_AAC', 'fr_DES', 'fr_MOB', 'ja_AAC', 'ja_DES', 'ja_MOB',
+        'ru_AAC', 'ru_DES', 'ru_MOB', 'zh_AAC', 'zh_DES', 'zh_MOB'],
+       dtype=object),
+ 'Views/Country/Access/Agent': array(['de_AAC_AAG', 'de_AAC_SPD', 'de_DES_AAG', 'de_MOB_AAG',
+        'en_AAC_AAG', 'en_AAC_SPD', 'en_DES_AAG', 'en_MOB_AAG',
+        'fr_AAC_AAG', 'fr_AAC_SPD', 'fr_DES_AAG', 'fr_MOB_AAG',
+        'ja_AAC_AAG', 'ja_AAC_SPD', 'ja_DES_AAG', 'ja_MOB_AAG',
+        'ru_AAC_AAG', 'ru_AAC_SPD', 'ru_DES_AAG', 'ru_MOB_AAG',
+        'zh_AAC_AAG', 'zh_AAC_SPD', 'zh_DES_AAG', 'zh_MOB_AAG'],
+       dtype=object),
+ 'Views/Country/Access/Agent/Topic': array(['de_AAC_AAG_001', 'de_AAC_AAG_010', 'de_AAC_AAG_014',
+        'de_AAC_AAG_045', 'de_AAC_AAG_063', 'de_AAC_AAG_100',
+        'de_AAC_AAG_110', 'de_AAC_AAG_123', 'de_AAC_AAG_143',
+        'de_AAC_SPD_012', 'de_AAC_SPD_074', 'de_AAC_SPD_080',
+        'de_AAC_SPD_105', 'de_AAC_SPD_115', 'de_AAC_SPD_133',
+        'de_DES_AAG_064', 'de_DES_AAG_116', 'de_DES_AAG_131',
+        'de_MOB_AAG_015', 'de_MOB_AAG_020', 'de_MOB_AAG_032',
+        'de_MOB_AAG_059', 'de_MOB_AAG_062', 'de_MOB_AAG_088',
+        'de_MOB_AAG_095', 'de_MOB_AAG_109', 'de_MOB_AAG_122',
+        'de_MOB_AAG_149', 'en_AAC_AAG_044', 'en_AAC_AAG_049',
+        'en_AAC_AAG_075', 'en_AAC_AAG_114', 'en_AAC_AAG_119',
+        'en_AAC_AAG_141', 'en_AAC_SPD_004', 'en_AAC_SPD_011',
+        'en_AAC_SPD_026', 'en_AAC_SPD_048', 'en_AAC_SPD_067',
+        'en_AAC_SPD_126', 'en_AAC_SPD_140', 'en_DES_AAG_016',
+        'en_DES_AAG_024', 'en_DES_AAG_042', 'en_DES_AAG_069',
+        'en_DES_AAG_082', 'en_DES_AAG_102', 'en_MOB_AAG_018',
+        'en_MOB_AAG_022', 'en_MOB_AAG_101', 'en_MOB_AAG_124',
+        'fr_AAC_AAG_029', 'fr_AAC_AAG_046', 'fr_AAC_AAG_070',
+        'fr_AAC_AAG_087', 'fr_AAC_AAG_098', 'fr_AAC_AAG_104',
+        'fr_AAC_AAG_111', 'fr_AAC_AAG_112', 'fr_AAC_AAG_142',
+        'fr_AAC_SPD_025', 'fr_AAC_SPD_027', 'fr_AAC_SPD_035',
+        'fr_AAC_SPD_077', 'fr_AAC_SPD_084', 'fr_AAC_SPD_097',
+        'fr_AAC_SPD_130', 'fr_DES_AAG_023', 'fr_DES_AAG_043',
+        'fr_DES_AAG_051', 'fr_DES_AAG_058', 'fr_DES_AAG_061',
+        'fr_DES_AAG_091', 'fr_DES_AAG_093', 'fr_DES_AAG_094',
+        'fr_DES_AAG_136', 'fr_MOB_AAG_006', 'fr_MOB_AAG_030',
+        'fr_MOB_AAG_066', 'fr_MOB_AAG_117', 'fr_MOB_AAG_120',
+        'fr_MOB_AAG_121', 'fr_MOB_AAG_135', 'fr_MOB_AAG_147',
+        'ja_AAC_AAG_038', 'ja_AAC_AAG_047', 'ja_AAC_AAG_055',
+        'ja_AAC_AAG_076', 'ja_AAC_AAG_099', 'ja_AAC_AAG_128',
+        'ja_AAC_AAG_132', 'ja_AAC_AAG_134', 'ja_AAC_AAG_137',
+        'ja_AAC_SPD_013', 'ja_AAC_SPD_034', 'ja_AAC_SPD_050',
+        'ja_AAC_SPD_060', 'ja_AAC_SPD_078', 'ja_AAC_SPD_106',
+        'ja_DES_AAG_079', 'ja_DES_AAG_081', 'ja_DES_AAG_113',
+        'ja_MOB_AAG_065', 'ja_MOB_AAG_073', 'ja_MOB_AAG_092',
+        'ja_MOB_AAG_127', 'ja_MOB_AAG_129', 'ja_MOB_AAG_144',
+        'ru_AAC_AAG_008', 'ru_AAC_AAG_145', 'ru_AAC_AAG_146',
+        'ru_AAC_SPD_000', 'ru_AAC_SPD_090', 'ru_AAC_SPD_148',
+        'ru_DES_AAG_003', 'ru_DES_AAG_007', 'ru_DES_AAG_017',
+        'ru_DES_AAG_041', 'ru_DES_AAG_071', 'ru_DES_AAG_072',
+        'ru_MOB_AAG_002', 'ru_MOB_AAG_040', 'ru_MOB_AAG_083',
+        'ru_MOB_AAG_086', 'ru_MOB_AAG_103', 'ru_MOB_AAG_107',
+        'ru_MOB_AAG_118', 'ru_MOB_AAG_125', 'zh_AAC_AAG_021',
+        'zh_AAC_AAG_033', 'zh_AAC_AAG_037', 'zh_AAC_AAG_052',
+        'zh_AAC_AAG_057', 'zh_AAC_AAG_085', 'zh_AAC_AAG_108',
+        'zh_AAC_SPD_039', 'zh_AAC_SPD_096', 'zh_DES_AAG_009',
+        'zh_DES_AAG_019', 'zh_DES_AAG_053', 'zh_DES_AAG_054',
+        'zh_DES_AAG_056', 'zh_DES_AAG_068', 'zh_DES_AAG_089',
+        'zh_DES_AAG_139', 'zh_MOB_AAG_005', 'zh_MOB_AAG_028',
+        'zh_MOB_AAG_031', 'zh_MOB_AAG_036', 'zh_MOB_AAG_138'], dtype=object)}
+
+
+

We split the dataframe in train/test splits.

+
+
Y_test_df = Y_df.groupby('unique_id').tail(7)
+Y_train_df = Y_df.drop(Y_test_df.index)
+
+
+
Y_test_df = Y_test_df.set_index('unique_id')
+Y_train_df = Y_train_df.set_index('unique_id')
+
+
+
+

2. Base Forecasts

+

The following cell computes the base forecast for each time series using the ETS and naive models. Observe that Y_hat_df contains the forecasts but they are not coherent.

+
+
from statsforecast.models import ETS, Naive
+from statsforecast.core import StatsForecast
+
+
+
fcst = StatsForecast(
+    df=Y_train_df, 
+    models=[ETS(season_length=7, model='ZAA'), Naive()], 
+    freq='D', 
+    n_jobs=-1
+)
+Y_hat_df = fcst.forecast(h=7)
+
+

Observe that the ETS model computes negative forecasts for some series.

+
+
Y_hat_df.query('ETS < 0')
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
dsETSNaive
unique_id
de_AAC_AAG_0012016-12-25-487.601532340.0
de_AAC_AAG_0012016-12-26-215.634201340.0
de_AAC_AAG_0012016-12-27-173.175613340.0
de_AAC_AAG_0012016-12-30-290.836060340.0
de_AAC_AAG_0012016-12-31-784.441040340.0
............
zh_AAC_AAG_0332016-12-31-86.52642137.0
zh_MOB2016-12-26-199.5348821036.0
zh_MOB2016-12-27-69.5272601036.0
zh_MOB_AAG2016-12-26-199.5348821036.0
zh_MOB_AAG2016-12-27-69.5272601036.0
+ +

99 rows Ɨ 3 columns

+
+
+
+
+
+

3. Non-Negative Reconciliation

+

The following cell makes the previous forecasts coherent and nonnegative using the HierarchicalReconciliation class.

+
+
from hierarchicalforecast.methods import MinTrace
+from hierarchicalforecast.core import HierarchicalReconciliation
+
+
+
reconcilers = [
+    MinTrace(method='ols'),
+    MinTrace(method='ols', nonnegative=True)
+]
+hrec = HierarchicalReconciliation(reconcilers=reconcilers)
+Y_rec_df = hrec.reconcile(Y_hat_df=Y_hat_df, Y_df=Y_train_df,
+                          S=S_df, tags=tags)
+
+

Observe that the nonnegative reconciliation method obtains nonnegative forecasts.

+
+
Y_rec_df.query('`ETS/MinTrace_method-ols_nonnegative-True` < 0')
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + +
dsETSNaiveETS/MinTrace_method-olsNaive/MinTrace_method-olsETS/MinTrace_method-ols_nonnegative-TrueNaive/MinTrace_method-ols_nonnegative-True
unique_id
+ +
+
+
+

The free reconciliation method gets negative forecasts.

+
+
Y_rec_df.query('`ETS/MinTrace_method-ols` < 0')
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
dsETSNaiveETS/MinTrace_method-olsNaive/MinTrace_method-olsETS/MinTrace_method-ols_nonnegative-TrueNaive/MinTrace_method-ols_nonnegative-True
unique_id
de_DES2016-12-25-2553.932861495.0-3468.745214495.02.262540e-15495.0
de_DES2016-12-26-2155.228271495.0-2985.587125495.01.356705e-30495.0
de_DES2016-12-27-2720.993896495.0-3698.680055495.06.857413e-30495.0
de_DES2016-12-29-3429.432617495.0-2965.207609495.02.456449e+02495.0
de_DES2016-12-30-3963.202637495.0-3217.360371495.03.646790e+02495.0
........................
zh_MOB_AAG_0362016-12-2675.298317115.0-165.799776115.03.207772e-14115.0
zh_MOB_AAG_0362016-12-2772.895554115.0-134.340626115.02.308198e-14115.0
zh_MOB_AAG_1382016-12-2594.79662365.0-47.00981365.03.116938e-1465.0
zh_MOB_AAG_1382016-12-2671.29398365.0-169.80411065.00.000000e+0065.0
zh_MOB_AAG_1382016-12-2762.04974465.0-145.18643665.00.000000e+0065.0
+ +

240 rows Ɨ 7 columns

+
+
+
+
+
+

4. Evaluation

+

The HierarchicalForecast package includes the HierarchicalEvaluation class to evaluate the different hierarchies and also is capable of compute scaled metrics compared to a benchmark model.

+
+
from hierarchicalforecast.evaluation import HierarchicalEvaluation
+
+
+
def mse(y, y_hat):
+    return np.mean((y-y_hat)**2)
+
+evaluator = HierarchicalEvaluation(evaluators=[mse])
+evaluation = evaluator.evaluate(
+        Y_hat_df=Y_rec_df, Y_test_df=Y_test_df, 
+        tags=tags, benchmark='Naive'
+)
+evaluation.filter(like='ETS', axis=1).T
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
levelOverallViewsViews/CountryViews/Country/AccessViews/Country/Access/AgentViews/Country/Access/Agent/Topic
metricmse-scaledmse-scaledmse-scaledmse-scaledmse-scaledmse-scaled
ETS1.0115850.73581.1903541.1036571.0895151.397139
ETS/MinTrace_method-ols0.9791630.6983551.0625211.1432771.1133491.354041
ETS/MinTrace_method-ols_nonnegative-True0.9450750.6778921.0046391.1847191.1414421.158672
+ +
+
+
+

Observe that the nonnegative reconciliation method performs better that its unconstrained counterpart.

+
+

References

+ + + +
+
+ +

If you find the code useful, please ā­ us on Github

+ +
+ + + + \ No newline at end of file diff --git a/examples/tourismlarge-evaluation.html b/examples/tourismlarge-evaluation.html new file mode 100644 index 00000000..52ea3237 --- /dev/null +++ b/examples/tourismlarge-evaluation.html @@ -0,0 +1,2305 @@ + + + + + + + + + + +hierarchicalforecast - Probabilistic Forecast Evaluation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ + +
+ +
+ + +
+ + + +
+ +
+
+

Probabilistic Forecast Evaluation

+
+ +
+
+ Hierarchical Forecastā€™s reconciliation and evaluation. +
+
+ + +
+ + + + +
+ + +
+ + +

This notebook offers a step to step guide to create a hierarchical forecasting pipeline.

+

In the pipeline we will use HierarchicalForecast and StatsForecast core class, to create base predictions, reconcile and evaluate them.

+

We will use the TourismL dataset that summarizes large Australian national visitor survey.

+

Outline 1. Installing Packages 2. Prepare TourismL dataset - Read and aggregate - StatsForecastā€™s Base Predictions 3. Reconciliar 4. Evaluar

+

Open In Colab

+
+

1. Installing HierarchicalForecast

+

We assume you have StatsForecast and HierarchicalForecast already installed, if not check this guide for instructions on how to install HierarchicalForecast.

+
+
# %%capture
+# !pip install hierarchicalforecast
+# !pip install -U numba statsforecast datasetsforecast
+
+
+
import os
+import numpy as np
+import pandas as pd
+import matplotlib.pyplot as plt
+
+from statsforecast.core import StatsForecast
+from statsforecast.models import AutoARIMA, Naive
+
+from hierarchicalforecast.core import HierarchicalReconciliation
+from hierarchicalforecast.evaluation import HierarchicalEvaluation
+from hierarchicalforecast.methods import BottomUp, TopDown, MinTrace, ERM
+
+from hierarchicalforecast.utils import is_strictly_hierarchical
+from hierarchicalforecast.utils import HierarchicalPlot, CodeTimer
+from hierarchicalforecast.evaluation import scaled_crps, msse, energy_score
+
+from datasetsforecast.hierarchical import HierarchicalData, HierarchicalInfo
+
+
/Users/cchallu/opt/anaconda3/envs/hierarchicalforecast/lib/python3.10/site-packages/statsforecast/core.py:21: TqdmWarning: IProgress not found. Please update jupyter and ipywidgets. See https://ipywidgets.readthedocs.io/en/stable/user_install.html
+  from tqdm.autonotebook import tqdm
+
+
+
+
+

2. Preparing TourismL Dataset

+
+

2.1 Read Hierarchical Dataset

+
+
# ['Labour', 'Traffic', 'TourismSmall', 'TourismLarge', 'Wiki2']
+dataset = 'TourismSmall' # 'TourismLarge'
+verbose = True
+intervals_method = 'bootstrap'
+LEVEL = np.arange(0, 100, 2)
+qs = [[50-lv/2, 50+lv/2] for lv in LEVEL]
+QUANTILES = np.sort(np.concatenate(qs)/100)
+
+
+
with CodeTimer('Read and Parse data   ', verbose):
+    print(f'{dataset}')
+    if not os.path.exists('./data'):
+        os.makedirs('./data')
+    
+    dataset_info = HierarchicalInfo[dataset]
+    Y_df, S_df, tags = HierarchicalData.load(directory=f'./data/{dataset}', group=dataset)
+    Y_df['ds'] = pd.to_datetime(Y_df['ds'])
+
+    # Train/Test Splits
+    horizon = dataset_info.horizon
+    seasonality = dataset_info.seasonality
+    Y_test_df = Y_df.groupby('unique_id').tail(horizon)
+    Y_train_df = Y_df.drop(Y_test_df.index)
+    Y_test_df = Y_test_df.set_index('unique_id')
+    Y_train_df = Y_train_df.set_index('unique_id')
+
+
TourismSmall
+Code block 'Read and Parse data   ' took:   0.99873 seconds
+
+
+
100%|ā–ˆā–ˆā–ˆā–ˆā–ˆā–ˆā–ˆā–ˆā–ˆā–ˆ| 1.30M/1.30M [00:00<00:00, 2.74MiB/s]
+INFO:datasetsforecast.utils:Successfully downloaded datasets.zip, 1297279, bytes.
+INFO:datasetsforecast.utils:Decompressing zip file...
+INFO:datasetsforecast.utils:Successfully decompressed data/TourismSmall/hierarchical/datasets.zip
+
+
+
+
dataset_info.seasonality
+
+
4
+
+
+
+
hplot = HierarchicalPlot(S=S_df, tags=tags)
+hplot.plot_summing_matrix()
+
+

+
+
+
+
Y_train_df
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
dsy
unique_id
total1998-03-3184503
total1998-06-3065312
total1998-09-3072753
total1998-12-3170880
total1999-03-3186893
.........
nt-oth-noncity2003-12-31132
nt-oth-noncity2004-03-3112
nt-oth-noncity2004-06-3040
nt-oth-noncity2004-09-30186
nt-oth-noncity2004-12-31144
+ +

2492 rows Ɨ 2 columns

+
+
+
+
+
+

2.2 StatsForecastā€™s Base Predictions

+

This cell computes the base predictions Y_hat_df for all the series in Y_df using StatsForecastā€™s AutoARIMA. Additionally we obtain insample predictions Y_fitted_df for the methods that require them.

+
+
with CodeTimer('Fit/Predict Model     ', verbose):
+    # Read to avoid unnecesary AutoARIMA computation
+    yhat_file = f'./data/{dataset}/Y_hat.csv'
+    yfitted_file = f'./data/{dataset}/Y_fitted.csv'
+
+    if os.path.exists(yhat_file):
+        Y_hat_df = pd.read_csv(yhat_file)
+        Y_fitted_df = pd.read_csv(yfitted_file)
+
+        Y_hat_df = Y_hat_df.set_index('unique_id')
+        Y_fitted_df = Y_fitted_df.set_index('unique_id')
+
+    else:
+        fcst = StatsForecast(
+            df=Y_train_df, 
+            models=[AutoARIMA(season_length=seasonality)],
+            fallback_model=[Naive()],
+            freq='M', 
+            n_jobs=-1
+        )
+        Y_hat_df = fcst.forecast(h=horizon, fitted=True, level=LEVEL)
+        Y_fitted_df = fcst.forecast_fitted_values()
+        Y_hat_df.to_csv(yhat_file)
+        Y_fitted_df.to_csv(yfitted_file)
+
+
+
Y_hat_df
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
dsAutoARIMAAutoARIMA-lo-98AutoARIMA-lo-96AutoARIMA-lo-94AutoARIMA-lo-92AutoARIMA-lo-90AutoARIMA-lo-88AutoARIMA-lo-86AutoARIMA-lo-84...AutoARIMA-hi-80AutoARIMA-hi-82AutoARIMA-hi-84AutoARIMA-hi-86AutoARIMA-hi-88AutoARIMA-hi-90AutoARIMA-hi-92AutoARIMA-hi-94AutoARIMA-hi-96AutoARIMA-hi-98
unique_id
bus2005-01-319673.4248057436.3564457698.4936527864.8115237989.9257818091.6962898178.3193368254.2705088322.276367...10905.79394510962.72558611024.57324211092.57910211168.53027311255.15332011356.92382811482.03808611648.35644511910.493164
bus2005-02-2810393.9003918156.8315438418.9687508585.2871098710.4013678812.1718758898.7949228974.7460949042.751953...11626.26953111683.20019511745.04882811813.05468811889.00585911975.62890612077.39941412202.51367212368.83203112630.968750
bus2005-03-3112028.1347669791.06640610053.20410210219.52148410344.63574210446.40625010533.02929710608.98144510676.986328...13260.50390613317.43554713379.28320313447.28906213523.24023413609.86328113711.63378913836.74804714003.06640614265.203125
bus2005-04-3010995.6796888758.6103529020.7480479187.0654309312.1796889413.9511729500.5742199576.5253919644.531250...12228.04785212284.97949212346.82812512414.83300812490.78515612577.40722712679.17871112804.29296912970.61035213232.748047
bus2005-05-319673.4248057262.0854497544.6435557723.9174807858.7783207968.4775398061.8486338143.7167978217.019531...11001.79687511063.16406211129.83007811203.13281211285.00097711378.37207011488.07128911622.93261711802.20605512084.764648
..................................................................
wa-vfr-noncity2005-04-30904.125549463.371521515.018616547.787048572.437439592.488647609.555359624.519531637.918213...1146.9305421158.1473391170.3328861183.7315671198.6956791215.7624511235.8137211260.4641111293.2325441344.879517
wa-vfr-noncity2005-05-31904.125549457.607361509.929901543.126831568.099670588.413086605.703003620.862854634.436707...1150.1059571161.4694821173.8143311187.3881841202.5480961219.8380131240.1514891265.1242681298.3211671350.643677
wa-vfr-noncity2005-06-30904.125549451.916687504.906036538.526062563.817139584.389465601.899719617.252808630.999634...1153.2409671164.7492681177.2514651190.9982911206.3514401223.8616941244.4339601269.7249761303.3450931356.334473
wa-vfr-noncity2005-07-31904.125549446.296722499.944611533.982483559.587830580.415833598.143738613.687622627.605286...1156.3369141167.9881591180.6457521194.5634771210.1074221227.8353271248.6632081274.2686771308.3065191361.954346
wa-vfr-noncity2005-08-31904.125549440.744904495.043365529.493958555.409851576.490417594.433289610.165649624.252136...1159.3952641171.1878661183.9990231198.0854491213.8178711231.7607421252.8413091278.7570801313.2077641367.506226
+ +

712 rows Ɨ 102 columns

+
+
+
+
+
Y_fitted_df
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
dsyAutoARIMAAutoARIMA-lo-98AutoARIMA-lo-96AutoARIMA-lo-94AutoARIMA-lo-92AutoARIMA-lo-90AutoARIMA-lo-88AutoARIMA-lo-86...AutoARIMA-hi-80AutoARIMA-hi-82AutoARIMA-hi-84AutoARIMA-hi-86AutoARIMA-hi-88AutoARIMA-hi-90AutoARIMA-hi-92AutoARIMA-hi-94AutoARIMA-hi-96AutoARIMA-hi-98
unique_id
bus1998-03-319815.09805.1845707568.6489267830.7241217997.0019538122.0864268223.8330088310.4355478386.369141...11037.26074211094.17871111156.01171911224.00097711299.93457011386.53710911488.28320311613.36816411779.64648412041.720703
bus1998-06-3011823.011811.1767589574.6406259836.71582010002.99414110128.07812510229.82519510316.42773410392.361328...13043.25293013100.16992213162.00390613229.99316413305.92675813392.52832013494.27539113619.36035213785.63769514047.712891
bus1998-09-3013565.013551.43457011314.89941411576.97363311743.25195311868.33691411970.08300812056.68554712132.619141...14783.51074214840.42871114902.26171914970.25097715046.18457015132.78710915234.53320315359.61816415525.89648415787.970703
bus1998-12-3111478.011466.5224619229.9863289492.0605479658.3388679783.4238289885.1699229971.77246110047.706055...12698.59765612755.51562512817.34863312885.33789112961.27148413047.87402313149.62011713274.70507813440.98339813703.057617
bus1999-03-3110027.09845.0117197608.4755867870.5507818036.8286138161.9130868263.6601568350.2626958426.195312...11077.08691411134.00488311195.83886711263.82812511339.76074211426.36328111528.11035211653.19433611819.47265612081.547852
..................................................................
wa-vfr-noncity2003-12-311177.0927.351196504.362732553.928040585.375671609.032471628.275513644.654297659.015320...1160.3695071171.1341551182.8284911195.6870121210.0480961226.4268801245.6699221269.3266601300.7742921350.339600
wa-vfr-noncity2004-03-31956.0969.565552546.577087596.142456627.590027651.246887670.489868686.868652701.229675...1202.5838621213.3485111225.0428471237.9014891252.2624511268.6412351287.8842771311.5410161342.9886471392.554077
wa-vfr-noncity2004-06-30772.0967.268921544.280457593.845764625.293396648.950195668.193237684.572021698.933044...1200.2871091211.0518801222.7462161235.6047361249.9658201266.3446041285.5876461309.2443851340.6920171390.257324
wa-vfr-noncity2004-09-30885.0934.251831511.263336560.828674592.276306615.933105635.176086651.554932665.915955...1167.2700201178.0347901189.7291261202.5876461216.9487301233.3275151252.5705571276.2272951307.6749271357.240234
wa-vfr-noncity2004-12-31797.0925.923462502.934998552.500305583.947937607.604736626.847778643.226562657.587585...1158.9417721169.7064211181.4007571194.2592771208.6203611224.9991461244.2421881267.8989261299.3465581348.911865
+ +

2492 rows Ɨ 103 columns

+
+
+
+
+
+
+

3. Reconciliate Predictions

+
+
with CodeTimer('Reconcile Predictions ', verbose):
+    if is_strictly_hierarchical(S=S_df.values.astype(np.float32), 
+        tags={key: S_df.index.get_indexer(val) for key, val in tags.items()}):
+        reconcilers = [
+            BottomUp(),
+            TopDown(method='average_proportions'),
+            TopDown(method='proportion_averages'),
+            MinTrace(method='ols'),
+            MinTrace(method='wls_var'),
+            MinTrace(method='mint_shrink'),
+            #ERM(method='reg_bu', lambda_reg=100) # Extremely inneficient
+            ERM(method='closed')
+        ]
+    else:
+        reconcilers = [
+            BottomUp(),
+            MinTrace(method='ols'),
+            MinTrace(method='wls_var'),
+            MinTrace(method='mint_shrink'),
+            #ERM(method='reg_bu', lambda_reg=100) # Extremely inneficient
+            ERM(method='closed')
+        ]
+    
+    hrec = HierarchicalReconciliation(reconcilers=reconcilers)
+    Y_rec_df = hrec.bootstrap_reconcile(Y_hat_df=Y_hat_df,
+                                        Y_df=Y_fitted_df,
+                                        S_df=S_df, tags=tags,
+                                        level=LEVEL,
+                                        intervals_method=intervals_method,
+                                        num_samples=10, num_seeds=10)
+
+    # Matching Y_test/Y_rec/S index ordering
+    Y_test_df = Y_test_df.reset_index()
+    Y_test_df.unique_id = Y_test_df.unique_id.astype('category')
+    Y_test_df.unique_id = Y_test_df.unique_id.cat.set_categories(S_df.index)
+    Y_test_df = Y_test_df.sort_values(by=['unique_id', 'ds'])
+
+    Y_rec_df = Y_rec_df.reset_index()
+    Y_rec_df.unique_id = Y_rec_df.unique_id.astype('category')
+    Y_rec_df.unique_id = Y_rec_df.unique_id.cat.set_categories(S_df.index)
+    Y_rec_df = Y_rec_df.sort_values(by=['seed', 'unique_id', 'ds'])
+
+    # Parsing model level columns
+    flat_cols = list(hrec.level_names.keys())
+    for model in hrec.level_names:
+        flat_cols += hrec.level_names[model]
+    for model in hrec.sample_names:
+        flat_cols += hrec.sample_names[model]
+    y_rec  = Y_rec_df[flat_cols]
+    model_columns = y_rec.columns
+
+    n_series = len(S_df)
+    n_seeds = len(Y_rec_df.seed.unique())
+    y_rec  = y_rec.values.reshape(n_seeds, n_series, horizon, len(model_columns))
+    y_test = Y_test_df['y'].values.reshape(n_series, horizon)
+    y_train = Y_train_df['y'].values.reshape(n_series, -1)
+
+
Code block 'Reconcile Predictions ' took:   11.73492 seconds
+
+
+
+
# Qualitative evaluation, of parsed quantiles
+row_idx = 0
+seed_idx = 0
+col_idxs = model_columns.get_indexer(hrec.level_names['AutoARIMA/BottomUp'])
+for i, col in enumerate(col_idxs):
+    plt.plot(y_rec[seed_idx, row_idx,:,col], color='orange', alpha=i/100)
+for i, col in enumerate(col_idxs):
+    plt.plot(y_rec[seed_idx+1, row_idx,:,col], color='green', alpha=i/100)
+plt.plot(y_test[row_idx,:], label='True')
+plt.title(f'{S_df.index[row_idx]} Visits \n' + \
+          f'AutoARIMA/BottomUp-{intervals_method}')
+
+plt.legend()
+plt.grid()
+plt.show()
+plt.close()
+
+

+
+
+
+
#Y_rec_df
+td_levels = hrec.level_names['AutoARIMA/TopDown_method-average_proportions']
+Y_rec_df[td_levels]
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
AutoARIMA/TopDown_method-average_proportions-lo-98AutoARIMA/TopDown_method-average_proportions-lo-96AutoARIMA/TopDown_method-average_proportions-lo-94AutoARIMA/TopDown_method-average_proportions-lo-92AutoARIMA/TopDown_method-average_proportions-lo-90AutoARIMA/TopDown_method-average_proportions-lo-88AutoARIMA/TopDown_method-average_proportions-lo-86AutoARIMA/TopDown_method-average_proportions-lo-84AutoARIMA/TopDown_method-average_proportions-lo-82AutoARIMA/TopDown_method-average_proportions-lo-80...AutoARIMA/TopDown_method-average_proportions-hi-80AutoARIMA/TopDown_method-average_proportions-hi-82AutoARIMA/TopDown_method-average_proportions-hi-84AutoARIMA/TopDown_method-average_proportions-hi-86AutoARIMA/TopDown_method-average_proportions-hi-88AutoARIMA/TopDown_method-average_proportions-hi-90AutoARIMA/TopDown_method-average_proportions-hi-92AutoARIMA/TopDown_method-average_proportions-hi-94AutoARIMA/TopDown_method-average_proportions-hi-96AutoARIMA/TopDown_method-average_proportions-hi-98
080750.38992080750.38992080750.38992082299.06178182299.06178182299.06178182600.02271682600.02271682600.02271682763.007090...88248.62422988248.62422988248.62422988248.62422988384.15344790507.44452290507.44452290507.44452290507.44452290507.444522
161825.84321061825.84321061825.84321061825.84321061825.84321061825.84321063374.51507263374.51507263374.51507263374.515072...68196.49940568196.49940568286.70565469324.07752069324.07752069437.01853471582.89781271582.89781271582.89781271582.897812
268249.62440468249.62440468249.62440468249.62440469798.29626669798.29626669798.29626669798.29626669798.29626669798.296266...74620.28059874620.28059874620.28059874699.21106775747.85871475747.85871475747.85871475815.62332278006.67900678006.679006
367456.03066167456.03066167456.03066167456.03066169004.70252369004.70252369004.70252369004.70252369004.70252369305.663457...73939.44466774954.26497174954.26497174954.26497174954.26497174954.26497175044.61778277213.08526377213.08526377213.085263
480371.81961180371.81961180371.81961180371.81961180371.81961181827.57116181920.49147281920.49147281920.49147281920.491472...87870.05392087870.05392087870.05392087870.05392088005.58313890128.87421390128.87421390128.87421390128.87421390128.874213
..................................................................
7115132.959504132.959504132.959504132.959504132.959504135.828870136.012021136.012021136.012021136.545910...147.738932147.738932147.738932152.191189152.191189152.191189152.191189152.191189152.191189152.191189
7116158.417227158.417227158.417227158.417227158.417227161.469743161.469743161.469743161.469743162.062953...170.974136171.174163173.196654173.196654173.196654173.419267177.648912177.648912177.648912177.648912
7117122.066765122.066765125.119281125.119281125.119281125.119281125.712492125.712492125.712492125.712492...134.623675134.623675134.801476136.846192136.846192136.846192137.024283141.298450141.298450141.298450
7118134.467576134.467576134.467576134.467576137.520093137.520093137.520093137.520093138.113303138.113303...145.762241145.875843147.202288149.247004149.247004149.247004149.425094153.699262153.699262153.699262
7119135.996894136.027420136.027420136.027420136.027420136.027420136.027420136.573173136.620630136.620630...145.531813145.531813145.709614147.754331147.754331147.754331147.932421152.206588152.206588152.206588
+ +

7120 rows Ɨ 100 columns

+
+
+
+
+
+

4. Evaluation

+
+
with CodeTimer('Evaluate Models CRPS  ', verbose):
+    crps_results = {'Dataset': [dataset] * len(['Overall'] + list(tags.keys())),
+                    'Level': ['Overall'] + list(tags.keys()),}
+
+    for model in hrec.level_names.keys():
+        crps_results[model] = []
+        for level in crps_results['Level']:
+            if level=='Overall':
+                row_idxs = np.arange(len(S_df))
+            else:
+                row_idxs = S_df.index.get_indexer(tags[level])
+            col_idxs = model_columns.get_indexer(hrec.level_names[model])
+            _y = y_test[row_idxs,:]
+            _y_rec_seeds = y_rec[:,row_idxs,:,:][:,:,:,col_idxs]
+
+            level_model_crps = []
+            for seed_idx in range(y_rec.shape[0]):
+                _y_rec = _y_rec_seeds[seed_idx,:,:,:]
+                level_model_crps.append(scaled_crps(y=_y, y_hat=_y_rec,
+                                                    quantiles=QUANTILES))
+            level_model_crps = f'{np.mean(level_model_crps):.4f}Ā±{(1.96 * np.std(level_model_crps)):.4f}'
+            crps_results[model].append(level_model_crps)
+
+    crps_results = pd.DataFrame(crps_results)
+
+crps_results
+
+
Code block 'Evaluate Models CRPS  ' took:   1.13514 seconds
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
DatasetLevelAutoARIMA/BottomUpAutoARIMA/TopDown_method-average_proportionsAutoARIMA/TopDown_method-proportion_averagesAutoARIMA/MinTrace_method-olsAutoARIMA/MinTrace_method-wls_varAutoARIMA/MinTrace_method-mint_shrinkAutoARIMA/ERM_method-closed_lambda_reg-0.01
0TourismSmallOverall0.0895Ā±0.00120.1195Ā±0.00080.1197Ā±0.00080.0927Ā±0.00100.0890Ā±0.00100.0898Ā±0.00090.1116Ā±0.0015
1TourismSmallCountry0.0481Ā±0.00160.0479Ā±0.00110.0479Ā±0.00110.0504Ā±0.00100.0510Ā±0.00110.0512Ā±0.00110.0525Ā±0.0015
2TourismSmallCountry/Purpose0.0699Ā±0.00160.0928Ā±0.00090.0931Ā±0.00090.0804Ā±0.00120.0724Ā±0.00120.0741Ā±0.00120.0927Ā±0.0015
3TourismSmallCountry/Purpose/State0.1085Ā±0.00110.1575Ā±0.00090.1579Ā±0.00090.1082Ā±0.00110.1043Ā±0.00090.1049Ā±0.00080.1325Ā±0.0018
4TourismSmallCountry/Purpose/State/CityNonCity0.1316Ā±0.00120.1799Ā±0.00080.1800Ā±0.00080.1319Ā±0.00130.1282Ā±0.00110.1290Ā±0.00100.1685Ā±0.0029
+ +
+
+
+
+
with CodeTimer('Evaluate Models MSSE  ', verbose):
+    msse_results = {'Dataset': [dataset] * len(['Overall'] + list(tags.keys())),
+                    'Level': ['Overall'] + list(tags.keys()),}
+    for model in hrec.level_names.keys():
+        msse_results[model] = []
+        for level in msse_results['Level']:
+            if level=='Overall':
+                row_idxs = np.arange(len(S_df))
+            else:
+                row_idxs = S_df.index.get_indexer(tags[level])
+            col_idx = model_columns.get_loc(model)
+            _y = y_test[row_idxs,:]
+            _y_train = y_train[row_idxs,:]
+            _y_hat_seeds = y_rec[:,row_idxs,:,:][:,:,:,col_idx]
+
+            level_model_msse = []
+            for seed_idx in range(y_rec.shape[0]):
+                _y_hat = _y_hat_seeds[seed_idx,:,:]
+                level_model_msse.append(msse(y=_y, y_hat=_y_hat, y_train=_y_train))
+            #level_model_msse = f'{np.mean(level_model_msse):.4f}Ā±{(1.96 * np.std(level_model_msse)):.4f}'
+            level_model_msse = f'{np.mean(level_model_msse):.4f}'
+            msse_results[model].append(level_model_msse)
+
+    msse_results = pd.DataFrame(msse_results)
+
+msse_results
+
+
Code block 'Evaluate Models MSSE  ' took:   0.73303 seconds
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
DatasetLevelAutoARIMA/BottomUpAutoARIMA/TopDown_method-average_proportionsAutoARIMA/TopDown_method-proportion_averagesAutoARIMA/MinTrace_method-olsAutoARIMA/MinTrace_method-wls_varAutoARIMA/MinTrace_method-mint_shrinkAutoARIMA/ERM_method-closed_lambda_reg-0.01
0TourismSmallOverall0.25300.36280.36490.30390.27890.28220.3942
1TourismSmallCountry0.25640.31800.31800.35220.33810.33940.4117
2TourismSmallCountry/Purpose0.20180.31780.32030.25570.21220.21750.3346
3TourismSmallCountry/Purpose/State0.32310.50770.51140.29430.28580.28900.4534
4TourismSmallCountry/Purpose/State/CityNonCity0.34230.50470.50990.32380.30830.31150.4791
+ +
+
+
+
+
with CodeTimer('Evaluate Models EScore', verbose):
+    energy_results = {'Dataset': [dataset] * len(['Overall'] + list(tags.keys())),
+                        'Level': ['Overall'] + list(tags.keys()),}
+    for model in hrec.sample_names.keys():
+        energy_results[model] = []
+        for level in energy_results['Level']:
+            if level=='Overall':
+                row_idxs = np.arange(len(S_df))
+            else:
+                row_idxs = S_df.index.get_indexer(tags[level])
+            col_idxs = model_columns.get_indexer(hrec.sample_names[model])
+            _y = y_test[row_idxs,:]
+            _y_sample1 = y_rec[0,row_idxs,:,:][:,:,col_idxs[:len(col_idxs)//2]]
+            _y_sample2 = y_rec[0,row_idxs,:,:][:,:,col_idxs[len(col_idxs)//2:]]
+            level_model_energy = energy_score(y=_y, 
+                                              y_sample1=_y_sample1,
+                                              y_sample2=_y_sample2,
+                                              beta=2)
+            energy_results[model].append(level_model_energy)
+    energy_results = pd.DataFrame(energy_results)
+
+energy_results
+
+
Code block 'Evaluate Models EScore' took:   0.19443 seconds
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
DatasetLevelAutoARIMA/BottomUpAutoARIMA/TopDown_method-average_proportionsAutoARIMA/TopDown_method-proportion_averagesAutoARIMA/MinTrace_method-olsAutoARIMA/MinTrace_method-wls_varAutoARIMA/MinTrace_method-mint_shrinkAutoARIMA/ERM_method-closed_lambda_reg-0.01
0TourismSmallOverall6.874103e+077.917294e+077.962361e+076.930268e+076.914837e+076.955018e+078.235776e+07
1TourismSmallCountry3.292999e+072.757131e+072.757129e+073.081254e+073.392861e+073.353851e+073.350023e+07
2TourismSmallCountry/Purpose1.894485e+072.661024e+072.683828e+072.218952e+071.932895e+071.984161e+072.681792e+07
3TourismSmallCountry/Purpose/State9.393103e+061.408613e+071.419471e+079.016056e+068.778983e+068.928542e+061.211747e+07
4TourismSmallCountry/Purpose/State/CityNonCity7.473085e+061.090527e+071.101934e+077.284562e+067.111832e+067.241519e+069.922145e+06
+ +
+
+
+
+
+

References

+ + + +
+ +

If you find the code useful, please ā­ us on Github

+ +
+ + + + \ No newline at end of file diff --git a/examples/tourismsmall.html b/examples/tourismsmall.html new file mode 100644 index 00000000..5ffece01 --- /dev/null +++ b/examples/tourismsmall.html @@ -0,0 +1,1004 @@ + + + + + + + + + + +hierarchicalforecast - Reconciliation Quick Start + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ + +
+ +
+ + +
+ + + +
+ +
+
+

Reconciliation Quick Start

+
+ +
+
+ Minimal Example of Hierarchical Reconciliation +
+
+ + +
+ + + + +
+ + +
+ + +

Large collections of time series organized into structures at different aggregation levels often require their forecasts to follow their aggregation constraints, which poses the challenge of creating novel algorithms capable of coherent forecasts.

+

The HierarchicalForecast package provides a wide collection of Python implementations of hierarchical forecasting algorithms that follow classic hierarchical reconciliation.

+

In this notebook we will show how to use the StatsForecast library to produce base forecasts, and use HierarchicalForecast package to perform hierarchical reconciliation.

+

You can run these experiments using CPU or GPU with Google Colab.

+

Open In Colab

+
+

1. Libraries

+
+
!pip install hierarchicalforecast
+!pip install -U numba statsforecast datasetsforecast
+
+
+
+

2. Load Data

+

In this example we will use the TourismSmall dataset. The following cell gets the time series for the different levels in the hierarchy, the summing matrix S which recovers the full dataset from the bottom level hierarchy and the indices of each hierarchy denoted by tags.

+
+
import numpy as np
+import pandas as pd
+
+from datasetsforecast.hierarchical import HierarchicalData
+
+
+
Y_df, S_df, tags = HierarchicalData.load('./data', 'TourismSmall')
+Y_df['ds'] = pd.to_datetime(Y_df['ds'])
+
+
+
Y_df.head()
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
unique_iddsy
0total1998-03-3184503
1total1998-06-3065312
2total1998-09-3072753
3total1998-12-3170880
4total1999-03-3186893
+ +
+
+
+
+
S_df.iloc[:5, :5]
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
nsw-hol-citynsw-hol-noncityvic-hol-cityvic-hol-noncityqld-hol-city
total1.01.01.01.01.0
hol1.01.01.01.01.0
vfr0.00.00.00.00.0
bus0.00.00.00.00.0
oth0.00.00.00.00.0
+ +
+
+
+
+
tags
+
+
{'Country': array(['total'], dtype=object),
+ 'Country/Purpose': array(['hol', 'vfr', 'bus', 'oth'], dtype=object),
+ 'Country/Purpose/State': array(['nsw-hol', 'vic-hol', 'qld-hol', 'sa-hol', 'wa-hol', 'tas-hol',
+        'nt-hol', 'nsw-vfr', 'vic-vfr', 'qld-vfr', 'sa-vfr', 'wa-vfr',
+        'tas-vfr', 'nt-vfr', 'nsw-bus', 'vic-bus', 'qld-bus', 'sa-bus',
+        'wa-bus', 'tas-bus', 'nt-bus', 'nsw-oth', 'vic-oth', 'qld-oth',
+        'sa-oth', 'wa-oth', 'tas-oth', 'nt-oth'], dtype=object),
+ 'Country/Purpose/State/CityNonCity': array(['nsw-hol-city', 'nsw-hol-noncity', 'vic-hol-city',
+        'vic-hol-noncity', 'qld-hol-city', 'qld-hol-noncity',
+        'sa-hol-city', 'sa-hol-noncity', 'wa-hol-city', 'wa-hol-noncity',
+        'tas-hol-city', 'tas-hol-noncity', 'nt-hol-city', 'nt-hol-noncity',
+        'nsw-vfr-city', 'nsw-vfr-noncity', 'vic-vfr-city',
+        'vic-vfr-noncity', 'qld-vfr-city', 'qld-vfr-noncity',
+        'sa-vfr-city', 'sa-vfr-noncity', 'wa-vfr-city', 'wa-vfr-noncity',
+        'tas-vfr-city', 'tas-vfr-noncity', 'nt-vfr-city', 'nt-vfr-noncity',
+        'nsw-bus-city', 'nsw-bus-noncity', 'vic-bus-city',
+        'vic-bus-noncity', 'qld-bus-city', 'qld-bus-noncity',
+        'sa-bus-city', 'sa-bus-noncity', 'wa-bus-city', 'wa-bus-noncity',
+        'tas-bus-city', 'tas-bus-noncity', 'nt-bus-city', 'nt-bus-noncity',
+        'nsw-oth-city', 'nsw-oth-noncity', 'vic-oth-city',
+        'vic-oth-noncity', 'qld-oth-city', 'qld-oth-noncity',
+        'sa-oth-city', 'sa-oth-noncity', 'wa-oth-city', 'wa-oth-noncity',
+        'tas-oth-city', 'tas-oth-noncity', 'nt-oth-city', 'nt-oth-noncity'],
+       dtype=object)}
+
+
+

We split the dataframe in train/test splits.

+
+
Y_test_df = Y_df.groupby('unique_id').tail(12)
+Y_train_df = Y_df.drop(Y_test_df.index)
+
+
+
Y_test_df = Y_test_df.set_index('unique_id')
+Y_train_df = Y_train_df.set_index('unique_id')
+
+
+
+

3. Base forecasts

+

The following cell computes the base forecast for each time series using the auto_arima and naive models. Observe that Y_hat_df contains the forecasts but they are not coherent.

+
+
from statsforecast.core import StatsForecast
+from statsforecast.models import AutoARIMA, Naive
+
+
+
fcst = StatsForecast(
+    df=Y_train_df, 
+    models=[AutoARIMA(season_length=12), Naive()], 
+    freq='M', 
+    n_jobs=-1
+)
+Y_hat_df = fcst.forecast(h=12)
+
+
+
+

4. Hierarchical reconciliation

+

The following cell makes the previous forecasts coherent using the HierarchicalReconciliation class. The used methods to make the forecasts coherent are:

+
    +
  • BottomUp: The reconciliation of the method is a simple addition to the upper levels.
  • +
  • TopDown: The second method constrains the base-level predictions to the top-most aggregate-level serie and then distributes it to the disaggregate series through the use of proportions.
  • +
  • MiddleOut: Anchors the base predictions in a middle level.
  • +
+
+
from hierarchicalforecast.core import HierarchicalReconciliation
+from hierarchicalforecast.methods import BottomUp, TopDown, MiddleOut
+
+
+
reconcilers = [
+    BottomUp(),
+    TopDown(method='forecast_proportions'),
+    MiddleOut(middle_level='Country/Purpose/State', 
+              top_down_method='forecast_proportions')
+]
+hrec = HierarchicalReconciliation(reconcilers=reconcilers)
+Y_rec_df = hrec.reconcile(Y_hat_df=Y_hat_df, Y_df=Y_train_df, 
+                          S=S_df, tags=tags)
+
+
+
+

5. Evaluation

+

The HierarchicalForecast package includes the HierarchicalEvaluation class to evaluate the different hierarchies and also is capable of compute scaled metrics compared to a benchmark model.

+
+
from hierarchicalforecast.evaluation import HierarchicalEvaluation
+
+
+
def mse(y, y_hat):
+    return np.mean((y-y_hat)**2)
+
+evaluator = HierarchicalEvaluation(evaluators=[mse])
+evaluation = evaluator.evaluate(
+        Y_hat_df=Y_rec_df, Y_test_df=Y_test_df, 
+        tags=tags, benchmark='Naive'
+)
+evaluation.filter(like='ARIMA', axis=1).T
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
levelOverallCountryCountry/PurposeCountry/Purpose/StateCountry/Purpose/State/CityNonCity
metricmse-scaledmse-scaledmse-scaledmse-scaledmse-scaled
AutoARIMA0.9583241.0519110.9039330.9093640.845968
AutoARIMA/BottomUp0.9115240.9843910.865380.8653840.845968
AutoARIMA/TopDown_method-forecast_proportions0.9730361.0519110.887650.9738430.949449
AutoARIMA/MiddleOut_middle_level-Country/Purpose/State_top_down_method-forecast_proportions0.8961470.9507730.8301910.9093640.885306
+ +
+
+
+
+

References

+ + + +
+
+ +

If you find the code useful, please ā­ us on Github

+ +
+ + + + \ No newline at end of file diff --git a/favicon_png.png b/favicon_png.png new file mode 100644 index 00000000..7c7684de Binary files /dev/null and b/favicon_png.png differ diff --git a/index.html b/index.html new file mode 100644 index 00000000..407b1d6e --- /dev/null +++ b/index.html @@ -0,0 +1,819 @@ + + + + + + + + + +hierarchicalforecast - Hierarchical Forecast šŸ‘‘ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ + +
+ +
+ + +
+ + + +
+ +
+
+

Hierarchical Forecast šŸ‘‘

+
+ + + +
+ + + + +
+ + +
+ + +

Large collections of time series organized into structures at different aggregation levels often require their forecasts to follow their aggregation constraints, which poses the challenge of creating novel algorithms capable of coherent forecasts.

+

HierarchicalForecast offers a collection of reconciliation methods, including BottomUp, TopDown, MiddleOut, MinTrace and ERM. And Probabilistic coherent predictions including Normality, Bootstrap, and PERMBU.

+
+

šŸŽŠ Features

+
    +
  • Classic reconciliation methods: +
      +
    • BottomUp: Simple addition to the upper levels.
    • +
    • TopDown: Distributes the top levels forecasts trough the hierarchies.
    • +
  • +
  • Alternative reconciliation methods: +
      +
    • MiddleOut: It anchors the base predictions in a middle level. The levels above the base predictions use the bottom-up approach, while the levels below use a top-down.
    • +
    • MinTrace: Minimizes the total forecast variance of the space of coherent forecasts, with the Minimum Trace reconciliation.
    • +
    • ERM: Optimizes the reconciliation matrix minimizing an L1 regularized objective.
    • +
  • +
  • Probabilistic coherent methods: +
      +
    • Normality: Uses MinTrace variance-covariance closed form matrix under a normality assumption.
    • +
    • Bootstrap: Generates distribution of hierarchically reconciled predictions using Gamakumaraā€™s bootstrap approach.
    • +
    • PERMBU: Reconciles independent sample predictions by reinjecting multivariate dependence with estimated rank permutation copulas, and performing a Bottom-Up aggregation.
    • +
  • +
+

Missing something? Please open an issue here or write us in Slack

+
+
+

šŸ“– Why?

+

Short: We want to contribute to the ML field by providing reliable baselines and benchmarks for hierarchical forecasting task in industry and academia. Hereā€™s the complete paper.

+

Verbose: HierarchicalForecast integrates publicly available processed datasets, evaluation metrics, and a curated set of statistical baselines. In this library we provide usage examples and references to extensive experiments where we showcase the baselineā€™s use and evaluate the accuracy of their predictions. With this work, we hope to contribute to Machine Learning forecasting by bridging the gap to statistical and econometric modeling, as well as providing tools for the development of novel hierarchical forecasting algorithms rooted in a thorough comparison of these well-established models. We intend to continue maintaining and increasing the repository, promoting collaboration across the forecasting community.

+
+
+

šŸ’» Installation

+
+

PyPI

+

You can install the released version of HierarchicalForecast from the Python package index with:

+
pip install hierarchicalforecast
+

(Installing inside a python virtualenvironment or a conda environment is recommended.)

+
+
+

Conda

+

Also you can install the released version of HierarchicalForecast from conda with:

+
conda install -c conda-forge hierarchicalforecast
+

(Installing inside a python virtualenvironment or a conda environment is recommended.)

+
+
+

Dev Mode

+

If you want to make some modifications to the code and see the effects in real time (without reinstalling), follow the steps below:

+
git clone https://github.com/Nixtla/hierarchicalforecast.git
+cd hierarchicalforecast
+pip install -e .
+
+
+
+

šŸ§¬ How to use

+

The following example needs statsforecast and datasetsforecast as additional packages. If not installed, install it via your preferred method, e.g. pip install statsforecast datasetsforecast. The datasetsforecast library allows us to download hierarhical datasets and we will use statsforecast to compute base forecasts to be reconciled.

+

You can open this example in Colab Open In Colab

+
import numpy as np
+import pandas as pd
+
+#obtain hierarchical dataset
+from datasetsforecast.hierarchical import HierarchicalData
+
+# compute base forecast no coherent
+from statsforecast.core import StatsForecast
+from statsforecast.models import AutoARIMA, Naive
+
+#obtain hierarchical reconciliation methods and evaluation
+from hierarchicalforecast.core import HierarchicalReconciliation
+from hierarchicalforecast.evaluation import HierarchicalEvaluation
+from hierarchicalforecast.methods import BottomUp, TopDown, MiddleOut
+
+
+# Load TourismSmall dataset
+Y_df, S, tags = HierarchicalData.load('./data', 'TourismSmall')
+Y_df['ds'] = pd.to_datetime(Y_df['ds'])
+
+#split train/test sets
+Y_test_df  = Y_df.groupby('unique_id').tail(12)
+Y_train_df = Y_df.drop(Y_test_df.index)
+Y_test_df  = Y_test_df.set_index('unique_id')
+Y_train_df = Y_train_df.set_index('unique_id')
+
+# Compute base auto-ARIMA predictions
+fcst = StatsForecast(df=Y_train_df, 
+                     models=[AutoARIMA(season_length=12), Naive()], 
+                     freq='M', n_jobs=-1)
+Y_hat_df = fcst.forecast(h=12)
+
+# Reconcile the base predictions
+reconcilers = [
+    BottomUp(),
+    TopDown(method='forecast_proportions'),
+    MiddleOut(middle_level='Country/Purpose/State',
+              top_down_method='forecast_proportions')
+]
+hrec = HierarchicalReconciliation(reconcilers=reconcilers)
+Y_rec_df = hrec.reconcile(Y_hat_df=Y_hat_df, Y_df=Y_train_df, 
+                          S=S, tags=tags)
+
+

Evaluation

+
def mse(y, y_hat):
+    return np.mean((y-y_hat)**2)
+
+evaluator = HierarchicalEvaluation(evaluators=[mse])
+evaluator.evaluate(Y_h=Y_rec_df, Y_test=Y_df_test, 
+                   tags=tags, benchmark='Naive')
+
+
+
+

How to cite

+

Hereā€™s the complete paper.

+
@article{olivares2022hierarchicalforecast,
+    author    = {Kin G. Olivares and
+                 Federico Garza and 
+                 David Luo and 
+                 Cristian ChallĆŗ and
+                 Max Mergenthaler and
+                 Souhaib Ben Taieb and
+                 Shanika L. Wickramasuriya and
+                 Artur Dubrawski},
+    title     = {{HierarchicalForecast}: A Reference Framework for Hierarchical Forecasting in Python},
+    journal   = {Work in progress paper, submitted to Journal of Machine Learning Research.},
+    volume    = {abs/2207.03517},
+    year      = {2022},
+    url       = {https://arxiv.org/abs/2207.03517},
+    archivePrefix = {arXiv}
+}
+ + +
+ +

If you find the code useful, please ā­ us on Github

+ +
+ + + + \ No newline at end of file diff --git a/listings.json b/listings.json new file mode 100644 index 00000000..71087fd5 --- /dev/null +++ b/listings.json @@ -0,0 +1,19 @@ +[ + { + "listing": "/examples/index.html", + "items": [ + "/examples/australiandomestictourism-bootstraped-intervals.html", + "/examples/australianprisonpopulation.html", + "/examples/australiandomestictourism.html", + "/examples/hierarchicalforecast-gluonts.html", + "/examples/installation.html", + "/examples/introduction.html", + "/examples/mlframeworksexample.html", + "/examples/nonnegativereconciliation.html", + "/examples/australiandomestictourism-intervals.html", + "/examples/australiandomestictourism-permbu-intervals.html", + "/examples/tourismlarge-evaluation.html", + "/examples/tourismsmall.html" + ] + } +] \ No newline at end of file diff --git a/methods.html b/methods.html new file mode 100644 index 00000000..ff8a1801 --- /dev/null +++ b/methods.html @@ -0,0 +1,1221 @@ + + + + + + + + + +hierarchicalforecast - Reconciliation Methods + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ + +
+ +
+ + +
+ + + +
+ +
+
+

Reconciliation Methods

+
+ + + +
+ + + + +
+ + +
+ + +

Large collections of time series organized into structures at different aggregation levels often require their forecasts to follow their aggregation constraints, which poses the challenge of creating novel algorithms capable of coherent forecasts.

The HierarchicalForecast package provides the most comprehensive collection of Python implementations of hierarchical forecasting algorithms that follow classic hierarchical reconciliation. All the methods have a reconcile function capable of reconcile base forecasts using numpy arrays.

+

Most reconciliation methods can be described by the following convenient linear algebra notation:

+

\[\tilde{\mathbf{y}}_{[a,b],\tau} = \mathbf{S}_{[a,b][b]} \mathbf{P}_{[b][a,b]} \hat{\mathbf{y}}_{[a,b],\tau}\]

+

where \(a, b\) represent the aggregate and bottom levels, \(\mathbf{S}_{[a,b][b]}\) contains the hierarchical aggregation constraints, and \(\mathbf{P}_{[b][a,b]}\) varies across reconciliation methods. The reconciled predictions are \(\tilde{\mathbf{y}}_{[a,b],\tau}\), and the base predictions \(\hat{\mathbf{y}}_{[a,b],\tau}\).

+
+

1. Bottom-Up

+
+

source

+
+

BottomUpSparse

+
+
 BottomUpSparse ()
+
+

BottomUpSparse Reconciliation Class.

+

This is the implementation of a Bottom Up reconciliation using the sparse matrix approach. It works much more efficient on datasets with many time series. [makoren: At least I hope so, I only checked up until ~20k time series, and thereā€™s no real improvement, it would be great to check for smth like 1M time series, where the dense S matrix really stops fitting in memory]

+

See the parent class for more details.

+
+

source

+
+
+

BottomUp

+
+
 BottomUp ()
+
+

Bottom Up Reconciliation Class. The most basic hierarchical reconciliation is performed using an Bottom-Up strategy. It was proposed for the first time by Orcutt in 1968. The corresponding hierarchical ā€œprojectionā€ matrix is defined as: \[\mathbf{P}_{\text{BU}} = [\mathbf{0}_{\mathrm{[b],[a]}}\;|\;\mathbf{I}_{\mathrm{[b][b]}}]\]

+

Parameters:
None

+

References:
- Orcutt, G.H., Watts, H.W., & Edwards, J.B.(1968). ā€œData aggregation and information lossā€. The American Economic Review, 58 , 773{787).

+
+

source

+
+
+

BottomUp.fit

+
+
 BottomUp.fit (S:numpy.ndarray, y_hat:numpy.ndarray,
+               idx_bottom:numpy.ndarray,
+               y_insample:Optional[numpy.ndarray]=None,
+               y_hat_insample:Optional[numpy.ndarray]=None,
+               sigmah:Optional[numpy.ndarray]=None,
+               intervals_method:Optional[str]=None,
+               num_samples:Optional[int]=None, seed:Optional[int]=None,
+               tags:Dict[str,numpy.ndarray]=None)
+
+

Bottom Up Fit Method.

+

Parameters:
S: Summing matrix of size (base, bottom).
y_hat: Forecast values of size (base, horizon).
idx_bottom: Indices corresponding to the bottom level of S, size (bottom).
level: float list 0-100, confidence levels for prediction intervals.
intervals_method: Sampler for prediction intevals, one of normality, bootstrap, permbu.
**sampler_kwargs: Coherent sampler instantiation arguments.

+

Returns:
self: object, fitted reconciler.

+
+

source

+
+
+

BottomUp.predict

+
+
 BottomUp.predict (S:numpy.ndarray, y_hat:numpy.ndarray,
+                   level:Optional[List[int]]=None)
+
+

Predict using reconciler.

+

Predict using fitted mean and probabilistic reconcilers.

+

Parameters:
S: Summing matrix of size (base, bottom).
y_hat: Forecast values of size (base, horizon).
level: float list 0-100, confidence levels for prediction intervals.

+

Returns:
y_tilde: Reconciliated predictions.

+
+

source

+
+
+

BottomUp.fit_predict

+
+
 BottomUp.fit_predict (S:numpy.ndarray, y_hat:numpy.ndarray,
+                       idx_bottom:numpy.ndarray,
+                       y_insample:Optional[numpy.ndarray]=None,
+                       y_hat_insample:Optional[numpy.ndarray]=None,
+                       sigmah:Optional[numpy.ndarray]=None,
+                       level:Optional[List[int]]=None,
+                       intervals_method:Optional[str]=None,
+                       num_samples:Optional[int]=None,
+                       seed:Optional[int]=None,
+                       tags:Dict[str,numpy.ndarray]=None)
+
+

BottomUp Reconciliation Method.

+

Parameters:
S: Summing matrix of size (base, bottom).
y_hat: Forecast values of size (base, horizon).
idx_bottom: Indices corresponding to the bottom level of S, size (bottom).
level: float list 0-100, confidence levels for prediction intervals.
intervals_method: Sampler for prediction intevals, one of normality, bootstrap, permbu.
**sampler_kwargs: Coherent sampler instantiation arguments.

+

Returns:
y_tilde: Reconciliated y_hat using the Bottom Up approach.

+
+

source

+
+
+

BottomUp.sample

+
+
 BottomUp.sample (num_samples:int)
+
+

Sample probabilistic coherent distribution.

+

Generates n samples from a probabilistic coherent distribution. The method uses fitted mean and probabilistic reconcilers, defined by the intervals_method selected during the reconcilerā€™s instantiation. Currently available: normality, bootstrap, permbu.

+

Parameters:
num_samples: int, number of samples generated from coherent distribution.

+

Returns:
samples: Coherent samples of size (num_series, horizon, num_samples).

+
+
+
+

2. Top-Down

+
+

source

+
+

TopDown

+
+
 TopDown (method:str)
+
+

Top Down Reconciliation Class.

+

The Top Down hierarchical reconciliation method, distributes the total aggregate predictions and decomposes it down the hierarchy using proportions \(\mathbf{p}_{\mathrm{[b]}}\) that can be actual historical values or estimated.

+

\[\mathbf{P}=[\mathbf{p}_{\mathrm{[b]}}\;|\;\mathbf{0}_{\mathrm{[b][a,b\;-1]}}]\] Parameters:
method: One of forecast_proportions, average_proportions and proportion_averages.

+

References:
- CW. Gross (1990). ā€œDisaggregation methods to expedite product line forecastingā€. Journal of Forecasting, 9 , 233ā€“254. doi:10.1002/for.3980090304.
- G. Fliedner (1999). ā€œAn investigation of aggregate variable time series forecast strategies with specific subaggregate time series statistical correlationā€. Computers and Operations Research, 26 , 1133ā€“1149. doi:10.1016/S0305-0548(99)00017-9.

+
+

source

+
+
+

TopDown.fit

+
+
 TopDown.fit (S, y_hat, y_insample:Optional[numpy.ndarray]=None,
+              y_hat_insample:Optional[numpy.ndarray]=None,
+              sigmah:Optional[numpy.ndarray]=None,
+              intervals_method:Optional[str]=None,
+              num_samples:Optional[int]=None, seed:Optional[int]=None,
+              tags:Dict[str,numpy.ndarray]=None,
+              idx_bottom:Optional[numpy.ndarray]=None)
+
+

TopDown Fit Method.

+

Parameters:
S: Summing matrix of size (base, bottom).
y_hat: Forecast values of size (base, horizon).
tags: Each key is a level and each value its S indices.
y_insample: Insample values of size (base, insample_size). Optional for forecast_proportions method.
idx_bottom: Indices corresponding to the bottom level of S, size (bottom).
level: float list 0-100, confidence levels for prediction intervals.
intervals_method: Sampler for prediction intevals, one of normality, bootstrap, permbu.
**sampler_kwargs: Coherent sampler instantiation arguments.

+

Returns:
self: object, fitted reconciler.

+
+

source

+
+
+

TopDown.predict

+
+
 TopDown.predict (S:numpy.ndarray, y_hat:numpy.ndarray,
+                  level:Optional[List[int]]=None)
+
+

Predict using reconciler.

+

Predict using fitted mean and probabilistic reconcilers.

+

Parameters:
S: Summing matrix of size (base, bottom).
y_hat: Forecast values of size (base, horizon).
level: float list 0-100, confidence levels for prediction intervals.

+

Returns:
y_tilde: Reconciliated predictions.

+
+

source

+
+
+

TopDown.fit_predict

+
+
 TopDown.fit_predict (S:numpy.ndarray, y_hat:numpy.ndarray,
+                      tags:Dict[str,numpy.ndarray],
+                      idx_bottom:numpy.ndarray=None,
+                      y_insample:Optional[numpy.ndarray]=None,
+                      y_hat_insample:Optional[numpy.ndarray]=None,
+                      sigmah:Optional[numpy.ndarray]=None,
+                      level:Optional[List[int]]=None,
+                      intervals_method:Optional[str]=None,
+                      num_samples:Optional[int]=None,
+                      seed:Optional[int]=None)
+
+

Top Down Reconciliation Method.

+

Parameters:
S: Summing matrix of size (base, bottom).
y_hat: Forecast values of size (base, horizon).
tags: Each key is a level and each value its S indices.
y_insample: Insample values of size (base, insample_size). Optional for forecast_proportions method.
idx_bottom: Indices corresponding to the bottom level of S, size (bottom).
level: float list 0-100, confidence levels for prediction intervals.
intervals_method: Sampler for prediction intevals, one of normality, bootstrap, permbu.
**sampler_kwargs: Coherent sampler instantiation arguments.

+

Returns:
y_tilde: Reconciliated y_hat using the Top Down approach.

+
+

source

+
+
+

TopDown.sample

+
+
 TopDown.sample (num_samples:int)
+
+

Sample probabilistic coherent distribution.

+

Generates n samples from a probabilistic coherent distribution. The method uses fitted mean and probabilistic reconcilers, defined by the intervals_method selected during the reconcilerā€™s instantiation. Currently available: normality, bootstrap, permbu.

+

Parameters:
num_samples: int, number of samples generated from coherent distribution.

+

Returns:
samples: Coherent samples of size (num_series, horizon, num_samples).

+
+
+
+

3. Middle-Out

+
+

source

+
+

MiddleOut

+
+
 MiddleOut (middle_level:str, top_down_method:str)
+
+

Middle Out Reconciliation Class.

+

This method is only available for strictly hierarchical structures. It anchors the base predictions in a middle level. The levels above the base predictions use the Bottom-Up approach, while the levels below use a Top-Down.

+

Parameters:
middle_level: Middle level.
top_down_method: One of forecast_proportions, average_proportions and proportion_averages.

+

References:
- Hyndman, R.J., & Athanasopoulos, G. (2021). ā€œForecasting: principles and practice, 3rd edition: Chapter 11: Forecasting hierarchical and grouped series.ā€. OTexts: Melbourne, Australia. OTexts.com/fpp3 Accessed on July 2022.

+
+

source

+
+
+

MiddleOut.fit_predict

+
+
 MiddleOut.fit_predict (S:numpy.ndarray, y_hat:numpy.ndarray,
+                        tags:Dict[str,numpy.ndarray],
+                        y_insample:Optional[numpy.ndarray]=None,
+                        level:Optional[List[int]]=None,
+                        intervals_method:Optional[str]=None)
+
+

Middle Out Reconciliation Method.

+

Parameters:
S: Summing matrix of size (base, bottom).
y_hat: Forecast values of size (base, horizon).
tags: Each key is a level and each value its S indices.
y_insample: Insample values of size (base, insample_size). Only used for forecast_proportions

+

Returns:
y_tilde: Reconciliated y_hat using the Middle Out approach.

+
+
+
+

4. Min-Trace

+
+

source

+
+

MinTraceSparse

+
+
 MinTraceSparse (method:str, nonnegative:bool=False,
+                 mint_shr_ridge:Optional[float]=2e-08)
+
+

MinTraceSparse Reconciliation Class.

+

This is the implementation of a subset of MinTrace features using the sparse matrix approach. It works much more efficient on datasets with many time series.

+

See the parent class for more details.

+

Currently supported: * Methods using diagonal W matrix, i.e. ā€œolsā€, ā€œwls_structā€, ā€œwls_varā€, * The standard MinT version (non-negative is not supported).

+

Note: due to the numerical instability of the matrix inversion when creating the P matrix, the method is NOT guaranteed to give identical results to the non-sparse version.

+
+

source

+
+
+

MinTrace

+
+
 MinTrace (method:str, nonnegative:bool=False,
+           mint_shr_ridge:Optional[float]=2e-08)
+
+

MinTrace Reconciliation Class.

+

This reconciliation algorithm proposed by Wickramasuriya et al. depends on a generalized least squares estimator and an estimator of the covariance matrix of the coherency errors \(\mathbf{W}_{h}\). The Min Trace algorithm minimizes the squared errors for the coherent forecasts under an unbiasedness assumption; the solution has a closed form.

+

\[\mathbf{P}_{\text{MinT}}=\left(\mathbf{S}^{\intercal}\mathbf{W}_{h}\mathbf{S}\right)^{-1} +\mathbf{S}^{\intercal}\mathbf{W}^{-1}_{h}\]

+

Parameters:
method: str, one of ols, wls_struct, wls_var, mint_shrink, mint_cov.
nonnegative: bool, reconciled forecasts should be nonnegative?
mint_shr_ridge: float=2e-8, ridge numeric protection to MinTrace-shr covariance estimator.

+

References:
- Wickramasuriya, S. L., Athanasopoulos, G., & Hyndman, R. J. (2019). ā€œOptimal forecast reconciliation for hierarchical and grouped time series through trace minimizationā€. Journal of the American Statistical Association, 114 , 804ā€“819. doi:10.1080/01621459.2018.1448825.. - Wickramasuriya, S.L., Turlach, B.A. & Hyndman, R.J. (2020). ā€œOptimal non-negative forecast reconciliationā€. Stat Comput 30, 1167ā€“1182, https://doi.org/10.1007/s11222-020-09930-0.

+
+

source

+
+
+

MinTrace.fit

+
+
 MinTrace.fit (S, y_hat, y_insample:Optional[numpy.ndarray]=None,
+               y_hat_insample:Optional[numpy.ndarray]=None,
+               sigmah:Optional[numpy.ndarray]=None,
+               intervals_method:Optional[str]=None,
+               num_samples:Optional[int]=None, seed:Optional[int]=None,
+               tags:Dict[str,numpy.ndarray]=None,
+               idx_bottom:Optional[numpy.ndarray]=None)
+
+

MinTrace Fit Method.

+

Parameters:
S: Summing matrix of size (base, bottom).
y_hat: Forecast values of size (base, horizon).
tags: Each key is a level and each value its S indices.
y_insample: Insample values of size (base, insample_size). Optional for forecast_proportions method.
idx_bottom: Indices corresponding to the bottom level of S, size (bottom).
level: float list 0-100, confidence levels for prediction intervals.
intervals_method: Sampler for prediction intevals, one of normality, bootstrap, permbu.
**sampler_kwargs: Coherent sampler instantiation arguments.

+

Returns:
self: object, fitted reconciler.

+
+

source

+
+
+

MinTrace.predict

+
+
 MinTrace.predict (S:numpy.ndarray, y_hat:numpy.ndarray,
+                   level:Optional[List[int]]=None)
+
+

Predict using reconciler.

+

Predict using fitted mean and probabilistic reconcilers.

+

Parameters:
S: Summing matrix of size (base, bottom).
y_hat: Forecast values of size (base, horizon).
level: float list 0-100, confidence levels for prediction intervals.

+

Returns:
y_tilde: Reconciliated predictions.

+
+

source

+
+
+

MinTrace.fit_predict

+
+
 MinTrace.fit_predict (S:numpy.ndarray, y_hat:numpy.ndarray,
+                       idx_bottom:numpy.ndarray=None,
+                       y_insample:Optional[numpy.ndarray]=None,
+                       y_hat_insample:Optional[numpy.ndarray]=None,
+                       sigmah:Optional[numpy.ndarray]=None,
+                       level:Optional[List[int]]=None,
+                       intervals_method:Optional[str]=None,
+                       num_samples:Optional[int]=None,
+                       seed:Optional[int]=None,
+                       tags:Dict[str,numpy.ndarray]=None)
+
+

MinTrace Reconciliation Method.

+

Parameters:
S: Summing matrix of size (base, bottom).
y_hat: Forecast values of size (base, horizon).
y_insample: Insample values of size (base, insample_size). Only used by wls_var, mint_cov, mint_shrink
y_hat_insample: Insample fitted values of size (base, insample_size). Only used by wls_var, mint_cov, mint_shrink
idx_bottom: Indices corresponding to the bottom level of S, size (bottom).
level: float list 0-100, confidence levels for prediction intervals.
sampler: Sampler for prediction intevals, one of normality, bootstrap, permbu.

+

Returns:
y_tilde: Reconciliated y_hat using the MinTrace approach.

+
+

source

+
+
+

MinTrace.sample

+
+
 MinTrace.sample (num_samples:int)
+
+

Sample probabilistic coherent distribution.

+

Generates n samples from a probabilistic coherent distribution. The method uses fitted mean and probabilistic reconcilers, defined by the intervals_method selected during the reconcilerā€™s instantiation. Currently available: normality, bootstrap, permbu.

+

Parameters:
num_samples: int, number of samples generated from coherent distribution.

+

Returns:
samples: Coherent samples of size (num_series, horizon, num_samples).

+
+
+
+

5. Optimal Combination

+
+

source

+
+

OptimalCombination

+
+
 OptimalCombination (method:str, nonnegative:bool=False)
+
+

Optimal Combination Reconciliation Class.

+

This reconciliation algorithm was proposed by Hyndman et al. 2011, the method uses generalized least squares estimator using the coherency errors covariance matrix. Consider the covariance of the base forecast \(\textrm{Var}(\epsilon_{h}) = \Sigma_{h}\), the \(\mathbf{P}\) matrix of this method is defined by: \[ \mathbf{P} = \left(\mathbf{S}^{\intercal}\Sigma_{h}^{\dagger}\mathbf{S}\right)^{-1}\mathbf{S}^{\intercal}\Sigma^{\dagger}_{h}\] where \(\Sigma_{h}^{\dagger}\) denotes the variance pseudo-inverse. The method was later proven equivalent to MinTrace variants.

+

Parameters:
method: str, allowed optimal combination methods: ā€˜olsā€™, ā€˜wls_structā€™.
nonnegative: bool, reconciled forecasts should be nonnegative?

+

References:
- Rob J. Hyndman, Roman A. Ahmed, George Athanasopoulos, Han Lin Shang (2010). ā€œOptimal Combination Forecasts for Hierarchical Time Seriesā€..
- Shanika L. Wickramasuriya, George Athanasopoulos and Rob J. Hyndman (2010). ā€œOptimal Combination Forecasts for Hierarchical Time Seriesā€.. - Wickramasuriya, S.L., Turlach, B.A. & Hyndman, R.J. (2020). ā€œOptimal non-negative forecast reconciliationā€. Stat Comput 30, 1167ā€“1182, https://doi.org/10.1007/s11222-020-09930-0.

+
+

source

+
+
+

OptimalCombination.fit

+
+
 OptimalCombination.fit (S, y_hat,
+                         y_insample:Optional[numpy.ndarray]=None,
+                         y_hat_insample:Optional[numpy.ndarray]=None,
+                         sigmah:Optional[numpy.ndarray]=None,
+                         intervals_method:Optional[str]=None,
+                         num_samples:Optional[int]=None,
+                         seed:Optional[int]=None,
+                         tags:Dict[str,numpy.ndarray]=None,
+                         idx_bottom:Optional[numpy.ndarray]=None)
+
+

MinTrace Fit Method.

+

Parameters:
S: Summing matrix of size (base, bottom).
y_hat: Forecast values of size (base, horizon).
tags: Each key is a level and each value its S indices.
y_insample: Insample values of size (base, insample_size). Optional for forecast_proportions method.
idx_bottom: Indices corresponding to the bottom level of S, size (bottom).
level: float list 0-100, confidence levels for prediction intervals.
intervals_method: Sampler for prediction intevals, one of normality, bootstrap, permbu.
**sampler_kwargs: Coherent sampler instantiation arguments.

+

Returns:
self: object, fitted reconciler.

+
+

source

+
+
+

OptimalCombination.predict

+
+
 OptimalCombination.predict (S:numpy.ndarray, y_hat:numpy.ndarray,
+                             level:Optional[List[int]]=None)
+
+

Predict using reconciler.

+

Predict using fitted mean and probabilistic reconcilers.

+

Parameters:
S: Summing matrix of size (base, bottom).
y_hat: Forecast values of size (base, horizon).
level: float list 0-100, confidence levels for prediction intervals.

+

Returns:
y_tilde: Reconciliated predictions.

+
+

source

+
+
+

OptimalCombination.fit_predict

+
+
 OptimalCombination.fit_predict (S:numpy.ndarray, y_hat:numpy.ndarray,
+                                 idx_bottom:numpy.ndarray=None,
+                                 y_insample:Optional[numpy.ndarray]=None, 
+                                 y_hat_insample:Optional[numpy.ndarray]=No
+                                 ne, sigmah:Optional[numpy.ndarray]=None,
+                                 level:Optional[List[int]]=None,
+                                 intervals_method:Optional[str]=None,
+                                 num_samples:Optional[int]=None,
+                                 seed:Optional[int]=None,
+                                 tags:Dict[str,numpy.ndarray]=None)
+
+

MinTrace Reconciliation Method.

+

Parameters:
S: Summing matrix of size (base, bottom).
y_hat: Forecast values of size (base, horizon).
y_insample: Insample values of size (base, insample_size). Only used by wls_var, mint_cov, mint_shrink
y_hat_insample: Insample fitted values of size (base, insample_size). Only used by wls_var, mint_cov, mint_shrink
idx_bottom: Indices corresponding to the bottom level of S, size (bottom).
level: float list 0-100, confidence levels for prediction intervals.
sampler: Sampler for prediction intevals, one of normality, bootstrap, permbu.

+

Returns:
y_tilde: Reconciliated y_hat using the MinTrace approach.

+
+

source

+
+
+

OptimalCombination.sample

+
+
 OptimalCombination.sample (num_samples:int)
+
+

Sample probabilistic coherent distribution.

+

Generates n samples from a probabilistic coherent distribution. The method uses fitted mean and probabilistic reconcilers, defined by the intervals_method selected during the reconcilerā€™s instantiation. Currently available: normality, bootstrap, permbu.

+

Parameters:
num_samples: int, number of samples generated from coherent distribution.

+

Returns:
samples: Coherent samples of size (num_series, horizon, num_samples).

+
+
+
+

6. Emp. Risk Minimization

+
+

source

+
+

ERM

+
+
 ERM (method:str, lambda_reg:float=0.01)
+
+

Optimal Combination Reconciliation Class.

+

The Empirical Risk Minimization reconciliation strategy relaxes the unbiasedness assumptions from previous reconciliation methods like MinT and optimizes square errors between the reconciled predictions and the validation data to obtain an optimal reconciliation matrix P.

+

The exact solution for \(\mathbf{P}\) (method='closed') follows the expression: \[\mathbf{P}^{*} = \left(\mathbf{S}^{\intercal}\mathbf{S}\right)^{-1}\mathbf{Y}^{\intercal}\hat{\mathbf{Y}}\left(\hat{\mathbf{Y}}\hat{\mathbf{Y}}\right)^{-1}\]

+

The alternative Lasso regularized \(\mathbf{P}\) solution (method='reg_bu') is useful when the observations of validation data is limited or the exact solution has low numerical stability. \[\mathbf{P}^{*} = \text{argmin}_{\mathbf{P}} ||\mathbf{Y}-\mathbf{S} \mathbf{P} \hat{Y} ||^{2}_{2} + \lambda ||\mathbf{P}-\mathbf{P}_{\text{BU}}||_{1}\]

+

Parameters:
method: str, one of closed, reg and reg_bu.
lambda_reg: float, l1 regularizer for reg and reg_bu.

+

References:
- Ben Taieb, S., & Koo, B. (2019). Regularized regression for hierarchical forecasting without unbiasedness conditions. In Proceedings of the 25th ACM SIGKDD International Conference on Knowledge Discovery & Data Mining KDD ā€™19 (p. 1337{1347). New York, NY, USA: Association for Computing Machinery..

+
+

source

+
+
+

ERM.fit

+
+
 ERM.fit (S, y_hat, y_insample, y_hat_insample,
+          sigmah:Optional[numpy.ndarray]=None,
+          intervals_method:Optional[str]=None,
+          num_samples:Optional[int]=None, seed:Optional[int]=None,
+          tags:Dict[str,numpy.ndarray]=None,
+          idx_bottom:Optional[numpy.ndarray]=None)
+
+

ERM Fit Method.

+

Parameters:
S: Summing matrix of size (base, bottom).
y_hat: Forecast values of size (base, horizon).
y_insample: Train values of size (base, insample_size).
y_hat_insample: Insample train predictions of size (base, insample_size).
idx_bottom: Indices corresponding to the bottom level of S, size (bottom).
level: float list 0-100, confidence levels for prediction intervals.
intervals_method: Sampler for prediction intevals, one of normality, bootstrap, permbu.
**sampler_kwargs: Coherent sampler instantiation arguments.

+

Returns:
self: object, fitted reconciler.

+
+

source

+
+
+

ERM.predict

+
+
 ERM.predict (S:numpy.ndarray, y_hat:numpy.ndarray,
+              level:Optional[List[int]]=None)
+
+

Predict using reconciler.

+

Predict using fitted mean and probabilistic reconcilers.

+

Parameters:
S: Summing matrix of size (base, bottom).
y_hat: Forecast values of size (base, horizon).
level: float list 0-100, confidence levels for prediction intervals.

+

Returns:
y_tilde: Reconciliated predictions.

+
+

source

+
+
+

ERM.fit_predict

+
+
 ERM.fit_predict (S:numpy.ndarray, y_hat:numpy.ndarray,
+                  idx_bottom:numpy.ndarray=None,
+                  y_insample:Optional[numpy.ndarray]=None,
+                  y_hat_insample:Optional[numpy.ndarray]=None,
+                  sigmah:Optional[numpy.ndarray]=None,
+                  level:Optional[List[int]]=None,
+                  intervals_method:Optional[str]=None,
+                  num_samples:Optional[int]=None, seed:Optional[int]=None,
+                  tags:Dict[str,numpy.ndarray]=None)
+
+

ERM Reconciliation Method.

+

Parameters:
S: Summing matrix of size (base, bottom).
y_hat: Forecast values of size (base, horizon).
y_insample: Train values of size (base, insample_size).
y_hat_insample: Insample train predictions of size (base, insample_size).
idx_bottom: Indices corresponding to the bottom level of S, size (bottom).
level: float list 0-100, confidence levels for prediction intervals.
intervals_method: Sampler for prediction intevals, one of normality, bootstrap, permbu.

+

Returns:
y_tilde: Reconciliated y_hat using the ERM approach.

+
+

source

+
+
+

ERM.sample

+
+
 ERM.sample (num_samples:int)
+
+

Sample probabilistic coherent distribution.

+

Generates n samples from a probabilistic coherent distribution. The method uses fitted mean and probabilistic reconcilers, defined by the intervals_method selected during the reconcilerā€™s instantiation. Currently available: normality, bootstrap, permbu.

+

Parameters:
num_samples: int, number of samples generated from coherent distribution.

+

Returns:
samples: Coherent samples of size (num_series, horizon, num_samples).

+
+
reconciler_args = dict(S=S, 
+                       y_hat=y_hat_base,
+                       y_insample=y_base,
+                       y_hat_insample=y_hat_base_insample,
+                       sigmah=sigmah,
+                       level=[80, 90],
+                       intervals_method='normality',
+                       num_samples=200,
+                       seed=0,
+                       tags=tags,
+                       idx_bottom=idx_bottom
+                       )
+
+
+
+
+

References

+
+

General Reconciliation

+ +
+
+

Optimal Reconciliation

+ +
+
+

Hierarchical Probabilistic Coherent Predictions

+ + + +
+
+ +

If you find the code useful, please ā­ us on Github

+ +
+ + + + \ No newline at end of file diff --git a/probabilistic_methods.html b/probabilistic_methods.html new file mode 100644 index 00000000..1c2cc138 --- /dev/null +++ b/probabilistic_methods.html @@ -0,0 +1,771 @@ + + + + + + + + + +hierarchicalforecast - Probabilistic Methods + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ + +
+ +
+ + +
+ + + +
+ +
+
+

Probabilistic Methods

+
+ + + +
+ + + + +
+ + +
+ + +

Here we provide a collection of methods designed to provide hierarchically coherent probabilistic distributions, which means that they generate samples of multivariate time series with hierarchical linear constraints.

+

We designed these methods to extend the core.HierarchicalForecast capabilities class. Check their usage example here.

+
+

1. Normality

+
+

source

+
+

Normality

+
+
 Normality (S:numpy.ndarray, P:numpy.ndarray, y_hat:numpy.ndarray,
+            sigmah:numpy.ndarray, W:numpy.ndarray, seed:int=0)
+
+

Normality Probabilistic Reconciliation Class.

+

The Normality method leverages the Gaussian Distribution linearity, to generate hierarchically coherent prediction distributions. This class is meant to be used as the sampler input as other HierarchicalForecast reconciliation classes.

+

Given base forecasts under a normal distribution: \[\hat{y}_{h} \sim \mathrm{N}(\hat{\boldsymbol{\mu}}, \hat{\mathbf{W}}_{h})\]

+

The reconciled forecasts are also normally distributed: \[\tilde{y}_{h} \sim \mathrm{N}(\mathbf{S}\mathbf{P}\hat{\boldsymbol{\mu}}, +\mathbf{S}\mathbf{P}\hat{\mathbf{W}}_{h} \mathbf{P}^{\intercal} \mathbf{S}^{\intercal})\]

+

Parameters:
S: np.array, summing matrix of size (base, bottom).
P: np.array, reconciliation matrix of size (bottom, base).
y_hat: Point forecasts values of size (base, horizon).
W: np.array, hierarchical covariance matrix of size (base, base).
sigmah: np.array, forecast standard dev. of size (base, horizon).
num_samples: int, number of bootstraped samples generated.
seed: int, random seed for numpy generatorā€™s replicability.

+

References:
- Panagiotelis A., Gamakumara P. Athanasopoulos G., and Hyndman R. J. (2022). ā€œProbabilistic forecast reconciliation: Properties, evaluation and score optimisationā€. European Journal of Operational Research.

+
+

source

+
+
+

Normality.get_samples

+
+
 Normality.get_samples (num_samples:int=None)
+
+

Normality Coherent Samples.

+

Obtains coherent samples under the Normality assumptions.

+

Parameters:
num_samples: int, number of samples generated from coherent distribution.

+

Returns:
samples: Coherent samples of size (base, horizon, num_samples).

+
+
+
+

2. Bootstrap

+
+

source

+
+

Bootstrap

+
+
 Bootstrap (S:numpy.ndarray, P:numpy.ndarray, y_hat:numpy.ndarray,
+            y_insample:numpy.ndarray, y_hat_insample:numpy.ndarray,
+            num_samples:int=100, seed:int=0, W:numpy.ndarray=None)
+
+

Bootstrap Probabilistic Reconciliation Class.

+

This method goes beyond the normality assumption for the base forecasts, the technique simulates future sample paths and uses them to generate base sample paths that are latered reconciled. This clever idea and its simplicity allows to generate coherent bootstraped prediction intervals for any reconciliation strategy. This class is meant to be used as the sampler input as other HierarchicalForecast reconciliation classes.

+

Given a boostraped set of simulated sample paths: \[(\hat{\mathbf{y}}^{[1]}_{\tau}, \dots ,\hat{\mathbf{y}}^{[B]}_{\tau})\]

+

The reconciled sample paths allow for reconciled distributional forecasts: \[(\mathbf{S}\mathbf{P}\hat{\mathbf{y}}^{[1]}_{\tau}, \dots ,\mathbf{S}\mathbf{P}\hat{\mathbf{y}}^{[B]}_{\tau})\]

+

Parameters:
S: np.array, summing matrix of size (base, bottom).
P: np.array, reconciliation matrix of size (bottom, base).
y_hat: Point forecasts values of size (base, horizon).
y_insample: Insample values of size (base, insample_size).
y_hat_insample: Insample point forecasts of size (base, insample_size).
num_samples: int, number of bootstraped samples generated.
seed: int, random seed for numpy generatorā€™s replicability.

+

References:
- Puwasala Gamakumara Ph. D. dissertation. Monash University, Econometrics and Business Statistics (2020). ā€œProbabilistic Forecast Reconciliationā€ - Panagiotelis A., Gamakumara P. Athanasopoulos G., and Hyndman R. J. (2022). ā€œProbabilistic forecast reconciliation: Properties, evaluation and score optimisationā€. European Journal of Operational Research.

+
+

source

+
+
+

Bootstrap.get_samples

+
+
 Bootstrap.get_samples (num_samples:int=None)
+
+

Bootstrap Sample Reconciliation Method.

+

Applies Bootstrap sample reconciliation method as defined by Gamakumara 2020. Generating independent sample paths and reconciling them with Bootstrap.

+

Parameters:
num_samples: int, number of samples generated from coherent distribution.

+

Returns:
samples: Coherent samples of size (base, horizon, num_samples).

+
+
+
+

3. PERMBU

+
+

source

+
+

PERMBU

+
+
 PERMBU (S:numpy.ndarray, tags:Dict[str,numpy.ndarray],
+         y_hat:numpy.ndarray, y_insample:numpy.ndarray,
+         y_hat_insample:numpy.ndarray, sigmah:numpy.ndarray,
+         num_samples:int=None, seed:int=0, P:numpy.ndarray=None)
+
+

PERMBU Probabilistic Reconciliation Class.

+

The PERMBU method leverages empirical bottom-level marginal distributions with empirical copula functions (describing bottom-level dependencies) to generate the distribution of aggregate-level distributions using BottomUp reconciliation. The sample reordering technique in the PERMBU method reinjects multivariate dependencies into independent bottom-level samples.

+
Algorithm:
+1.   For all series compute conditional marginals distributions.
+2.   Compute residuals $\hat{\epsilon}_{i,t}$ and obtain rank permutations.
+2.   Obtain K-sample from the bottom-level series predictions.
+3.   Apply recursively through the hierarchical structure:<br>
+    3.1.   For a given aggregate series $i$ and its children series:<br>
+    3.2.   Obtain children's empirical joint using sample reordering copula.<br>
+    3.2.   From the children's joint obtain the aggregate series's samples.    
+

Parameters:
S: np.array, summing matrix of size (base, bottom).
tags: Each key is a level and each value its S indices.
y_insample: Insample values of size (base, insample_size).
y_hat_insample: Insample point forecasts of size (base, insample_size).
sigmah: np.array, forecast standard dev. of size (base, horizon).
num_samples: int, number of normal prediction samples generated.
seed: int, random seed for numpy generatorā€™s replicability.

+

References:
- Taieb, Souhaib Ben and Taylor, James W and Hyndman, Rob J. (2017). Coherent probabilistic forecasts for hierarchical time series. International conference on machine learning ICML.

+
+

source

+
+
+

PERMBU.get_samples

+
+
 PERMBU.get_samples (num_samples:int=None)
+
+

PERMBU Sample Reconciliation Method.

+

Applies PERMBU reconciliation method as defined by Taieb et. al 2017. Generating independent base prediction samples, restoring its multivariate dependence using estimated copula with reordering and applying the BottomUp aggregation to the new samples.

+

Parameters:
num_samples: int, number of samples generated from coherent distribution.

+

Returns:
samples: Coherent samples of size (base, horizon, num_samples).

+
+
+
+

References

+ + + +
+ +

If you find the code useful, please ā­ us on Github

+ +
+ + + + \ No newline at end of file diff --git a/robots.txt b/robots.txt new file mode 100644 index 00000000..ea76c916 --- /dev/null +++ b/robots.txt @@ -0,0 +1 @@ +Sitemap: https://Nixtla.github.io/hierarchicalforecast/sitemap.xml diff --git a/search.json b/search.json new file mode 100644 index 00000000..8adfe4ec --- /dev/null +++ b/search.json @@ -0,0 +1,541 @@ +[ + { + "objectID": "evaluation.html", + "href": "evaluation.html", + "title": "Hierarchical Evaluation", + "section": "", + "text": "To assist the evaluation of hierarchical forecasting systems, we make available accuracy metrics along with the HierarchicalEvaluation module that facilitates the measurement of predictionā€™s accuracy through the hierarchy levels.\nThe available metrics include point and probabilistic multivariate scoring rules that were used in previous hierarchical forecasting studies.\nIf you find the code useful, please ā­ us on Github" + }, + { + "objectID": "evaluation.html#relative-mean-squared-error", + "href": "evaluation.html#relative-mean-squared-error", + "title": "Hierarchical Evaluation", + "section": "Relative Mean Squared Error", + "text": "Relative Mean Squared Error\n\nsource\n\nrel_mse\n\n rel_mse (y, y_hat, y_train, mask=None)\n\nRelative Mean Squared Error\nComputes Relative mean squared error (RelMSE), as proposed by Hyndman & Koehler (2006) as an alternative to percentage errors, to avoid measure unstability.\n\\[ \\mathrm{RelMSE}(\\mathbf{y}, \\mathbf{\\hat{y}}, \\mathbf{\\hat{y}}^{naive1}) =\n\\frac{\\mathrm{MSE}(\\mathbf{y}, \\mathbf{\\hat{y}})}{\\mathrm{MSE}(\\mathbf{y}, \\mathbf{\\hat{y}}^{naive1})} \\]\nParameters: y: numpy array, Actual values of size (n_series, horizon). y_hat: numpy array, Predicted values (n_series, horizon). mask: numpy array, Specifies date stamps per serie to consider in loss.\nReturns: loss: float.\nReferences: - Hyndman, R. J and Koehler, A. B. (2006). ā€œAnother look at measures of forecast accuracyā€, International Journal of Forecasting, Volume 22, Issue 4. - Kin G. Olivares, O. Nganba Meetei, Ruijun Ma, Rohan Reddy, Mengfei Cao, Lee Dicker. ā€œProbabilistic Hierarchical Forecasting with Deep Poisson Mixtures. Submitted to the International Journal Forecasting, Working paper available at arxiv." + }, + { + "objectID": "evaluation.html#mean-squared-scaled-error", + "href": "evaluation.html#mean-squared-scaled-error", + "title": "Hierarchical Evaluation", + "section": "Mean Squared Scaled Error", + "text": "Mean Squared Scaled Error\n\nsource\n\nmsse\n\n msse (y, y_hat, y_train, mask=None)\n\nMean Squared Scaled Error\nComputes Mean squared scaled error (MSSE), as proposed by Hyndman & Koehler (2006) as an alternative to percentage errors, to avoid measure unstability.\n\\[ \\mathrm{MSSE}(\\mathbf{y}, \\mathbf{\\hat{y}}, \\mathbf{y}^{in-sample}) =\n\\frac{\\frac{1}{h} \\sum^{t+h}_{\\tau=t+1} (y_{\\tau} - \\hat{y}_{\\tau})^2}{\\frac{1}{t-1} \\sum^{t}_{\\tau=2} (y_{\\tau} - y_{\\tau-1})^2},\\]\nwhere \\(n\\) (\\(n=\\)n) is the size of the training data, and \\(h\\) is the forecasting horizon (\\(h=\\)horizon).\nParameters: y: numpy array, Actual values of size (n_series, horizon). y_hat: numpy array, Predicted values (n_series, horizon). y_train: numpy array, Predicted values (n_series, n). mask: numpy array, Specifies date stamps per serie to consider in loss.\nReturns: loss: float.\nReferences: - Hyndman, R. J and Koehler, A. B. (2006). ā€œAnother look at measures of forecast accuracyā€, International Journal of Forecasting, Volume 22, Issue 4." + }, + { + "objectID": "evaluation.html#scaled-crps", + "href": "evaluation.html#scaled-crps", + "title": "Hierarchical Evaluation", + "section": "Scaled CRPS", + "text": "Scaled CRPS\n\nsource\n\nscaled_crps\n\n scaled_crps (y, y_hat, quantiles)\n\nScaled Continues Ranked Probability Score\nCalculates a scaled variation of the CRPS, as proposed by Rangapuram (2021), to measure the accuracy of predicted quantiles y_hat compared to the observation y.\nThis metric averages percentual weighted absolute deviations as defined by the quantile losses.\n\\[ \\mathrm{sCRPS}(\\hat{F}_{\\tau}, \\mathbf{y}_{\\tau}) = \\frac{2}{N} \\sum_{i}\n\\int^{1}_{0}\n\\frac{\\mathrm{QL}(\\hat{F}_{i,\\tau}, y_{i,\\tau})_{q}}{\\sum_{i} | y_{i,\\tau} |} dq \\]\nwhere \\(\\hat{F}_{\\tau}\\) is the an estimated multivariate distribution, and \\(y_{i,\\tau}\\) are its realizations.\nParameters: y: numpy array, Actual values of size (n_series, horizon). y_hat: numpy array, Predicted quantiles of size (n_series, horizon, n_quantiles). quantiles: numpy array,(n_quantiles). Quantiles to estimate from the distribution of y.\nReturns: loss: float.\nReferences: - Gneiting, Tilmann. (2011). ā€œQuantiles as optimal point forecastsā€. International Journal of Forecasting. - Spyros Makridakis, Evangelos Spiliotis, Vassilios Assimakopoulos, Zhi Chen, Anil Gaba, Ilia Tsetlin, Robert L. Winkler. (2022). ā€œThe M5 uncertainty competition: Results, findings and conclusionsā€. International Journal of Forecasting. - Syama Sundar Rangapuram, Lucien D Werner, Konstantinos Benidis, Pedro Mercado, Jan Gasthaus, Tim Januschowski. (2021). ā€œEnd-to-End Learning of Coherent Probabilistic Forecasts for Hierarchical Time Seriesā€. Proceedings of the 38th International Conference on Machine Learning (ICML)." + }, + { + "objectID": "evaluation.html#energy-score", + "href": "evaluation.html#energy-score", + "title": "Hierarchical Evaluation", + "section": "Energy Score", + "text": "Energy Score\n\nsource\n\nenergy_score\n\n energy_score (y, y_sample1, y_sample2, beta=2)\n\nEnergy Score\nCalculates Gneitingā€™s Energy Score sample approximation for y and independent multivariate samples y_sample1 and y_sample2. The Energy Score generalizes the CRPS (beta=1) in the multivariate setting.\n\\[ \\mathrm{ES}(\\mathbf{y}_{\\tau}, \\mathbf{\\hat{y}}_{\\tau}, \\mathbf{\\hat{y}}_{\\tau}')\n= \\frac{1}{2} \\mathbb{E}_{\\hat{P}} \\left[ ||\\mathbf{\\hat{y}}_{\\tau} - \\mathbf{\\hat{y}}_{\\tau}'||^{\\beta} \\right]\n- \\mathbb{E}_{\\hat{P}} \\left[ ||\\mathbf{y}_{\\tau} - \\mathbf{\\hat{y}}_{\\tau}||^{\\beta} \\right]\n\\quad \\beta \\in (0,2]\\]\nwhere \\(\\mathbf{\\hat{y}}_{\\tau}, \\mathbf{\\hat{y}}_{\\tau}'\\) are independent samples drawn from \\(\\hat{P}\\).\nParameters: y: numpy array, Actual values of size (n_series, horizon). y_sample1: numpy array, predictive distribution sample of size (n_series, horizon, n_samples). y_sample2: numpy array, predictive distribution sample of size (n_series, horizon, n_samples). beta: float in (0,2], defines the energy scoreā€™s power for the euclidean metric.\nReturns: score: float.\nReferences: - Gneiting, Tilmann, and Adrian E. Raftery. (2007). ā€œStrictly proper scoring rules, prediction and estimationā€. Journal of the American Statistical Association. - Anastasios Panagiotelis, Puwasala Gamakumara, George Athanasopoulos, Rob J. Hyndman. (2022). ā€œProbabilistic forecast reconciliation: Properties, evaluation and score optimisationā€. European Journal of Operational Research.\n\nsource\n\n\nlog_score\n\n log_score (y, y_hat, cov, allow_singular=True)\n\nLog Score.\nOne of the simplest multivariate probability scoring rules, it evaluates the negative density at the value of the realisation.\n\\[ \\mathrm{LS}(\\mathbf{y}_{\\tau}, \\mathbf{P}(\\theta_{\\tau}))\n= - \\log(f(\\mathbf{y}_{\\tau}, \\theta_{\\tau}))\\]\nwhere \\(f\\) is the density, \\(\\mathbf{P}(\\theta_{\\tau})\\) is a parametric distribution and \\(f(\\mathbf{y}_{\\tau}, \\theta_{\\tau})\\) represents its density. For the moment we only support multivariate normal log score.\n\\[f(\\mathbf{y}_{\\tau}, \\theta_{\\tau}) =\n(2\\pi )^{-k/2}\\det({\\boldsymbol{\\Sigma }})^{-1/2}\n\\,\\exp \\left(\n-{\\frac {1}{2}}(\\mathbf{y}_{\\tau} -\\hat{\\mathbf{y}}_{\\tau})^{\\!{\\mathsf{T}}}\n{\\boldsymbol{\\Sigma }}^{-1}\n(\\mathbf{y}_{\\tau} -\\hat{\\mathbf{y}}_{\\tau})\n\\right)\\]\nParameters: y: numpy array, Actual values of size (n_series, horizon). y_hat: numpy array, Predicted values (n_series, horizon). cov: numpy matrix, Predicted values covariance (n_series, n_series, horizon). allow_singular: bool=True, if true allows singular covariance.\nReturns: score: float.\n\nx = np.linspace(0, 5, 10, endpoint=False)\ny = multivariate_normal.pdf(x, mean=2.5, cov=0.5)\ny" + }, + { + "objectID": "examples/tourismlarge-evaluation.html", + "href": "examples/tourismlarge-evaluation.html", + "title": "Probabilistic Forecast Evaluation", + "section": "", + "text": "This notebook offers a step to step guide to create a hierarchical forecasting pipeline.\nIn the pipeline we will use HierarchicalForecast and StatsForecast core class, to create base predictions, reconcile and evaluate them.\nWe will use the TourismL dataset that summarizes large Australian national visitor survey.\nOutline 1. Installing Packages 2. Prepare TourismL dataset - Read and aggregate - StatsForecastā€™s Base Predictions 3. Reconciliar 4. Evaluar\nIf you find the code useful, please ā­ us on Github" + }, + { + "objectID": "examples/tourismlarge-evaluation.html#installing-hierarchicalforecast", + "href": "examples/tourismlarge-evaluation.html#installing-hierarchicalforecast", + "title": "Probabilistic Forecast Evaluation", + "section": "1. Installing HierarchicalForecast", + "text": "1. Installing HierarchicalForecast\nWe assume you have StatsForecast and HierarchicalForecast already installed, if not check this guide for instructions on how to install HierarchicalForecast.\n\n# %%capture\n# !pip install hierarchicalforecast\n# !pip install -U numba statsforecast datasetsforecast\n\n\nimport os\nimport numpy as np\nimport pandas as pd\nimport matplotlib.pyplot as plt\n\nfrom statsforecast.core import StatsForecast\nfrom statsforecast.models import AutoARIMA, Naive\n\nfrom hierarchicalforecast.core import HierarchicalReconciliation\nfrom hierarchicalforecast.evaluation import HierarchicalEvaluation\nfrom hierarchicalforecast.methods import BottomUp, TopDown, MinTrace, ERM\n\nfrom hierarchicalforecast.utils import is_strictly_hierarchical\nfrom hierarchicalforecast.utils import HierarchicalPlot, CodeTimer\nfrom hierarchicalforecast.evaluation import scaled_crps, msse, energy_score\n\nfrom datasetsforecast.hierarchical import HierarchicalData, HierarchicalInfo\n\n/Users/cchallu/opt/anaconda3/envs/hierarchicalforecast/lib/python3.10/site-packages/statsforecast/core.py:21: TqdmWarning: IProgress not found. Please update jupyter and ipywidgets. See https://ipywidgets.readthedocs.io/en/stable/user_install.html\n from tqdm.autonotebook import tqdm" + }, + { + "objectID": "examples/tourismlarge-evaluation.html#preparing-tourisml-dataset", + "href": "examples/tourismlarge-evaluation.html#preparing-tourisml-dataset", + "title": "Probabilistic Forecast Evaluation", + "section": "2. Preparing TourismL Dataset", + "text": "2. Preparing TourismL Dataset\n\n2.1 Read Hierarchical Dataset\n\n# ['Labour', 'Traffic', 'TourismSmall', 'TourismLarge', 'Wiki2']\ndataset = 'TourismSmall' # 'TourismLarge'\nverbose = True\nintervals_method = 'bootstrap'\nLEVEL = np.arange(0, 100, 2)\nqs = [[50-lv/2, 50+lv/2] for lv in LEVEL]\nQUANTILES = np.sort(np.concatenate(qs)/100)\n\n\nwith CodeTimer('Read and Parse data ', verbose):\n print(f'{dataset}')\n if not os.path.exists('./data'):\n os.makedirs('./data')\n \n dataset_info = HierarchicalInfo[dataset]\n Y_df, S_df, tags = HierarchicalData.load(directory=f'./data/{dataset}', group=dataset)\n Y_df['ds'] = pd.to_datetime(Y_df['ds'])\n\n # Train/Test Splits\n horizon = dataset_info.horizon\n seasonality = dataset_info.seasonality\n Y_test_df = Y_df.groupby('unique_id').tail(horizon)\n Y_train_df = Y_df.drop(Y_test_df.index)\n Y_test_df = Y_test_df.set_index('unique_id')\n Y_train_df = Y_train_df.set_index('unique_id')\n\nTourismSmall\nCode block 'Read and Parse data ' took: 0.99873 seconds\n\n\n100%|ā–ˆā–ˆā–ˆā–ˆā–ˆā–ˆā–ˆā–ˆā–ˆā–ˆ| 1.30M/1.30M [00:00<00:00, 2.74MiB/s]\nINFO:datasetsforecast.utils:Successfully downloaded datasets.zip, 1297279, bytes.\nINFO:datasetsforecast.utils:Decompressing zip file...\nINFO:datasetsforecast.utils:Successfully decompressed data/TourismSmall/hierarchical/datasets.zip\n\n\n\ndataset_info.seasonality\n\n4\n\n\n\nhplot = HierarchicalPlot(S=S_df, tags=tags)\nhplot.plot_summing_matrix()\n\n\n\n\n\nY_train_df\n\n\n\n\n\n\n\n\nds\ny\n\n\nunique_id\n\n\n\n\n\n\ntotal\n1998-03-31\n84503\n\n\ntotal\n1998-06-30\n65312\n\n\ntotal\n1998-09-30\n72753\n\n\ntotal\n1998-12-31\n70880\n\n\ntotal\n1999-03-31\n86893\n\n\n...\n...\n...\n\n\nnt-oth-noncity\n2003-12-31\n132\n\n\nnt-oth-noncity\n2004-03-31\n12\n\n\nnt-oth-noncity\n2004-06-30\n40\n\n\nnt-oth-noncity\n2004-09-30\n186\n\n\nnt-oth-noncity\n2004-12-31\n144\n\n\n\n\n2492 rows Ɨ 2 columns\n\n\n\n\n\n2.2 StatsForecastā€™s Base Predictions\nThis cell computes the base predictions Y_hat_df for all the series in Y_df using StatsForecastā€™s AutoARIMA. Additionally we obtain insample predictions Y_fitted_df for the methods that require them.\n\nwith CodeTimer('Fit/Predict Model ', verbose):\n # Read to avoid unnecesary AutoARIMA computation\n yhat_file = f'./data/{dataset}/Y_hat.csv'\n yfitted_file = f'./data/{dataset}/Y_fitted.csv'\n\n if os.path.exists(yhat_file):\n Y_hat_df = pd.read_csv(yhat_file)\n Y_fitted_df = pd.read_csv(yfitted_file)\n\n Y_hat_df = Y_hat_df.set_index('unique_id')\n Y_fitted_df = Y_fitted_df.set_index('unique_id')\n\n else:\n fcst = StatsForecast(\n df=Y_train_df, \n models=[AutoARIMA(season_length=seasonality)],\n fallback_model=[Naive()],\n freq='M', \n n_jobs=-1\n )\n Y_hat_df = fcst.forecast(h=horizon, fitted=True, level=LEVEL)\n Y_fitted_df = fcst.forecast_fitted_values()\n Y_hat_df.to_csv(yhat_file)\n Y_fitted_df.to_csv(yfitted_file)\n\n\nY_hat_df\n\n\n\n\n\n\n\n\nds\nAutoARIMA\nAutoARIMA-lo-98\nAutoARIMA-lo-96\nAutoARIMA-lo-94\nAutoARIMA-lo-92\nAutoARIMA-lo-90\nAutoARIMA-lo-88\nAutoARIMA-lo-86\nAutoARIMA-lo-84\n...\nAutoARIMA-hi-80\nAutoARIMA-hi-82\nAutoARIMA-hi-84\nAutoARIMA-hi-86\nAutoARIMA-hi-88\nAutoARIMA-hi-90\nAutoARIMA-hi-92\nAutoARIMA-hi-94\nAutoARIMA-hi-96\nAutoARIMA-hi-98\n\n\nunique_id\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\nbus\n2005-01-31\n9673.424805\n7436.356445\n7698.493652\n7864.811523\n7989.925781\n8091.696289\n8178.319336\n8254.270508\n8322.276367\n...\n10905.793945\n10962.725586\n11024.573242\n11092.579102\n11168.530273\n11255.153320\n11356.923828\n11482.038086\n11648.356445\n11910.493164\n\n\nbus\n2005-02-28\n10393.900391\n8156.831543\n8418.968750\n8585.287109\n8710.401367\n8812.171875\n8898.794922\n8974.746094\n9042.751953\n...\n11626.269531\n11683.200195\n11745.048828\n11813.054688\n11889.005859\n11975.628906\n12077.399414\n12202.513672\n12368.832031\n12630.968750\n\n\nbus\n2005-03-31\n12028.134766\n9791.066406\n10053.204102\n10219.521484\n10344.635742\n10446.406250\n10533.029297\n10608.981445\n10676.986328\n...\n13260.503906\n13317.435547\n13379.283203\n13447.289062\n13523.240234\n13609.863281\n13711.633789\n13836.748047\n14003.066406\n14265.203125\n\n\nbus\n2005-04-30\n10995.679688\n8758.610352\n9020.748047\n9187.065430\n9312.179688\n9413.951172\n9500.574219\n9576.525391\n9644.531250\n...\n12228.047852\n12284.979492\n12346.828125\n12414.833008\n12490.785156\n12577.407227\n12679.178711\n12804.292969\n12970.610352\n13232.748047\n\n\nbus\n2005-05-31\n9673.424805\n7262.085449\n7544.643555\n7723.917480\n7858.778320\n7968.477539\n8061.848633\n8143.716797\n8217.019531\n...\n11001.796875\n11063.164062\n11129.830078\n11203.132812\n11285.000977\n11378.372070\n11488.071289\n11622.932617\n11802.206055\n12084.764648\n\n\n...\n...\n...\n...\n...\n...\n...\n...\n...\n...\n...\n...\n...\n...\n...\n...\n...\n...\n...\n...\n...\n...\n\n\nwa-vfr-noncity\n2005-04-30\n904.125549\n463.371521\n515.018616\n547.787048\n572.437439\n592.488647\n609.555359\n624.519531\n637.918213\n...\n1146.930542\n1158.147339\n1170.332886\n1183.731567\n1198.695679\n1215.762451\n1235.813721\n1260.464111\n1293.232544\n1344.879517\n\n\nwa-vfr-noncity\n2005-05-31\n904.125549\n457.607361\n509.929901\n543.126831\n568.099670\n588.413086\n605.703003\n620.862854\n634.436707\n...\n1150.105957\n1161.469482\n1173.814331\n1187.388184\n1202.548096\n1219.838013\n1240.151489\n1265.124268\n1298.321167\n1350.643677\n\n\nwa-vfr-noncity\n2005-06-30\n904.125549\n451.916687\n504.906036\n538.526062\n563.817139\n584.389465\n601.899719\n617.252808\n630.999634\n...\n1153.240967\n1164.749268\n1177.251465\n1190.998291\n1206.351440\n1223.861694\n1244.433960\n1269.724976\n1303.345093\n1356.334473\n\n\nwa-vfr-noncity\n2005-07-31\n904.125549\n446.296722\n499.944611\n533.982483\n559.587830\n580.415833\n598.143738\n613.687622\n627.605286\n...\n1156.336914\n1167.988159\n1180.645752\n1194.563477\n1210.107422\n1227.835327\n1248.663208\n1274.268677\n1308.306519\n1361.954346\n\n\nwa-vfr-noncity\n2005-08-31\n904.125549\n440.744904\n495.043365\n529.493958\n555.409851\n576.490417\n594.433289\n610.165649\n624.252136\n...\n1159.395264\n1171.187866\n1183.999023\n1198.085449\n1213.817871\n1231.760742\n1252.841309\n1278.757080\n1313.207764\n1367.506226\n\n\n\n\n712 rows Ɨ 102 columns\n\n\n\n\nY_fitted_df\n\n\n\n\n\n\n\n\nds\ny\nAutoARIMA\nAutoARIMA-lo-98\nAutoARIMA-lo-96\nAutoARIMA-lo-94\nAutoARIMA-lo-92\nAutoARIMA-lo-90\nAutoARIMA-lo-88\nAutoARIMA-lo-86\n...\nAutoARIMA-hi-80\nAutoARIMA-hi-82\nAutoARIMA-hi-84\nAutoARIMA-hi-86\nAutoARIMA-hi-88\nAutoARIMA-hi-90\nAutoARIMA-hi-92\nAutoARIMA-hi-94\nAutoARIMA-hi-96\nAutoARIMA-hi-98\n\n\nunique_id\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\nbus\n1998-03-31\n9815.0\n9805.184570\n7568.648926\n7830.724121\n7997.001953\n8122.086426\n8223.833008\n8310.435547\n8386.369141\n...\n11037.260742\n11094.178711\n11156.011719\n11224.000977\n11299.934570\n11386.537109\n11488.283203\n11613.368164\n11779.646484\n12041.720703\n\n\nbus\n1998-06-30\n11823.0\n11811.176758\n9574.640625\n9836.715820\n10002.994141\n10128.078125\n10229.825195\n10316.427734\n10392.361328\n...\n13043.252930\n13100.169922\n13162.003906\n13229.993164\n13305.926758\n13392.528320\n13494.275391\n13619.360352\n13785.637695\n14047.712891\n\n\nbus\n1998-09-30\n13565.0\n13551.434570\n11314.899414\n11576.973633\n11743.251953\n11868.336914\n11970.083008\n12056.685547\n12132.619141\n...\n14783.510742\n14840.428711\n14902.261719\n14970.250977\n15046.184570\n15132.787109\n15234.533203\n15359.618164\n15525.896484\n15787.970703\n\n\nbus\n1998-12-31\n11478.0\n11466.522461\n9229.986328\n9492.060547\n9658.338867\n9783.423828\n9885.169922\n9971.772461\n10047.706055\n...\n12698.597656\n12755.515625\n12817.348633\n12885.337891\n12961.271484\n13047.874023\n13149.620117\n13274.705078\n13440.983398\n13703.057617\n\n\nbus\n1999-03-31\n10027.0\n9845.011719\n7608.475586\n7870.550781\n8036.828613\n8161.913086\n8263.660156\n8350.262695\n8426.195312\n...\n11077.086914\n11134.004883\n11195.838867\n11263.828125\n11339.760742\n11426.363281\n11528.110352\n11653.194336\n11819.472656\n12081.547852\n\n\n...\n...\n...\n...\n...\n...\n...\n...\n...\n...\n...\n...\n...\n...\n...\n...\n...\n...\n...\n...\n...\n...\n\n\nwa-vfr-noncity\n2003-12-31\n1177.0\n927.351196\n504.362732\n553.928040\n585.375671\n609.032471\n628.275513\n644.654297\n659.015320\n...\n1160.369507\n1171.134155\n1182.828491\n1195.687012\n1210.048096\n1226.426880\n1245.669922\n1269.326660\n1300.774292\n1350.339600\n\n\nwa-vfr-noncity\n2004-03-31\n956.0\n969.565552\n546.577087\n596.142456\n627.590027\n651.246887\n670.489868\n686.868652\n701.229675\n...\n1202.583862\n1213.348511\n1225.042847\n1237.901489\n1252.262451\n1268.641235\n1287.884277\n1311.541016\n1342.988647\n1392.554077\n\n\nwa-vfr-noncity\n2004-06-30\n772.0\n967.268921\n544.280457\n593.845764\n625.293396\n648.950195\n668.193237\n684.572021\n698.933044\n...\n1200.287109\n1211.051880\n1222.746216\n1235.604736\n1249.965820\n1266.344604\n1285.587646\n1309.244385\n1340.692017\n1390.257324\n\n\nwa-vfr-noncity\n2004-09-30\n885.0\n934.251831\n511.263336\n560.828674\n592.276306\n615.933105\n635.176086\n651.554932\n665.915955\n...\n1167.270020\n1178.034790\n1189.729126\n1202.587646\n1216.948730\n1233.327515\n1252.570557\n1276.227295\n1307.674927\n1357.240234\n\n\nwa-vfr-noncity\n2004-12-31\n797.0\n925.923462\n502.934998\n552.500305\n583.947937\n607.604736\n626.847778\n643.226562\n657.587585\n...\n1158.941772\n1169.706421\n1181.400757\n1194.259277\n1208.620361\n1224.999146\n1244.242188\n1267.898926\n1299.346558\n1348.911865\n\n\n\n\n2492 rows Ɨ 103 columns" + }, + { + "objectID": "examples/tourismlarge-evaluation.html#reconciliate-predictions", + "href": "examples/tourismlarge-evaluation.html#reconciliate-predictions", + "title": "Probabilistic Forecast Evaluation", + "section": "3. Reconciliate Predictions", + "text": "3. Reconciliate Predictions\n\nwith CodeTimer('Reconcile Predictions ', verbose):\n if is_strictly_hierarchical(S=S_df.values.astype(np.float32), \n tags={key: S_df.index.get_indexer(val) for key, val in tags.items()}):\n reconcilers = [\n BottomUp(),\n TopDown(method='average_proportions'),\n TopDown(method='proportion_averages'),\n MinTrace(method='ols'),\n MinTrace(method='wls_var'),\n MinTrace(method='mint_shrink'),\n #ERM(method='reg_bu', lambda_reg=100) # Extremely inneficient\n ERM(method='closed')\n ]\n else:\n reconcilers = [\n BottomUp(),\n MinTrace(method='ols'),\n MinTrace(method='wls_var'),\n MinTrace(method='mint_shrink'),\n #ERM(method='reg_bu', lambda_reg=100) # Extremely inneficient\n ERM(method='closed')\n ]\n \n hrec = HierarchicalReconciliation(reconcilers=reconcilers)\n Y_rec_df = hrec.bootstrap_reconcile(Y_hat_df=Y_hat_df,\n Y_df=Y_fitted_df,\n S_df=S_df, tags=tags,\n level=LEVEL,\n intervals_method=intervals_method,\n num_samples=10, num_seeds=10)\n\n # Matching Y_test/Y_rec/S index ordering\n Y_test_df = Y_test_df.reset_index()\n Y_test_df.unique_id = Y_test_df.unique_id.astype('category')\n Y_test_df.unique_id = Y_test_df.unique_id.cat.set_categories(S_df.index)\n Y_test_df = Y_test_df.sort_values(by=['unique_id', 'ds'])\n\n Y_rec_df = Y_rec_df.reset_index()\n Y_rec_df.unique_id = Y_rec_df.unique_id.astype('category')\n Y_rec_df.unique_id = Y_rec_df.unique_id.cat.set_categories(S_df.index)\n Y_rec_df = Y_rec_df.sort_values(by=['seed', 'unique_id', 'ds'])\n\n # Parsing model level columns\n flat_cols = list(hrec.level_names.keys())\n for model in hrec.level_names:\n flat_cols += hrec.level_names[model]\n for model in hrec.sample_names:\n flat_cols += hrec.sample_names[model]\n y_rec = Y_rec_df[flat_cols]\n model_columns = y_rec.columns\n\n n_series = len(S_df)\n n_seeds = len(Y_rec_df.seed.unique())\n y_rec = y_rec.values.reshape(n_seeds, n_series, horizon, len(model_columns))\n y_test = Y_test_df['y'].values.reshape(n_series, horizon)\n y_train = Y_train_df['y'].values.reshape(n_series, -1)\n\nCode block 'Reconcile Predictions ' took: 11.73492 seconds\n\n\n\n# Qualitative evaluation, of parsed quantiles\nrow_idx = 0\nseed_idx = 0\ncol_idxs = model_columns.get_indexer(hrec.level_names['AutoARIMA/BottomUp'])\nfor i, col in enumerate(col_idxs):\n plt.plot(y_rec[seed_idx, row_idx,:,col], color='orange', alpha=i/100)\nfor i, col in enumerate(col_idxs):\n plt.plot(y_rec[seed_idx+1, row_idx,:,col], color='green', alpha=i/100)\nplt.plot(y_test[row_idx,:], label='True')\nplt.title(f'{S_df.index[row_idx]} Visits \\n' + \\\n f'AutoARIMA/BottomUp-{intervals_method}')\n\nplt.legend()\nplt.grid()\nplt.show()\nplt.close()\n\n\n\n\n\n#Y_rec_df\ntd_levels = hrec.level_names['AutoARIMA/TopDown_method-average_proportions']\nY_rec_df[td_levels]\n\n\n\n\n\n\n\n\nAutoARIMA/TopDown_method-average_proportions-lo-98\nAutoARIMA/TopDown_method-average_proportions-lo-96\nAutoARIMA/TopDown_method-average_proportions-lo-94\nAutoARIMA/TopDown_method-average_proportions-lo-92\nAutoARIMA/TopDown_method-average_proportions-lo-90\nAutoARIMA/TopDown_method-average_proportions-lo-88\nAutoARIMA/TopDown_method-average_proportions-lo-86\nAutoARIMA/TopDown_method-average_proportions-lo-84\nAutoARIMA/TopDown_method-average_proportions-lo-82\nAutoARIMA/TopDown_method-average_proportions-lo-80\n...\nAutoARIMA/TopDown_method-average_proportions-hi-80\nAutoARIMA/TopDown_method-average_proportions-hi-82\nAutoARIMA/TopDown_method-average_proportions-hi-84\nAutoARIMA/TopDown_method-average_proportions-hi-86\nAutoARIMA/TopDown_method-average_proportions-hi-88\nAutoARIMA/TopDown_method-average_proportions-hi-90\nAutoARIMA/TopDown_method-average_proportions-hi-92\nAutoARIMA/TopDown_method-average_proportions-hi-94\nAutoARIMA/TopDown_method-average_proportions-hi-96\nAutoARIMA/TopDown_method-average_proportions-hi-98\n\n\n\n\n0\n80750.389920\n80750.389920\n80750.389920\n82299.061781\n82299.061781\n82299.061781\n82600.022716\n82600.022716\n82600.022716\n82763.007090\n...\n88248.624229\n88248.624229\n88248.624229\n88248.624229\n88384.153447\n90507.444522\n90507.444522\n90507.444522\n90507.444522\n90507.444522\n\n\n1\n61825.843210\n61825.843210\n61825.843210\n61825.843210\n61825.843210\n61825.843210\n63374.515072\n63374.515072\n63374.515072\n63374.515072\n...\n68196.499405\n68196.499405\n68286.705654\n69324.077520\n69324.077520\n69437.018534\n71582.897812\n71582.897812\n71582.897812\n71582.897812\n\n\n2\n68249.624404\n68249.624404\n68249.624404\n68249.624404\n69798.296266\n69798.296266\n69798.296266\n69798.296266\n69798.296266\n69798.296266\n...\n74620.280598\n74620.280598\n74620.280598\n74699.211067\n75747.858714\n75747.858714\n75747.858714\n75815.623322\n78006.679006\n78006.679006\n\n\n3\n67456.030661\n67456.030661\n67456.030661\n67456.030661\n69004.702523\n69004.702523\n69004.702523\n69004.702523\n69004.702523\n69305.663457\n...\n73939.444667\n74954.264971\n74954.264971\n74954.264971\n74954.264971\n74954.264971\n75044.617782\n77213.085263\n77213.085263\n77213.085263\n\n\n4\n80371.819611\n80371.819611\n80371.819611\n80371.819611\n80371.819611\n81827.571161\n81920.491472\n81920.491472\n81920.491472\n81920.491472\n...\n87870.053920\n87870.053920\n87870.053920\n87870.053920\n88005.583138\n90128.874213\n90128.874213\n90128.874213\n90128.874213\n90128.874213\n\n\n...\n...\n...\n...\n...\n...\n...\n...\n...\n...\n...\n...\n...\n...\n...\n...\n...\n...\n...\n...\n...\n...\n\n\n7115\n132.959504\n132.959504\n132.959504\n132.959504\n132.959504\n135.828870\n136.012021\n136.012021\n136.012021\n136.545910\n...\n147.738932\n147.738932\n147.738932\n152.191189\n152.191189\n152.191189\n152.191189\n152.191189\n152.191189\n152.191189\n\n\n7116\n158.417227\n158.417227\n158.417227\n158.417227\n158.417227\n161.469743\n161.469743\n161.469743\n161.469743\n162.062953\n...\n170.974136\n171.174163\n173.196654\n173.196654\n173.196654\n173.419267\n177.648912\n177.648912\n177.648912\n177.648912\n\n\n7117\n122.066765\n122.066765\n125.119281\n125.119281\n125.119281\n125.119281\n125.712492\n125.712492\n125.712492\n125.712492\n...\n134.623675\n134.623675\n134.801476\n136.846192\n136.846192\n136.846192\n137.024283\n141.298450\n141.298450\n141.298450\n\n\n7118\n134.467576\n134.467576\n134.467576\n134.467576\n137.520093\n137.520093\n137.520093\n137.520093\n138.113303\n138.113303\n...\n145.762241\n145.875843\n147.202288\n149.247004\n149.247004\n149.247004\n149.425094\n153.699262\n153.699262\n153.699262\n\n\n7119\n135.996894\n136.027420\n136.027420\n136.027420\n136.027420\n136.027420\n136.027420\n136.573173\n136.620630\n136.620630\n...\n145.531813\n145.531813\n145.709614\n147.754331\n147.754331\n147.754331\n147.932421\n152.206588\n152.206588\n152.206588\n\n\n\n\n7120 rows Ɨ 100 columns" + }, + { + "objectID": "examples/tourismlarge-evaluation.html#evaluation", + "href": "examples/tourismlarge-evaluation.html#evaluation", + "title": "Probabilistic Forecast Evaluation", + "section": "4. Evaluation", + "text": "4. Evaluation\n\nwith CodeTimer('Evaluate Models CRPS ', verbose):\n crps_results = {'Dataset': [dataset] * len(['Overall'] + list(tags.keys())),\n 'Level': ['Overall'] + list(tags.keys()),}\n\n for model in hrec.level_names.keys():\n crps_results[model] = []\n for level in crps_results['Level']:\n if level=='Overall':\n row_idxs = np.arange(len(S_df))\n else:\n row_idxs = S_df.index.get_indexer(tags[level])\n col_idxs = model_columns.get_indexer(hrec.level_names[model])\n _y = y_test[row_idxs,:]\n _y_rec_seeds = y_rec[:,row_idxs,:,:][:,:,:,col_idxs]\n\n level_model_crps = []\n for seed_idx in range(y_rec.shape[0]):\n _y_rec = _y_rec_seeds[seed_idx,:,:,:]\n level_model_crps.append(scaled_crps(y=_y, y_hat=_y_rec,\n quantiles=QUANTILES))\n level_model_crps = f'{np.mean(level_model_crps):.4f}Ā±{(1.96 * np.std(level_model_crps)):.4f}'\n crps_results[model].append(level_model_crps)\n\n crps_results = pd.DataFrame(crps_results)\n\ncrps_results\n\nCode block 'Evaluate Models CRPS ' took: 1.13514 seconds\n\n\n\n\n\n\n\n\n\nDataset\nLevel\nAutoARIMA/BottomUp\nAutoARIMA/TopDown_method-average_proportions\nAutoARIMA/TopDown_method-proportion_averages\nAutoARIMA/MinTrace_method-ols\nAutoARIMA/MinTrace_method-wls_var\nAutoARIMA/MinTrace_method-mint_shrink\nAutoARIMA/ERM_method-closed_lambda_reg-0.01\n\n\n\n\n0\nTourismSmall\nOverall\n0.0895Ā±0.0012\n0.1195Ā±0.0008\n0.1197Ā±0.0008\n0.0927Ā±0.0010\n0.0890Ā±0.0010\n0.0898Ā±0.0009\n0.1116Ā±0.0015\n\n\n1\nTourismSmall\nCountry\n0.0481Ā±0.0016\n0.0479Ā±0.0011\n0.0479Ā±0.0011\n0.0504Ā±0.0010\n0.0510Ā±0.0011\n0.0512Ā±0.0011\n0.0525Ā±0.0015\n\n\n2\nTourismSmall\nCountry/Purpose\n0.0699Ā±0.0016\n0.0928Ā±0.0009\n0.0931Ā±0.0009\n0.0804Ā±0.0012\n0.0724Ā±0.0012\n0.0741Ā±0.0012\n0.0927Ā±0.0015\n\n\n3\nTourismSmall\nCountry/Purpose/State\n0.1085Ā±0.0011\n0.1575Ā±0.0009\n0.1579Ā±0.0009\n0.1082Ā±0.0011\n0.1043Ā±0.0009\n0.1049Ā±0.0008\n0.1325Ā±0.0018\n\n\n4\nTourismSmall\nCountry/Purpose/State/CityNonCity\n0.1316Ā±0.0012\n0.1799Ā±0.0008\n0.1800Ā±0.0008\n0.1319Ā±0.0013\n0.1282Ā±0.0011\n0.1290Ā±0.0010\n0.1685Ā±0.0029\n\n\n\n\n\n\n\n\nwith CodeTimer('Evaluate Models MSSE ', verbose):\n msse_results = {'Dataset': [dataset] * len(['Overall'] + list(tags.keys())),\n 'Level': ['Overall'] + list(tags.keys()),}\n for model in hrec.level_names.keys():\n msse_results[model] = []\n for level in msse_results['Level']:\n if level=='Overall':\n row_idxs = np.arange(len(S_df))\n else:\n row_idxs = S_df.index.get_indexer(tags[level])\n col_idx = model_columns.get_loc(model)\n _y = y_test[row_idxs,:]\n _y_train = y_train[row_idxs,:]\n _y_hat_seeds = y_rec[:,row_idxs,:,:][:,:,:,col_idx]\n\n level_model_msse = []\n for seed_idx in range(y_rec.shape[0]):\n _y_hat = _y_hat_seeds[seed_idx,:,:]\n level_model_msse.append(msse(y=_y, y_hat=_y_hat, y_train=_y_train))\n #level_model_msse = f'{np.mean(level_model_msse):.4f}Ā±{(1.96 * np.std(level_model_msse)):.4f}'\n level_model_msse = f'{np.mean(level_model_msse):.4f}'\n msse_results[model].append(level_model_msse)\n\n msse_results = pd.DataFrame(msse_results)\n\nmsse_results\n\nCode block 'Evaluate Models MSSE ' took: 0.73303 seconds\n\n\n\n\n\n\n\n\n\nDataset\nLevel\nAutoARIMA/BottomUp\nAutoARIMA/TopDown_method-average_proportions\nAutoARIMA/TopDown_method-proportion_averages\nAutoARIMA/MinTrace_method-ols\nAutoARIMA/MinTrace_method-wls_var\nAutoARIMA/MinTrace_method-mint_shrink\nAutoARIMA/ERM_method-closed_lambda_reg-0.01\n\n\n\n\n0\nTourismSmall\nOverall\n0.2530\n0.3628\n0.3649\n0.3039\n0.2789\n0.2822\n0.3942\n\n\n1\nTourismSmall\nCountry\n0.2564\n0.3180\n0.3180\n0.3522\n0.3381\n0.3394\n0.4117\n\n\n2\nTourismSmall\nCountry/Purpose\n0.2018\n0.3178\n0.3203\n0.2557\n0.2122\n0.2175\n0.3346\n\n\n3\nTourismSmall\nCountry/Purpose/State\n0.3231\n0.5077\n0.5114\n0.2943\n0.2858\n0.2890\n0.4534\n\n\n4\nTourismSmall\nCountry/Purpose/State/CityNonCity\n0.3423\n0.5047\n0.5099\n0.3238\n0.3083\n0.3115\n0.4791\n\n\n\n\n\n\n\n\nwith CodeTimer('Evaluate Models EScore', verbose):\n energy_results = {'Dataset': [dataset] * len(['Overall'] + list(tags.keys())),\n 'Level': ['Overall'] + list(tags.keys()),}\n for model in hrec.sample_names.keys():\n energy_results[model] = []\n for level in energy_results['Level']:\n if level=='Overall':\n row_idxs = np.arange(len(S_df))\n else:\n row_idxs = S_df.index.get_indexer(tags[level])\n col_idxs = model_columns.get_indexer(hrec.sample_names[model])\n _y = y_test[row_idxs,:]\n _y_sample1 = y_rec[0,row_idxs,:,:][:,:,col_idxs[:len(col_idxs)//2]]\n _y_sample2 = y_rec[0,row_idxs,:,:][:,:,col_idxs[len(col_idxs)//2:]]\n level_model_energy = energy_score(y=_y, \n y_sample1=_y_sample1,\n y_sample2=_y_sample2,\n beta=2)\n energy_results[model].append(level_model_energy)\n energy_results = pd.DataFrame(energy_results)\n\nenergy_results\n\nCode block 'Evaluate Models EScore' took: 0.19443 seconds\n\n\n\n\n\n\n\n\n\nDataset\nLevel\nAutoARIMA/BottomUp\nAutoARIMA/TopDown_method-average_proportions\nAutoARIMA/TopDown_method-proportion_averages\nAutoARIMA/MinTrace_method-ols\nAutoARIMA/MinTrace_method-wls_var\nAutoARIMA/MinTrace_method-mint_shrink\nAutoARIMA/ERM_method-closed_lambda_reg-0.01\n\n\n\n\n0\nTourismSmall\nOverall\n6.874103e+07\n7.917294e+07\n7.962361e+07\n6.930268e+07\n6.914837e+07\n6.955018e+07\n8.235776e+07\n\n\n1\nTourismSmall\nCountry\n3.292999e+07\n2.757131e+07\n2.757129e+07\n3.081254e+07\n3.392861e+07\n3.353851e+07\n3.350023e+07\n\n\n2\nTourismSmall\nCountry/Purpose\n1.894485e+07\n2.661024e+07\n2.683828e+07\n2.218952e+07\n1.932895e+07\n1.984161e+07\n2.681792e+07\n\n\n3\nTourismSmall\nCountry/Purpose/State\n9.393103e+06\n1.408613e+07\n1.419471e+07\n9.016056e+06\n8.778983e+06\n8.928542e+06\n1.211747e+07\n\n\n4\nTourismSmall\nCountry/Purpose/State/CityNonCity\n7.473085e+06\n1.090527e+07\n1.101934e+07\n7.284562e+06\n7.111832e+06\n7.241519e+06\n9.922145e+06" + }, + { + "objectID": "examples/tourismlarge-evaluation.html#references", + "href": "examples/tourismlarge-evaluation.html#references", + "title": "Probabilistic Forecast Evaluation", + "section": "References", + "text": "References\n\nSyama Sundar Rangapuram, Lucien D Werner, Konstantinos Benidis, Pedro Mercado, Jan Gasthaus, Tim Januschowski. (2021). \"End-to-End Learning of Coherent Probabilistic Forecasts for Hierarchical Time Series\". Proceedings of the 38th International Conference on Machine Learning (ICML).\nKin G. Olivares, O. Nganba Meetei, Ruijun Ma, Rohan Reddy, Mengfei Cao, Lee Dicker (2022). ā€œProbabilistic Hierarchical Forecasting with Deep Poisson Mixturesā€. Submitted to the International Journal Forecasting, Working paper available at arxiv." + }, + { + "objectID": "examples/hierarchicalforecast-gluonts.html", + "href": "examples/hierarchicalforecast-gluonts.html", + "title": "GluonTS", + "section": "", + "text": "This example notebook demonstrates the compatibility of HierarchicalForecastā€™s reconciliation methods with popular machine-learning libraries, specifically GluonTS.\nThe notebook utilizes the GluonTS DeepAREstimator to create base forecasts for the TourismLarge Hierarchical Dataset. We make the base forecasts compatible with HierarchicalForecastā€™s reconciliation functions via the samples_to_quantiles_df utility function that transforms GluonTSā€™ output forecasts into a compatible data frame format. After that, we use HierarchicalForecast to reconcile the base predictions.\nReferences - David Salinas, Valentin Flunkert, Jan Gasthaus, Tim Januschowski (2020). ā€œDeepAR: Probabilistic forecasting with autoregressive recurrent networksā€. International Journal of Forecasting. - Alexander Alexandrov et. al (2020). ā€œGluonTS: Probabilistic and Neural Time Series Modeling in Pythonā€. Journal of Machine Learning Research.\nYou can run these experiments using CPU or GPU with Google Colab.\nIf you find the code useful, please ā­ us on Github" + }, + { + "objectID": "examples/hierarchicalforecast-gluonts.html#installing-packages", + "href": "examples/hierarchicalforecast-gluonts.html#installing-packages", + "title": "GluonTS", + "section": "1. Installing packages", + "text": "1. Installing packages\n\n!pip install mxnet-cu112\n\n\nimport mxnet as mx\n\nassert mx.context.num_gpus()>0\n\n\n!pip install gluonts\n!pip install datasetsforecast\n!pip install git+https://github.com/Nixtla/hierarchicalforecast.git\n\n\nimport numpy as np\nimport pandas as pd\n\nfrom datasetsforecast.hierarchical import HierarchicalData\n\nfrom gluonts.mx.trainer import Trainer\nfrom gluonts.dataset.pandas import PandasDataset\nfrom gluonts.mx.model.deepar import DeepAREstimator\n\nfrom hierarchicalforecast.methods import BottomUp, MinTrace\nfrom hierarchicalforecast.core import HierarchicalReconciliation\nfrom hierarchicalforecast.evaluation import scaled_crps\nfrom hierarchicalforecast.utils import samples_to_quantiles_df\n\n/usr/local/lib/python3.10/dist-packages/gluonts/json.py:101: UserWarning: Using `json`-module for json-handling. Consider installing one of `orjson`, `ujson` to speed up serialization and deserialization.\n warnings.warn(" + }, + { + "objectID": "examples/hierarchicalforecast-gluonts.html#load-hierarchical-dataset", + "href": "examples/hierarchicalforecast-gluonts.html#load-hierarchical-dataset", + "title": "GluonTS", + "section": "2. Load hierarchical dataset", + "text": "2. Load hierarchical dataset\nThis detailed Australian Tourism Dataset comes from the National Visitor Survey, managed by the Tourism Research Australia, it is composed of 555 monthly series from 1998 to 2016, it is organized geographically, and purpose of travel. The natural geographical hierarchy comprises seven states, divided further in 27 zones and 76 regions. The purpose of travel categories are holiday, visiting friends and relatives (VFR), business and other. The MinT (Wickramasuriya et al., 2019), among other hierarchical forecasting studies has used the dataset it in the past. The dataset can be accessed in the MinT reconciliation webpage, although other sources are available.\n\n\n\n\n\n\n\n\n\nGeographical Division\nNumber of series per division\nNumber of series per purpose\nTotal\n\n\n\n\nAustralia\n1\n4\n5\n\n\nStates\n7\n28\n35\n\n\nZones\n27\n108\n135\n\n\nRegions\n76\n304\n380\n\n\nTotal\n111\n444\n555\n\n\n\n\ndataset = 'TourismLarge'\nY_df, S_df, tags = HierarchicalData.load(directory = \"./data\", group=dataset)\nY_df['ds'] = pd.to_datetime(Y_df['ds'])\n\n\ndef sort_hier_df(Y_df, S_df):\n # sorts unique_id lexicographically\n Y_df.unique_id = Y_df.unique_id.astype('category')\n Y_df.unique_id = Y_df.unique_id.cat.set_categories(S_df.index)\n Y_df = Y_df.sort_values(by=['unique_id', 'ds'])\n return Y_df\n\nY_df = sort_hier_df(Y_df, S_df)\n\n\nhorizon = 12\n\nY_test_df = Y_df.groupby('unique_id').tail(horizon)\nY_train_df = Y_df.drop(Y_test_df.index)\nY_train_df\n\n\n \n \n \n\n\n\n\n\n\nunique_id\nds\ny\n\n\n\n\n0\nTotalAll\n1998-01-01\n45151.071280\n\n\n1\nTotalAll\n1998-02-01\n17294.699551\n\n\n2\nTotalAll\n1998-03-01\n20725.114184\n\n\n3\nTotalAll\n1998-04-01\n25388.612353\n\n\n4\nTotalAll\n1998-05-01\n20330.035211\n\n\n...\n...\n...\n...\n\n\n126523\nGBDOth\n2015-08-01\n17.683774\n\n\n126524\nGBDOth\n2015-09-01\n0.000000\n\n\n126525\nGBDOth\n2015-10-01\n0.000000\n\n\n126526\nGBDOth\n2015-11-01\n0.000000\n\n\n126527\nGBDOth\n2015-12-01\n0.000000\n\n\n\n\n\n119880 rows Ɨ 3 columns\n\n \n \n \n \n \n \n \n \n \n\n \n \n \n \n\n\n\nds = PandasDataset.from_long_dataframe(Y_train_df, target=\"y\", item_id=\"unique_id\")" + }, + { + "objectID": "examples/hierarchicalforecast-gluonts.html#fit-and-predict-model", + "href": "examples/hierarchicalforecast-gluonts.html#fit-and-predict-model", + "title": "GluonTS", + "section": "3. Fit and Predict Model", + "text": "3. Fit and Predict Model\n\nestimator = DeepAREstimator(\n freq=\"M\",\n prediction_length=horizon,\n trainer=Trainer(ctx = mx.context.gpu(),\n epochs=20),\n)\npredictor = estimator.train(ds)\n\nforecast_it = predictor.predict(ds, num_samples=1000)\n\nforecasts = list(forecast_it)\nforecasts = np.array([arr.samples for arr in forecasts])\nforecasts.shape\n\n100%|ā–ˆā–ˆā–ˆā–ˆā–ˆā–ˆā–ˆā–ˆā–ˆā–ˆ| 50/50 [00:11<00:00, 4.39it/s, epoch=1/20, avg_epoch_loss=5.35]\n100%|ā–ˆā–ˆā–ˆā–ˆā–ˆā–ˆā–ˆā–ˆā–ˆā–ˆ| 50/50 [00:05<00:00, 8.75it/s, epoch=2/20, avg_epoch_loss=5.22]\n100%|ā–ˆā–ˆā–ˆā–ˆā–ˆā–ˆā–ˆā–ˆā–ˆā–ˆ| 50/50 [00:03<00:00, 14.41it/s, epoch=3/20, avg_epoch_loss=5.17]\n100%|ā–ˆā–ˆā–ˆā–ˆā–ˆā–ˆā–ˆā–ˆā–ˆā–ˆ| 50/50 [00:02<00:00, 20.76it/s, epoch=4/20, avg_epoch_loss=5.02]\n100%|ā–ˆā–ˆā–ˆā–ˆā–ˆā–ˆā–ˆā–ˆā–ˆā–ˆ| 50/50 [00:02<00:00, 19.27it/s, epoch=5/20, avg_epoch_loss=5.05]\n100%|ā–ˆā–ˆā–ˆā–ˆā–ˆā–ˆā–ˆā–ˆā–ˆā–ˆ| 50/50 [00:04<00:00, 11.52it/s, epoch=6/20, avg_epoch_loss=5.12]\n100%|ā–ˆā–ˆā–ˆā–ˆā–ˆā–ˆā–ˆā–ˆā–ˆā–ˆ| 50/50 [00:03<00:00, 16.59it/s, epoch=7/20, avg_epoch_loss=4.97]\n100%|ā–ˆā–ˆā–ˆā–ˆā–ˆā–ˆā–ˆā–ˆā–ˆā–ˆ| 50/50 [00:03<00:00, 16.27it/s, epoch=8/20, avg_epoch_loss=4.97]\n100%|ā–ˆā–ˆā–ˆā–ˆā–ˆā–ˆā–ˆā–ˆā–ˆā–ˆ| 50/50 [00:02<00:00, 19.96it/s, epoch=9/20, avg_epoch_loss=5.11]\n100%|ā–ˆā–ˆā–ˆā–ˆā–ˆā–ˆā–ˆā–ˆā–ˆā–ˆ| 50/50 [00:04<00:00, 11.36it/s, epoch=10/20, avg_epoch_loss=4.97]\n100%|ā–ˆā–ˆā–ˆā–ˆā–ˆā–ˆā–ˆā–ˆā–ˆā–ˆ| 50/50 [00:03<00:00, 16.62it/s, epoch=11/20, avg_epoch_loss=5.05]\n100%|ā–ˆā–ˆā–ˆā–ˆā–ˆā–ˆā–ˆā–ˆā–ˆā–ˆ| 50/50 [00:02<00:00, 17.76it/s, epoch=12/20, avg_epoch_loss=5.04]\n100%|ā–ˆā–ˆā–ˆā–ˆā–ˆā–ˆā–ˆā–ˆā–ˆā–ˆ| 50/50 [00:02<00:00, 21.56it/s, epoch=13/20, avg_epoch_loss=4.99]\n100%|ā–ˆā–ˆā–ˆā–ˆā–ˆā–ˆā–ˆā–ˆā–ˆā–ˆ| 50/50 [00:02<00:00, 20.64it/s, epoch=14/20, avg_epoch_loss=5.03]\n100%|ā–ˆā–ˆā–ˆā–ˆā–ˆā–ˆā–ˆā–ˆā–ˆā–ˆ| 50/50 [00:03<00:00, 13.22it/s, epoch=15/20, avg_epoch_loss=4.97]\n100%|ā–ˆā–ˆā–ˆā–ˆā–ˆā–ˆā–ˆā–ˆā–ˆā–ˆ| 50/50 [00:02<00:00, 17.79it/s, epoch=16/20, avg_epoch_loss=4.95]\n100%|ā–ˆā–ˆā–ˆā–ˆā–ˆā–ˆā–ˆā–ˆā–ˆā–ˆ| 50/50 [00:02<00:00, 18.29it/s, epoch=17/20, avg_epoch_loss=5.02]\n100%|ā–ˆā–ˆā–ˆā–ˆā–ˆā–ˆā–ˆā–ˆā–ˆā–ˆ| 50/50 [00:02<00:00, 17.73it/s, epoch=18/20, avg_epoch_loss=5.02]\n100%|ā–ˆā–ˆā–ˆā–ˆā–ˆā–ˆā–ˆā–ˆā–ˆā–ˆ| 50/50 [00:02<00:00, 19.10it/s, epoch=19/20, avg_epoch_loss=5.02]\n100%|ā–ˆā–ˆā–ˆā–ˆā–ˆā–ˆā–ˆā–ˆā–ˆā–ˆ| 50/50 [00:03<00:00, 13.29it/s, epoch=20/20, avg_epoch_loss=5]\n\n\n(555, 1000, 12)" + }, + { + "objectID": "examples/hierarchicalforecast-gluonts.html#reconciliation", + "href": "examples/hierarchicalforecast-gluonts.html#reconciliation", + "title": "GluonTS", + "section": "4. Reconciliation", + "text": "4. Reconciliation\n\nlevel = np.arange(1, 100, 2)\n\n#transform the output of DeepAREstimator to a form that is compatible with HierarchicalForecast\nquantiles, forecast_df = samples_to_quantiles_df(samples=forecasts, \n unique_ids=S_df.index, \n dates=Y_test_df['ds'].unique(), \n level=level,\n model_name='DeepAREstimator')\n\n#reconcile forecasts\nreconcilers = [\n BottomUp(),\n MinTrace('ols')\n]\nhrec = HierarchicalReconciliation(reconcilers=reconcilers)\n\nforecast_rec = hrec.reconcile(Y_hat_df=forecast_df, S=S_df, tags=tags, level=level)\n\n\nforecast_rec\n\n\n \n \n \n\n\n\n\n\n\nds\nDeepAREstimator\nDeepAREstimator-median\nDeepAREstimator-lo-99\nDeepAREstimator-lo-97\nDeepAREstimator-lo-95\nDeepAREstimator-lo-93\nDeepAREstimator-lo-91\nDeepAREstimator-lo-89\nDeepAREstimator-lo-87\n...\nDeepAREstimator/MinTrace_method-ols-hi-81\nDeepAREstimator/MinTrace_method-ols-hi-83\nDeepAREstimator/MinTrace_method-ols-hi-85\nDeepAREstimator/MinTrace_method-ols-hi-87\nDeepAREstimator/MinTrace_method-ols-hi-89\nDeepAREstimator/MinTrace_method-ols-hi-91\nDeepAREstimator/MinTrace_method-ols-hi-93\nDeepAREstimator/MinTrace_method-ols-hi-95\nDeepAREstimator/MinTrace_method-ols-hi-97\nDeepAREstimator/MinTrace_method-ols-hi-99\n\n\nunique_id\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\nTotalAll\n2016-01-01\n43165.929688\n43002.058594\n27712.297979\n30371.243516\n32741.458740\n33305.429492\n34446.465957\n35164.380410\n35732.592422\n...\n48703.132046\n48956.752480\n49233.843836\n49540.743219\n49886.826218\n50286.877928\n50766.394577\n51375.717577\n52240.506366\n53910.351214\n\n\nTotalAll\n2016-02-01\n20326.796875\n20469.210938\n13156.550879\n15086.488257\n15738.457031\n16134.386343\n16696.160010\n16828.676436\n17139.442129\n...\n22902.635244\n23019.412684\n23146.997118\n23288.306411\n23447.657478\n23631.857993\n23852.647485\n24133.205242\n24531.390118\n25300.256426\n\n\nTotalAll\n2016-03-01\n24362.203125\n24237.250977\n17340.837197\n18470.071582\n19132.180615\n19658.168945\n19974.223359\n20339.483584\n20519.382959\n...\n26759.166634\n26873.896338\n26999.243530\n27138.074912\n27294.631699\n27475.602189\n27692.520055\n27968.158127\n28359.360682\n29114.744632\n\n\nTotalAll\n2016-04-01\n29131.662109\n29236.008789\n19923.623740\n21814.112246\n22685.987500\n23350.113418\n23721.056963\n24168.286201\n24513.198066\n...\n32277.209370\n32427.584386\n32591.875632\n32773.840464\n32979.037796\n33216.233913\n33500.545877\n33861.821798\n34374.566871\n35364.640665\n\n\nTotalAll\n2016-05-01\n22587.779297\n22638.541016\n14453.285947\n16236.985869\n17163.251807\n17894.046758\n18559.204453\n18789.053066\n19055.381455\n...\n25400.976716\n25532.984575\n25677.208902\n25836.948122\n26017.082170\n26225.306596\n26474.892029\n26792.040860\n27242.158045\n28111.301901\n\n\n...\n...\n...\n...\n...\n...\n...\n...\n...\n...\n...\n...\n...\n...\n...\n...\n...\n...\n...\n...\n...\n...\n\n\nGBDOth\n2016-08-01\n-0.300811\n-0.316894\n-2.994549\n-2.208182\n-2.005075\n-1.725068\n-1.620723\n-1.501304\n-1.355108\n...\n27.151595\n28.293141\n29.540330\n30.921685\n32.479405\n34.280039\n36.438344\n39.180908\n43.073324\n50.589300\n\n\nGBDOth\n2016-09-01\n-0.089410\n-0.079164\n-2.981229\n-2.356738\n-1.812428\n-1.499515\n-1.365453\n-1.199702\n-1.120727\n...\n24.912080\n26.035044\n27.261932\n28.620801\n30.153165\n31.924489\n34.047662\n36.745584\n40.574640\n47.968273\n\n\nGBDOth\n2016-10-01\n-0.196041\n-0.207104\n-2.829650\n-2.270969\n-1.674091\n-1.289834\n-1.153728\n-1.078916\n-1.029915\n...\n25.423958\n26.550973\n27.782287\n29.146059\n30.683952\n32.461666\n34.592499\n37.300154\n41.143025\n48.563331\n\n\nGBDOth\n2016-11-01\n-0.315826\n-0.274183\n-2.461571\n-1.829249\n-1.535889\n-1.329642\n-1.260961\n-1.134465\n-1.007276\n...\n25.125960\n26.257991\n27.494784\n28.864625\n30.409361\n32.194986\n34.335301\n37.055005\n40.914977\n48.368305\n\n\nGBDOth\n2016-12-01\n-0.291579\n-0.268462\n-3.987842\n-2.078746\n-1.619226\n-1.385310\n-1.253607\n-1.156472\n-1.092625\n...\n26.216098\n27.310535\n28.506255\n29.830604\n31.324041\n33.050366\n35.119603\n37.748987\n41.480772\n48.686579\n\n\n\n\n\n6660 rows Ɨ 305 columns" + }, + { + "objectID": "examples/hierarchicalforecast-gluonts.html#evaluation", + "href": "examples/hierarchicalforecast-gluonts.html#evaluation", + "title": "GluonTS", + "section": "5. Evaluation", + "text": "5. Evaluation\nTo evaluate we use a scaled variation of the CRPS, as proposed by Rangapuram (2021), to measure the accuracy of predicted quantiles y_hat compared to the observation y.\n\\[ \\mathrm{sCRPS}(\\hat{F}_{\\tau}, \\mathbf{y}_{\\tau}) = \\frac{2}{N} \\sum_{i}\n\\int^{1}_{0}\n\\frac{\\mathrm{QL}(\\hat{F}_{i,\\tau}, y_{i,\\tau})_{q}}{\\sum_{i} | y_{i,\\tau} |} dq \\]\nAs you can see, HierarchicalForecast results improve on the results of specialized algorithms like HierE2E.\n\nrec_model_names = ['DeepAREstimator/MinTrace_method-ols', 'DeepAREstimator/BottomUp']\n\nquantiles = np.array(quantiles[1:]) #remove first quantile (median)\nn_quantiles = len(quantiles)\nn_series = len(S_df)\n\nfor name in rec_model_names:\n quantile_columns = [col for col in forecast_rec.columns if (name+'-') in col]\n y_rec = forecast_rec[quantile_columns].values \n y_test = Y_test_df['y'].values\n\n y_rec = y_rec.reshape(n_series, horizon, n_quantiles)\n y_test = y_test.reshape(n_series, horizon)\n scrps = scaled_crps(y=y_test, y_hat=y_rec, quantiles=quantiles)\n print(\"{:<40} {:.5f}\".format(name+\":\", scrps))\n\nDeepAREstimator/MinTrace_method-ols: 0.12632\nDeepAREstimator/BottomUp: 0.13933" + }, + { + "objectID": "examples/mlframeworksexample.html", + "href": "examples/mlframeworksexample.html", + "title": "Neural/MLForecast", + "section": "", + "text": "This example notebook demonstrates the compatibility of HierarchicalForecastā€™s reconciliation methods with popular machine-learning libraries, specifically NeuralForecast and MLForecast.\nThe notebook utilizes NBEATS and XGBRegressor models to create base forecasts for the TourismLarge Hierarchical Dataset. After that, we use HierarchicalForecast to reconcile the base predictions.\nReferences - Boris N. Oreshkin, Dmitri Carpov, Nicolas Chapados, Yoshua Bengio (2019). ā€œN-BEATS: Neural basis expansion analysis for interpretable time series forecastingā€. url: https://arxiv.org/abs/1905.10437 - Tianqi Chen and Carlos Guestrin. ā€œXGBoost: A Scalable Tree Boosting Systemā€. In: Proceedings of the 22nd ACM SIGKDD International Conference on Knowledge Discovery and Data Mining. KDD ā€™16. San Francisco, California, USA: Association for Computing Machinery, 2016, pp.Ā 785ā€“794. isbn: 9781450342322. doi: 10.1145/2939672.2939785. url: https://doi.org/10.1145/2939672.2939785 (cit. on p.Ā 26).\nYou can run these experiments using CPU or GPU with Google Colab.\nIf you find the code useful, please ā­ us on Github" + }, + { + "objectID": "examples/mlframeworksexample.html#installing-packages", + "href": "examples/mlframeworksexample.html#installing-packages", + "title": "Neural/MLForecast", + "section": "1. Installing packages", + "text": "1. Installing packages\n\n!pip install datasetsforecast mlforecast \n!pip install git+https://github.com/Nixtla/neuralforecast.git\n!pip install git+https://github.com/Nixtla/hierarchicalforecast.git\n\n\nimport numpy as np\nimport pandas as pd\n\nfrom datasetsforecast.hierarchical import HierarchicalData\n\nfrom neuralforecast import NeuralForecast\nfrom neuralforecast.models import NBEATS\nfrom neuralforecast.losses.pytorch import GMM\n\nfrom mlforecast import MLForecast\nfrom window_ops.expanding import expanding_mean\nfrom mlforecast.utils import PredictionIntervals\nfrom mlforecast.target_transforms import Differences\nimport xgboost as xgb\n\n#obtain hierarchical reconciliation methods and evaluation\nfrom hierarchicalforecast.methods import BottomUp, MinTrace\nfrom hierarchicalforecast.utils import HierarchicalPlot\nfrom hierarchicalforecast.core import HierarchicalReconciliation\nfrom hierarchicalforecast.evaluation import scaled_crps" + }, + { + "objectID": "examples/mlframeworksexample.html#load-hierarchical-dataset", + "href": "examples/mlframeworksexample.html#load-hierarchical-dataset", + "title": "Neural/MLForecast", + "section": "2. Load hierarchical dataset", + "text": "2. Load hierarchical dataset\nThis detailed Australian Tourism Dataset comes from the National Visitor Survey, managed by the Tourism Research Australia, it is composed of 555 monthly series from 1998 to 2016, it is organized geographically, and purpose of travel. The natural geographical hierarchy comprises seven states, divided further in 27 zones and 76 regions. The purpose of travel categories are holiday, visiting friends and relatives (VFR), business and other. The MinT (Wickramasuriya et al., 2019), among other hierarchical forecasting studies has used the dataset it in the past. The dataset can be accessed in the MinT reconciliation webpage, although other sources are available.\n\n\n\n\n\n\n\n\n\nGeographical Division\nNumber of series per division\nNumber of series per purpose\nTotal\n\n\n\n\nAustralia\n1\n4\n5\n\n\nStates\n7\n28\n35\n\n\nZones\n27\n108\n135\n\n\nRegions\n76\n304\n380\n\n\nTotal\n111\n444\n555\n\n\n\n\nY_df, S_df, tags = HierarchicalData.load('./data', 'TourismLarge')\nY_df['ds'] = pd.to_datetime(Y_df['ds'])\n\n\nY_df.head()\n\n\n \n \n \n\n\n\n\n\n\nunique_id\nds\ny\n\n\n\n\n0\nTotalAll\n1998-01-01\n45151.071280\n\n\n1\nTotalAll\n1998-02-01\n17294.699551\n\n\n2\nTotalAll\n1998-03-01\n20725.114184\n\n\n3\nTotalAll\n1998-04-01\n25388.612353\n\n\n4\nTotalAll\n1998-05-01\n20330.035211\n\n\n\n\n\n\n \n \n \n \n \n \n \n \n \n\n \n \n \n \n\n\nVisualize the aggregation matrix.\n\nhplot = HierarchicalPlot(S=S_df, tags=tags)\nhplot.plot_summing_matrix()\n\n\n\n\nSplit the dataframe in train/test splits.\n\ndef sort_hier_df(Y_df, S_df):\n # sorts unique_id lexicographically\n Y_df.unique_id = Y_df.unique_id.astype('category')\n Y_df.unique_id = Y_df.unique_id.cat.set_categories(S_df.index)\n Y_df = Y_df.sort_values(by=['unique_id', 'ds'])\n return Y_df\n\nY_df = sort_hier_df(Y_df, S_df)\n\n\nhorizon = 12\nY_test_df = Y_df.groupby('unique_id').tail(horizon)\nY_train_df = Y_df.drop(Y_test_df.index)" + }, + { + "objectID": "examples/mlframeworksexample.html#fit-and-predict-models", + "href": "examples/mlframeworksexample.html#fit-and-predict-models", + "title": "Neural/MLForecast", + "section": "3. Fit and Predict Models", + "text": "3. Fit and Predict Models\nHierarchicalForecast is compatible with many different ML models. Here, we show two examples: 1. NBEATS, a MLP-based deep neural architecture. 2. XGBRegressor, a tree-based architecture.\n\nlevel = np.arange(0, 100, 2)\nqs = [[50-lv/2, 50+lv/2] for lv in level]\nquantiles = np.sort(np.concatenate(qs)/100)\n\n#fit/predict NBEATS from NeuralForecast\nnbeats = NBEATS(h=horizon,\n input_size=2*horizon,\n loss=GMM(n_components=10, quantiles=quantiles),\n scaler_type='robust',\n max_steps=2000)\nnf = NeuralForecast(models=[nbeats], freq='MS')\nnf.fit(df=Y_train_df)\nY_hat_nf = nf.predict()\n\n#fit/predict XGBRegressor from MLForecast\nmf = MLForecast(models=[xgb.XGBRegressor()], \n freq='MS',\n lags=[1,2,12,24],\n date_features=['month'],\n )\nmf.fit(Y_train_df, prediction_intervals=PredictionIntervals(n_windows=10, window_size=horizon)) \nY_hat_mf = mf.predict(horizon, level=level).set_index('unique_id')\n\nINFO:lightning_fabric.utilities.seed:Global seed set to 1\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\nY_hat_nf\n\n\n \n \n \n\n\n\n\n\n\nds\nNBEATS\nNBEATS-lo-98.0\nNBEATS-lo-96.0\nNBEATS-lo-94.0\nNBEATS-lo-92.0\nNBEATS-lo-90.0\nNBEATS-lo-88.0\nNBEATS-lo-86.0\nNBEATS-lo-84.0\n...\nNBEATS-hi-80.0\nNBEATS-hi-82.0\nNBEATS-hi-84.0\nNBEATS-hi-86.0\nNBEATS-hi-88.0\nNBEATS-hi-90.0\nNBEATS-hi-92.0\nNBEATS-hi-94.0\nNBEATS-hi-96.0\nNBEATS-hi-98.0\n\n\nunique_id\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\nTotalAll\n2016-01-01\n44304.039062\n24825.771484\n26974.607422\n27405.914062\n27881.269531\n28640.238281\n29469.513672\n30213.277344\n31009.929688\n...\n51838.828125\n52150.523438\n52404.886719\n52564.652344\n52951.238281\n53216.839844\n53689.351562\n54015.074219\n54545.882812\n55752.621094\n\n\nTotalAll\n2016-02-01\n20877.984375\n17909.365234\n18334.902344\n18577.355469\n18653.085938\n18755.072266\n18839.824219\n18965.947266\n19074.134766\n...\n22756.220703\n22892.509766\n23029.402344\n23133.941406\n23221.666016\n23385.628906\n23587.021484\n23862.343750\n24243.560547\n24526.462891\n\n\nTotalAll\n2016-03-01\n23444.972656\n18971.355469\n19329.705078\n19472.619141\n19756.503906\n19843.703125\n20075.363281\n20126.689453\n20259.271484\n...\n26024.242188\n26116.677734\n26196.498047\n26342.339844\n26535.798828\n26758.476562\n26934.582031\n27097.130859\n27441.996094\n27704.375000\n\n\nTotalAll\n2016-04-01\n28927.132812\n24030.257812\n24540.779297\n24732.566406\n24988.001953\n25160.744141\n25304.658203\n25456.001953\n25567.078125\n...\n31568.966797\n31698.855469\n31856.851562\n32097.916016\n32211.320312\n32345.988281\n32510.902344\n32724.638672\n33078.031250\n33525.035156\n\n\nTotalAll\n2016-05-01\n22716.433594\n19728.511719\n19910.925781\n20089.443359\n20214.955078\n20269.906250\n20355.708984\n20441.349609\n20491.029297\n...\n24937.335938\n25114.396484\n25270.279297\n25446.765625\n25676.287109\n26028.427734\n26440.011719\n27477.541016\n28452.419922\n29793.591797\n\n\n...\n...\n...\n...\n...\n...\n...\n...\n...\n...\n...\n...\n...\n...\n...\n...\n...\n...\n...\n...\n...\n...\n\n\nGBDOth\n2016-08-01\n4.731373\n-30.691290\n-8.694043\n-2.576124\n-2.196553\n-2.069076\n-1.913422\n-1.854156\n-1.767804\n...\n9.252028\n10.948211\n12.031944\n14.396760\n18.523523\n43.287716\n58.207531\n69.754929\n81.399673\n116.701561\n\n\nGBDOth\n2016-09-01\n5.685491\n-32.813366\n-11.985416\n-2.978264\n-2.413029\n-2.120405\n-1.788605\n-1.673310\n-1.550562\n...\n12.787840\n14.330542\n15.563581\n16.996040\n29.901039\n45.086597\n60.724380\n75.462578\n92.432518\n125.217796\n\n\nGBDOth\n2016-10-01\n4.760162\n-51.105358\n-27.034277\n-8.493114\n-2.859874\n-2.140030\n-1.905673\n-1.764797\n-1.621011\n...\n10.930604\n11.960605\n13.876516\n14.839364\n18.540100\n32.251144\n48.573261\n65.301460\n83.327026\n113.249001\n\n\nGBDOth\n2016-11-01\n6.491304\n-31.302568\n-6.776994\n-2.816422\n-2.196187\n-2.002094\n-1.806302\n-1.613474\n-1.538146\n...\n14.449442\n15.161877\n17.715519\n22.247185\n39.648643\n52.634579\n67.812111\n75.647865\n85.764038\n116.143196\n\n\nGBDOth\n2016-12-01\n6.683663\n-37.663929\n-13.461041\n-2.384047\n-2.037058\n-1.877487\n-1.620457\n-1.436237\n-1.304141\n...\n15.086438\n16.038090\n18.206852\n24.431122\n35.078407\n44.138805\n62.435913\n77.259911\n104.585594\n123.915787\n\n\n\n\n\n6660 rows Ɨ 102 columns\n\n \n \n \n \n \n \n \n \n \n\n \n \n \n \n\n\n\nY_hat_mf\n\n\n \n \n \n\n\n\n\n\n\nds\nXGBRegressor\nXGBRegressor-lo-98\nXGBRegressor-lo-96\nXGBRegressor-lo-94\nXGBRegressor-lo-92\nXGBRegressor-lo-90\nXGBRegressor-lo-88\nXGBRegressor-lo-86\nXGBRegressor-lo-84\n...\nXGBRegressor-hi-80\nXGBRegressor-hi-82\nXGBRegressor-hi-84\nXGBRegressor-hi-86\nXGBRegressor-hi-88\nXGBRegressor-hi-90\nXGBRegressor-hi-92\nXGBRegressor-hi-94\nXGBRegressor-hi-96\nXGBRegressor-hi-98\n\n\nunique_id\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\nTotalAll\n2016-01-01\n43891.531250\n39048.053886\n39398.911368\n39749.768850\n40100.626332\n40451.483815\n40561.804681\n40586.219613\n40610.634545\n...\n47123.598090\n47148.013022\n47172.427955\n47196.842887\n47221.257819\n47331.578685\n47682.436168\n48033.293650\n48384.151132\n48735.008614\n\n\nTotalAll\n2016-02-01\n20715.656250\n18476.756884\n18482.492537\n18488.228190\n18493.963842\n18499.699495\n18539.212692\n18590.789297\n18642.365902\n...\n22685.793388\n22737.369993\n22788.946598\n22840.523203\n22892.099808\n22931.613005\n22937.348658\n22943.084310\n22948.819963\n22954.555616\n\n\nTotalAll\n2016-03-01\n23008.896484\n17292.312227\n17323.859641\n17355.407055\n17386.954469\n17418.501883\n17582.396869\n17793.558844\n18004.720819\n...\n27590.748200\n27801.910175\n28013.072150\n28224.234125\n28435.396100\n28599.291085\n28630.838500\n28662.385914\n28693.933328\n28725.480742\n\n\nTotalAll\n2016-04-01\n27731.050781\n22333.047144\n22537.510145\n22741.973145\n22946.436145\n23150.899146\n23233.881164\n23273.477118\n23313.073071\n...\n32069.836584\n32109.432538\n32149.028491\n32188.624445\n32228.220398\n32311.202417\n32515.665417\n32720.128417\n32924.591418\n33129.054418\n\n\nTotalAll\n2016-05-01\n24898.529297\n21768.004677\n21859.564615\n21951.124552\n22042.684490\n22134.244428\n22222.835407\n22310.366042\n22397.896678\n...\n27224.100644\n27311.631280\n27399.161916\n27486.692551\n27574.223187\n27662.814166\n27754.374104\n27845.934041\n27937.493979\n28029.053917\n\n\n...\n...\n...\n...\n...\n...\n...\n...\n...\n...\n...\n...\n...\n...\n...\n...\n...\n...\n...\n...\n...\n...\n\n\nGBDOth\n2016-08-01\n8.842277\n-2.504086\n-1.296329\n-0.088571\n1.119187\n2.326944\n2.794274\n2.997165\n3.200056\n...\n14.078715\n14.281606\n14.484497\n14.687388\n14.890279\n15.357609\n16.565366\n17.773124\n18.980882\n20.188639\n\n\nGBDOth\n2016-09-01\n4.991811\n-1.879816\n-1.879816\n-1.879816\n-1.879816\n-1.879816\n-1.635940\n-1.304965\n-0.973990\n...\n10.295662\n10.626637\n10.957612\n11.288587\n11.619561\n11.863438\n11.863438\n11.863438\n11.863438\n11.863438\n\n\nGBDOth\n2016-10-01\n8.647715\n2.339581\n2.339581\n2.339581\n2.339581\n2.339581\n2.339581\n2.339581\n2.339581\n...\n14.955848\n14.955848\n14.955848\n14.955848\n14.955848\n14.955848\n14.955848\n14.955848\n14.955848\n14.955848\n\n\nGBDOth\n2016-11-01\n5.180346\n0.451095\n0.451095\n0.451095\n0.451095\n0.451095\n0.451095\n0.451095\n0.451095\n...\n9.909597\n9.909597\n9.909597\n9.909597\n9.909597\n9.909597\n9.909597\n9.909597\n9.909597\n9.909597\n\n\nGBDOth\n2016-12-01\n6.052622\n1.791351\n1.791351\n1.791351\n1.791351\n1.791351\n1.791351\n1.791351\n1.791351\n...\n10.313892\n10.313892\n10.313892\n10.313892\n10.313892\n10.313892\n10.313892\n10.313892\n10.313892\n10.313892\n\n\n\n\n\n6660 rows Ɨ 102 columns" + }, + { + "objectID": "examples/mlframeworksexample.html#reconcile-predictions", + "href": "examples/mlframeworksexample.html#reconcile-predictions", + "title": "Neural/MLForecast", + "section": "4. Reconcile Predictions", + "text": "4. Reconcile Predictions\nWith minimal parsing, we can reconcile the raw output predictions with different HierarchicalForecast reconciliation methods.\n\n\n\n\n\n\nReconciliation Methods Availability\n\n\n\n\n\nThe following reconciliation methods require access to insample predictions: - ERM(method='closed'), ERM(method='reg_bu') - TopDown(method='average_proportions'), TopDown(method='proportion_averages') - MiddleOut(top_down_method='average_proportions'), MiddleOut(top_down_method='proportion_averages') - MinTrace(method='wls_var'), MinTrace(method='mint_cov'), MinTrace(method='mint_shrink')\nYou can obtain NeuralForecastā€™s insample predictions via the NeuralForecast.predict_insample method.\nWe are working on making MLForecastā€™s insample predictions available.\n\n\n\n\nreconcilers = [\n BottomUp(),\n MinTrace('ols')\n]\nhrec = HierarchicalReconciliation(reconcilers=reconcilers)\n\nY_rec_nf = hrec.reconcile(Y_hat_df=Y_hat_nf, Y_df = Y_train_df, S=S_df, tags=tags, level=level)\nY_rec_mf = hrec.reconcile(Y_hat_df=Y_hat_mf, Y_df = Y_train_df, S=S_df, tags=tags, level=level)" + }, + { + "objectID": "examples/mlframeworksexample.html#evaluation", + "href": "examples/mlframeworksexample.html#evaluation", + "title": "Neural/MLForecast", + "section": "5. Evaluation", + "text": "5. Evaluation\nTo evaluate we use a scaled variation of the CRPS, as proposed by Rangapuram (2021), to measure the accuracy of predicted quantiles y_hat compared to the observation y.\n\\[ \\mathrm{sCRPS}(\\hat{F}_{\\tau}, \\mathbf{y}_{\\tau}) = \\frac{2}{N} \\sum_{i}\n\\int^{1}_{0}\n\\frac{\\mathrm{QL}(\\hat{F}_{i,\\tau}, y_{i,\\tau})_{q}}{\\sum_{i} | y_{i,\\tau} |} dq \\]\n\nrec_model_names_nf = ['NBEATS/BottomUp', 'NBEATS/MinTrace_method-ols']\nrec_model_names_mf = ['XGBRegressor/BottomUp', 'XGBRegressor/MinTrace_method-ols']\n\nn_quantiles = len(quantiles)\nn_series = len(S_df)\n\nfor name in rec_model_names_nf:\n quantile_columns = [col for col in Y_rec_nf.columns if (name+'-lo') in col or (name+'-hi') in col]\n y_rec = Y_rec_nf[quantile_columns].values \n y_test = Y_test_df['y'].values\n\n y_rec = y_rec.reshape(n_series, horizon, n_quantiles)\n y_test = y_test.reshape(n_series, horizon)\n scrps = scaled_crps(y=y_test, y_hat=y_rec, quantiles=quantiles)\n print(\"{:<40} {:.5f}\".format(name+\":\", scrps))\n\nfor name in rec_model_names_mf:\n quantile_columns = [col for col in Y_rec_mf.columns if (name+'-lo') in col or (name+'-hi') in col]\n y_rec = Y_rec_mf[quantile_columns].values \n y_test = Y_test_df['y'].values\n\n y_rec = y_rec.reshape(n_series, horizon, n_quantiles)\n y_test = y_test.reshape(n_series, horizon)\n scrps = scaled_crps(y=y_test, y_hat=y_rec, quantiles=quantiles)\n print(\"{:<40} {:.5f}\".format(name+\":\", scrps))\n\nNBEATS/BottomUp: 0.12853\nNBEATS/MinTrace_method-ols: 0.12945\nXGBRegressor/BottomUp: 0.13202\nXGBRegressor/MinTrace_method-ols: 0.13417" + }, + { + "objectID": "examples/mlframeworksexample.html#visualizations", + "href": "examples/mlframeworksexample.html#visualizations", + "title": "Neural/MLForecast", + "section": "6. Visualizations", + "text": "6. Visualizations\n\nplot_nf = pd.concat([Y_df.set_index(['unique_id', 'ds']), \n Y_rec_nf.set_index('ds', append=True)], axis=1)\nplot_nf = plot_nf.reset_index('ds')\n\nplot_mf = pd.concat([Y_df.set_index(['unique_id', 'ds']), \n Y_rec_mf.set_index('ds', append=True)], axis=1)\nplot_mf = plot_mf.reset_index('ds')\n\n\nhplot.plot_series(\n series='TotalVis',\n Y_df=plot_nf, \n models=['y', 'NBEATS', 'NBEATS/BottomUp', 'NBEATS/MinTrace_method-ols'],\n level=[80]\n)\n\n\n\n\n\nhplot.plot_series(\n series='TotalVis',\n Y_df=plot_mf, \n models=['y', 'XGBRegressor', 'XGBRegressor/BottomUp', 'XGBRegressor/MinTrace_method-ols'],\n level=[80]\n)" + }, + { + "objectID": "examples/australianprisonpopulation.html", + "href": "examples/australianprisonpopulation.html", + "title": "Geographical Aggregation (Prison Population)", + "section": "", + "text": "In many applications, a set of time series is hierarchically organized. Examples include the presence of geographic levels, products, or categories that define different types of aggregations. In such scenarios, forecasters are often required to provide predictions for all disaggregate and aggregate series. A natural desire is for those predictions to be ā€œcoherentā€, that is, for the bottom series to add up precisely to the forecasts of the aggregated series.\nIn this notebook we present an example on how to use HierarchicalForecast to produce coherent forecasts between geographical levels. We will use the Australian Prison Population dataset.\nWe will first load the dataset and produce base forecasts using an ETS model from StatsForecast, and then reconciliate the forecasts with several reconciliation algorithms from HierarchicalForecast. Finally, we show the performance is comparable with the results reported by the Forecasting: Principles and Practice which uses the R package fable.\nYou can run these experiments using CPU or GPU with Google Colab.\n!pip install hierarchicalforecast\n!pip install -U statsforecast numba\nIf you find the code useful, please ā­ us on Github" + }, + { + "objectID": "examples/australianprisonpopulation.html#load-and-process-data", + "href": "examples/australianprisonpopulation.html#load-and-process-data", + "title": "Geographical Aggregation (Prison Population)", + "section": "1. Load and Process Data", + "text": "1. Load and Process Data\nThe dataset only contains the time series at the lowest level, so we need to create the time series for all hierarchies.\n\nimport numpy as np\nimport pandas as pd\n\n\nY_df = pd.read_csv('https://OTexts.com/fpp3/extrafiles/prison_population.csv')\nY_df = Y_df.rename({'Count': 'y', 'Date': 'ds'}, axis=1)\nY_df.insert(0, 'Country', 'Australia')\nY_df = Y_df[['Country', 'State', 'Gender', 'Legal', 'Indigenous', 'ds', 'y']]\nY_df['ds'] = pd.to_datetime(Y_df['ds'])\nY_df.head()\n\n\n\n\n\n\n\n\nCountry\nState\nGender\nLegal\nIndigenous\nds\ny\n\n\n\n\n0\nAustralia\nACT\nFemale\nRemanded\nATSI\n2005-03-01\n0\n\n\n1\nAustralia\nACT\nFemale\nRemanded\nNon-ATSI\n2005-03-01\n2\n\n\n2\nAustralia\nACT\nFemale\nSentenced\nATSI\n2005-03-01\n0\n\n\n3\nAustralia\nACT\nFemale\nSentenced\nNon-ATSI\n2005-03-01\n5\n\n\n4\nAustralia\nACT\nMale\nRemanded\nATSI\n2005-03-01\n7\n\n\n\n\n\n\n\nThe dataset can be grouped in the following grouped structure.\n\nhiers = [\n ['Country'],\n ['Country', 'State'], \n ['Country', 'Gender'], \n ['Country', 'Legal'], \n ['Country', 'State', 'Gender', 'Legal']\n]\n\nUsing the aggregate function from HierarchicalForecast we can get the full set of time series.\n\nfrom hierarchicalforecast.utils import aggregate\n\n\nY_df, S_df, tags = aggregate(Y_df, hiers)\nY_df['y'] = Y_df['y']/1e3\nY_df = Y_df.reset_index()\n\n\nY_df.head()\n\n\n\n\n\n\n\n\nunique_id\nds\ny\n\n\n\n\n0\nAustralia\n2005-03-01\n24.296\n\n\n1\nAustralia\n2005-06-01\n24.643\n\n\n2\nAustralia\n2005-09-01\n24.511\n\n\n3\nAustralia\n2005-12-01\n24.393\n\n\n4\nAustralia\n2006-03-01\n24.524\n\n\n\n\n\n\n\n\nS_df.iloc[:5, :5]\n\n\n\n\n\n\n\n\nAustralia/ACT/Female/Remanded\nAustralia/ACT/Female/Sentenced\nAustralia/ACT/Male/Remanded\nAustralia/ACT/Male/Sentenced\nAustralia/NSW/Female/Remanded\n\n\n\n\nAustralia\n1.0\n1.0\n1.0\n1.0\n1.0\n\n\nAustralia/ACT\n1.0\n1.0\n1.0\n1.0\n0.0\n\n\nAustralia/NSW\n0.0\n0.0\n0.0\n0.0\n1.0\n\n\nAustralia/NT\n0.0\n0.0\n0.0\n0.0\n0.0\n\n\nAustralia/QLD\n0.0\n0.0\n0.0\n0.0\n0.0\n\n\n\n\n\n\n\n\ntags\n\n{'Country': array(['Australia'], dtype=object),\n 'Country/State': array(['Australia/ACT', 'Australia/NSW', 'Australia/NT', 'Australia/QLD',\n 'Australia/SA', 'Australia/TAS', 'Australia/VIC', 'Australia/WA'],\n dtype=object),\n 'Country/Gender': array(['Australia/Female', 'Australia/Male'], dtype=object),\n 'Country/Legal': array(['Australia/Remanded', 'Australia/Sentenced'], dtype=object),\n 'Country/State/Gender/Legal': ['Australia/ACT/Female/Remanded',\n 'Australia/ACT/Female/Sentenced',\n 'Australia/ACT/Male/Remanded',\n 'Australia/ACT/Male/Sentenced',\n 'Australia/NSW/Female/Remanded',\n 'Australia/NSW/Female/Sentenced',\n 'Australia/NSW/Male/Remanded',\n 'Australia/NSW/Male/Sentenced',\n 'Australia/NT/Female/Remanded',\n 'Australia/NT/Female/Sentenced',\n 'Australia/NT/Male/Remanded',\n 'Australia/NT/Male/Sentenced',\n 'Australia/QLD/Female/Remanded',\n 'Australia/QLD/Female/Sentenced',\n 'Australia/QLD/Male/Remanded',\n 'Australia/QLD/Male/Sentenced',\n 'Australia/SA/Female/Remanded',\n 'Australia/SA/Female/Sentenced',\n 'Australia/SA/Male/Remanded',\n 'Australia/SA/Male/Sentenced',\n 'Australia/TAS/Female/Remanded',\n 'Australia/TAS/Female/Sentenced',\n 'Australia/TAS/Male/Remanded',\n 'Australia/TAS/Male/Sentenced',\n 'Australia/VIC/Female/Remanded',\n 'Australia/VIC/Female/Sentenced',\n 'Australia/VIC/Male/Remanded',\n 'Australia/VIC/Male/Sentenced',\n 'Australia/WA/Female/Remanded',\n 'Australia/WA/Female/Sentenced',\n 'Australia/WA/Male/Remanded',\n 'Australia/WA/Male/Sentenced']}\n\n\n\nSplit Train/Test sets\nWe use the final two years (8 quarters) as test set.\n\nY_test_df = Y_df.groupby('unique_id').tail(8)\nY_train_df = Y_df.drop(Y_test_df.index)\n\n\nY_test_df = Y_test_df.set_index('unique_id')\nY_train_df = Y_train_df.set_index('unique_id')" + }, + { + "objectID": "examples/australianprisonpopulation.html#computing-base-forecasts", + "href": "examples/australianprisonpopulation.html#computing-base-forecasts", + "title": "Geographical Aggregation (Prison Population)", + "section": "2. Computing base forecasts", + "text": "2. Computing base forecasts\nThe following cell computes the base forecasts for each time series in Y_df using the ETS model. Observe that Y_hat_df contains the forecasts but they are not coherent.\n\nfrom statsforecast.models import ETS\nfrom statsforecast.core import StatsForecast\n\n\nfcst = StatsForecast(df=Y_train_df,\n models=[ETS(season_length=4, model='ZMZ')], \n freq='QS', n_jobs=-1)\nY_hat_df = fcst.forecast(h=8, fitted=True)\nY_fitted_df = fcst.forecast_fitted_values()" + }, + { + "objectID": "examples/australianprisonpopulation.html#reconcile-forecasts", + "href": "examples/australianprisonpopulation.html#reconcile-forecasts", + "title": "Geographical Aggregation (Prison Population)", + "section": "3. Reconcile forecasts", + "text": "3. Reconcile forecasts\nThe following cell makes the previous forecasts coherent using the HierarchicalReconciliation class. Since the hierarchy structure is not strict, we canā€™t use methods such as TopDown or MiddleOut. In this example we use BottomUp and MinTrace.\n\nfrom hierarchicalforecast.methods import BottomUp, MinTrace\nfrom hierarchicalforecast.core import HierarchicalReconciliation\n\n\nreconcilers = [\n BottomUp(),\n MinTrace(method='mint_shrink')\n]\nhrec = HierarchicalReconciliation(reconcilers=reconcilers)\nY_rec_df = hrec.reconcile(Y_hat_df=Y_hat_df, Y_df=Y_fitted_df, S=S_df, tags=tags)\n\nThe dataframe Y_rec_df contains the reconciled forecasts.\n\nY_rec_df.head()\n\n\n\n\n\n\n\n\nds\nETS\nETS/BottomUp\nETS/MinTrace_method-mint_shrink\n\n\nunique_id\n\n\n\n\n\n\n\n\nAustralia\n2015-01-01\n34.799496\n34.933891\n34.927244\n\n\nAustralia\n2015-04-01\n35.192638\n35.473560\n35.440861\n\n\nAustralia\n2015-07-01\n35.188217\n35.687363\n35.476427\n\n\nAustralia\n2015-10-01\n35.888626\n36.010685\n35.946153\n\n\nAustralia\n2016-01-01\n36.045437\n36.400101\n36.244707" + }, + { + "objectID": "examples/australianprisonpopulation.html#evaluation", + "href": "examples/australianprisonpopulation.html#evaluation", + "title": "Geographical Aggregation (Prison Population)", + "section": "4. Evaluation", + "text": "4. Evaluation\nThe HierarchicalForecast package includes the HierarchicalEvaluation class to evaluate the different hierarchies and also is capable of compute scaled metrics compared to a benchmark model.\n\nfrom hierarchicalforecast.evaluation import HierarchicalEvaluation\n\n\ndef mase(y, y_hat, y_insample, seasonality=4):\n errors = np.mean(np.abs(y - y_hat), axis=1)\n scale = np.mean(np.abs(y_insample[:, seasonality:] - y_insample[:, :-seasonality]), axis=1)\n return np.mean(errors / scale)\n\neval_tags = {}\neval_tags['Total'] = tags['Country']\neval_tags['State'] = tags['Country/State']\neval_tags['Legal status'] = tags['Country/Legal']\neval_tags['Gender'] = tags['Country/Gender']\neval_tags['Bottom'] = tags['Country/State/Gender/Legal']\neval_tags['All series'] = np.concatenate(list(tags.values()))\n\nevaluator = HierarchicalEvaluation(evaluators=[mase])\nevaluation = evaluator.evaluate(\n Y_hat_df=Y_rec_df, Y_test_df=Y_test_df,\n tags=eval_tags,\n Y_df=Y_train_df\n)\nevaluation = evaluation.reset_index().drop(columns='metric').drop(0).set_index('level')\nevaluation.columns = ['Base', 'BottomUp', 'MinTrace(mint_shrink)']\nevaluation.applymap('{:.2f}'.format)\n\n\n\n\n\n\n\n\nBase\nBottomUp\nMinTrace(mint_shrink)\n\n\nlevel\n\n\n\n\n\n\n\nTotal\n1.36\n1.02\n1.16\n\n\nState\n1.54\n1.57\n1.61\n\n\nLegal status\n2.40\n2.50\n2.40\n\n\nGender\n1.08\n0.81\n0.95\n\n\nBottom\n2.17\n2.17\n2.16\n\n\nAll series\n2.00\n2.00\n2.00\n\n\n\n\n\n\n\n\nFable Comparison\nObserve that we can recover the results reported by the Forecasting: Principles and Practice book. The original results were calculated using the R package fable.\n\n\n\nFableā€™s reconciliation results\n\n\n\n\nReferences\n\nHyndman, R.J., & Athanasopoulos, G. (2021). ā€œForecasting: principles and practice, 3rd edition: Chapter 11: Forecasting hierarchical and grouped series.ā€. OTexts: Melbourne, Australia. OTexts.com/fpp3 Accessed on July 2022.\nRob Hyndman, Alan Lee, Earo Wang, Shanika Wickramasuriya, and Maintainer Earo Wang (2021). ā€œhts: Hierarchical and Grouped Time Seriesā€. URL https://CRAN.R-project.org/package=hts. R package version 0.3.1.\nMitchell Oā€™Hara-Wild, Rob Hyndman, Earo Wang, Gabriel Caceres, Tim-Gunnar Hensel, and Timothy Hyndman (2021). ā€œfable: Forecasting Models for Tidy Time Seriesā€. URL https://CRAN.R-project.org/package=fable. R package version 6.0.2." + }, + { + "objectID": "examples/nonnegativereconciliation.html", + "href": "examples/nonnegativereconciliation.html", + "title": "Non-Negative MinTrace", + "section": "", + "text": "Large collections of time series organized into structures at different aggregation levels often require their forecasts to follow their aggregation constraints and to be nonnegative, which poses the challenge of creating novel algorithms capable of coherent forecasts.\nThe HierarchicalForecast package provides a wide collection of Python implementations of hierarchical forecasting algorithms that follow nonnegative hierarchical reconciliation.\nIn this notebook, we will show how to use the HierarchicalForecast package to perform nonnegative reconciliation of forecasts on Wiki2 dataset.\nYou can run these experiments using CPU or GPU with Google Colab.\n!pip install hierarchicalforecast statsforecast datasetsforecast\nIf you find the code useful, please ā­ us on Github" + }, + { + "objectID": "examples/nonnegativereconciliation.html#load-data", + "href": "examples/nonnegativereconciliation.html#load-data", + "title": "Non-Negative MinTrace", + "section": "1. Load Data", + "text": "1. Load Data\nIn this example we will use the Wiki2 dataset. The following cell gets the time series for the different levels in the hierarchy, the summing dataframe S_df which recovers the full dataset from the bottom level hierarchy and the indices of each hierarchy denoted by tags.\n\nimport numpy as np\nimport pandas as pd\n\nfrom datasetsforecast.hierarchical import HierarchicalData\n\n\nY_df, S_df, tags = HierarchicalData.load('./data', 'Wiki2')\nY_df['ds'] = pd.to_datetime(Y_df['ds'])\n\n\nY_df.head()\n\n\n\n\n\n\n\n\nunique_id\nds\ny\n\n\n\n\n0\nTotal\n2016-01-01\n156508\n\n\n1\nTotal\n2016-01-02\n129902\n\n\n2\nTotal\n2016-01-03\n138203\n\n\n3\nTotal\n2016-01-04\n115017\n\n\n4\nTotal\n2016-01-05\n126042\n\n\n\n\n\n\n\n\nS_df.iloc[:5, :5]\n\n\n\n\n\n\n\n\nde_AAC_AAG_001\nde_AAC_AAG_010\nde_AAC_AAG_014\nde_AAC_AAG_045\nde_AAC_AAG_063\n\n\n\n\nTotal\n1\n1\n1\n1\n1\n\n\nde\n1\n1\n1\n1\n1\n\n\nen\n0\n0\n0\n0\n0\n\n\nfr\n0\n0\n0\n0\n0\n\n\nja\n0\n0\n0\n0\n0\n\n\n\n\n\n\n\n\ntags\n\n{'Views': array(['Total'], dtype=object),\n 'Views/Country': array(['de', 'en', 'fr', 'ja', 'ru', 'zh'], dtype=object),\n 'Views/Country/Access': array(['de_AAC', 'de_DES', 'de_MOB', 'en_AAC', 'en_DES', 'en_MOB',\n 'fr_AAC', 'fr_DES', 'fr_MOB', 'ja_AAC', 'ja_DES', 'ja_MOB',\n 'ru_AAC', 'ru_DES', 'ru_MOB', 'zh_AAC', 'zh_DES', 'zh_MOB'],\n dtype=object),\n 'Views/Country/Access/Agent': array(['de_AAC_AAG', 'de_AAC_SPD', 'de_DES_AAG', 'de_MOB_AAG',\n 'en_AAC_AAG', 'en_AAC_SPD', 'en_DES_AAG', 'en_MOB_AAG',\n 'fr_AAC_AAG', 'fr_AAC_SPD', 'fr_DES_AAG', 'fr_MOB_AAG',\n 'ja_AAC_AAG', 'ja_AAC_SPD', 'ja_DES_AAG', 'ja_MOB_AAG',\n 'ru_AAC_AAG', 'ru_AAC_SPD', 'ru_DES_AAG', 'ru_MOB_AAG',\n 'zh_AAC_AAG', 'zh_AAC_SPD', 'zh_DES_AAG', 'zh_MOB_AAG'],\n dtype=object),\n 'Views/Country/Access/Agent/Topic': array(['de_AAC_AAG_001', 'de_AAC_AAG_010', 'de_AAC_AAG_014',\n 'de_AAC_AAG_045', 'de_AAC_AAG_063', 'de_AAC_AAG_100',\n 'de_AAC_AAG_110', 'de_AAC_AAG_123', 'de_AAC_AAG_143',\n 'de_AAC_SPD_012', 'de_AAC_SPD_074', 'de_AAC_SPD_080',\n 'de_AAC_SPD_105', 'de_AAC_SPD_115', 'de_AAC_SPD_133',\n 'de_DES_AAG_064', 'de_DES_AAG_116', 'de_DES_AAG_131',\n 'de_MOB_AAG_015', 'de_MOB_AAG_020', 'de_MOB_AAG_032',\n 'de_MOB_AAG_059', 'de_MOB_AAG_062', 'de_MOB_AAG_088',\n 'de_MOB_AAG_095', 'de_MOB_AAG_109', 'de_MOB_AAG_122',\n 'de_MOB_AAG_149', 'en_AAC_AAG_044', 'en_AAC_AAG_049',\n 'en_AAC_AAG_075', 'en_AAC_AAG_114', 'en_AAC_AAG_119',\n 'en_AAC_AAG_141', 'en_AAC_SPD_004', 'en_AAC_SPD_011',\n 'en_AAC_SPD_026', 'en_AAC_SPD_048', 'en_AAC_SPD_067',\n 'en_AAC_SPD_126', 'en_AAC_SPD_140', 'en_DES_AAG_016',\n 'en_DES_AAG_024', 'en_DES_AAG_042', 'en_DES_AAG_069',\n 'en_DES_AAG_082', 'en_DES_AAG_102', 'en_MOB_AAG_018',\n 'en_MOB_AAG_022', 'en_MOB_AAG_101', 'en_MOB_AAG_124',\n 'fr_AAC_AAG_029', 'fr_AAC_AAG_046', 'fr_AAC_AAG_070',\n 'fr_AAC_AAG_087', 'fr_AAC_AAG_098', 'fr_AAC_AAG_104',\n 'fr_AAC_AAG_111', 'fr_AAC_AAG_112', 'fr_AAC_AAG_142',\n 'fr_AAC_SPD_025', 'fr_AAC_SPD_027', 'fr_AAC_SPD_035',\n 'fr_AAC_SPD_077', 'fr_AAC_SPD_084', 'fr_AAC_SPD_097',\n 'fr_AAC_SPD_130', 'fr_DES_AAG_023', 'fr_DES_AAG_043',\n 'fr_DES_AAG_051', 'fr_DES_AAG_058', 'fr_DES_AAG_061',\n 'fr_DES_AAG_091', 'fr_DES_AAG_093', 'fr_DES_AAG_094',\n 'fr_DES_AAG_136', 'fr_MOB_AAG_006', 'fr_MOB_AAG_030',\n 'fr_MOB_AAG_066', 'fr_MOB_AAG_117', 'fr_MOB_AAG_120',\n 'fr_MOB_AAG_121', 'fr_MOB_AAG_135', 'fr_MOB_AAG_147',\n 'ja_AAC_AAG_038', 'ja_AAC_AAG_047', 'ja_AAC_AAG_055',\n 'ja_AAC_AAG_076', 'ja_AAC_AAG_099', 'ja_AAC_AAG_128',\n 'ja_AAC_AAG_132', 'ja_AAC_AAG_134', 'ja_AAC_AAG_137',\n 'ja_AAC_SPD_013', 'ja_AAC_SPD_034', 'ja_AAC_SPD_050',\n 'ja_AAC_SPD_060', 'ja_AAC_SPD_078', 'ja_AAC_SPD_106',\n 'ja_DES_AAG_079', 'ja_DES_AAG_081', 'ja_DES_AAG_113',\n 'ja_MOB_AAG_065', 'ja_MOB_AAG_073', 'ja_MOB_AAG_092',\n 'ja_MOB_AAG_127', 'ja_MOB_AAG_129', 'ja_MOB_AAG_144',\n 'ru_AAC_AAG_008', 'ru_AAC_AAG_145', 'ru_AAC_AAG_146',\n 'ru_AAC_SPD_000', 'ru_AAC_SPD_090', 'ru_AAC_SPD_148',\n 'ru_DES_AAG_003', 'ru_DES_AAG_007', 'ru_DES_AAG_017',\n 'ru_DES_AAG_041', 'ru_DES_AAG_071', 'ru_DES_AAG_072',\n 'ru_MOB_AAG_002', 'ru_MOB_AAG_040', 'ru_MOB_AAG_083',\n 'ru_MOB_AAG_086', 'ru_MOB_AAG_103', 'ru_MOB_AAG_107',\n 'ru_MOB_AAG_118', 'ru_MOB_AAG_125', 'zh_AAC_AAG_021',\n 'zh_AAC_AAG_033', 'zh_AAC_AAG_037', 'zh_AAC_AAG_052',\n 'zh_AAC_AAG_057', 'zh_AAC_AAG_085', 'zh_AAC_AAG_108',\n 'zh_AAC_SPD_039', 'zh_AAC_SPD_096', 'zh_DES_AAG_009',\n 'zh_DES_AAG_019', 'zh_DES_AAG_053', 'zh_DES_AAG_054',\n 'zh_DES_AAG_056', 'zh_DES_AAG_068', 'zh_DES_AAG_089',\n 'zh_DES_AAG_139', 'zh_MOB_AAG_005', 'zh_MOB_AAG_028',\n 'zh_MOB_AAG_031', 'zh_MOB_AAG_036', 'zh_MOB_AAG_138'], dtype=object)}\n\n\nWe split the dataframe in train/test splits.\n\nY_test_df = Y_df.groupby('unique_id').tail(7)\nY_train_df = Y_df.drop(Y_test_df.index)\n\n\nY_test_df = Y_test_df.set_index('unique_id')\nY_train_df = Y_train_df.set_index('unique_id')" + }, + { + "objectID": "examples/nonnegativereconciliation.html#base-forecasts", + "href": "examples/nonnegativereconciliation.html#base-forecasts", + "title": "Non-Negative MinTrace", + "section": "2. Base Forecasts", + "text": "2. Base Forecasts\nThe following cell computes the base forecast for each time series using the ETS and naive models. Observe that Y_hat_df contains the forecasts but they are not coherent.\n\nfrom statsforecast.models import ETS, Naive\nfrom statsforecast.core import StatsForecast\n\n\nfcst = StatsForecast(\n df=Y_train_df, \n models=[ETS(season_length=7, model='ZAA'), Naive()], \n freq='D', \n n_jobs=-1\n)\nY_hat_df = fcst.forecast(h=7)\n\nObserve that the ETS model computes negative forecasts for some series.\n\nY_hat_df.query('ETS < 0')\n\n\n\n\n\n\n\n\nds\nETS\nNaive\n\n\nunique_id\n\n\n\n\n\n\n\nde_AAC_AAG_001\n2016-12-25\n-487.601532\n340.0\n\n\nde_AAC_AAG_001\n2016-12-26\n-215.634201\n340.0\n\n\nde_AAC_AAG_001\n2016-12-27\n-173.175613\n340.0\n\n\nde_AAC_AAG_001\n2016-12-30\n-290.836060\n340.0\n\n\nde_AAC_AAG_001\n2016-12-31\n-784.441040\n340.0\n\n\n...\n...\n...\n...\n\n\nzh_AAC_AAG_033\n2016-12-31\n-86.526421\n37.0\n\n\nzh_MOB\n2016-12-26\n-199.534882\n1036.0\n\n\nzh_MOB\n2016-12-27\n-69.527260\n1036.0\n\n\nzh_MOB_AAG\n2016-12-26\n-199.534882\n1036.0\n\n\nzh_MOB_AAG\n2016-12-27\n-69.527260\n1036.0\n\n\n\n\n99 rows Ɨ 3 columns" + }, + { + "objectID": "examples/nonnegativereconciliation.html#non-negative-reconciliation", + "href": "examples/nonnegativereconciliation.html#non-negative-reconciliation", + "title": "Non-Negative MinTrace", + "section": "3. Non-Negative Reconciliation", + "text": "3. Non-Negative Reconciliation\nThe following cell makes the previous forecasts coherent and nonnegative using the HierarchicalReconciliation class.\n\nfrom hierarchicalforecast.methods import MinTrace\nfrom hierarchicalforecast.core import HierarchicalReconciliation\n\n\nreconcilers = [\n MinTrace(method='ols'),\n MinTrace(method='ols', nonnegative=True)\n]\nhrec = HierarchicalReconciliation(reconcilers=reconcilers)\nY_rec_df = hrec.reconcile(Y_hat_df=Y_hat_df, Y_df=Y_train_df,\n S=S_df, tags=tags)\n\nObserve that the nonnegative reconciliation method obtains nonnegative forecasts.\n\nY_rec_df.query('`ETS/MinTrace_method-ols_nonnegative-True` < 0')\n\n\n\n\n\n\n\n\nds\nETS\nNaive\nETS/MinTrace_method-ols\nNaive/MinTrace_method-ols\nETS/MinTrace_method-ols_nonnegative-True\nNaive/MinTrace_method-ols_nonnegative-True\n\n\nunique_id\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\nThe free reconciliation method gets negative forecasts.\n\nY_rec_df.query('`ETS/MinTrace_method-ols` < 0')\n\n\n\n\n\n\n\n\nds\nETS\nNaive\nETS/MinTrace_method-ols\nNaive/MinTrace_method-ols\nETS/MinTrace_method-ols_nonnegative-True\nNaive/MinTrace_method-ols_nonnegative-True\n\n\nunique_id\n\n\n\n\n\n\n\n\n\n\n\nde_DES\n2016-12-25\n-2553.932861\n495.0\n-3468.745214\n495.0\n2.262540e-15\n495.0\n\n\nde_DES\n2016-12-26\n-2155.228271\n495.0\n-2985.587125\n495.0\n1.356705e-30\n495.0\n\n\nde_DES\n2016-12-27\n-2720.993896\n495.0\n-3698.680055\n495.0\n6.857413e-30\n495.0\n\n\nde_DES\n2016-12-29\n-3429.432617\n495.0\n-2965.207609\n495.0\n2.456449e+02\n495.0\n\n\nde_DES\n2016-12-30\n-3963.202637\n495.0\n-3217.360371\n495.0\n3.646790e+02\n495.0\n\n\n...\n...\n...\n...\n...\n...\n...\n...\n\n\nzh_MOB_AAG_036\n2016-12-26\n75.298317\n115.0\n-165.799776\n115.0\n3.207772e-14\n115.0\n\n\nzh_MOB_AAG_036\n2016-12-27\n72.895554\n115.0\n-134.340626\n115.0\n2.308198e-14\n115.0\n\n\nzh_MOB_AAG_138\n2016-12-25\n94.796623\n65.0\n-47.009813\n65.0\n3.116938e-14\n65.0\n\n\nzh_MOB_AAG_138\n2016-12-26\n71.293983\n65.0\n-169.804110\n65.0\n0.000000e+00\n65.0\n\n\nzh_MOB_AAG_138\n2016-12-27\n62.049744\n65.0\n-145.186436\n65.0\n0.000000e+00\n65.0\n\n\n\n\n240 rows Ɨ 7 columns" + }, + { + "objectID": "examples/nonnegativereconciliation.html#evaluation", + "href": "examples/nonnegativereconciliation.html#evaluation", + "title": "Non-Negative MinTrace", + "section": "4. Evaluation", + "text": "4. Evaluation\nThe HierarchicalForecast package includes the HierarchicalEvaluation class to evaluate the different hierarchies and also is capable of compute scaled metrics compared to a benchmark model.\n\nfrom hierarchicalforecast.evaluation import HierarchicalEvaluation\n\n\ndef mse(y, y_hat):\n return np.mean((y-y_hat)**2)\n\nevaluator = HierarchicalEvaluation(evaluators=[mse])\nevaluation = evaluator.evaluate(\n Y_hat_df=Y_rec_df, Y_test_df=Y_test_df, \n tags=tags, benchmark='Naive'\n)\nevaluation.filter(like='ETS', axis=1).T\n\n\n\n\n\n\n\nlevel\nOverall\nViews\nViews/Country\nViews/Country/Access\nViews/Country/Access/Agent\nViews/Country/Access/Agent/Topic\n\n\nmetric\nmse-scaled\nmse-scaled\nmse-scaled\nmse-scaled\nmse-scaled\nmse-scaled\n\n\n\n\nETS\n1.011585\n0.7358\n1.190354\n1.103657\n1.089515\n1.397139\n\n\nETS/MinTrace_method-ols\n0.979163\n0.698355\n1.062521\n1.143277\n1.113349\n1.354041\n\n\nETS/MinTrace_method-ols_nonnegative-True\n0.945075\n0.677892\n1.004639\n1.184719\n1.141442\n1.158672\n\n\n\n\n\n\n\nObserve that the nonnegative reconciliation method performs better that its unconstrained counterpart.\n\nReferences\n\nHyndman, R.J., & Athanasopoulos, G. (2021). ā€œForecasting: principles and practice, 3rd edition: Chapter 11: Forecasting hierarchical and grouped series.ā€. OTexts: Melbourne, Australia. OTexts.com/fpp3 Accessed on July 2022.\nWickramasuriya, S. L., Athanasopoulos, G., & Hyndman, R. J. (2019). \"Optimal forecast reconciliation for hierarchical and grouped time series through trace minimization\". Journal of the American Statistical Association, 114 , 804ā€“819. doi:10.1080/01621459.2018.1448825..\nWickramasuriya, S.L., Turlach, B.A. & Hyndman, R.J. (2020). \"Optimal non-negative forecast reconciliationā€. Stat Comput 30, 1167ā€“1182, https://doi.org/10.1007/s11222-020-09930-0." + }, + { + "objectID": "examples/australiandomestictourism.html", + "href": "examples/australiandomestictourism.html", + "title": "Geographical Aggregation (Tourism)", + "section": "", + "text": "In many applications, a set of time series is hierarchically organized. Examples include the presence of geographic levels, products, or categories that define different types of aggregations. In such scenarios, forecasters are often required to provide predictions for all disaggregate and aggregate series. A natural desire is for those predictions to be ā€œcoherentā€, that is, for the bottom series to add up precisely to the forecasts of the aggregated series.\nIn this notebook we present an example on how to use HierarchicalForecast to produce coherent forecasts between geographical levels. We will use the classic Australian Domestic Tourism (Tourism) dataset, which contains monthly time series of the number of visitors to each state of Australia.\nWe will first load the Tourism data and produce base forecasts using an ETS model from StatsForecast, and then reconciliate the forecasts with several reconciliation algorithms from HierarchicalForecast. Finally, we show the performance is comparable with the results reported by the Forecasting: Principles and Practice which uses the R package fable.\nYou can run these experiments using CPU or GPU with Google Colab.\n!pip install hierarchicalforecast\n!pip install -U statsforecast numba\nIf you find the code useful, please ā­ us on Github" + }, + { + "objectID": "examples/australiandomestictourism.html#load-and-process-data", + "href": "examples/australiandomestictourism.html#load-and-process-data", + "title": "Geographical Aggregation (Tourism)", + "section": "1. Load and Process Data", + "text": "1. Load and Process Data\nIn this example we will use the Tourism dataset from the Forecasting: Principles and Practice book.\nThe dataset only contains the time series at the lowest level, so we need to create the time series for all hierarchies.\n\nimport numpy as np\nimport pandas as pd\n\n\nY_df = pd.read_csv('https://raw.githubusercontent.com/Nixtla/transfer-learning-time-series/main/datasets/tourism.csv')\nY_df = Y_df.rename({'Trips': 'y', 'Quarter': 'ds'}, axis=1)\nY_df.insert(0, 'Country', 'Australia')\nY_df = Y_df[['Country', 'Region', 'State', 'Purpose', 'ds', 'y']]\nY_df['ds'] = Y_df['ds'].str.replace(r'(\\d+) (Q\\d)', r'\\1-\\2', regex=True)\nY_df['ds'] = pd.to_datetime(Y_df['ds'])\nY_df.head()\n\n\n\n\n\n\n\n\nCountry\nRegion\nState\nPurpose\nds\ny\n\n\n\n\n0\nAustralia\nAdelaide\nSouth Australia\nBusiness\n1998-01-01\n135.077690\n\n\n1\nAustralia\nAdelaide\nSouth Australia\nBusiness\n1998-04-01\n109.987316\n\n\n2\nAustralia\nAdelaide\nSouth Australia\nBusiness\n1998-07-01\n166.034687\n\n\n3\nAustralia\nAdelaide\nSouth Australia\nBusiness\n1998-10-01\n127.160464\n\n\n4\nAustralia\nAdelaide\nSouth Australia\nBusiness\n1999-01-01\n137.448533\n\n\n\n\n\n\n\nThe dataset can be grouped in the following non-strictly hierarchical structure.\n\nspec = [\n ['Country'],\n ['Country', 'State'], \n ['Country', 'Purpose'], \n ['Country', 'State', 'Region'], \n ['Country', 'State', 'Purpose'], \n ['Country', 'State', 'Region', 'Purpose']\n]\n\nUsing the aggregate function from HierarchicalForecast we can get the full set of time series.\n\nfrom hierarchicalforecast.utils import aggregate\n\n\nY_df, S_df, tags = aggregate(Y_df, spec)\nY_df = Y_df.reset_index()\n\n\nY_df.head()\n\n\n\n\n\n\n\n\nunique_id\nds\ny\n\n\n\n\n0\nAustralia\n1998-01-01\n23182.197269\n\n\n1\nAustralia\n1998-04-01\n20323.380067\n\n\n2\nAustralia\n1998-07-01\n19826.640511\n\n\n3\nAustralia\n1998-10-01\n20830.129891\n\n\n4\nAustralia\n1999-01-01\n22087.353380\n\n\n\n\n\n\n\n\nS_df.iloc[:5, :5]\n\n\n\n\n\n\n\n\nAustralia/ACT/Canberra/Business\nAustralia/ACT/Canberra/Holiday\nAustralia/ACT/Canberra/Other\nAustralia/ACT/Canberra/Visiting\nAustralia/New South Wales/Blue Mountains/Business\n\n\n\n\nAustralia\n1.0\n1.0\n1.0\n1.0\n1.0\n\n\nAustralia/ACT\n1.0\n1.0\n1.0\n1.0\n0.0\n\n\nAustralia/New South Wales\n0.0\n0.0\n0.0\n0.0\n1.0\n\n\nAustralia/Northern Territory\n0.0\n0.0\n0.0\n0.0\n0.0\n\n\nAustralia/Queensland\n0.0\n0.0\n0.0\n0.0\n0.0\n\n\n\n\n\n\n\n\ntags['Country/Purpose']\n\narray(['Australia/Business', 'Australia/Holiday', 'Australia/Other',\n 'Australia/Visiting'], dtype=object)\n\n\n\nSplit Train/Test sets\nWe use the final two years (8 quarters) as test set.\n\nY_test_df = Y_df.groupby('unique_id').tail(8)\nY_train_df = Y_df.drop(Y_test_df.index)\n\n\nY_test_df = Y_test_df.set_index('unique_id')\nY_train_df = Y_train_df.set_index('unique_id')\n\n\nY_train_df.groupby('unique_id').size()\n\nunique_id\nAustralia 72\nAustralia/ACT 72\nAustralia/ACT/Business 72\nAustralia/ACT/Canberra 72\nAustralia/ACT/Canberra/Business 72\n ..\nAustralia/Western Australia/Experience Perth/Other 72\nAustralia/Western Australia/Experience Perth/Visiting 72\nAustralia/Western Australia/Holiday 72\nAustralia/Western Australia/Other 72\nAustralia/Western Australia/Visiting 72\nLength: 425, dtype: int64" + }, + { + "objectID": "examples/australiandomestictourism.html#computing-base-forecasts", + "href": "examples/australiandomestictourism.html#computing-base-forecasts", + "title": "Geographical Aggregation (Tourism)", + "section": "2. Computing base forecasts", + "text": "2. Computing base forecasts\nThe following cell computes the base forecasts for each time series in Y_df using the ETS model. Observe that Y_hat_df contains the forecasts but they are not coherent.\n\nfrom statsforecast.models import ETS\nfrom statsforecast.core import StatsForecast\n\n\nfcst = StatsForecast(df=Y_train_df, \n models=[ETS(season_length=4, model='ZZA')], \n freq='QS', n_jobs=-1)\nY_hat_df = fcst.forecast(h=8, fitted=True)\nY_fitted_df = fcst.forecast_fitted_values()" + }, + { + "objectID": "examples/australiandomestictourism.html#reconcile-forecasts", + "href": "examples/australiandomestictourism.html#reconcile-forecasts", + "title": "Geographical Aggregation (Tourism)", + "section": "3. Reconcile forecasts", + "text": "3. Reconcile forecasts\nThe following cell makes the previous forecasts coherent using the HierarchicalReconciliation class. Since the hierarchy structure is not strict, we canā€™t use methods such as TopDown or MiddleOut. In this example we use BottomUp and MinTrace.\n\nfrom hierarchicalforecast.methods import BottomUp, MinTrace\nfrom hierarchicalforecast.core import HierarchicalReconciliation\n\n\nreconcilers = [\n BottomUp(),\n MinTrace(method='mint_shrink'),\n MinTrace(method='ols')\n]\nhrec = HierarchicalReconciliation(reconcilers=reconcilers)\nY_rec_df = hrec.reconcile(Y_hat_df=Y_hat_df, Y_df=Y_fitted_df, S=S_df, tags=tags)\n\nThe dataframe Y_rec_df contains the reconciled forecasts.\n\nY_rec_df.head()\n\n\n\n\n\n\n\n\nds\nETS\nETS/BottomUp\nETS/MinTrace_method-mint_shrink\nETS/MinTrace_method-ols\n\n\nunique_id\n\n\n\n\n\n\n\n\n\nAustralia\n2016-01-01\n25990.068359\n24379.679688\n25438.888351\n25894.418893\n\n\nAustralia\n2016-04-01\n24458.490234\n22902.664062\n23925.188541\n24357.230480\n\n\nAustralia\n2016-07-01\n23974.056641\n22412.984375\n23440.310338\n23865.929521\n\n\nAustralia\n2016-10-01\n24563.455078\n23127.638672\n24101.001833\n24470.783968\n\n\nAustralia\n2017-01-01\n25990.068359\n24516.175781\n25556.667616\n25901.382401" + }, + { + "objectID": "examples/australiandomestictourism.html#evaluation", + "href": "examples/australiandomestictourism.html#evaluation", + "title": "Geographical Aggregation (Tourism)", + "section": "4. Evaluation", + "text": "4. Evaluation\nThe HierarchicalForecast package includes the HierarchicalEvaluation class to evaluate the different hierarchies and also is capable of compute scaled metrics compared to a benchmark model.\n\nfrom hierarchicalforecast.evaluation import HierarchicalEvaluation\n\n\ndef rmse(y, y_hat):\n return np.mean(np.sqrt(np.mean((y-y_hat)**2, axis=1)))\n\ndef mase(y, y_hat, y_insample, seasonality=4):\n errors = np.mean(np.abs(y - y_hat), axis=1)\n scale = np.mean(np.abs(y_insample[:, seasonality:] - y_insample[:, :-seasonality]), axis=1)\n return np.mean(errors / scale)\n\neval_tags = {}\neval_tags['Total'] = tags['Country']\neval_tags['Purpose'] = tags['Country/Purpose']\neval_tags['State'] = tags['Country/State']\neval_tags['Regions'] = tags['Country/State/Region']\neval_tags['Bottom'] = tags['Country/State/Region/Purpose']\neval_tags['All'] = np.concatenate(list(tags.values()))\n\nevaluator = HierarchicalEvaluation(evaluators=[rmse, mase])\nevaluation = evaluator.evaluate(\n Y_hat_df=Y_rec_df, Y_test_df=Y_test_df,\n tags=eval_tags, Y_df=Y_train_df\n)\nevaluation = evaluation.drop('Overall')\nevaluation.columns = ['Base', 'BottomUp', 'MinTrace(mint_shrink)', 'MinTrace(ols)']\nevaluation = evaluation.applymap('{:.2f}'.format)\n\n/var/folders/rp/97y9_3ns23v01hdn0rp9ndw40000gp/T/ipykernel_46857/2768439279.py:22: PerformanceWarning: dropping on a non-lexsorted multi-index without a level parameter may impact performance.\n evaluation = evaluation.drop('Overall')\n\n\n\nRMSE\nThe following table shows the performance measured using RMSE across levels for each reconciliation method.\n\nevaluation.query('metric == \"rmse\"')\n\n\n\n\n\n\n\n\n\nBase\nBottomUp\nMinTrace(mint_shrink)\nMinTrace(ols)\n\n\nlevel\nmetric\n\n\n\n\n\n\n\n\nTotal\nrmse\n1743.29\n3028.93\n2102.47\n1818.94\n\n\nPurpose\nrmse\n534.75\n791.28\n574.84\n515.53\n\n\nState\nrmse\n308.15\n413.43\n315.89\n287.34\n\n\nRegions\nrmse\n51.65\n55.14\n46.48\n46.29\n\n\nBottom\nrmse\n19.37\n19.37\n17.78\n18.19\n\n\nAll\nrmse\n45.19\n54.95\n44.59\n42.71\n\n\n\n\n\n\n\n\n\nMASE\nThe following table shows the performance measured using MASE across levels for each reconciliation method.\n\nevaluation.query('metric == \"mase\"')\n\n\n\n\n\n\n\n\n\nBase\nBottomUp\nMinTrace(mint_shrink)\nMinTrace(ols)\n\n\nlevel\nmetric\n\n\n\n\n\n\n\n\nTotal\nmase\n1.59\n3.16\n2.05\n1.67\n\n\nPurpose\nmase\n1.32\n2.28\n1.48\n1.25\n\n\nState\nmase\n1.39\n1.90\n1.39\n1.25\n\n\nRegions\nmase\n1.12\n1.19\n1.01\n0.99\n\n\nBottom\nmase\n0.98\n0.98\n0.94\n1.01\n\n\nAll\nmase\n1.03\n1.08\n0.98\n1.02\n\n\n\n\n\n\n\n\n\nComparison fable\nObserve that we can recover the results reported by the Forecasting: Principles and Practice. The original results were calculated using the R package fable.\n\n\n\nFableā€™s reconciliation results\n\n\n\n\nReferences\n\nHyndman, R.J., & Athanasopoulos, G. (2021). ā€œForecasting: principles and practice, 3rd edition: Chapter 11: Forecasting hierarchical and grouped series.ā€. OTexts: Melbourne, Australia. OTexts.com/fpp3 Accessed on July 2022.\nRob Hyndman, Alan Lee, Earo Wang, Shanika Wickramasuriya, and Maintainer Earo Wang (2021). ā€œhts: Hierarchical and Grouped Time Seriesā€. URL https://CRAN.R-project.org/package=hts. R package version 0.3.1.\nMitchell Oā€™Hara-Wild, Rob Hyndman, Earo Wang, Gabriel Caceres, Tim-Gunnar Hensel, and Timothy Hyndman (2021). ā€œfable: Forecasting Models for Tidy Time Seriesā€. URL https://CRAN.R-project.org/package=fable. R package version 6.0.2." + }, + { + "objectID": "index.html", + "href": "index.html", + "title": "Hierarchical Forecast šŸ‘‘", + "section": "", + "text": "Large collections of time series organized into structures at different aggregation levels often require their forecasts to follow their aggregation constraints, which poses the challenge of creating novel algorithms capable of coherent forecasts.\nHierarchicalForecast offers a collection of reconciliation methods, including BottomUp, TopDown, MiddleOut, MinTrace and ERM. And Probabilistic coherent predictions including Normality, Bootstrap, and PERMBU.\nIf you find the code useful, please ā­ us on Github" + }, + { + "objectID": "index.html#features", + "href": "index.html#features", + "title": "Hierarchical Forecast šŸ‘‘", + "section": "šŸŽŠ Features", + "text": "šŸŽŠ Features\n\nClassic reconciliation methods:\n\nBottomUp: Simple addition to the upper levels.\nTopDown: Distributes the top levels forecasts trough the hierarchies.\n\nAlternative reconciliation methods:\n\nMiddleOut: It anchors the base predictions in a middle level. The levels above the base predictions use the bottom-up approach, while the levels below use a top-down.\nMinTrace: Minimizes the total forecast variance of the space of coherent forecasts, with the Minimum Trace reconciliation.\nERM: Optimizes the reconciliation matrix minimizing an L1 regularized objective.\n\nProbabilistic coherent methods:\n\nNormality: Uses MinTrace variance-covariance closed form matrix under a normality assumption.\nBootstrap: Generates distribution of hierarchically reconciled predictions using Gamakumaraā€™s bootstrap approach.\nPERMBU: Reconciles independent sample predictions by reinjecting multivariate dependence with estimated rank permutation copulas, and performing a Bottom-Up aggregation.\n\n\nMissing something? Please open an issue here or write us in" + }, + { + "objectID": "index.html#why", + "href": "index.html#why", + "title": "Hierarchical Forecast šŸ‘‘", + "section": "šŸ“– Why?", + "text": "šŸ“– Why?\nShort: We want to contribute to the ML field by providing reliable baselines and benchmarks for hierarchical forecasting task in industry and academia. Hereā€™s the complete paper.\nVerbose: HierarchicalForecast integrates publicly available processed datasets, evaluation metrics, and a curated set of statistical baselines. In this library we provide usage examples and references to extensive experiments where we showcase the baselineā€™s use and evaluate the accuracy of their predictions. With this work, we hope to contribute to Machine Learning forecasting by bridging the gap to statistical and econometric modeling, as well as providing tools for the development of novel hierarchical forecasting algorithms rooted in a thorough comparison of these well-established models. We intend to continue maintaining and increasing the repository, promoting collaboration across the forecasting community." + }, + { + "objectID": "index.html#installation", + "href": "index.html#installation", + "title": "Hierarchical Forecast šŸ‘‘", + "section": "šŸ’» Installation", + "text": "šŸ’» Installation\n\nPyPI\nYou can install the released version of HierarchicalForecast from the Python package index with:\npip install hierarchicalforecast\n(Installing inside a python virtualenvironment or a conda environment is recommended.)\n\n\nConda\nAlso you can install the released version of HierarchicalForecast from conda with:\nconda install -c conda-forge hierarchicalforecast\n(Installing inside a python virtualenvironment or a conda environment is recommended.)\n\n\nDev Mode\nIf you want to make some modifications to the code and see the effects in real time (without reinstalling), follow the steps below:\ngit clone https://github.com/Nixtla/hierarchicalforecast.git\ncd hierarchicalforecast\npip install -e ." + }, + { + "objectID": "index.html#how-to-use", + "href": "index.html#how-to-use", + "title": "Hierarchical Forecast šŸ‘‘", + "section": "šŸ§¬ How to use", + "text": "šŸ§¬ How to use\nThe following example needs statsforecast and datasetsforecast as additional packages. If not installed, install it via your preferred method, e.g.Ā pip install statsforecast datasetsforecast. The datasetsforecast library allows us to download hierarhical datasets and we will use statsforecast to compute base forecasts to be reconciled.\nYou can open this example in Colab \nimport numpy as np\nimport pandas as pd\n\n#obtain hierarchical dataset\nfrom datasetsforecast.hierarchical import HierarchicalData\n\n# compute base forecast no coherent\nfrom statsforecast.core import StatsForecast\nfrom statsforecast.models import AutoARIMA, Naive\n\n#obtain hierarchical reconciliation methods and evaluation\nfrom hierarchicalforecast.core import HierarchicalReconciliation\nfrom hierarchicalforecast.evaluation import HierarchicalEvaluation\nfrom hierarchicalforecast.methods import BottomUp, TopDown, MiddleOut\n\n\n# Load TourismSmall dataset\nY_df, S, tags = HierarchicalData.load('./data', 'TourismSmall')\nY_df['ds'] = pd.to_datetime(Y_df['ds'])\n\n#split train/test sets\nY_test_df = Y_df.groupby('unique_id').tail(12)\nY_train_df = Y_df.drop(Y_test_df.index)\nY_test_df = Y_test_df.set_index('unique_id')\nY_train_df = Y_train_df.set_index('unique_id')\n\n# Compute base auto-ARIMA predictions\nfcst = StatsForecast(df=Y_train_df, \n models=[AutoARIMA(season_length=12), Naive()], \n freq='M', n_jobs=-1)\nY_hat_df = fcst.forecast(h=12)\n\n# Reconcile the base predictions\nreconcilers = [\n BottomUp(),\n TopDown(method='forecast_proportions'),\n MiddleOut(middle_level='Country/Purpose/State',\n top_down_method='forecast_proportions')\n]\nhrec = HierarchicalReconciliation(reconcilers=reconcilers)\nY_rec_df = hrec.reconcile(Y_hat_df=Y_hat_df, Y_df=Y_train_df, \n S=S, tags=tags)\n\nEvaluation\ndef mse(y, y_hat):\n return np.mean((y-y_hat)**2)\n\nevaluator = HierarchicalEvaluation(evaluators=[mse])\nevaluator.evaluate(Y_h=Y_rec_df, Y_test=Y_df_test, \n tags=tags, benchmark='Naive')" + }, + { + "objectID": "index.html#how-to-cite", + "href": "index.html#how-to-cite", + "title": "Hierarchical Forecast šŸ‘‘", + "section": "How to cite", + "text": "How to cite\nHereā€™s the complete paper.\n@article{olivares2022hierarchicalforecast,\n author = {Kin G. Olivares and\n Federico Garza and \n David Luo and \n Cristian ChallĆŗ and\n Max Mergenthaler and\n Souhaib Ben Taieb and\n Shanika L. Wickramasuriya and\n Artur Dubrawski},\n title = {{HierarchicalForecast}: A Reference Framework for Hierarchical Forecasting in Python},\n journal = {Work in progress paper, submitted to Journal of Machine Learning Research.},\n volume = {abs/2207.03517},\n year = {2022},\n url = {https://arxiv.org/abs/2207.03517},\n archivePrefix = {arXiv}\n}" + }, + { + "objectID": "utils.html", + "href": "utils.html", + "title": "Aggregation/Visualization Utils", + "section": "", + "text": "The HierarchicalForecast package contains utility functions to wrangle and visualize hierarchical series datasets. The aggregate function of the module allows you to create a hierarchy from categorical variables representing the structure levels, returning also the aggregation contraints matrix \\(\\mathbf{S}\\).\nIn addition, HierarchicalForecast ensures compatibility of its reconciliation methods with other popular machine-learning libraries via its external forecast adapters that transform output base forecasts from external libraries into a compatible data frame format.\n\n Aggregate Function \n\nsource\n\naggregate\n\n aggregate (df:pandas.core.frame.DataFrame, spec:List[List[str]],\n is_balanced:bool=False, sparse_s:bool=False)\n\nUtils Aggregation Function. Aggregates bottom level series contained in the pd.DataFrame df according to levels defined in the spec list applying the agg_fn (sum, mean).\nParameters: df: pd.DataFrame with columns ['ds', 'y'] and columns to aggregate. spec: List of levels. Each element of the list contains a list of columns of df to aggregate. is_balanced: bool=False, whether Y_bottom_df is balanced, if not we balance. sparse_s: bool=False, whether the returned S_df should be a sparse DataFrame. Returns: Y_df, S_df, tags: tuple with hierarchically structured series Y_df (\\(\\mathbf{y}_{[a,b]}\\)), summing dataframe S_df, and hierarchical aggregation indexes tags.\n\n\n\n Hierarchical Visualization \n\nsource\n\nHierarchicalPlot\n\n HierarchicalPlot (S:pandas.core.frame.DataFrame,\n tags:Dict[str,numpy.ndarray])\n\nHierarchical Plot\nThis class contains a collection of matplotlib visualization methods, suited for small to medium sized hierarchical series.\nParameters: S: pd.DataFrame with summing matrix of size (base, bottom), see aggregate function. tags: np.ndarray, with hierarchical aggregation indexes, where each key is a level and its value contains tags associated to that level.\n\nsource\n\n\nplot_summing_matrix\n\n plot_summing_matrix ()\n\nSummation Constraints plot\nThis method simply plots the hierarchical aggregation constraints matrix \\(\\mathbf{S}\\).\n\nsource\n\n\nplot_series\n\n plot_series (series:str, Y_df:Optional[pandas.core.frame.DataFrame]=None,\n models:Optional[List[str]]=None,\n level:Optional[List[int]]=None)\n\nSingle Series plot\nParameters: series: str, string identifying the 'unique_id' any-level series to plot. Y_df: pd.DataFrame, hierarchically structured series (\\(\\mathbf{y}_{[a,b]}\\)). It contains columns ['unique_id', 'ds', 'y'], it may have 'models'. models: List[str], string identifying filtering model columns. level: float list 0-100, confidence levels for prediction intervals available in Y_df.\nReturns: Single series plot with filtered models and prediction interval level.\n\nsource\n\n\nplot_hierarchically_linked_series\n\n plot_hierarchically_linked_series (bottom_series:str,\n Y_df:Optional[pandas.core.frame.DataFr\n ame]=None,\n models:Optional[List[str]]=None,\n level:Optional[List[int]]=None)\n\nHierarchically Linked Series plot\nParameters: bottom_series: str, string identifying the 'unique_id' bottom-level series to plot. Y_df: pd.DataFrame, hierarchically structured series (\\(\\mathbf{y}_{[a,b]}\\)). It contains columns [ā€˜unique_idā€™, ā€˜dsā€™, ā€˜yā€™] and models. models: List[str], string identifying filtering model columns. level: float list 0-100, confidence levels for prediction intervals available in Y_df.\nReturns: Collection of hierarchilly linked series plots associated with the bottom_series and filtered models and prediction interval level.\n\nsource\n\n\nplot_hierarchical_predictions_gap\n\n plot_hierarchical_predictions_gap (Y_df:pandas.core.frame.DataFrame,\n models:Optional[List[str]]=None,\n xlabel:Optional=None,\n ylabel:Optional=None)\n\nHierarchically Predictions Gap plot\nParameters: Y_df: pd.DataFrame, hierarchically structured series (\\(\\mathbf{y}_{[a,b]}\\)). It contains columns [ā€˜unique_idā€™, ā€˜dsā€™, ā€˜yā€™] and models. models: List[str], string identifying filtering model columns. xlabel: str, string for the plotā€™s x axis label. ylable: str, string for the plotā€™s y axis label.\nReturns: Plots of aggregated predictions at different levels of the hierarchical structure. The aggregation is performed according to the tag levels see aggregate function.\n\nfrom statsforecast.core import StatsForecast\nfrom statsforecast.models import AutoARIMA, ETS, Naive\nfrom datasetsforecast.hierarchical import HierarchicalData\n\nY_df, S, tags = HierarchicalData.load('./data', 'Labour')\nY_df['ds'] = pd.to_datetime(Y_df['ds'])\n\nY_test_df = Y_df.groupby('unique_id').tail(24)\nY_train_df = Y_df.drop(Y_test_df.index)\nY_test_df = Y_test_df.set_index('unique_id')\nY_train_df = Y_train_df.set_index('unique_id')\n\nfcst = StatsForecast(\n df=Y_train_df, \n #models=[AutoARIMA(season_length=12), Naive()], \n models=[ETS(season_length=12, model='AAZ')],\n freq='MS', \n n_jobs=-1\n)\nY_hat_df = fcst.forecast(h=24)\n\n# Plot prediction difference of different aggregation\n# Levels Country, Country/Region, Country/Gender/Region ...\nhplots = HierarchicalPlot(S=S, tags=tags)\n\nhplots.plot_hierarchical_predictions_gap(\n Y_df=Y_hat_df, models='ETS',\n xlabel='Month', ylabel='Predictions',\n)\n\n\n\n\n External Forecast Adapters \n\nsource\n\nsamples_to_quantiles_df\n\n samples_to_quantiles_df (samples:numpy.ndarray, unique_ids:Iterable[str],\n dates:Iterable,\n quantiles:Optional[Iterable[float]]=None,\n level:Optional[Iterable[int]]=None,\n model_name:Optional[str]='model')\n\nTransform Random Samples into HierarchicalForecast input. Auxiliary function to create compatible HierarchicalForecast input Y_hat_df dataframe.\nParameters: samples: numpy array. Samples from forecast distribution of shape [n_series, n_samples, horizon]. unique_ids: string list. Unique identifiers for each time series. dates: datetime list. List of forecast dates. quantiles: float list in [0., 1.]. Alternative to level, quantiles to estimate from y distribution. level: int list in [0,100]. Probability levels for prediction intervals. model_name: string. Name of forecasting model.\nReturns: quantiles: float list in [0., 1.]. quantiles to estimate from y distribution . Y_hat_df: pd.DataFrame. With base quantile forecasts with columns ds and models to reconcile indexed by unique_id.\n\n\n\n\n\nIf you find the code useful, please ā­ us on Github" + }, + { + "objectID": "methods.html", + "href": "methods.html", + "title": " Reconciliation Methods ", + "section": "", + "text": "Large collections of time series organized into structures at different aggregation levels often require their forecasts to follow their aggregation constraints, which poses the challenge of creating novel algorithms capable of coherent forecasts. The HierarchicalForecast package provides the most comprehensive collection of Python implementations of hierarchical forecasting algorithms that follow classic hierarchical reconciliation. All the methods have a reconcile function capable of reconcile base forecasts using numpy arrays.\nMost reconciliation methods can be described by the following convenient linear algebra notation:\n\\[\\tilde{\\mathbf{y}}_{[a,b],\\tau} = \\mathbf{S}_{[a,b][b]} \\mathbf{P}_{[b][a,b]} \\hat{\\mathbf{y}}_{[a,b],\\tau}\\]\nwhere \\(a, b\\) represent the aggregate and bottom levels, \\(\\mathbf{S}_{[a,b][b]}\\) contains the hierarchical aggregation constraints, and \\(\\mathbf{P}_{[b][a,b]}\\) varies across reconciliation methods. The reconciled predictions are \\(\\tilde{\\mathbf{y}}_{[a,b],\\tau}\\), and the base predictions \\(\\hat{\\mathbf{y}}_{[a,b],\\tau}\\).\n\n 1. Bottom-Up \n\nsource\n\nBottomUpSparse\n\n BottomUpSparse ()\n\nBottomUpSparse Reconciliation Class.\nThis is the implementation of a Bottom Up reconciliation using the sparse matrix approach. It works much more efficient on datasets with many time series. [makoren: At least I hope so, I only checked up until ~20k time series, and thereā€™s no real improvement, it would be great to check for smth like 1M time series, where the dense S matrix really stops fitting in memory]\nSee the parent class for more details.\n\nsource\n\n\nBottomUp\n\n BottomUp ()\n\nBottom Up Reconciliation Class. The most basic hierarchical reconciliation is performed using an Bottom-Up strategy. It was proposed for the first time by Orcutt in 1968. The corresponding hierarchical ā€œprojectionā€ matrix is defined as: \\[\\mathbf{P}_{\\text{BU}} = [\\mathbf{0}_{\\mathrm{[b],[a]}}\\;|\\;\\mathbf{I}_{\\mathrm{[b][b]}}]\\]\nParameters: None\nReferences: - Orcutt, G.H., Watts, H.W., & Edwards, J.B.(1968). ā€œData aggregation and information lossā€. The American Economic Review, 58 , 773{787).\n\nsource\n\n\nBottomUp.fit\n\n BottomUp.fit (S:numpy.ndarray, y_hat:numpy.ndarray,\n idx_bottom:numpy.ndarray,\n y_insample:Optional[numpy.ndarray]=None,\n y_hat_insample:Optional[numpy.ndarray]=None,\n sigmah:Optional[numpy.ndarray]=None,\n intervals_method:Optional[str]=None,\n num_samples:Optional[int]=None, seed:Optional[int]=None,\n tags:Dict[str,numpy.ndarray]=None)\n\nBottom Up Fit Method.\nParameters: S: Summing matrix of size (base, bottom). y_hat: Forecast values of size (base, horizon). idx_bottom: Indices corresponding to the bottom level of S, size (bottom). level: float list 0-100, confidence levels for prediction intervals. intervals_method: Sampler for prediction intevals, one of normality, bootstrap, permbu. **sampler_kwargs: Coherent sampler instantiation arguments.\nReturns: self: object, fitted reconciler.\n\nsource\n\n\nBottomUp.predict\n\n BottomUp.predict (S:numpy.ndarray, y_hat:numpy.ndarray,\n level:Optional[List[int]]=None)\n\nPredict using reconciler.\nPredict using fitted mean and probabilistic reconcilers.\nParameters: S: Summing matrix of size (base, bottom). y_hat: Forecast values of size (base, horizon). level: float list 0-100, confidence levels for prediction intervals.\nReturns: y_tilde: Reconciliated predictions.\n\nsource\n\n\nBottomUp.fit_predict\n\n BottomUp.fit_predict (S:numpy.ndarray, y_hat:numpy.ndarray,\n idx_bottom:numpy.ndarray,\n y_insample:Optional[numpy.ndarray]=None,\n y_hat_insample:Optional[numpy.ndarray]=None,\n sigmah:Optional[numpy.ndarray]=None,\n level:Optional[List[int]]=None,\n intervals_method:Optional[str]=None,\n num_samples:Optional[int]=None,\n seed:Optional[int]=None,\n tags:Dict[str,numpy.ndarray]=None)\n\nBottomUp Reconciliation Method.\nParameters: S: Summing matrix of size (base, bottom). y_hat: Forecast values of size (base, horizon). idx_bottom: Indices corresponding to the bottom level of S, size (bottom). level: float list 0-100, confidence levels for prediction intervals. intervals_method: Sampler for prediction intevals, one of normality, bootstrap, permbu. **sampler_kwargs: Coherent sampler instantiation arguments.\nReturns: y_tilde: Reconciliated y_hat using the Bottom Up approach.\n\nsource\n\n\nBottomUp.sample\n\n BottomUp.sample (num_samples:int)\n\nSample probabilistic coherent distribution.\nGenerates n samples from a probabilistic coherent distribution. The method uses fitted mean and probabilistic reconcilers, defined by the intervals_method selected during the reconcilerā€™s instantiation. Currently available: normality, bootstrap, permbu.\nParameters: num_samples: int, number of samples generated from coherent distribution.\nReturns: samples: Coherent samples of size (num_series, horizon, num_samples).\n\n\n\n 2. Top-Down \n\nsource\n\nTopDown\n\n TopDown (method:str)\n\nTop Down Reconciliation Class.\nThe Top Down hierarchical reconciliation method, distributes the total aggregate predictions and decomposes it down the hierarchy using proportions \\(\\mathbf{p}_{\\mathrm{[b]}}\\) that can be actual historical values or estimated.\n\\[\\mathbf{P}=[\\mathbf{p}_{\\mathrm{[b]}}\\;|\\;\\mathbf{0}_{\\mathrm{[b][a,b\\;-1]}}]\\] Parameters: method: One of forecast_proportions, average_proportions and proportion_averages.\nReferences: - CW. Gross (1990). ā€œDisaggregation methods to expedite product line forecastingā€. Journal of Forecasting, 9 , 233ā€“254. doi:10.1002/for.3980090304. - G. Fliedner (1999). ā€œAn investigation of aggregate variable time series forecast strategies with specific subaggregate time series statistical correlationā€. Computers and Operations Research, 26 , 1133ā€“1149. doi:10.1016/S0305-0548(99)00017-9.\n\nsource\n\n\nTopDown.fit\n\n TopDown.fit (S, y_hat, y_insample:Optional[numpy.ndarray]=None,\n y_hat_insample:Optional[numpy.ndarray]=None,\n sigmah:Optional[numpy.ndarray]=None,\n intervals_method:Optional[str]=None,\n num_samples:Optional[int]=None, seed:Optional[int]=None,\n tags:Dict[str,numpy.ndarray]=None,\n idx_bottom:Optional[numpy.ndarray]=None)\n\nTopDown Fit Method.\nParameters: S: Summing matrix of size (base, bottom). y_hat: Forecast values of size (base, horizon). tags: Each key is a level and each value its S indices. y_insample: Insample values of size (base, insample_size). Optional for forecast_proportions method. idx_bottom: Indices corresponding to the bottom level of S, size (bottom). level: float list 0-100, confidence levels for prediction intervals. intervals_method: Sampler for prediction intevals, one of normality, bootstrap, permbu. **sampler_kwargs: Coherent sampler instantiation arguments.\nReturns: self: object, fitted reconciler.\n\nsource\n\n\nTopDown.predict\n\n TopDown.predict (S:numpy.ndarray, y_hat:numpy.ndarray,\n level:Optional[List[int]]=None)\n\nPredict using reconciler.\nPredict using fitted mean and probabilistic reconcilers.\nParameters: S: Summing matrix of size (base, bottom). y_hat: Forecast values of size (base, horizon). level: float list 0-100, confidence levels for prediction intervals.\nReturns: y_tilde: Reconciliated predictions.\n\nsource\n\n\nTopDown.fit_predict\n\n TopDown.fit_predict (S:numpy.ndarray, y_hat:numpy.ndarray,\n tags:Dict[str,numpy.ndarray],\n idx_bottom:numpy.ndarray=None,\n y_insample:Optional[numpy.ndarray]=None,\n y_hat_insample:Optional[numpy.ndarray]=None,\n sigmah:Optional[numpy.ndarray]=None,\n level:Optional[List[int]]=None,\n intervals_method:Optional[str]=None,\n num_samples:Optional[int]=None,\n seed:Optional[int]=None)\n\nTop Down Reconciliation Method.\nParameters: S: Summing matrix of size (base, bottom). y_hat: Forecast values of size (base, horizon). tags: Each key is a level and each value its S indices. y_insample: Insample values of size (base, insample_size). Optional for forecast_proportions method. idx_bottom: Indices corresponding to the bottom level of S, size (bottom). level: float list 0-100, confidence levels for prediction intervals. intervals_method: Sampler for prediction intevals, one of normality, bootstrap, permbu. **sampler_kwargs: Coherent sampler instantiation arguments.\nReturns: y_tilde: Reconciliated y_hat using the Top Down approach.\n\nsource\n\n\nTopDown.sample\n\n TopDown.sample (num_samples:int)\n\nSample probabilistic coherent distribution.\nGenerates n samples from a probabilistic coherent distribution. The method uses fitted mean and probabilistic reconcilers, defined by the intervals_method selected during the reconcilerā€™s instantiation. Currently available: normality, bootstrap, permbu.\nParameters: num_samples: int, number of samples generated from coherent distribution.\nReturns: samples: Coherent samples of size (num_series, horizon, num_samples).\n\n\n\n 3. Middle-Out \n\nsource\n\nMiddleOut\n\n MiddleOut (middle_level:str, top_down_method:str)\n\nMiddle Out Reconciliation Class.\nThis method is only available for strictly hierarchical structures. It anchors the base predictions in a middle level. The levels above the base predictions use the Bottom-Up approach, while the levels below use a Top-Down.\nParameters: middle_level: Middle level. top_down_method: One of forecast_proportions, average_proportions and proportion_averages.\nReferences: - Hyndman, R.J., & Athanasopoulos, G. (2021). ā€œForecasting: principles and practice, 3rd edition: Chapter 11: Forecasting hierarchical and grouped series.ā€. OTexts: Melbourne, Australia. OTexts.com/fpp3 Accessed on July 2022.\n\nsource\n\n\nMiddleOut.fit_predict\n\n MiddleOut.fit_predict (S:numpy.ndarray, y_hat:numpy.ndarray,\n tags:Dict[str,numpy.ndarray],\n y_insample:Optional[numpy.ndarray]=None,\n level:Optional[List[int]]=None,\n intervals_method:Optional[str]=None)\n\nMiddle Out Reconciliation Method.\nParameters: S: Summing matrix of size (base, bottom). y_hat: Forecast values of size (base, horizon). tags: Each key is a level and each value its S indices. y_insample: Insample values of size (base, insample_size). Only used for forecast_proportions\nReturns: y_tilde: Reconciliated y_hat using the Middle Out approach.\n\n\n\n 4. Min-Trace \n\nsource\n\nMinTraceSparse\n\n MinTraceSparse (method:str, nonnegative:bool=False,\n mint_shr_ridge:Optional[float]=2e-08)\n\nMinTraceSparse Reconciliation Class.\nThis is the implementation of a subset of MinTrace features using the sparse matrix approach. It works much more efficient on datasets with many time series.\nSee the parent class for more details.\nCurrently supported: * Methods using diagonal W matrix, i.e.Ā ā€œolsā€, ā€œwls_structā€, ā€œwls_varā€, * The standard MinT version (non-negative is not supported).\nNote: due to the numerical instability of the matrix inversion when creating the P matrix, the method is NOT guaranteed to give identical results to the non-sparse version.\n\nsource\n\n\nMinTrace\n\n MinTrace (method:str, nonnegative:bool=False,\n mint_shr_ridge:Optional[float]=2e-08)\n\nMinTrace Reconciliation Class.\nThis reconciliation algorithm proposed by Wickramasuriya et al.Ā depends on a generalized least squares estimator and an estimator of the covariance matrix of the coherency errors \\(\\mathbf{W}_{h}\\). The Min Trace algorithm minimizes the squared errors for the coherent forecasts under an unbiasedness assumption; the solution has a closed form.\n\\[\\mathbf{P}_{\\text{MinT}}=\\left(\\mathbf{S}^{\\intercal}\\mathbf{W}_{h}\\mathbf{S}\\right)^{-1}\n\\mathbf{S}^{\\intercal}\\mathbf{W}^{-1}_{h}\\]\nParameters: method: str, one of ols, wls_struct, wls_var, mint_shrink, mint_cov. nonnegative: bool, reconciled forecasts should be nonnegative? mint_shr_ridge: float=2e-8, ridge numeric protection to MinTrace-shr covariance estimator.\nReferences: - Wickramasuriya, S. L., Athanasopoulos, G., & Hyndman, R. J. (2019). ā€œOptimal forecast reconciliation for hierarchical and grouped time series through trace minimizationā€. Journal of the American Statistical Association, 114 , 804ā€“819. doi:10.1080/01621459.2018.1448825.. - Wickramasuriya, S.L., Turlach, B.A. & Hyndman, R.J. (2020). ā€œOptimal non-negative forecast reconciliationā€. Stat Comput 30, 1167ā€“1182, https://doi.org/10.1007/s11222-020-09930-0.\n\nsource\n\n\nMinTrace.fit\n\n MinTrace.fit (S, y_hat, y_insample:Optional[numpy.ndarray]=None,\n y_hat_insample:Optional[numpy.ndarray]=None,\n sigmah:Optional[numpy.ndarray]=None,\n intervals_method:Optional[str]=None,\n num_samples:Optional[int]=None, seed:Optional[int]=None,\n tags:Dict[str,numpy.ndarray]=None,\n idx_bottom:Optional[numpy.ndarray]=None)\n\nMinTrace Fit Method.\nParameters: S: Summing matrix of size (base, bottom). y_hat: Forecast values of size (base, horizon). tags: Each key is a level and each value its S indices. y_insample: Insample values of size (base, insample_size). Optional for forecast_proportions method. idx_bottom: Indices corresponding to the bottom level of S, size (bottom). level: float list 0-100, confidence levels for prediction intervals. intervals_method: Sampler for prediction intevals, one of normality, bootstrap, permbu. **sampler_kwargs: Coherent sampler instantiation arguments.\nReturns: self: object, fitted reconciler.\n\nsource\n\n\nMinTrace.predict\n\n MinTrace.predict (S:numpy.ndarray, y_hat:numpy.ndarray,\n level:Optional[List[int]]=None)\n\nPredict using reconciler.\nPredict using fitted mean and probabilistic reconcilers.\nParameters: S: Summing matrix of size (base, bottom). y_hat: Forecast values of size (base, horizon). level: float list 0-100, confidence levels for prediction intervals.\nReturns: y_tilde: Reconciliated predictions.\n\nsource\n\n\nMinTrace.fit_predict\n\n MinTrace.fit_predict (S:numpy.ndarray, y_hat:numpy.ndarray,\n idx_bottom:numpy.ndarray=None,\n y_insample:Optional[numpy.ndarray]=None,\n y_hat_insample:Optional[numpy.ndarray]=None,\n sigmah:Optional[numpy.ndarray]=None,\n level:Optional[List[int]]=None,\n intervals_method:Optional[str]=None,\n num_samples:Optional[int]=None,\n seed:Optional[int]=None,\n tags:Dict[str,numpy.ndarray]=None)\n\nMinTrace Reconciliation Method.\nParameters: S: Summing matrix of size (base, bottom). y_hat: Forecast values of size (base, horizon). y_insample: Insample values of size (base, insample_size). Only used by wls_var, mint_cov, mint_shrink y_hat_insample: Insample fitted values of size (base, insample_size). Only used by wls_var, mint_cov, mint_shrink idx_bottom: Indices corresponding to the bottom level of S, size (bottom). level: float list 0-100, confidence levels for prediction intervals. sampler: Sampler for prediction intevals, one of normality, bootstrap, permbu.\nReturns: y_tilde: Reconciliated y_hat using the MinTrace approach.\n\nsource\n\n\nMinTrace.sample\n\n MinTrace.sample (num_samples:int)\n\nSample probabilistic coherent distribution.\nGenerates n samples from a probabilistic coherent distribution. The method uses fitted mean and probabilistic reconcilers, defined by the intervals_method selected during the reconcilerā€™s instantiation. Currently available: normality, bootstrap, permbu.\nParameters: num_samples: int, number of samples generated from coherent distribution.\nReturns: samples: Coherent samples of size (num_series, horizon, num_samples).\n\n\n\n 5. Optimal Combination \n\nsource\n\nOptimalCombination\n\n OptimalCombination (method:str, nonnegative:bool=False)\n\nOptimal Combination Reconciliation Class.\nThis reconciliation algorithm was proposed by Hyndman et al.Ā 2011, the method uses generalized least squares estimator using the coherency errors covariance matrix. Consider the covariance of the base forecast \\(\\textrm{Var}(\\epsilon_{h}) = \\Sigma_{h}\\), the \\(\\mathbf{P}\\) matrix of this method is defined by: \\[ \\mathbf{P} = \\left(\\mathbf{S}^{\\intercal}\\Sigma_{h}^{\\dagger}\\mathbf{S}\\right)^{-1}\\mathbf{S}^{\\intercal}\\Sigma^{\\dagger}_{h}\\] where \\(\\Sigma_{h}^{\\dagger}\\) denotes the variance pseudo-inverse. The method was later proven equivalent to MinTrace variants.\nParameters: method: str, allowed optimal combination methods: ā€˜olsā€™, ā€˜wls_structā€™. nonnegative: bool, reconciled forecasts should be nonnegative?\nReferences: - Rob J. Hyndman, Roman A. Ahmed, George Athanasopoulos, Han Lin Shang (2010). ā€œOptimal Combination Forecasts for Hierarchical Time Seriesā€.. - Shanika L. Wickramasuriya, George Athanasopoulos and Rob J. Hyndman (2010). ā€œOptimal Combination Forecasts for Hierarchical Time Seriesā€.. - Wickramasuriya, S.L., Turlach, B.A. & Hyndman, R.J. (2020). ā€œOptimal non-negative forecast reconciliationā€. Stat Comput 30, 1167ā€“1182, https://doi.org/10.1007/s11222-020-09930-0.\n\nsource\n\n\nOptimalCombination.fit\n\n OptimalCombination.fit (S, y_hat,\n y_insample:Optional[numpy.ndarray]=None,\n y_hat_insample:Optional[numpy.ndarray]=None,\n sigmah:Optional[numpy.ndarray]=None,\n intervals_method:Optional[str]=None,\n num_samples:Optional[int]=None,\n seed:Optional[int]=None,\n tags:Dict[str,numpy.ndarray]=None,\n idx_bottom:Optional[numpy.ndarray]=None)\n\nMinTrace Fit Method.\nParameters: S: Summing matrix of size (base, bottom). y_hat: Forecast values of size (base, horizon). tags: Each key is a level and each value its S indices. y_insample: Insample values of size (base, insample_size). Optional for forecast_proportions method. idx_bottom: Indices corresponding to the bottom level of S, size (bottom). level: float list 0-100, confidence levels for prediction intervals. intervals_method: Sampler for prediction intevals, one of normality, bootstrap, permbu. **sampler_kwargs: Coherent sampler instantiation arguments.\nReturns: self: object, fitted reconciler.\n\nsource\n\n\nOptimalCombination.predict\n\n OptimalCombination.predict (S:numpy.ndarray, y_hat:numpy.ndarray,\n level:Optional[List[int]]=None)\n\nPredict using reconciler.\nPredict using fitted mean and probabilistic reconcilers.\nParameters: S: Summing matrix of size (base, bottom). y_hat: Forecast values of size (base, horizon). level: float list 0-100, confidence levels for prediction intervals.\nReturns: y_tilde: Reconciliated predictions.\n\nsource\n\n\nOptimalCombination.fit_predict\n\n OptimalCombination.fit_predict (S:numpy.ndarray, y_hat:numpy.ndarray,\n idx_bottom:numpy.ndarray=None,\n y_insample:Optional[numpy.ndarray]=None, \n y_hat_insample:Optional[numpy.ndarray]=No\n ne, sigmah:Optional[numpy.ndarray]=None,\n level:Optional[List[int]]=None,\n intervals_method:Optional[str]=None,\n num_samples:Optional[int]=None,\n seed:Optional[int]=None,\n tags:Dict[str,numpy.ndarray]=None)\n\nMinTrace Reconciliation Method.\nParameters: S: Summing matrix of size (base, bottom). y_hat: Forecast values of size (base, horizon). y_insample: Insample values of size (base, insample_size). Only used by wls_var, mint_cov, mint_shrink y_hat_insample: Insample fitted values of size (base, insample_size). Only used by wls_var, mint_cov, mint_shrink idx_bottom: Indices corresponding to the bottom level of S, size (bottom). level: float list 0-100, confidence levels for prediction intervals. sampler: Sampler for prediction intevals, one of normality, bootstrap, permbu.\nReturns: y_tilde: Reconciliated y_hat using the MinTrace approach.\n\nsource\n\n\nOptimalCombination.sample\n\n OptimalCombination.sample (num_samples:int)\n\nSample probabilistic coherent distribution.\nGenerates n samples from a probabilistic coherent distribution. The method uses fitted mean and probabilistic reconcilers, defined by the intervals_method selected during the reconcilerā€™s instantiation. Currently available: normality, bootstrap, permbu.\nParameters: num_samples: int, number of samples generated from coherent distribution.\nReturns: samples: Coherent samples of size (num_series, horizon, num_samples).\n\n\n\n 6. Emp. Risk Minimization \n\nsource\n\nERM\n\n ERM (method:str, lambda_reg:float=0.01)\n\nOptimal Combination Reconciliation Class.\nThe Empirical Risk Minimization reconciliation strategy relaxes the unbiasedness assumptions from previous reconciliation methods like MinT and optimizes square errors between the reconciled predictions and the validation data to obtain an optimal reconciliation matrix P.\nThe exact solution for \\(\\mathbf{P}\\) (method='closed') follows the expression: \\[\\mathbf{P}^{*} = \\left(\\mathbf{S}^{\\intercal}\\mathbf{S}\\right)^{-1}\\mathbf{Y}^{\\intercal}\\hat{\\mathbf{Y}}\\left(\\hat{\\mathbf{Y}}\\hat{\\mathbf{Y}}\\right)^{-1}\\]\nThe alternative Lasso regularized \\(\\mathbf{P}\\) solution (method='reg_bu') is useful when the observations of validation data is limited or the exact solution has low numerical stability. \\[\\mathbf{P}^{*} = \\text{argmin}_{\\mathbf{P}} ||\\mathbf{Y}-\\mathbf{S} \\mathbf{P} \\hat{Y} ||^{2}_{2} + \\lambda ||\\mathbf{P}-\\mathbf{P}_{\\text{BU}}||_{1}\\]\nParameters: method: str, one of closed, reg and reg_bu. lambda_reg: float, l1 regularizer for reg and reg_bu.\nReferences: - Ben Taieb, S., & Koo, B. (2019). Regularized regression for hierarchical forecasting without unbiasedness conditions. In Proceedings of the 25th ACM SIGKDD International Conference on Knowledge Discovery & Data Mining KDD ā€™19 (p.Ā 1337{1347). New York, NY, USA: Association for Computing Machinery..\n\nsource\n\n\nERM.fit\n\n ERM.fit (S, y_hat, y_insample, y_hat_insample,\n sigmah:Optional[numpy.ndarray]=None,\n intervals_method:Optional[str]=None,\n num_samples:Optional[int]=None, seed:Optional[int]=None,\n tags:Dict[str,numpy.ndarray]=None,\n idx_bottom:Optional[numpy.ndarray]=None)\n\nERM Fit Method.\nParameters: S: Summing matrix of size (base, bottom). y_hat: Forecast values of size (base, horizon). y_insample: Train values of size (base, insample_size). y_hat_insample: Insample train predictions of size (base, insample_size). idx_bottom: Indices corresponding to the bottom level of S, size (bottom). level: float list 0-100, confidence levels for prediction intervals. intervals_method: Sampler for prediction intevals, one of normality, bootstrap, permbu. **sampler_kwargs: Coherent sampler instantiation arguments.\nReturns: self: object, fitted reconciler.\n\nsource\n\n\nERM.predict\n\n ERM.predict (S:numpy.ndarray, y_hat:numpy.ndarray,\n level:Optional[List[int]]=None)\n\nPredict using reconciler.\nPredict using fitted mean and probabilistic reconcilers.\nParameters: S: Summing matrix of size (base, bottom). y_hat: Forecast values of size (base, horizon). level: float list 0-100, confidence levels for prediction intervals.\nReturns: y_tilde: Reconciliated predictions.\n\nsource\n\n\nERM.fit_predict\n\n ERM.fit_predict (S:numpy.ndarray, y_hat:numpy.ndarray,\n idx_bottom:numpy.ndarray=None,\n y_insample:Optional[numpy.ndarray]=None,\n y_hat_insample:Optional[numpy.ndarray]=None,\n sigmah:Optional[numpy.ndarray]=None,\n level:Optional[List[int]]=None,\n intervals_method:Optional[str]=None,\n num_samples:Optional[int]=None, seed:Optional[int]=None,\n tags:Dict[str,numpy.ndarray]=None)\n\nERM Reconciliation Method.\nParameters: S: Summing matrix of size (base, bottom). y_hat: Forecast values of size (base, horizon). y_insample: Train values of size (base, insample_size). y_hat_insample: Insample train predictions of size (base, insample_size). idx_bottom: Indices corresponding to the bottom level of S, size (bottom). level: float list 0-100, confidence levels for prediction intervals. intervals_method: Sampler for prediction intevals, one of normality, bootstrap, permbu.\nReturns: y_tilde: Reconciliated y_hat using the ERM approach.\n\nsource\n\n\nERM.sample\n\n ERM.sample (num_samples:int)\n\nSample probabilistic coherent distribution.\nGenerates n samples from a probabilistic coherent distribution. The method uses fitted mean and probabilistic reconcilers, defined by the intervals_method selected during the reconcilerā€™s instantiation. Currently available: normality, bootstrap, permbu.\nParameters: num_samples: int, number of samples generated from coherent distribution.\nReturns: samples: Coherent samples of size (num_series, horizon, num_samples).\n\nreconciler_args = dict(S=S, \n y_hat=y_hat_base,\n y_insample=y_base,\n y_hat_insample=y_hat_base_insample,\n sigmah=sigmah,\n level=[80, 90],\n intervals_method='normality',\n num_samples=200,\n seed=0,\n tags=tags,\n idx_bottom=idx_bottom\n )\n\n\n\n\n References \n\nGeneral Reconciliation\n\nOrcutt, G.H., Watts, H.W., & Edwards, J.B.(1968). Data aggregation and information loss. The American Economic Review, 58 , 773{787).\nDisaggregation methods to expedite product line forecasting. Journal of Forecasting, 9 , 233ā€“254. doi:10.1002/for.3980090304.\nAn investigation of aggregate variable time series forecast strategies with specific subaggregate time series statistical correlation. Computers and Operations Research, 26 , 1133ā€“1149. doi:10.1016/S0305-0548(99)00017-9.\nHyndman, R.J., & Athanasopoulos, G. (2021). ā€œForecasting: principles and practice, 3rd edition: Chapter 11: Forecasting hierarchical and grouped series.ā€. OTexts: Melbourne, Australia. OTexts.com/fpp3 Accessed on July 2022.\n\n\n\nOptimal Reconciliation\n\nRob J. Hyndman, Roman A. Ahmed, George Athanasopoulos, Han Lin Shang. ā€œOptimal Combination Forecasts for Hierarchical Time Seriesā€ (2010).\nShanika L. Wickramasuriya, George Athanasopoulos and Rob J. Hyndman. ā€œOptimal Combination Forecasts for Hierarchical Time Seriesā€ (2010).\nBen Taieb, S., & Koo, B. (2019). Regularized regression for hierarchical forecasting without unbiasedness conditions. In Proceedings of the 25th ACM SIGKDD International Conference on Knowledge Discovery & Data Mining KDD ā€™19 (p.Ā 1337{1347). New York, NY, USA: Association for Computing Machinery.\n\n\n\nHierarchical Probabilistic Coherent Predictions\n\nPuwasala Gamakumara Ph. D. dissertation. Monash University, Econometrics and Business Statistics. ā€œProbabilistic Forecast Reconciliationā€.\nTaieb, Souhaib Ben and Taylor, James W and Hyndman, Rob J. (2017). Coherent probabilistic forecasts for hierarchical time series. International conference on machine learning ICML.\n\n\n\n\n\n\nIf you find the code useful, please ā­ us on Github" + }, + { + "objectID": "core.html", + "href": "core.html", + "title": " Core ", + "section": "", + "text": "HierarchicalForecast contains pure Python implementations of hierarchical reconciliation methods as well as a core.HierarchicalReconciliation wrapper class that enables easy interaction with these methods through pandas DataFrames containing the hierarchical time series and the base predictions.\nThe core.HierarchicalReconciliation reconciliation class operates with the hierarchical time series pd.DataFrame Y_df, the base predictions pd.DataFrame Y_hat_df, the aggregation constraints matrix S. For more information on the creation of aggregation constraints matrix see the utils aggregation method.\n\n core.HierarchicalReconciliation \n\nsource\n\ninit\n\n init (reconcilers:List[Callable])\n\nHierarchical Reconciliation Class.\nThe core.HierarchicalReconciliation class allows you to efficiently fit multiple HierarchicaForecast methods for a collection of time series and base predictions stored in pandas DataFrames. The Y_df dataframe identifies series and datestamps with the unique_id and ds columns while the y column denotes the target time series variable. The Y_h dataframe stores the base predictions, example (AutoARIMA, ETS, etc.).\nParameters: reconcilers: A list of instantiated classes of the reconciliation methods module .\nReferences: Rob J. Hyndman and George Athanasopoulos (2018). ā€œForecasting principles and practice, Hierarchical and Grouped Seriesā€.\n\nsource\n\n\nHierarchicalReconciliation\n\n HierarchicalReconciliation (reconcilers:List[Callable])\n\nHierarchical Reconciliation Class.\nThe core.HierarchicalReconciliation class allows you to efficiently fit multiple HierarchicaForecast methods for a collection of time series and base predictions stored in pandas DataFrames. The Y_df dataframe identifies series and datestamps with the unique_id and ds columns while the y column denotes the target time series variable. The Y_h dataframe stores the base predictions, example (AutoARIMA, ETS, etc.).\nParameters: reconcilers: A list of instantiated classes of the reconciliation methods module .\nReferences: Rob J. Hyndman and George Athanasopoulos (2018). ā€œForecasting principles and practice, Hierarchical and Grouped Seriesā€.\n\nsource\n\n\nreconcile\n\n reconcile (Y_hat_df:pandas.core.frame.DataFrame,\n S:pandas.core.frame.DataFrame, tags:Dict[str,numpy.ndarray],\n Y_df:Optional[pandas.core.frame.DataFrame]=None,\n level:Optional[List[int]]=None,\n intervals_method:str='normality', num_samples:int=-1,\n seed:int=0, sort_df:bool=True, is_balanced:bool=False)\n\nHierarchical Reconciliation Method.\nThe reconcile method is analogous to SKLearn fit_predict method, it applies different reconciliation techniques instantiated in the reconcilers list.\nMost reconciliation methods can be described by the following convenient linear algebra notation:\n\\[\\tilde{\\mathbf{y}}_{[a,b],\\tau} = \\mathbf{S}_{[a,b][b]} \\mathbf{P}_{[b][a,b]} \\hat{\\mathbf{y}}_{[a,b],\\tau}\\]\nwhere \\(a, b\\) represent the aggregate and bottom levels, \\(\\mathbf{S}_{[a,b][b]}\\) contains the hierarchical aggregation constraints, and \\(\\mathbf{P}_{[b][a,b]}\\) varies across reconciliation methods. The reconciled predictions are \\(\\tilde{\\mathbf{y}}_{[a,b],\\tau}\\), and the base predictions \\(\\hat{\\mathbf{y}}_{[a,b],\\tau}\\).\nParameters: Y_hat_df: pd.DataFrame, base forecasts with columns ds and models to reconcile indexed by unique_id. Y_df: pd.DataFrame, training set of base time series with columns ['ds', 'y'] indexed by unique_id. If a class of self.reconciles receives y_hat_insample, Y_df must include them as columns. S: pd.DataFrame with summing matrix of size (base, bottom), see aggregate method. tags: Each key is a level and its value contains tags associated to that level. level: positive float list [0,100), confidence levels for prediction intervals. intervals_method: str, method used to calculate prediction intervals, one of normality, bootstrap, permbu. num_samples: int=-1, if positive return that many probabilistic coherent samples. seed: int=0, random seed for numpy generatorā€™s replicability. sort_df : bool (default=True), if True, sort df by [unique_id,ds]. is_balanced: bool=False, wether Y_df is balanced, set it to True to speed things up if Y_df is balanced.\nReturns: Y_tilde_df: pd.DataFrame, with reconciled predictions.\n\nsource\n\n\nbootstrap_reconcile\n\n bootstrap_reconcile (Y_hat_df:pandas.core.frame.DataFrame,\n S_df:pandas.core.frame.DataFrame,\n tags:Dict[str,numpy.ndarray],\n Y_df:Optional[pandas.core.frame.DataFrame]=None,\n level:Optional[List[int]]=None,\n intervals_method:str='normality',\n num_samples:int=-1, num_seeds:int=1,\n sort_df:bool=True)\n\nBootstraped Hierarchical Reconciliation Method.\nApplies N times, based on different random seeds, the reconcile method for the different reconciliation techniques instantiated in the reconcilers list.\nParameters: Y_hat_df: pd.DataFrame, base forecasts with columns ds and models to reconcile indexed by unique_id. Y_df: pd.DataFrame, training set of base time series with columns ['ds', 'y'] indexed by unique_id. If a class of self.reconciles receives y_hat_insample, Y_df must include them as columns. S: pd.DataFrame with summing matrix of size (base, bottom), see aggregate method. tags: Each key is a level and its value contains tags associated to that level. level: positive float list [0,100), confidence levels for prediction intervals. intervals_method: str, method used to calculate prediction intervals, one of normality, bootstrap, permbu. num_samples: int=-1, if positive return that many probabilistic coherent samples. num_seeds: int=1, random seed for numpy generatorā€™s replicability. sort_df : bool (default=True), if True, sort df by [unique_id,ds].\nReturns: Y_bootstrap_df: pd.DataFrame, with bootstraped reconciled predictions.\n\n\n\n Example \n\nimport numpy as np\nimport pandas as pd\n\nfrom statsforecast.core import StatsForecast\nfrom statsforecast.models import ETS, Naive\n\nfrom hierarchicalforecast.utils import aggregate\nfrom hierarchicalforecast.core import HierarchicalReconciliation\nfrom hierarchicalforecast.methods import BottomUp, MinTrace\n\n# Load TourismSmall dataset\ndf = pd.read_csv('https://raw.githubusercontent.com/Nixtla/transfer-learning-time-series/main/datasets/tourism.csv')\ndf = df.rename({'Trips': 'y', 'Quarter': 'ds'}, axis=1)\ndf.insert(0, 'Country', 'Australia')\n\n# Create hierarchical seires based on geographic levels and purpose\n# And Convert quarterly ds string to pd.datetime format\nhierarchy_levels = [['Country'],\n ['Country', 'State'], \n ['Country', 'Purpose'], \n ['Country', 'State', 'Region'], \n ['Country', 'State', 'Purpose'], \n ['Country', 'State', 'Region', 'Purpose']]\n\nY_df, S_df, tags = aggregate(df=df, spec=hierarchy_levels)\nqs = Y_df['ds'].str.replace(r'(\\d+) (Q\\d)', r'\\1-\\2', regex=True)\nY_df['ds'] = pd.PeriodIndex(qs, freq='Q').to_timestamp()\nY_df = Y_df.reset_index()\n\n# Split train/test sets\nY_test_df = Y_df.groupby('unique_id').tail(4)\nY_train_df = Y_df.drop(Y_test_df.index)\n\n# Compute base auto-ETS predictions\n# Careful identifying correct data freq, this data quarterly 'Q'\nfcst = StatsForecast(df=Y_train_df,\n #models=[ETS(season_length=12), Naive()],\n models=[Naive()],\n freq='Q', n_jobs=-1)\nY_hat_df = fcst.forecast(h=4, fitted=True)\nY_fitted_df = fcst.forecast_fitted_values()\n\n# Reconcile the base predictions\nY_train_df = Y_train_df.reset_index().set_index('unique_id')\nY_hat_df = Y_hat_df.reset_index().set_index('unique_id')\nreconcilers = [BottomUp(),\n MinTrace(method='mint_shrink')]\nhrec = HierarchicalReconciliation(reconcilers=reconcilers)\nY_rec_df = hrec.reconcile(Y_hat_df=Y_hat_df, \n Y_df=Y_fitted_df,\n S=S_df, tags=tags)\nY_rec_df.groupby('unique_id').head(2)\n\n\n\n\n\nIf you find the code useful, please ā­ us on Github" + }, + { + "objectID": "probabilistic_methods.html", + "href": "probabilistic_methods.html", + "title": " Probabilistic Methods ", + "section": "", + "text": "Here we provide a collection of methods designed to provide hierarchically coherent probabilistic distributions, which means that they generate samples of multivariate time series with hierarchical linear constraints.\nWe designed these methods to extend the core.HierarchicalForecast capabilities class. Check their usage example here.\n\n 1. Normality \n\nsource\n\nNormality\n\n Normality (S:numpy.ndarray, P:numpy.ndarray, y_hat:numpy.ndarray,\n sigmah:numpy.ndarray, W:numpy.ndarray, seed:int=0)\n\nNormality Probabilistic Reconciliation Class.\nThe Normality method leverages the Gaussian Distribution linearity, to generate hierarchically coherent prediction distributions. This class is meant to be used as the sampler input as other HierarchicalForecast reconciliation classes.\nGiven base forecasts under a normal distribution: \\[\\hat{y}_{h} \\sim \\mathrm{N}(\\hat{\\boldsymbol{\\mu}}, \\hat{\\mathbf{W}}_{h})\\]\nThe reconciled forecasts are also normally distributed: \\[\\tilde{y}_{h} \\sim \\mathrm{N}(\\mathbf{S}\\mathbf{P}\\hat{\\boldsymbol{\\mu}},\n\\mathbf{S}\\mathbf{P}\\hat{\\mathbf{W}}_{h} \\mathbf{P}^{\\intercal} \\mathbf{S}^{\\intercal})\\]\nParameters: S: np.array, summing matrix of size (base, bottom). P: np.array, reconciliation matrix of size (bottom, base). y_hat: Point forecasts values of size (base, horizon). W: np.array, hierarchical covariance matrix of size (base, base). sigmah: np.array, forecast standard dev. of size (base, horizon). num_samples: int, number of bootstraped samples generated. seed: int, random seed for numpy generatorā€™s replicability.\nReferences: - Panagiotelis A., Gamakumara P. Athanasopoulos G., and Hyndman R. J. (2022). ā€œProbabilistic forecast reconciliation: Properties, evaluation and score optimisationā€. European Journal of Operational Research.\n\nsource\n\n\nNormality.get_samples\n\n Normality.get_samples (num_samples:int=None)\n\nNormality Coherent Samples.\nObtains coherent samples under the Normality assumptions.\nParameters: num_samples: int, number of samples generated from coherent distribution.\nReturns: samples: Coherent samples of size (base, horizon, num_samples).\n\n\n\n 2. Bootstrap \n\nsource\n\nBootstrap\n\n Bootstrap (S:numpy.ndarray, P:numpy.ndarray, y_hat:numpy.ndarray,\n y_insample:numpy.ndarray, y_hat_insample:numpy.ndarray,\n num_samples:int=100, seed:int=0, W:numpy.ndarray=None)\n\nBootstrap Probabilistic Reconciliation Class.\nThis method goes beyond the normality assumption for the base forecasts, the technique simulates future sample paths and uses them to generate base sample paths that are latered reconciled. This clever idea and its simplicity allows to generate coherent bootstraped prediction intervals for any reconciliation strategy. This class is meant to be used as the sampler input as other HierarchicalForecast reconciliation classes.\nGiven a boostraped set of simulated sample paths: \\[(\\hat{\\mathbf{y}}^{[1]}_{\\tau}, \\dots ,\\hat{\\mathbf{y}}^{[B]}_{\\tau})\\]\nThe reconciled sample paths allow for reconciled distributional forecasts: \\[(\\mathbf{S}\\mathbf{P}\\hat{\\mathbf{y}}^{[1]}_{\\tau}, \\dots ,\\mathbf{S}\\mathbf{P}\\hat{\\mathbf{y}}^{[B]}_{\\tau})\\]\nParameters: S: np.array, summing matrix of size (base, bottom). P: np.array, reconciliation matrix of size (bottom, base). y_hat: Point forecasts values of size (base, horizon). y_insample: Insample values of size (base, insample_size). y_hat_insample: Insample point forecasts of size (base, insample_size). num_samples: int, number of bootstraped samples generated. seed: int, random seed for numpy generatorā€™s replicability.\nReferences: - Puwasala Gamakumara Ph. D. dissertation. Monash University, Econometrics and Business Statistics (2020). ā€œProbabilistic Forecast Reconciliationā€ - Panagiotelis A., Gamakumara P. Athanasopoulos G., and Hyndman R. J. (2022). ā€œProbabilistic forecast reconciliation: Properties, evaluation and score optimisationā€. European Journal of Operational Research.\n\nsource\n\n\nBootstrap.get_samples\n\n Bootstrap.get_samples (num_samples:int=None)\n\nBootstrap Sample Reconciliation Method.\nApplies Bootstrap sample reconciliation method as defined by Gamakumara 2020. Generating independent sample paths and reconciling them with Bootstrap.\nParameters: num_samples: int, number of samples generated from coherent distribution.\nReturns: samples: Coherent samples of size (base, horizon, num_samples).\n\n\n\n 3. PERMBU \n\nsource\n\nPERMBU\n\n PERMBU (S:numpy.ndarray, tags:Dict[str,numpy.ndarray],\n y_hat:numpy.ndarray, y_insample:numpy.ndarray,\n y_hat_insample:numpy.ndarray, sigmah:numpy.ndarray,\n num_samples:int=None, seed:int=0, P:numpy.ndarray=None)\n\nPERMBU Probabilistic Reconciliation Class.\nThe PERMBU method leverages empirical bottom-level marginal distributions with empirical copula functions (describing bottom-level dependencies) to generate the distribution of aggregate-level distributions using BottomUp reconciliation. The sample reordering technique in the PERMBU method reinjects multivariate dependencies into independent bottom-level samples.\nAlgorithm:\n1. For all series compute conditional marginals distributions.\n2. Compute residuals $\\hat{\\epsilon}_{i,t}$ and obtain rank permutations.\n2. Obtain K-sample from the bottom-level series predictions.\n3. Apply recursively through the hierarchical structure:<br>\n 3.1. For a given aggregate series $i$ and its children series:<br>\n 3.2. Obtain children's empirical joint using sample reordering copula.<br>\n 3.2. From the children's joint obtain the aggregate series's samples. \nParameters: S: np.array, summing matrix of size (base, bottom). tags: Each key is a level and each value its S indices. y_insample: Insample values of size (base, insample_size). y_hat_insample: Insample point forecasts of size (base, insample_size). sigmah: np.array, forecast standard dev. of size (base, horizon). num_samples: int, number of normal prediction samples generated. seed: int, random seed for numpy generatorā€™s replicability.\nReferences: - Taieb, Souhaib Ben and Taylor, James W and Hyndman, Rob J. (2017). Coherent probabilistic forecasts for hierarchical time series. International conference on machine learning ICML.\n\nsource\n\n\nPERMBU.get_samples\n\n PERMBU.get_samples (num_samples:int=None)\n\nPERMBU Sample Reconciliation Method.\nApplies PERMBU reconciliation method as defined by Taieb et. al 2017. Generating independent base prediction samples, restoring its multivariate dependence using estimated copula with reordering and applying the BottomUp aggregation to the new samples.\nParameters: num_samples: int, number of samples generated from coherent distribution.\nReturns: samples: Coherent samples of size (base, horizon, num_samples).\n\n\n\n References \n\nRob J. Hyndman and George Athanasopoulos (2018). ā€œForecasting principles and practice, Reconciled distributional forecastsā€.\nPuwasala Gamakumara Ph. D. dissertation. Monash University, Econometrics and Business Statistics (2020). ā€œProbabilistic Forecast Reconciliationā€\nPanagiotelis A., Gamakumara P. Athanasopoulos G., and Hyndman R. J. (2022). ā€œProbabilistic forecast reconciliation: Properties, evaluation and score optimisationā€. European Journal of Operational Research.\nTaieb, Souhaib Ben and Taylor, James W and Hyndman, Rob J. (2017). Coherent probabilistic forecasts for hierarchical time series. International conference on machine learning ICML.\n\n\n\n\n\nIf you find the code useful, please ā­ us on Github" + }, + { + "objectID": "examples/index.html", + "href": "examples/index.html", + "title": "Tutorials", + "section": "", + "text": "Click through to any of these tutorials to get started with HierarchicalForecastā€™s features.\n\n\n\n\n\n\n\n\n\n\nTitle\n\n\n\n\n\n\nBootstrap\n\n\n\n\nGeographical Aggregation (Prison Population)\n\n\n\n\nGeographical Aggregation (Tourism)\n\n\n\n\nGluonTS\n\n\n\n\nInstall\n\n\n\n\nIntroduction\n\n\n\n\nNeural/MLForecast\n\n\n\n\nNon-Negative MinTrace\n\n\n\n\nNormality\n\n\n\n\nPERMBU\n\n\n\n\nProbabilistic Forecast Evaluation\n\n\n\n\nReconciliation Quick Start\n\n\n\n\n\n\nNo matching items\n\nIf you find the code useful, please ā­ us on Github" + }, + { + "objectID": "examples/australiandomestictourism-bootstraped-intervals.html", + "href": "examples/australiandomestictourism-bootstraped-intervals.html", + "title": "Bootstrap", + "section": "", + "text": "In many cases, only the time series at the lowest level of the hierarchies (bottom time series) are available. HierarchicalForecast has tools to create time series for all hierarchies and also allows you to calculate prediction intervals for all hierarchies. In this notebook we will see how to do it.\n!pip install hierarchicalforecast statsforecast\nimport numpy as np\nimport pandas as pd\nimport matplotlib.pyplot as plt\n\n# compute base forecast no coherent\nfrom statsforecast.models import ETS, Naive\nfrom statsforecast.core import StatsForecast\n\n#obtain hierarchical reconciliation methods and evaluation\nfrom hierarchicalforecast.methods import BottomUp, MinTrace\nfrom hierarchicalforecast.utils import aggregate, HierarchicalPlot\nfrom hierarchicalforecast.core import HierarchicalReconciliation\nfrom hierarchicalforecast.evaluation import HierarchicalEvaluation\n\n/Users/fedex/miniconda3/envs/hierarchicalforecast/lib/python3.10/site-packages/statsforecast/core.py:21: TqdmWarning: IProgress not found. Please update jupyter and ipywidgets. See https://ipywidgets.readthedocs.io/en/stable/user_install.html\n from tqdm.autonotebook import tqdm\nIf you find the code useful, please ā­ us on Github" + }, + { + "objectID": "examples/australiandomestictourism-bootstraped-intervals.html#aggregate-bottom-time-series", + "href": "examples/australiandomestictourism-bootstraped-intervals.html#aggregate-bottom-time-series", + "title": "Bootstrap", + "section": "Aggregate bottom time series", + "text": "Aggregate bottom time series\nIn this example we will use the Tourism dataset from the Forecasting: Principles and Practice book. The dataset only contains the time series at the lowest level, so we need to create the time series for all hierarchies.\n\nY_df = pd.read_csv('https://raw.githubusercontent.com/Nixtla/transfer-learning-time-series/main/datasets/tourism.csv')\nY_df = Y_df.rename({'Trips': 'y', 'Quarter': 'ds'}, axis=1)\nY_df.insert(0, 'Country', 'Australia')\nY_df = Y_df[['Country', 'Region', 'State', 'Purpose', 'ds', 'y']]\nY_df['ds'] = Y_df['ds'].str.replace(r'(\\d+) (Q\\d)', r'\\1-\\2', regex=True)\nY_df['ds'] = pd.to_datetime(Y_df['ds'])\nY_df.head()\n\n\n\n\n\n\n\n\nCountry\nRegion\nState\nPurpose\nds\ny\n\n\n\n\n0\nAustralia\nAdelaide\nSouth Australia\nBusiness\n1998-01-01\n135.077690\n\n\n1\nAustralia\nAdelaide\nSouth Australia\nBusiness\n1998-04-01\n109.987316\n\n\n2\nAustralia\nAdelaide\nSouth Australia\nBusiness\n1998-07-01\n166.034687\n\n\n3\nAustralia\nAdelaide\nSouth Australia\nBusiness\n1998-10-01\n127.160464\n\n\n4\nAustralia\nAdelaide\nSouth Australia\nBusiness\n1999-01-01\n137.448533\n\n\n\n\n\n\n\nThe dataset can be grouped in the following non-strictly hierarchical structure.\n\nspec = [\n ['Country'],\n ['Country', 'State'], \n ['Country', 'Purpose'], \n ['Country', 'State', 'Region'], \n ['Country', 'State', 'Purpose'], \n ['Country', 'State', 'Region', 'Purpose']\n]\n\nUsing the aggregate function from HierarchicalForecast we can generate: 1. Y_df: the hierarchical structured series \\(\\mathbf{y}_{[a,b]\\tau}\\) 2. S_df: the aggregation constraings dataframe with \\(S_{[a,b]}\\) 3. tags: a list with the ā€˜unique_idsā€™ conforming each aggregation level.\n\nY_df, S_df, tags = aggregate(df=Y_df, spec=spec)\nY_df = Y_df.reset_index()\n\n/Users/fedex/miniconda3/envs/hierarchicalforecast/lib/python3.10/site-packages/sklearn/preprocessing/_encoders.py:828: FutureWarning: `sparse` was renamed to `sparse_output` in version 1.2 and will be removed in 1.4. `sparse_output` is ignored unless you leave `sparse` to its default value.\n warnings.warn(\n\n\n\nY_df.head()\n\n\n\n\n\n\n\n\nunique_id\nds\ny\n\n\n\n\n0\nAustralia\n1998-01-01\n23182.197269\n\n\n1\nAustralia\n1998-04-01\n20323.380067\n\n\n2\nAustralia\n1998-07-01\n19826.640511\n\n\n3\nAustralia\n1998-10-01\n20830.129891\n\n\n4\nAustralia\n1999-01-01\n22087.353380\n\n\n\n\n\n\n\n\nS_df.iloc[:5, :5]\n\n\n\n\n\n\n\n\nAustralia/ACT/Canberra/Business\nAustralia/ACT/Canberra/Holiday\nAustralia/ACT/Canberra/Other\nAustralia/ACT/Canberra/Visiting\nAustralia/New South Wales/Blue Mountains/Business\n\n\n\n\nAustralia\n1.0\n1.0\n1.0\n1.0\n1.0\n\n\nAustralia/ACT\n1.0\n1.0\n1.0\n1.0\n0.0\n\n\nAustralia/New South Wales\n0.0\n0.0\n0.0\n0.0\n1.0\n\n\nAustralia/Northern Territory\n0.0\n0.0\n0.0\n0.0\n0.0\n\n\nAustralia/Queensland\n0.0\n0.0\n0.0\n0.0\n0.0\n\n\n\n\n\n\n\n\ntags['Country/Purpose']\n\narray(['Australia/Business', 'Australia/Holiday', 'Australia/Other',\n 'Australia/Visiting'], dtype=object)\n\n\nWe can visualize the S_df dataframe and Y_df using the HierarchicalPlot class as follows.\n\nhplot = HierarchicalPlot(S=S_df, tags=tags)\n\n\nhplot.plot_summing_matrix()\n\n\n\n\n\nhplot.plot_hierarchically_linked_series(\n bottom_series='Australia/ACT/Canberra/Holiday',\n Y_df=Y_df.set_index('unique_id')\n)\n\n\n\n\n\nSplit Train/Test sets\nWe use the final two years (8 quarters) as test set.\n\nY_test_df = Y_df.groupby('unique_id').tail(8)\nY_train_df = Y_df.drop(Y_test_df.index)\n\n\nY_test_df = Y_test_df.set_index('unique_id')\nY_train_df = Y_train_df.set_index('unique_id')\n\n\nY_train_df.groupby('unique_id').size()\n\nunique_id\nAustralia 72\nAustralia/ACT 72\nAustralia/ACT/Business 72\nAustralia/ACT/Canberra 72\nAustralia/ACT/Canberra/Business 72\n ..\nAustralia/Western Australia/Experience Perth/Other 72\nAustralia/Western Australia/Experience Perth/Visiting 72\nAustralia/Western Australia/Holiday 72\nAustralia/Western Australia/Other 72\nAustralia/Western Australia/Visiting 72\nLength: 425, dtype: int64" + }, + { + "objectID": "examples/australiandomestictourism-bootstraped-intervals.html#computing-base-forecasts", + "href": "examples/australiandomestictourism-bootstraped-intervals.html#computing-base-forecasts", + "title": "Bootstrap", + "section": "Computing Base Forecasts", + "text": "Computing Base Forecasts\nThe following cell computes the base forecasts for each time series in Y_df using the AutoETS and model. Observe that Y_hat_df contains the forecasts but they are not coherent. Since we are computing prediction intervals using bootstrapping, we only need the fitted values of the models.\n\nfcst = StatsForecast(df=Y_train_df, \n models=[ETS(season_length=4, model='ZAA')],\n freq='QS', n_jobs=-1)\nY_hat_df = fcst.forecast(h=8, fitted=True)\nY_fitted_df = fcst.forecast_fitted_values()\n\n/Users/fedex/miniconda3/envs/hierarchicalforecast/lib/python3.10/site-packages/statsforecast/models.py:526: FutureWarning: `ETS` will be deprecated in future versions of `StatsForecast`. Please use `AutoETS` instead.\n ETS._warn()" + }, + { + "objectID": "examples/australiandomestictourism-bootstraped-intervals.html#reconcile-base-forecasts", + "href": "examples/australiandomestictourism-bootstraped-intervals.html#reconcile-base-forecasts", + "title": "Bootstrap", + "section": "Reconcile Base Forecasts", + "text": "Reconcile Base Forecasts\nThe following cell makes the previous forecasts coherent using the HierarchicalReconciliation class. Since the hierarchy structure is not strict, we canā€™t use methods such as TopDown or MiddleOut. In this example we use BottomUp and MinTrace. If you want to calculate prediction intervals, you have to use the level argument as follows and set intervals_method='bootstrap'.\n\nreconcilers = [\n BottomUp(),\n MinTrace(method='mint_shrink'),\n MinTrace(method='ols')\n]\nhrec = HierarchicalReconciliation(reconcilers=reconcilers)\nY_rec_df = hrec.reconcile(Y_hat_df=Y_hat_df, Y_df=Y_fitted_df, S=S_df, \n tags=tags, level=[80, 90], \n intervals_method='bootstrap')\n\nThe dataframe Y_rec_df contains the reconciled forecasts.\n\nY_rec_df.head()\n\n\n\n\n\n\n\n\nds\nETS\nETS/BottomUp\nETS/BottomUp-lo-90\nETS/BottomUp-lo-80\nETS/BottomUp-hi-80\nETS/BottomUp-hi-90\nETS/MinTrace_method-mint_shrink\nETS/MinTrace_method-mint_shrink-lo-90\nETS/MinTrace_method-mint_shrink-lo-80\nETS/MinTrace_method-mint_shrink-hi-80\nETS/MinTrace_method-mint_shrink-hi-90\nETS/MinTrace_method-ols\nETS/MinTrace_method-ols-lo-90\nETS/MinTrace_method-ols-lo-80\nETS/MinTrace_method-ols-hi-80\nETS/MinTrace_method-ols-hi-90\n\n\nunique_id\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\nAustralia\n2016-01-01\n26080.878906\n24487.349609\n23244.120996\n23333.694727\n25381.792969\n25426.333984\n25532.523559\n24428.911701\n24709.210638\n26365.606934\n26476.255501\n26034.114241\n24914.136375\n25100.462938\n27102.735022\n27176.416922\n\n\nAustralia\n2016-04-01\n24587.011719\n23069.744141\n21826.519434\n21912.962891\n23946.606250\n24281.447266\n24118.557177\n23199.968626\n23295.244252\n25108.470410\n25489.383606\n24567.460995\n23484.050568\n23640.638423\n25709.763678\n25809.249492\n\n\nAustralia\n2016-07-01\n24147.308594\n22689.777344\n21297.136719\n21530.438281\n23701.173828\n24155.820312\n23731.251387\n22627.639669\n22818.729182\n24821.488458\n25246.867432\n24150.134898\n23030.156834\n23155.025556\n25359.992376\n25404.841402\n\n\nAustralia\n2016-10-01\n24794.041016\n23429.757812\n22037.123047\n22276.453125\n24241.417969\n24441.160156\n24486.549344\n23385.927232\n23600.704525\n25353.555625\n25481.478557\n24831.584516\n23725.924464\n23836.475174\n25900.205254\n25977.265089\n\n\nAustralia\n2017-01-01\n26284.000000\n24940.042969\n23696.722754\n23904.382812\n25814.941406\n25974.169922\n26041.867488\n24972.077858\n25158.986710\n26918.104747\n27135.580845\n26348.203335\n25254.659324\n25487.502291\n27410.873035\n27477.334507" + }, + { + "objectID": "examples/australiandomestictourism-bootstraped-intervals.html#plot-predictions", + "href": "examples/australiandomestictourism-bootstraped-intervals.html#plot-predictions", + "title": "Bootstrap", + "section": "Plot Predictions", + "text": "Plot Predictions\nThen we can plot the probabilist forecasts using the following function.\n\nplot_df = pd.concat([Y_df.set_index(['unique_id', 'ds']), \n Y_rec_df.set_index('ds', append=True)], axis=1)\nplot_df = plot_df.reset_index('ds')\n\n\nPlot single time series\n\nhplot.plot_series(\n series='Australia',\n Y_df=plot_df, \n models=['y', 'ETS', 'ETS/MinTrace_method-ols', 'ETS/MinTrace_method-mint_shrink'],\n level=[80]\n)\n\n\n\n\n\n# Since we are plotting a bottom time series\n# the probabilistic and mean forecasts\n# differ due to bootstrapping\nhplot.plot_series(\n series='Australia/Western Australia/Experience Perth/Visiting',\n Y_df=plot_df, \n models=['y', 'ETS', 'ETS/BottomUp'],\n level=[80]\n)\n\n\n\n\n\n\nPlot hierarchichally linked time series\n\nhplot.plot_hierarchically_linked_series(\n bottom_series='Australia/Western Australia/Experience Perth/Visiting',\n Y_df=plot_df, \n models=['y', 'ETS', 'ETS/MinTrace_method-ols', 'ETS/BottomUp'],\n level=[80]\n)\n\n\n\n\n\n# ACT only has Canberra\nhplot.plot_hierarchically_linked_series(\n bottom_series='Australia/ACT/Canberra/Other',\n Y_df=plot_df, \n models=['y', 'ETS/MinTrace_method-mint_shrink'],\n level=[80, 90]\n)\n\n\n\n\n\n\nReferences\n\nHyndman, R.J., & Athanasopoulos, G. (2021). ā€œForecasting: principles and practice, 3rd edition: Chapter 11: Forecasting hierarchical and grouped series.ā€. OTexts: Melbourne, Australia. OTexts.com/fpp3 Accessed on July 2022.\nShanika L. Wickramasuriya, George Athanasopoulos, and Rob J. Hyndman. Optimal forecast reconciliation for hierarchical and grouped time series through trace minimization.Journal of the American Statistical Association, 114(526):804ā€“819, 2019. doi: 10.1080/01621459.2018.1448825. URL https://robjhyndman.com/publications/mint/.\nPuwasala Gamakumara Ph. D. dissertation. Monash University, Econometrics and Business Statistics (2020). ā€œProbabilistic Forecast Reconciliationā€" + }, + { + "objectID": "examples/introduction.html", + "href": "examples/introduction.html", + "title": "Introduction", + "section": "", + "text": "You can run these experiments using CPU or GPU with Google Colab.\nIf you find the code useful, please ā­ us on Github" + }, + { + "objectID": "examples/introduction.html#hierarchical-series", + "href": "examples/introduction.html#hierarchical-series", + "title": "Introduction", + "section": "1. Hierarchical Series", + "text": "1. Hierarchical Series\nIn many applications, a set of time series is hierarchically organized. Examples include the presence of geographic levels, products, or categories that define different types of aggregations.\nIn such scenarios, forecasters are often required to provide predictions for all disaggregate and aggregate series. A natural desire is for those predictions to be ā€œcoherentā€, that is, for the bottom series to add up precisely to the forecasts of the aggregated series.\n\n\n\nFigure 1. A two level time series hierarchical structure, with four bottom level variables.\n\n\nFigure 1. shows a simple hierarchical structure where we have four bottom-level series, two middle-level series, and the top level representing the total aggregation. Its hierarchical aggregations or coherency constraints are:\n\\[\\begin{align}\n y_{\\mathrm{Total},\\tau} = y_{\\beta_{1},\\tau}+y_{\\beta_{2},\\tau}+y_{\\beta_{3},\\tau}+y_{\\beta_{4},\\tau}\n \\qquad \\qquad \\qquad \\qquad \\qquad \\\\\n \\mathbf{y}_{[a],\\tau}=\\left[y_{\\mathrm{Total},\\tau},\\; y_{\\beta_{1},\\tau}+y_{\\beta_{2},\\tau},\\;y_{\\beta_{3},\\tau}+y_{\\beta_{4},\\tau}\\right]^{\\intercal}\n \\qquad\n \\mathbf{y}_{[b],\\tau}=\\left[ y_{\\beta_{1},\\tau},\\; y_{\\beta_{2},\\tau},\\; y_{\\beta_{3},\\tau},\\; y_{\\beta_{4},\\tau} \\right]^{\\intercal}\n\\end{align}\\]\nLuckily these constraints can be compactly expressed with the following matrices:\n\\[\\begin{align}\n\\mathbf{S}_{[a,b][b]}\n=\n\\begin{bmatrix}\n\\mathbf{A}_{\\mathrm{[a][b]}} \\\\\n \\\\\n \\\\\n\\mathbf{I}_{\\mathrm{[b][b]}} \\\\\n \\\\\n\\end{bmatrix}\n=\n\\begin{bmatrix}\n1 & 1 & 1 & 1 \\\\\n1 & 1 & 0 & 0 \\\\\n0 & 0 & 1 & 1 \\\\\n1 & 0 & 0 & 0 \\\\\n0 & 1 & 0 & 0 \\\\\n0 & 0 & 1 & 0 \\\\\n0 & 0 & 0 & 1 \\\\\n\\end{bmatrix}\n\\end{align}\\]\nwhere \\(\\mathbf{A}_{[a,b][b]}\\) aggregates the bottom series to the upper levels, and \\(\\mathbf{I}_{\\mathrm{[b][b]}}\\) is an identity matrix. The representation of the hierarchical series is then:\n\\[\\begin{align}\n\\mathbf{y}_{[a,b],\\tau} = \\mathbf{S}_{[a,b][b]} \\mathbf{y}_{[b],\\tau}\n\\end{align}\\]\nTo visualize an example, in Figure 2. One can think of the hierarchical time series structure levels to represent different geographical aggregations. For example, in Figure 2. the top level is the total aggregation of series within a country, the middle level being its states and the bottom level its regions.\n\n\n\nFigure 2. A hierarchy can be composed of geographic levels. In this example the top level corresponds to country aggregation, middle level to states, and bottom level to regions." + }, + { + "objectID": "examples/introduction.html#hierarchical-forecast", + "href": "examples/introduction.html#hierarchical-forecast", + "title": "Introduction", + "section": "2. Hierarchical Forecast", + "text": "2. Hierarchical Forecast\nTo achieve ā€œcoherencyā€, most statistical solutions to the hierarchical forecasting challenge implement a two-stage reconciliation process.\n1. First, we obtain a set of the base forecast \\(\\mathbf{\\hat{y}}_{[a,b],\\tau}\\) 2. Later, we reconcile them into coherent forecasts \\(\\mathbf{\\tilde{y}}_{[a,b],\\tau}\\).\nMost hierarchical reconciliation methods can be expressed by the following transformations:\n\\[\\begin{align}\n\\tilde{\\mathbf{y}}_{[a,b],\\tau} = \\mathbf{S}_{[a,b][b]} \\mathbf{P}_{[b][a,b]} \\hat{\\mathbf{y}}_{[a,b],\\tau}\n\\end{align}\\]\nThe HierarchicalForecast library offers a Python collection of reconciliation methods, datasets, evaluation and visualization tools for the task. Among its available reconciliation methods we have BottomUp, TopDown, MiddleOut, MinTrace, ERM. Among its probabilistic coherent methods we have Normality, Bootstrap, PERMBU." + }, + { + "objectID": "examples/introduction.html#minimal-example", + "href": "examples/introduction.html#minimal-example", + "title": "Introduction", + "section": "3. Minimal Example", + "text": "3. Minimal Example\n\n!pip install hierarchicalforecast\n!pip install -U numba statsforecast datasetsforecast\n\n\nWrangling Data\n\nimport numpy as np\nimport pandas as pd\n\nWe are going to creat a synthetic data set to illustrate a hierarchical time series structure like the one in Figure 1.\nWe will create a two level structure with four bottom series where aggregations of the series are self evident.\n\n# Create Figure 1. synthetic bottom data\nds = pd.date_range(start='2000-01-01', end='2000-08-01', freq='MS')\ny_base = np.arange(1,9)\nr1 = y_base * (10**1)\nr2 = y_base * (10**1)\nr3 = y_base * (10**2)\nr4 = y_base * (10**2)\n\nys = np.concatenate([r1, r2, r3, r4])\nds = np.tile(ds, 4)\nunique_ids = ['r1'] * 8 + ['r2'] * 8 + ['r3'] * 8 + ['r4'] * 8\ntop_level = 'Australia'\nmiddle_level = ['State1'] * 16 + ['State2'] * 16\nbottom_level = unique_ids\n\nbottom_df = dict(ds=ds,\n top_level=top_level, \n middle_level=middle_level, \n bottom_level=bottom_level,\n y=ys)\nbottom_df = pd.DataFrame(bottom_df)\nbottom_df.groupby('bottom_level').head(2)\n\n\n\n\n\n\n\n\nds\ntop_level\nmiddle_level\nbottom_level\ny\n\n\n\n\n0\n2000-01-01\nAustralia\nState1\nr1\n10\n\n\n1\n2000-02-01\nAustralia\nState1\nr1\n20\n\n\n8\n2000-01-01\nAustralia\nState1\nr2\n10\n\n\n9\n2000-02-01\nAustralia\nState1\nr2\n20\n\n\n16\n2000-01-01\nAustralia\nState2\nr3\n100\n\n\n17\n2000-02-01\nAustralia\nState2\nr3\n200\n\n\n24\n2000-01-01\nAustralia\nState2\nr4\n100\n\n\n25\n2000-02-01\nAustralia\nState2\nr4\n200\n\n\n\n\n\n\n\nThe previously introduced hierarchical series \\(\\mathbf{y}_{[a,b]\\tau}\\) is captured within the Y_hier_df dataframe.\nThe aggregation constraints matrix \\(\\mathbf{S}_{[a][b]}\\) is captured within the S_df dataframe.\nFinally the tags contains a list within Y_hier_df composing each hierarchical level, for example the tags['top_level'] contains Australiaā€™s aggregated series index.\n\nfrom hierarchicalforecast.utils import aggregate\n\n\n# Create hierarchical structure and constraints\nhierarchy_levels = [['top_level'],\n ['top_level', 'middle_level'],\n ['top_level', 'middle_level', 'bottom_level']]\nY_hier_df, S_df, tags = aggregate(df=bottom_df, spec=hierarchy_levels)\nY_hier_df = Y_hier_df.reset_index()\nprint('S_df.shape', S_df.shape)\nprint('Y_hier_df.shape', Y_hier_df.shape)\nprint(\"tags['top_level']\", tags['top_level'])\n\nS_df.shape (7, 4)\nY_hier_df.shape (56, 3)\ntags['top_level'] ['Australia']\n\n\n/Users/cchallu/opt/anaconda3/envs/hierarchicalforecast/lib/python3.10/site-packages/sklearn/preprocessing/_encoders.py:828: FutureWarning: `sparse` was renamed to `sparse_output` in version 1.2 and will be removed in 1.4. `sparse_output` is ignored unless you leave `sparse` to its default value.\n warnings.warn(\n\n\n\nY_hier_df.groupby('unique_id').head(2)\n\n\n\n\n\n\n\n\nunique_id\nds\ny\n\n\n\n\n0\nAustralia\n2000-01-01\n220.0\n\n\n1\nAustralia\n2000-02-01\n440.0\n\n\n8\nAustralia/State1\n2000-01-01\n20.0\n\n\n9\nAustralia/State1\n2000-02-01\n40.0\n\n\n16\nAustralia/State2\n2000-01-01\n200.0\n\n\n17\nAustralia/State2\n2000-02-01\n400.0\n\n\n24\nAustralia/State1/r1\n2000-01-01\n10.0\n\n\n25\nAustralia/State1/r1\n2000-02-01\n20.0\n\n\n32\nAustralia/State1/r2\n2000-01-01\n10.0\n\n\n33\nAustralia/State1/r2\n2000-02-01\n20.0\n\n\n40\nAustralia/State2/r3\n2000-01-01\n100.0\n\n\n41\nAustralia/State2/r3\n2000-02-01\n200.0\n\n\n48\nAustralia/State2/r4\n2000-01-01\n100.0\n\n\n49\nAustralia/State2/r4\n2000-02-01\n200.0\n\n\n\n\n\n\n\n\nS_df\n\n\n\n\n\n\n\n\nAustralia/State1/r1\nAustralia/State1/r2\nAustralia/State2/r3\nAustralia/State2/r4\n\n\n\n\nAustralia\n1.0\n1.0\n1.0\n1.0\n\n\nAustralia/State1\n1.0\n1.0\n0.0\n0.0\n\n\nAustralia/State2\n0.0\n0.0\n1.0\n1.0\n\n\nAustralia/State1/r1\n1.0\n0.0\n0.0\n0.0\n\n\nAustralia/State1/r2\n0.0\n1.0\n0.0\n0.0\n\n\nAustralia/State2/r3\n0.0\n0.0\n1.0\n0.0\n\n\nAustralia/State2/r4\n0.0\n0.0\n0.0\n1.0\n\n\n\n\n\n\n\n\n\nBase Predictions\nNext, we compute the base forecast for each time series using the naive model. Observe that Y_hat_df contains the forecasts but they are not coherent.\n\nfrom statsforecast.models import Naive\nfrom statsforecast.core import StatsForecast\n\n\n# Split train/test sets\nY_test_df = Y_hier_df.groupby('unique_id').tail(4)\nY_train_df = Y_hier_df.drop(Y_test_df.index)\n\n# Compute base Naive predictions\n# Careful identifying correct data freq, this data quarterly 'Q'\nfcst = StatsForecast(df=Y_train_df,\n models=[Naive()],\n freq='Q', n_jobs=-1)\nY_hat_df = fcst.forecast(h=4, fitted=True)\nY_fitted_df = fcst.forecast_fitted_values()\n\n\n\nReconciliation\n\nfrom hierarchicalforecast.methods import BottomUp\nfrom hierarchicalforecast.core import HierarchicalReconciliation\n\n\n# You can select a reconciler from our collection\nreconcilers = [BottomUp()] # MinTrace(method='mint_shrink')\nhrec = HierarchicalReconciliation(reconcilers=reconcilers)\n\nY_rec_df = hrec.reconcile(Y_hat_df=Y_hat_df, \n Y_df=Y_fitted_df,\n S=S_df, tags=tags)\nY_rec_df.groupby('unique_id').head(2)\n\n\n\n\n\n\n\n\nds\nNaive\nNaive/BottomUp\n\n\nunique_id\n\n\n\n\n\n\n\nAustralia\n2000-06-30\n880.0\n880.0\n\n\nAustralia\n2000-09-30\n880.0\n880.0\n\n\nAustralia/State1\n2000-06-30\n80.0\n80.0\n\n\nAustralia/State1\n2000-09-30\n80.0\n80.0\n\n\nAustralia/State2\n2000-06-30\n800.0\n800.0\n\n\nAustralia/State2\n2000-09-30\n800.0\n800.0\n\n\nAustralia/State1/r1\n2000-06-30\n40.0\n40.0\n\n\nAustralia/State1/r1\n2000-09-30\n40.0\n40.0\n\n\nAustralia/State1/r2\n2000-06-30\n40.0\n40.0\n\n\nAustralia/State1/r2\n2000-09-30\n40.0\n40.0\n\n\nAustralia/State2/r3\n2000-06-30\n400.0\n400.0\n\n\nAustralia/State2/r3\n2000-09-30\n400.0\n400.0\n\n\nAustralia/State2/r4\n2000-06-30\n400.0\n400.0\n\n\nAustralia/State2/r4\n2000-09-30\n400.0\n400.0" + }, + { + "objectID": "examples/introduction.html#references", + "href": "examples/introduction.html#references", + "title": "Introduction", + "section": "References", + "text": "References\n\nHyndman, R.J., & Athanasopoulos, G. (2021). ā€œForecasting: principles and practice, 3rd edition: Chapter 11: Forecasting hierarchical and grouped series.ā€. OTexts: Melbourne, Australia. OTexts.com/fpp3 Accessed on July 2022.\nOrcutt, G.H., Watts, H.W., & Edwards, J.B.(1968). Data aggregation and information loss. The American Economic Review, 58 , 773{787).\nDisaggregation methods to expedite product line forecasting. Journal of Forecasting, 9 , 233ā€“254. doi:10.1002/for.3980090304.\nWickramasuriya, S. L., Athanasopoulos, G., & Hyndman, R. J. (2019). \"Optimal forecast reconciliation for hierarchical and grouped time series through trace minimization\". Journal of the American Statistical Association, 114 , 804ā€“819. doi:10.1080/01621459.2018.1448825.\nBen Taieb, S., & Koo, B. (2019). Regularized regression for hierarchical forecasting without unbiasedness conditions. In Proceedings of the 25th ACM SIGKDD International Conference on Knowledge Discovery & Data Mining KDD ā€™19 (p.Ā 1337{1347). New York, NY, USA: Association for Computing Machinery." + }, + { + "objectID": "examples/tourismsmall.html", + "href": "examples/tourismsmall.html", + "title": "Reconciliation Quick Start", + "section": "", + "text": "Large collections of time series organized into structures at different aggregation levels often require their forecasts to follow their aggregation constraints, which poses the challenge of creating novel algorithms capable of coherent forecasts.\nThe HierarchicalForecast package provides a wide collection of Python implementations of hierarchical forecasting algorithms that follow classic hierarchical reconciliation.\nIn this notebook we will show how to use the StatsForecast library to produce base forecasts, and use HierarchicalForecast package to perform hierarchical reconciliation.\nYou can run these experiments using CPU or GPU with Google Colab.\nIf you find the code useful, please ā­ us on Github" + }, + { + "objectID": "examples/tourismsmall.html#libraries", + "href": "examples/tourismsmall.html#libraries", + "title": "Reconciliation Quick Start", + "section": "1. Libraries", + "text": "1. Libraries\n\n!pip install hierarchicalforecast\n!pip install -U numba statsforecast datasetsforecast" + }, + { + "objectID": "examples/tourismsmall.html#load-data", + "href": "examples/tourismsmall.html#load-data", + "title": "Reconciliation Quick Start", + "section": "2. Load Data", + "text": "2. Load Data\nIn this example we will use the TourismSmall dataset. The following cell gets the time series for the different levels in the hierarchy, the summing matrix S which recovers the full dataset from the bottom level hierarchy and the indices of each hierarchy denoted by tags.\n\nimport numpy as np\nimport pandas as pd\n\nfrom datasetsforecast.hierarchical import HierarchicalData\n\n\nY_df, S_df, tags = HierarchicalData.load('./data', 'TourismSmall')\nY_df['ds'] = pd.to_datetime(Y_df['ds'])\n\n\nY_df.head()\n\n\n\n\n\n\n\n\nunique_id\nds\ny\n\n\n\n\n0\ntotal\n1998-03-31\n84503\n\n\n1\ntotal\n1998-06-30\n65312\n\n\n2\ntotal\n1998-09-30\n72753\n\n\n3\ntotal\n1998-12-31\n70880\n\n\n4\ntotal\n1999-03-31\n86893\n\n\n\n\n\n\n\n\nS_df.iloc[:5, :5]\n\n\n\n\n\n\n\n\nnsw-hol-city\nnsw-hol-noncity\nvic-hol-city\nvic-hol-noncity\nqld-hol-city\n\n\n\n\ntotal\n1.0\n1.0\n1.0\n1.0\n1.0\n\n\nhol\n1.0\n1.0\n1.0\n1.0\n1.0\n\n\nvfr\n0.0\n0.0\n0.0\n0.0\n0.0\n\n\nbus\n0.0\n0.0\n0.0\n0.0\n0.0\n\n\noth\n0.0\n0.0\n0.0\n0.0\n0.0\n\n\n\n\n\n\n\n\ntags\n\n{'Country': array(['total'], dtype=object),\n 'Country/Purpose': array(['hol', 'vfr', 'bus', 'oth'], dtype=object),\n 'Country/Purpose/State': array(['nsw-hol', 'vic-hol', 'qld-hol', 'sa-hol', 'wa-hol', 'tas-hol',\n 'nt-hol', 'nsw-vfr', 'vic-vfr', 'qld-vfr', 'sa-vfr', 'wa-vfr',\n 'tas-vfr', 'nt-vfr', 'nsw-bus', 'vic-bus', 'qld-bus', 'sa-bus',\n 'wa-bus', 'tas-bus', 'nt-bus', 'nsw-oth', 'vic-oth', 'qld-oth',\n 'sa-oth', 'wa-oth', 'tas-oth', 'nt-oth'], dtype=object),\n 'Country/Purpose/State/CityNonCity': array(['nsw-hol-city', 'nsw-hol-noncity', 'vic-hol-city',\n 'vic-hol-noncity', 'qld-hol-city', 'qld-hol-noncity',\n 'sa-hol-city', 'sa-hol-noncity', 'wa-hol-city', 'wa-hol-noncity',\n 'tas-hol-city', 'tas-hol-noncity', 'nt-hol-city', 'nt-hol-noncity',\n 'nsw-vfr-city', 'nsw-vfr-noncity', 'vic-vfr-city',\n 'vic-vfr-noncity', 'qld-vfr-city', 'qld-vfr-noncity',\n 'sa-vfr-city', 'sa-vfr-noncity', 'wa-vfr-city', 'wa-vfr-noncity',\n 'tas-vfr-city', 'tas-vfr-noncity', 'nt-vfr-city', 'nt-vfr-noncity',\n 'nsw-bus-city', 'nsw-bus-noncity', 'vic-bus-city',\n 'vic-bus-noncity', 'qld-bus-city', 'qld-bus-noncity',\n 'sa-bus-city', 'sa-bus-noncity', 'wa-bus-city', 'wa-bus-noncity',\n 'tas-bus-city', 'tas-bus-noncity', 'nt-bus-city', 'nt-bus-noncity',\n 'nsw-oth-city', 'nsw-oth-noncity', 'vic-oth-city',\n 'vic-oth-noncity', 'qld-oth-city', 'qld-oth-noncity',\n 'sa-oth-city', 'sa-oth-noncity', 'wa-oth-city', 'wa-oth-noncity',\n 'tas-oth-city', 'tas-oth-noncity', 'nt-oth-city', 'nt-oth-noncity'],\n dtype=object)}\n\n\nWe split the dataframe in train/test splits.\n\nY_test_df = Y_df.groupby('unique_id').tail(12)\nY_train_df = Y_df.drop(Y_test_df.index)\n\n\nY_test_df = Y_test_df.set_index('unique_id')\nY_train_df = Y_train_df.set_index('unique_id')" + }, + { + "objectID": "examples/tourismsmall.html#base-forecasts", + "href": "examples/tourismsmall.html#base-forecasts", + "title": "Reconciliation Quick Start", + "section": "3. Base forecasts", + "text": "3. Base forecasts\nThe following cell computes the base forecast for each time series using the auto_arima and naive models. Observe that Y_hat_df contains the forecasts but they are not coherent.\n\nfrom statsforecast.core import StatsForecast\nfrom statsforecast.models import AutoARIMA, Naive\n\n\nfcst = StatsForecast(\n df=Y_train_df, \n models=[AutoARIMA(season_length=12), Naive()], \n freq='M', \n n_jobs=-1\n)\nY_hat_df = fcst.forecast(h=12)" + }, + { + "objectID": "examples/tourismsmall.html#hierarchical-reconciliation", + "href": "examples/tourismsmall.html#hierarchical-reconciliation", + "title": "Reconciliation Quick Start", + "section": "4. Hierarchical reconciliation", + "text": "4. Hierarchical reconciliation\nThe following cell makes the previous forecasts coherent using the HierarchicalReconciliation class. The used methods to make the forecasts coherent are:\n\nBottomUp: The reconciliation of the method is a simple addition to the upper levels.\nTopDown: The second method constrains the base-level predictions to the top-most aggregate-level serie and then distributes it to the disaggregate series through the use of proportions.\nMiddleOut: Anchors the base predictions in a middle level.\n\n\nfrom hierarchicalforecast.core import HierarchicalReconciliation\nfrom hierarchicalforecast.methods import BottomUp, TopDown, MiddleOut\n\n\nreconcilers = [\n BottomUp(),\n TopDown(method='forecast_proportions'),\n MiddleOut(middle_level='Country/Purpose/State', \n top_down_method='forecast_proportions')\n]\nhrec = HierarchicalReconciliation(reconcilers=reconcilers)\nY_rec_df = hrec.reconcile(Y_hat_df=Y_hat_df, Y_df=Y_train_df, \n S=S_df, tags=tags)" + }, + { + "objectID": "examples/tourismsmall.html#evaluation", + "href": "examples/tourismsmall.html#evaluation", + "title": "Reconciliation Quick Start", + "section": "5. Evaluation", + "text": "5. Evaluation\nThe HierarchicalForecast package includes the HierarchicalEvaluation class to evaluate the different hierarchies and also is capable of compute scaled metrics compared to a benchmark model.\n\nfrom hierarchicalforecast.evaluation import HierarchicalEvaluation\n\n\ndef mse(y, y_hat):\n return np.mean((y-y_hat)**2)\n\nevaluator = HierarchicalEvaluation(evaluators=[mse])\nevaluation = evaluator.evaluate(\n Y_hat_df=Y_rec_df, Y_test_df=Y_test_df, \n tags=tags, benchmark='Naive'\n)\nevaluation.filter(like='ARIMA', axis=1).T\n\n\n\n\n\n\n\nlevel\nOverall\nCountry\nCountry/Purpose\nCountry/Purpose/State\nCountry/Purpose/State/CityNonCity\n\n\nmetric\nmse-scaled\nmse-scaled\nmse-scaled\nmse-scaled\nmse-scaled\n\n\n\n\nAutoARIMA\n0.958324\n1.051911\n0.903933\n0.909364\n0.845968\n\n\nAutoARIMA/BottomUp\n0.911524\n0.984391\n0.86538\n0.865384\n0.845968\n\n\nAutoARIMA/TopDown_method-forecast_proportions\n0.973036\n1.051911\n0.88765\n0.973843\n0.949449\n\n\nAutoARIMA/MiddleOut_middle_level-Country/Purpose/State_top_down_method-forecast_proportions\n0.896147\n0.950773\n0.830191\n0.909364\n0.885306\n\n\n\n\n\n\n\n\nReferences\n\nOrcutt, G.H., Watts, H.W., & Edwards, J.B.(1968). Data aggregation and information loss. The American Economic Review, 58 , 773{787).\nDisaggregation methods to expedite product line forecasting. Journal of Forecasting, 9 , 233ā€“254. doi:10.1002/for.3980090304.\nAn investigation of aggregate variable time series forecast strategies with specific subaggregate time series statistical correlation. Computers and Operations Research, 26 , 1133ā€“1149. doi:10.1016/S0305-0548(99)00017-9.\nHyndman, R.J., & Athanasopoulos, G. (2021). ā€œForecasting: principles and practice, 3rd edition: Chapter 11: Forecasting hierarchical and grouped series.ā€. OTexts: Melbourne, Australia. OTexts.com/fpp3 Accessed on July 2022." + }, + { + "objectID": "examples/installation.html", + "href": "examples/installation.html", + "title": "Install", + "section": "", + "text": "You can install the released version of HierachicalForecast from the Python package index with:\npip install hierarchicalforecast\nor\nconda install -c conda-forge hierarchicalforecast\n\n\n\n\n\n\nTip\n\n\n\nWe recommend installing your libraries inside a python virtual or conda environment.\n\n\n\nUser our env (optional)\nIf you donā€™t have a Conda environment and need tools like Numba, Pandas, NumPy, Jupyter, StatsModels, and Nbdev you can use ours by following these steps:\n\nClone the HierachicalForecast repo:\n\n$ git clone https://github.com/Nixtla/hierachicalforecast.git && cd hierachicalforecast\n\nCreate the environment using the environment.yml file:\n\n$ conda env create -f environment.yml\n\nActivate the environment:\n\n$ conda activate statsforecast\n\n\n\n\nIf you find the code useful, please ā­ us on Github" + }, + { + "objectID": "examples/australiandomestictourism-intervals.html", + "href": "examples/australiandomestictourism-intervals.html", + "title": "Normality", + "section": "", + "text": "In many cases, only the time series at the lowest level of the hierarchies (bottom time series) are available. HierarchicalForecast has tools to create time series for all hierarchies and also allows you to calculate prediction intervals for all hierarchies. In this notebook we will see how to do it.\n!pip install hierarchicalforecast statsforecast\nimport numpy as np\nimport pandas as pd\nimport matplotlib.pyplot as plt\n\n# compute base forecast no coherent\nfrom statsforecast.models import AutoARIMA\nfrom statsforecast.core import StatsForecast\n\n#obtain hierarchical reconciliation methods and evaluation\nfrom hierarchicalforecast.methods import BottomUp, MinTrace\nfrom hierarchicalforecast.utils import aggregate, HierarchicalPlot\nfrom hierarchicalforecast.core import HierarchicalReconciliation\nfrom hierarchicalforecast.evaluation import HierarchicalEvaluation\n\n/Users/fedex/miniconda3/envs/hierarchicalforecast/lib/python3.10/site-packages/statsforecast/core.py:21: TqdmWarning: IProgress not found. Please update jupyter and ipywidgets. See https://ipywidgets.readthedocs.io/en/stable/user_install.html\n from tqdm.autonotebook import tqdm\nIf you find the code useful, please ā­ us on Github" + }, + { + "objectID": "examples/australiandomestictourism-intervals.html#aggregate-bottom-time-series", + "href": "examples/australiandomestictourism-intervals.html#aggregate-bottom-time-series", + "title": "Normality", + "section": "Aggregate bottom time series", + "text": "Aggregate bottom time series\nIn this example we will use the Tourism dataset from the Forecasting: Principles and Practice book. The dataset only contains the time series at the lowest level, so we need to create the time series for all hierarchies.\n\nY_df = pd.read_csv('https://raw.githubusercontent.com/Nixtla/transfer-learning-time-series/main/datasets/tourism.csv')\nY_df = Y_df.rename({'Trips': 'y', 'Quarter': 'ds'}, axis=1)\nY_df.insert(0, 'Country', 'Australia')\nY_df = Y_df[['Country', 'Region', 'State', 'Purpose', 'ds', 'y']]\nY_df['ds'] = Y_df['ds'].str.replace(r'(\\d+) (Q\\d)', r'\\1-\\2', regex=True)\nY_df['ds'] = pd.to_datetime(Y_df['ds'])\nY_df.head()\n\n\n\n\n\n\n\n\nCountry\nRegion\nState\nPurpose\nds\ny\n\n\n\n\n0\nAustralia\nAdelaide\nSouth Australia\nBusiness\n1998-01-01\n135.077690\n\n\n1\nAustralia\nAdelaide\nSouth Australia\nBusiness\n1998-04-01\n109.987316\n\n\n2\nAustralia\nAdelaide\nSouth Australia\nBusiness\n1998-07-01\n166.034687\n\n\n3\nAustralia\nAdelaide\nSouth Australia\nBusiness\n1998-10-01\n127.160464\n\n\n4\nAustralia\nAdelaide\nSouth Australia\nBusiness\n1999-01-01\n137.448533\n\n\n\n\n\n\n\nThe dataset can be grouped in the following non-strictly hierarchical structure.\n\nspec = [\n ['Country'],\n ['Country', 'State'], \n ['Country', 'Purpose'], \n ['Country', 'State', 'Region'], \n ['Country', 'State', 'Purpose'], \n ['Country', 'State', 'Region', 'Purpose']\n]\n\nUsing the aggregate function from HierarchicalForecast we can generate: 1. Y_df: the hierarchical structured series \\(\\mathbf{y}_{[a,b]\\tau}\\) 2. S_df: the aggregation constraings dataframe with \\(S_{[a,b]}\\) 3. tags: a list with the ā€˜unique_idsā€™ conforming each aggregation level.\n\nY_df, S_df, tags = aggregate(df=Y_df, spec=spec)\nY_df = Y_df.reset_index()\n\n/Users/fedex/miniconda3/envs/hierarchicalforecast/lib/python3.10/site-packages/sklearn/preprocessing/_encoders.py:828: FutureWarning: `sparse` was renamed to `sparse_output` in version 1.2 and will be removed in 1.4. `sparse_output` is ignored unless you leave `sparse` to its default value.\n warnings.warn(\n\n\n\nY_df.head()\n\n\n\n\n\n\n\n\nunique_id\nds\ny\n\n\n\n\n0\nAustralia\n1998-01-01\n23182.197269\n\n\n1\nAustralia\n1998-04-01\n20323.380067\n\n\n2\nAustralia\n1998-07-01\n19826.640511\n\n\n3\nAustralia\n1998-10-01\n20830.129891\n\n\n4\nAustralia\n1999-01-01\n22087.353380\n\n\n\n\n\n\n\n\nS_df.iloc[:5, :5]\n\n\n\n\n\n\n\n\nAustralia/ACT/Canberra/Business\nAustralia/ACT/Canberra/Holiday\nAustralia/ACT/Canberra/Other\nAustralia/ACT/Canberra/Visiting\nAustralia/New South Wales/Blue Mountains/Business\n\n\n\n\nAustralia\n1.0\n1.0\n1.0\n1.0\n1.0\n\n\nAustralia/ACT\n1.0\n1.0\n1.0\n1.0\n0.0\n\n\nAustralia/New South Wales\n0.0\n0.0\n0.0\n0.0\n1.0\n\n\nAustralia/Northern Territory\n0.0\n0.0\n0.0\n0.0\n0.0\n\n\nAustralia/Queensland\n0.0\n0.0\n0.0\n0.0\n0.0\n\n\n\n\n\n\n\n\ntags['Country/Purpose']\n\narray(['Australia/Business', 'Australia/Holiday', 'Australia/Other',\n 'Australia/Visiting'], dtype=object)\n\n\nWe can visualize the S matrix and the data using the HierarchicalPlot class as follows.\n\nhplot = HierarchicalPlot(S=S_df, tags=tags)\n\n\nhplot.plot_summing_matrix()\n\n\n\n\n\nhplot.plot_hierarchically_linked_series(\n bottom_series='Australia/ACT/Canberra/Holiday',\n Y_df=Y_df.set_index('unique_id')\n)\n\n\n\n\n\nSplit Train/Test sets\nWe use the final two years (8 quarters) as test set.\n\nY_test_df = Y_df.groupby('unique_id').tail(8)\nY_train_df = Y_df.drop(Y_test_df.index)\n\n\nY_test_df = Y_test_df.set_index('unique_id')\nY_train_df = Y_train_df.set_index('unique_id')\n\n\nY_train_df.groupby('unique_id').size()\n\nunique_id\nAustralia 72\nAustralia/ACT 72\nAustralia/ACT/Business 72\nAustralia/ACT/Canberra 72\nAustralia/ACT/Canberra/Business 72\n ..\nAustralia/Western Australia/Experience Perth/Other 72\nAustralia/Western Australia/Experience Perth/Visiting 72\nAustralia/Western Australia/Holiday 72\nAustralia/Western Australia/Other 72\nAustralia/Western Australia/Visiting 72\nLength: 425, dtype: int64" + }, + { + "objectID": "examples/australiandomestictourism-intervals.html#computing-base-forecasts", + "href": "examples/australiandomestictourism-intervals.html#computing-base-forecasts", + "title": "Normality", + "section": "Computing base forecasts", + "text": "Computing base forecasts\nThe following cell computes the base forecasts for each time series in Y_df using the AutoARIMA and model. Observe that Y_hat_df contains the forecasts but they are not coherent. To reconcile the prediction intervals we need to calculate the uncoherent intervals using the level argument of StatsForecast.\n\nfcst = StatsForecast(df=Y_train_df,\n models=[AutoARIMA(season_length=4)], \n freq='QS', n_jobs=-1)\nY_hat_df = fcst.forecast(h=8, fitted=True, level=[80, 90])\nY_fitted_df = fcst.forecast_fitted_values()" + }, + { + "objectID": "examples/australiandomestictourism-intervals.html#reconcile-forecasts", + "href": "examples/australiandomestictourism-intervals.html#reconcile-forecasts", + "title": "Normality", + "section": "Reconcile forecasts", + "text": "Reconcile forecasts\nThe following cell makes the previous forecasts coherent using the HierarchicalReconciliation class. Since the hierarchy structure is not strict, we canā€™t use methods such as TopDown or MiddleOut. In this example we use BottomUp and MinTrace. If you want to calculate prediction intervals, you have to use the level argument as follows.\n\nreconcilers = [\n BottomUp(),\n MinTrace(method='mint_shrink'),\n MinTrace(method='ols')\n]\nhrec = HierarchicalReconciliation(reconcilers=reconcilers)\nY_rec_df = hrec.reconcile(Y_hat_df=Y_hat_df, Y_df=Y_fitted_df, \n S=S_df, tags=tags, level=[80, 90])\n\nThe dataframe Y_rec_df contains the reconciled forecasts.\n\nY_rec_df.head()\n\n\n\n\n\n\n\n\nds\nAutoARIMA\nAutoARIMA-lo-90\nAutoARIMA-lo-80\nAutoARIMA-hi-80\nAutoARIMA-hi-90\nAutoARIMA/BottomUp\nAutoARIMA/BottomUp-lo-90\nAutoARIMA/BottomUp-lo-80\nAutoARIMA/BottomUp-hi-80\n...\nAutoARIMA/MinTrace_method-mint_shrink\nAutoARIMA/MinTrace_method-mint_shrink-lo-90\nAutoARIMA/MinTrace_method-mint_shrink-lo-80\nAutoARIMA/MinTrace_method-mint_shrink-hi-80\nAutoARIMA/MinTrace_method-mint_shrink-hi-90\nAutoARIMA/MinTrace_method-ols\nAutoARIMA/MinTrace_method-ols-lo-90\nAutoARIMA/MinTrace_method-ols-lo-80\nAutoARIMA/MinTrace_method-ols-hi-80\nAutoARIMA/MinTrace_method-ols-hi-90\n\n\nunique_id\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\nAustralia\n2016-01-01\n26212.554688\n24694.224609\n25029.580078\n27395.527344\n27730.884766\n24368.099609\n23674.076441\n23827.366706\n24908.832513\n...\n25205.749397\n24453.417115\n24619.586229\n25791.912565\n25958.081679\n26059.047512\n24978.608364\n25217.247087\n26900.847937\n27139.486661\n\n\nAustralia\n2016-04-01\n25033.667969\n23324.066406\n23701.669922\n26365.666016\n26743.269531\n22395.921875\n21629.482078\n21798.767146\n22993.076604\n...\n23720.833190\n22915.772233\n23093.587632\n24348.078748\n24525.894148\n24769.464257\n23554.946551\n23823.199470\n25715.729045\n25983.981963\n\n\nAustralia\n2016-07-01\n24507.027344\n22625.500000\n23041.076172\n25972.978516\n26388.554688\n22004.169922\n21182.945074\n21364.330624\n22644.009219\n...\n23167.123691\n22316.298074\n22504.221604\n23830.025777\n24017.949308\n24205.855344\n22870.661086\n23165.568073\n25246.142616\n25541.049603\n\n\nAustralia\n2016-10-01\n25598.929688\n23559.919922\n24010.281250\n27187.578125\n27637.937500\n22325.056641\n21456.892977\n21648.645996\n23001.467285\n...\n23982.251913\n23087.313715\n23284.980478\n24679.523348\n24877.190111\n25271.861336\n23825.782311\n24145.180634\n26398.542038\n26717.940362\n\n\nAustralia\n2017-01-01\n26982.578125\n24651.535156\n25166.396484\n28798.757812\n29313.619141\n23258.001953\n22296.178714\n22508.618508\n24007.385398\n...\n25002.243615\n24016.747195\n24234.415731\n25770.071498\n25987.740034\n26611.143736\n24959.636647\n25324.408272\n27897.879201\n28262.650825\n\n\n\n\n5 rows Ɨ 21 columns" + }, + { + "objectID": "examples/australiandomestictourism-intervals.html#plot-forecasts", + "href": "examples/australiandomestictourism-intervals.html#plot-forecasts", + "title": "Normality", + "section": "Plot forecasts", + "text": "Plot forecasts\nThen we can plot the probabilistic forecasts using the following function.\n\nplot_df = pd.concat([Y_df.set_index(['unique_id', 'ds']), \n Y_rec_df.set_index('ds', append=True)], axis=1)\nplot_df = plot_df.reset_index('ds')\n\n\nPlot single time series\n\nhplot.plot_series(\n series='Australia',\n Y_df=plot_df, \n models=['y', 'AutoARIMA', 'AutoARIMA/MinTrace_method-ols'],\n level=[80]\n)\n\n\n\n\n\n# Since we are plotting a bottom time series\n# the probabilistic and mean forecasts\n# are the same\nhplot.plot_series(\n series='Australia/Western Australia/Experience Perth/Visiting',\n Y_df=plot_df, \n models=['y', 'AutoARIMA', 'AutoARIMA/BottomUp'],\n level=[80]\n)\n\n\n\n\n\n\nPlot hierarchichally linked time series\n\nhplot.plot_hierarchically_linked_series(\n bottom_series='Australia/Western Australia/Experience Perth/Visiting',\n Y_df=plot_df, \n models=['y', 'AutoARIMA', 'AutoARIMA/MinTrace_method-ols', 'AutoARIMA/BottomUp'],\n level=[80]\n)\n\n\n\n\n\n# ACT only has Canberra\nhplot.plot_hierarchically_linked_series(\n bottom_series='Australia/ACT/Canberra/Other',\n Y_df=plot_df, \n models=['y', 'AutoARIMA/MinTrace_method-mint_shrink'],\n level=[80, 90]\n)\n\n\n\n\n\n\nReferences\n\nHyndman, R.J., & Athanasopoulos, G. (2021). ā€œForecasting: principles and practice, 3rd edition: Chapter 11: Forecasting hierarchical and grouped series.ā€. OTexts: Melbourne, Australia. OTexts.com/fpp3 Accessed on July 2022.\nShanika L. Wickramasuriya, George Athanasopoulos, and Rob J. Hyndman. Optimal forecast reconciliation for hierarchical and grouped time series through trace minimization.Journal of the American Statistical Association, 114(526):804ā€“819, 2019. doi: 10.1080/01621459.2018.1448825. URL https://robjhyndman.com/publications/mint/." + }, + { + "objectID": "examples/australiandomestictourism-permbu-intervals.html", + "href": "examples/australiandomestictourism-permbu-intervals.html", + "title": "PERMBU", + "section": "", + "text": "In many cases, only the time series at the lowest level of the hierarchies (bottom time series) are available. HierarchicalForecast has tools to create time series for all hierarchies and also allows you to calculate prediction intervals for all hierarchies. In this notebook we will see how to do it.\n!pip install hierarchicalforecast statsforecast\nimport numpy as np\nimport pandas as pd\nimport matplotlib.pyplot as plt\n\n# compute base forecast no coherent\nfrom statsforecast.models import AutoARIMA\nfrom statsforecast.core import StatsForecast\n\n#obtain hierarchical reconciliation methods and evaluation\nfrom hierarchicalforecast.methods import BottomUp, MinTrace\nfrom hierarchicalforecast.utils import aggregate, HierarchicalPlot\nfrom hierarchicalforecast.core import HierarchicalReconciliation\nfrom hierarchicalforecast.evaluation import HierarchicalEvaluation\n\n/Users/fedex/miniconda3/envs/hierarchicalforecast/lib/python3.10/site-packages/statsforecast/core.py:21: TqdmWarning: IProgress not found. Please update jupyter and ipywidgets. See https://ipywidgets.readthedocs.io/en/stable/user_install.html\n from tqdm.autonotebook import tqdm\nIf you find the code useful, please ā­ us on Github" + }, + { + "objectID": "examples/australiandomestictourism-permbu-intervals.html#aggregate-bottom-time-series", + "href": "examples/australiandomestictourism-permbu-intervals.html#aggregate-bottom-time-series", + "title": "PERMBU", + "section": "Aggregate bottom time series", + "text": "Aggregate bottom time series\nIn this example we will use the Tourism dataset from the Forecasting: Principles and Practice book. The dataset only contains the time series at the lowest level, so we need to create the time series for all hierarchies.\n\nY_df = pd.read_csv('https://raw.githubusercontent.com/Nixtla/transfer-learning-time-series/main/datasets/tourism.csv')\nY_df = Y_df.rename({'Trips': 'y', 'Quarter': 'ds'}, axis=1)\nY_df.insert(0, 'Country', 'Australia')\nY_df = Y_df[['Country', 'Region', 'State', 'Purpose', 'ds', 'y']]\nY_df['ds'] = Y_df['ds'].str.replace(r'(\\d+) (Q\\d)', r'\\1-\\2', regex=True)\nY_df['ds'] = pd.to_datetime(Y_df['ds'])\nY_df.head()\n\n\n\n\n\n\n\n\nCountry\nRegion\nState\nPurpose\nds\ny\n\n\n\n\n0\nAustralia\nAdelaide\nSouth Australia\nBusiness\n1998-01-01\n135.077690\n\n\n1\nAustralia\nAdelaide\nSouth Australia\nBusiness\n1998-04-01\n109.987316\n\n\n2\nAustralia\nAdelaide\nSouth Australia\nBusiness\n1998-07-01\n166.034687\n\n\n3\nAustralia\nAdelaide\nSouth Australia\nBusiness\n1998-10-01\n127.160464\n\n\n4\nAustralia\nAdelaide\nSouth Australia\nBusiness\n1999-01-01\n137.448533\n\n\n\n\n\n\n\nThe dataset can be grouped in the following strictly hierarchical structure.\n\nspec = [\n ['Country'],\n ['Country', 'State'], \n ['Country', 'State', 'Region']\n]\n\nUsing the aggregate function from HierarchicalForecast we can get the full set of time series.\n\nY_df, S_df, tags = aggregate(df=Y_df, spec=spec)\nY_df = Y_df.reset_index()\n\n/Users/fedex/miniconda3/envs/hierarchicalforecast/lib/python3.10/site-packages/sklearn/preprocessing/_encoders.py:828: FutureWarning: `sparse` was renamed to `sparse_output` in version 1.2 and will be removed in 1.4. `sparse_output` is ignored unless you leave `sparse` to its default value.\n warnings.warn(\n\n\n\nY_df.head()\n\n\n\n\n\n\n\n\nunique_id\nds\ny\n\n\n\n\n0\nAustralia\n1998-01-01\n23182.197269\n\n\n1\nAustralia\n1998-04-01\n20323.380067\n\n\n2\nAustralia\n1998-07-01\n19826.640511\n\n\n3\nAustralia\n1998-10-01\n20830.129891\n\n\n4\nAustralia\n1999-01-01\n22087.353380\n\n\n\n\n\n\n\n\nS_df.iloc[:5, :5]\n\n\n\n\n\n\n\n\nAustralia/ACT/Canberra\nAustralia/New South Wales/Blue Mountains\nAustralia/New South Wales/Capital Country\nAustralia/New South Wales/Central Coast\nAustralia/New South Wales/Central NSW\n\n\n\n\nAustralia\n1.0\n1.0\n1.0\n1.0\n1.0\n\n\nAustralia/ACT\n1.0\n0.0\n0.0\n0.0\n0.0\n\n\nAustralia/New South Wales\n0.0\n1.0\n1.0\n1.0\n1.0\n\n\nAustralia/Northern Territory\n0.0\n0.0\n0.0\n0.0\n0.0\n\n\nAustralia/Queensland\n0.0\n0.0\n0.0\n0.0\n0.0\n\n\n\n\n\n\n\n\ntags['Country/State']\n\narray(['Australia/ACT', 'Australia/New South Wales',\n 'Australia/Northern Territory', 'Australia/Queensland',\n 'Australia/South Australia', 'Australia/Tasmania',\n 'Australia/Victoria', 'Australia/Western Australia'], dtype=object)\n\n\nWe can visualize the S matrix and the data using the HierarchicalPlot class as follows.\n\nhplot = HierarchicalPlot(S=S_df, tags=tags)\n\n\nhplot.plot_summing_matrix()\n\n\n\n\n\nhplot.plot_hierarchically_linked_series(\n bottom_series='Australia/ACT/Canberra',\n Y_df=Y_df.set_index('unique_id')\n)\n\n\n\n\n\nSplit Train/Test sets\nWe use the final two years (8 quarters) as test set.\n\nY_test_df = Y_df.groupby('unique_id').tail(8)\nY_train_df = Y_df.drop(Y_test_df.index)\n\n\nY_test_df = Y_test_df.set_index('unique_id')\nY_train_df = Y_train_df.set_index('unique_id')\n\n\nY_train_df.groupby('unique_id').size()\n\nunique_id\nAustralia 72\nAustralia/ACT 72\nAustralia/ACT/Canberra 72\nAustralia/New South Wales 72\nAustralia/New South Wales/Blue Mountains 72\n ..\nAustralia/Western Australia/Australia's Coral Coast 72\nAustralia/Western Australia/Australia's Golden Outback 72\nAustralia/Western Australia/Australia's North West 72\nAustralia/Western Australia/Australia's South West 72\nAustralia/Western Australia/Experience Perth 72\nLength: 85, dtype: int64" + }, + { + "objectID": "examples/australiandomestictourism-permbu-intervals.html#computing-base-forecasts", + "href": "examples/australiandomestictourism-permbu-intervals.html#computing-base-forecasts", + "title": "PERMBU", + "section": "Computing base forecasts", + "text": "Computing base forecasts\nThe following cell computes the base forecasts for each time series in Y_df using the AutoARIMA and model. Observe that Y_hat_df contains the forecasts but they are not coherent. To reconcile the prediction intervals we need to calculate the uncoherent intervals using the level argument of StatsForecast.\n\nfcst = StatsForecast(df=Y_train_df,\n models=[AutoARIMA(season_length=4)], \n freq='QS', n_jobs=-1)\nY_hat_df = fcst.forecast(h=8, fitted=True, level=[80, 90])\nY_fitted_df = fcst.forecast_fitted_values()" + }, + { + "objectID": "examples/australiandomestictourism-permbu-intervals.html#reconcile-forecasts-and-compute-prediction-intervals-using-permbu", + "href": "examples/australiandomestictourism-permbu-intervals.html#reconcile-forecasts-and-compute-prediction-intervals-using-permbu", + "title": "PERMBU", + "section": "Reconcile forecasts and compute prediction intervals using PERMBU", + "text": "Reconcile forecasts and compute prediction intervals using PERMBU\nThe following cell makes the previous forecasts coherent using the HierarchicalReconciliation class. In this example we use BottomUp and MinTrace. If you want to calculate prediction intervals, you have to use the level argument as follows and also intervals_method='permbu'.\n\nreconcilers = [\n BottomUp(),\n MinTrace(method='mint_shrink'),\n MinTrace(method='ols')\n]\nhrec = HierarchicalReconciliation(reconcilers=reconcilers)\nY_rec_df = hrec.reconcile(Y_hat_df=Y_hat_df, Y_df=Y_fitted_df,\n S=S_df, tags=tags,\n level=[80, 90], intervals_method='permbu')\n\n/Users/fedex/miniconda3/envs/hierarchicalforecast/lib/python3.10/site-packages/sklearn/preprocessing/_encoders.py:828: FutureWarning: `sparse` was renamed to `sparse_output` in version 1.2 and will be removed in 1.4. `sparse_output` is ignored unless you leave `sparse` to its default value.\n warnings.warn(\n/Users/fedex/miniconda3/envs/hierarchicalforecast/lib/python3.10/site-packages/sklearn/preprocessing/_encoders.py:828: FutureWarning: `sparse` was renamed to `sparse_output` in version 1.2 and will be removed in 1.4. `sparse_output` is ignored unless you leave `sparse` to its default value.\n warnings.warn(\n/Users/fedex/miniconda3/envs/hierarchicalforecast/lib/python3.10/site-packages/sklearn/preprocessing/_encoders.py:828: FutureWarning: `sparse` was renamed to `sparse_output` in version 1.2 and will be removed in 1.4. `sparse_output` is ignored unless you leave `sparse` to its default value.\n warnings.warn(\n/Users/fedex/miniconda3/envs/hierarchicalforecast/lib/python3.10/site-packages/sklearn/preprocessing/_encoders.py:828: FutureWarning: `sparse` was renamed to `sparse_output` in version 1.2 and will be removed in 1.4. `sparse_output` is ignored unless you leave `sparse` to its default value.\n warnings.warn(\n/Users/fedex/miniconda3/envs/hierarchicalforecast/lib/python3.10/site-packages/sklearn/preprocessing/_encoders.py:828: FutureWarning: `sparse` was renamed to `sparse_output` in version 1.2 and will be removed in 1.4. `sparse_output` is ignored unless you leave `sparse` to its default value.\n warnings.warn(\n/Users/fedex/miniconda3/envs/hierarchicalforecast/lib/python3.10/site-packages/sklearn/preprocessing/_encoders.py:828: FutureWarning: `sparse` was renamed to `sparse_output` in version 1.2 and will be removed in 1.4. `sparse_output` is ignored unless you leave `sparse` to its default value.\n warnings.warn(\n\n\nThe dataframe Y_rec_df contains the reconciled forecasts.\n\nY_rec_df.head()\n\n\n\n\n\n\n\n\nds\nAutoARIMA\nAutoARIMA-lo-90\nAutoARIMA-lo-80\nAutoARIMA-hi-80\nAutoARIMA-hi-90\nAutoARIMA/BottomUp\nAutoARIMA/BottomUp-lo-90\nAutoARIMA/BottomUp-lo-80\nAutoARIMA/BottomUp-hi-80\n...\nAutoARIMA/MinTrace_method-mint_shrink\nAutoARIMA/MinTrace_method-mint_shrink-lo-90\nAutoARIMA/MinTrace_method-mint_shrink-lo-80\nAutoARIMA/MinTrace_method-mint_shrink-hi-80\nAutoARIMA/MinTrace_method-mint_shrink-hi-90\nAutoARIMA/MinTrace_method-ols\nAutoARIMA/MinTrace_method-ols-lo-90\nAutoARIMA/MinTrace_method-ols-lo-80\nAutoARIMA/MinTrace_method-ols-hi-80\nAutoARIMA/MinTrace_method-ols-hi-90\n\n\nunique_id\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\nAustralia\n2016-01-01\n26212.554688\n24694.224609\n25029.580078\n27395.527344\n27730.884766\n24865.636719\n24106.802510\n24373.962043\n25423.566450\n...\n25395.411928\n24733.046633\n24824.274681\n25939.345007\n25998.692460\n26133.758953\n25516.484518\n25600.644926\n26662.923204\n26855.585562\n\n\nAustralia\n2016-04-01\n25033.667969\n23324.066406\n23701.669922\n26365.666016\n26743.269531\n23247.097656\n22696.597930\n22821.256357\n23830.632567\n...\n23986.272540\n23289.811432\n23525.630785\n24563.998645\n24739.826238\n24934.260399\n24402.570904\n24481.968560\n25567.085565\n25696.312229\n\n\nAustralia\n2016-07-01\n24507.027344\n22625.500000\n23041.076172\n25972.978516\n26388.554688\n22658.207031\n21816.988906\n22011.905035\n23158.311619\n...\n23345.821184\n22688.605574\n22780.602574\n23934.244609\n24033.906220\n24374.026569\n23539.673724\n23797.836651\n24893.463090\n25098.321828\n\n\nAustralia\n2016-10-01\n25598.929688\n23559.919922\n24010.281250\n27187.578125\n27637.937500\n23330.804688\n22567.948299\n22694.449708\n23850.068162\n...\n24275.423420\n23392.040447\n23626.165662\n24828.207813\n24958.926002\n25477.951913\n24793.436993\n24911.271547\n26006.244161\n26152.362329\n\n\nAustralia\n2017-01-01\n26982.578125\n24651.535156\n25166.396484\n28798.757812\n29313.619141\n24497.001953\n23578.418503\n23731.657437\n25114.564017\n...\n25485.433879\n24549.969625\n24802.819691\n26073.792872\n26284.826386\n26842.564741\n26037.725561\n26248.171831\n27436.222761\n27668.067666\n\n\n\n\n5 rows Ɨ 21 columns" + }, + { + "objectID": "examples/australiandomestictourism-permbu-intervals.html#plot-forecasts", + "href": "examples/australiandomestictourism-permbu-intervals.html#plot-forecasts", + "title": "PERMBU", + "section": "Plot forecasts", + "text": "Plot forecasts\nThen we can plot the probabilist forecasts using the following function.\n\nplot_df = pd.concat([Y_df.set_index(['unique_id', 'ds']), \n Y_rec_df.set_index('ds', append=True)], axis=1)\nplot_df = plot_df.reset_index('ds')\n\n\nPlot single time series\n\nhplot.plot_series(\n series='Australia',\n Y_df=plot_df, \n models=['y', 'AutoARIMA', \n 'AutoARIMA/MinTrace_method-ols',\n 'AutoARIMA/BottomUp'\n ],\n level=[80]\n)\n\n\n\n\n\n\nPlot hierarchichally linked time series\n\nhplot.plot_hierarchically_linked_series(\n bottom_series='Australia/Western Australia/Experience Perth',\n Y_df=plot_df, \n models=['y', 'AutoARIMA', 'AutoARIMA/MinTrace_method-ols', 'AutoARIMA/BottomUp'],\n level=[80]\n)\n\n\n\n\n\n# ACT only has Canberra\nhplot.plot_hierarchically_linked_series(\n bottom_series='Australia/ACT/Canberra',\n Y_df=plot_df, \n models=['y', 'AutoARIMA/MinTrace_method-mint_shrink'],\n level=[80, 90]\n)\n\n\n\n\n\n\nReferences\n\nHyndman, R.J., & Athanasopoulos, G. (2021). ā€œForecasting: principles and practice, 3rd edition: Chapter 11: Forecasting hierarchical and grouped series.ā€. OTexts: Melbourne, Australia. OTexts.com/fpp3 Accessed on July 2022.\nShanika L. Wickramasuriya, George Athanasopoulos, and Rob J. Hyndman. Optimal forecast reconciliation for hierarchical and grouped time series through trace minimization.Journal of the American Statistical Association, 114(526):804ā€“819, 2019. doi: 10.1080/01621459.2018.1448825. URL https://robjhyndman.com/publications/mint/." + } +] \ No newline at end of file diff --git a/site_libs/bootstrap/bootstrap-icons.css b/site_libs/bootstrap/bootstrap-icons.css new file mode 100644 index 00000000..94f19404 --- /dev/null +++ b/site_libs/bootstrap/bootstrap-icons.css @@ -0,0 +1,2018 @@ +@font-face { + font-display: block; + font-family: "bootstrap-icons"; + src: +url("./bootstrap-icons.woff?2ab2cbbe07fcebb53bdaa7313bb290f2") format("woff"); +} + +.bi::before, +[class^="bi-"]::before, +[class*=" bi-"]::before { + display: inline-block; + font-family: bootstrap-icons !important; + font-style: normal; + font-weight: normal !important; + font-variant: normal; + text-transform: none; + line-height: 1; + vertical-align: -.125em; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} + +.bi-123::before { content: "\f67f"; } +.bi-alarm-fill::before { content: "\f101"; } +.bi-alarm::before { content: "\f102"; } +.bi-align-bottom::before { content: "\f103"; } +.bi-align-center::before { content: "\f104"; } +.bi-align-end::before { content: "\f105"; } +.bi-align-middle::before { content: "\f106"; } +.bi-align-start::before { content: "\f107"; } +.bi-align-top::before { content: "\f108"; } +.bi-alt::before { content: "\f109"; } +.bi-app-indicator::before { content: "\f10a"; } +.bi-app::before { content: "\f10b"; } +.bi-archive-fill::before { content: "\f10c"; } +.bi-archive::before { content: "\f10d"; } +.bi-arrow-90deg-down::before { content: "\f10e"; } +.bi-arrow-90deg-left::before { content: "\f10f"; } +.bi-arrow-90deg-right::before { content: "\f110"; } +.bi-arrow-90deg-up::before { content: "\f111"; } +.bi-arrow-bar-down::before { content: "\f112"; } +.bi-arrow-bar-left::before { content: "\f113"; } +.bi-arrow-bar-right::before { content: "\f114"; } +.bi-arrow-bar-up::before { content: "\f115"; } +.bi-arrow-clockwise::before { content: "\f116"; } +.bi-arrow-counterclockwise::before { content: "\f117"; } +.bi-arrow-down-circle-fill::before { content: "\f118"; } +.bi-arrow-down-circle::before { content: "\f119"; } +.bi-arrow-down-left-circle-fill::before { content: "\f11a"; } +.bi-arrow-down-left-circle::before { content: "\f11b"; } +.bi-arrow-down-left-square-fill::before { content: "\f11c"; } +.bi-arrow-down-left-square::before { content: "\f11d"; } +.bi-arrow-down-left::before { content: "\f11e"; } +.bi-arrow-down-right-circle-fill::before { content: "\f11f"; } +.bi-arrow-down-right-circle::before { content: "\f120"; } +.bi-arrow-down-right-square-fill::before { content: "\f121"; } +.bi-arrow-down-right-square::before { content: "\f122"; } +.bi-arrow-down-right::before { content: "\f123"; } +.bi-arrow-down-short::before { content: "\f124"; } +.bi-arrow-down-square-fill::before { content: "\f125"; } +.bi-arrow-down-square::before { content: "\f126"; } +.bi-arrow-down-up::before { content: "\f127"; } +.bi-arrow-down::before { content: "\f128"; } +.bi-arrow-left-circle-fill::before { content: "\f129"; } +.bi-arrow-left-circle::before { content: "\f12a"; } +.bi-arrow-left-right::before { content: "\f12b"; } +.bi-arrow-left-short::before { content: "\f12c"; } +.bi-arrow-left-square-fill::before { content: "\f12d"; } +.bi-arrow-left-square::before { content: "\f12e"; } +.bi-arrow-left::before { content: "\f12f"; } +.bi-arrow-repeat::before { content: "\f130"; } +.bi-arrow-return-left::before { content: "\f131"; } +.bi-arrow-return-right::before { content: "\f132"; } +.bi-arrow-right-circle-fill::before { content: "\f133"; } +.bi-arrow-right-circle::before { content: "\f134"; } +.bi-arrow-right-short::before { content: "\f135"; } +.bi-arrow-right-square-fill::before { content: "\f136"; } +.bi-arrow-right-square::before { content: "\f137"; } +.bi-arrow-right::before { content: "\f138"; } +.bi-arrow-up-circle-fill::before { content: "\f139"; } +.bi-arrow-up-circle::before { content: "\f13a"; } +.bi-arrow-up-left-circle-fill::before { content: "\f13b"; } +.bi-arrow-up-left-circle::before { content: "\f13c"; } +.bi-arrow-up-left-square-fill::before { content: "\f13d"; } +.bi-arrow-up-left-square::before { content: "\f13e"; } +.bi-arrow-up-left::before { content: "\f13f"; } +.bi-arrow-up-right-circle-fill::before { content: "\f140"; } +.bi-arrow-up-right-circle::before { content: "\f141"; } +.bi-arrow-up-right-square-fill::before { content: "\f142"; } +.bi-arrow-up-right-square::before { content: "\f143"; } +.bi-arrow-up-right::before { content: "\f144"; } +.bi-arrow-up-short::before { content: "\f145"; } +.bi-arrow-up-square-fill::before { content: "\f146"; } +.bi-arrow-up-square::before { content: "\f147"; } +.bi-arrow-up::before { content: "\f148"; } +.bi-arrows-angle-contract::before { content: "\f149"; } +.bi-arrows-angle-expand::before { content: "\f14a"; } +.bi-arrows-collapse::before { content: "\f14b"; } +.bi-arrows-expand::before { content: "\f14c"; } +.bi-arrows-fullscreen::before { content: "\f14d"; } +.bi-arrows-move::before { content: "\f14e"; } +.bi-aspect-ratio-fill::before { content: "\f14f"; } +.bi-aspect-ratio::before { content: "\f150"; } +.bi-asterisk::before { content: "\f151"; } +.bi-at::before { content: "\f152"; } +.bi-award-fill::before { content: "\f153"; } +.bi-award::before { content: "\f154"; } +.bi-back::before { content: "\f155"; } +.bi-backspace-fill::before { content: "\f156"; } +.bi-backspace-reverse-fill::before { content: "\f157"; } +.bi-backspace-reverse::before { content: "\f158"; } +.bi-backspace::before { content: "\f159"; } +.bi-badge-3d-fill::before { content: "\f15a"; } +.bi-badge-3d::before { content: "\f15b"; } +.bi-badge-4k-fill::before { content: "\f15c"; } +.bi-badge-4k::before { content: "\f15d"; } +.bi-badge-8k-fill::before { content: "\f15e"; } +.bi-badge-8k::before { content: "\f15f"; } +.bi-badge-ad-fill::before { content: "\f160"; } +.bi-badge-ad::before { content: "\f161"; } +.bi-badge-ar-fill::before { content: "\f162"; } +.bi-badge-ar::before { content: "\f163"; } +.bi-badge-cc-fill::before { content: "\f164"; } +.bi-badge-cc::before { content: "\f165"; } +.bi-badge-hd-fill::before { content: "\f166"; } +.bi-badge-hd::before { content: "\f167"; } +.bi-badge-tm-fill::before { content: "\f168"; } +.bi-badge-tm::before { content: "\f169"; } +.bi-badge-vo-fill::before { content: "\f16a"; } +.bi-badge-vo::before { content: "\f16b"; } +.bi-badge-vr-fill::before { content: "\f16c"; } +.bi-badge-vr::before { content: "\f16d"; } +.bi-badge-wc-fill::before { content: "\f16e"; } +.bi-badge-wc::before { content: "\f16f"; } +.bi-bag-check-fill::before { content: "\f170"; } +.bi-bag-check::before { content: "\f171"; } +.bi-bag-dash-fill::before { content: "\f172"; } +.bi-bag-dash::before { content: "\f173"; } +.bi-bag-fill::before { content: "\f174"; } +.bi-bag-plus-fill::before { content: "\f175"; } +.bi-bag-plus::before { content: "\f176"; } +.bi-bag-x-fill::before { content: "\f177"; } +.bi-bag-x::before { content: "\f178"; } +.bi-bag::before { content: "\f179"; } +.bi-bar-chart-fill::before { content: "\f17a"; } +.bi-bar-chart-line-fill::before { content: "\f17b"; } +.bi-bar-chart-line::before { content: "\f17c"; } +.bi-bar-chart-steps::before { content: "\f17d"; } +.bi-bar-chart::before { content: "\f17e"; } +.bi-basket-fill::before { content: "\f17f"; } +.bi-basket::before { content: "\f180"; } +.bi-basket2-fill::before { content: "\f181"; } +.bi-basket2::before { content: "\f182"; } +.bi-basket3-fill::before { content: "\f183"; } +.bi-basket3::before { content: "\f184"; } +.bi-battery-charging::before { content: "\f185"; } +.bi-battery-full::before { content: "\f186"; } +.bi-battery-half::before { content: "\f187"; } +.bi-battery::before { content: "\f188"; } +.bi-bell-fill::before { content: "\f189"; } +.bi-bell::before { content: "\f18a"; } +.bi-bezier::before { content: "\f18b"; } +.bi-bezier2::before { content: "\f18c"; } +.bi-bicycle::before { content: "\f18d"; } +.bi-binoculars-fill::before { content: "\f18e"; } +.bi-binoculars::before { content: "\f18f"; } +.bi-blockquote-left::before { content: "\f190"; } +.bi-blockquote-right::before { content: "\f191"; } +.bi-book-fill::before { content: "\f192"; } +.bi-book-half::before { content: "\f193"; } +.bi-book::before { content: "\f194"; } +.bi-bookmark-check-fill::before { content: "\f195"; } +.bi-bookmark-check::before { content: "\f196"; } +.bi-bookmark-dash-fill::before { content: "\f197"; } +.bi-bookmark-dash::before { content: "\f198"; } +.bi-bookmark-fill::before { content: "\f199"; } +.bi-bookmark-heart-fill::before { content: "\f19a"; } +.bi-bookmark-heart::before { content: "\f19b"; } +.bi-bookmark-plus-fill::before { content: "\f19c"; } +.bi-bookmark-plus::before { content: "\f19d"; } +.bi-bookmark-star-fill::before { content: "\f19e"; } +.bi-bookmark-star::before { content: "\f19f"; } +.bi-bookmark-x-fill::before { content: "\f1a0"; } +.bi-bookmark-x::before { content: "\f1a1"; } +.bi-bookmark::before { content: "\f1a2"; } +.bi-bookmarks-fill::before { content: "\f1a3"; } +.bi-bookmarks::before { content: "\f1a4"; } +.bi-bookshelf::before { content: "\f1a5"; } +.bi-bootstrap-fill::before { content: "\f1a6"; } +.bi-bootstrap-reboot::before { content: "\f1a7"; } +.bi-bootstrap::before { content: "\f1a8"; } +.bi-border-all::before { content: "\f1a9"; } +.bi-border-bottom::before { content: "\f1aa"; } +.bi-border-center::before { content: "\f1ab"; } +.bi-border-inner::before { content: "\f1ac"; } +.bi-border-left::before { content: "\f1ad"; } +.bi-border-middle::before { content: "\f1ae"; } +.bi-border-outer::before { content: "\f1af"; } +.bi-border-right::before { content: "\f1b0"; } +.bi-border-style::before { content: "\f1b1"; } +.bi-border-top::before { content: "\f1b2"; } +.bi-border-width::before { content: "\f1b3"; } +.bi-border::before { content: "\f1b4"; } +.bi-bounding-box-circles::before { content: "\f1b5"; } +.bi-bounding-box::before { content: "\f1b6"; } +.bi-box-arrow-down-left::before { content: "\f1b7"; } +.bi-box-arrow-down-right::before { content: "\f1b8"; } +.bi-box-arrow-down::before { content: "\f1b9"; } +.bi-box-arrow-in-down-left::before { content: "\f1ba"; } +.bi-box-arrow-in-down-right::before { content: "\f1bb"; } +.bi-box-arrow-in-down::before { content: "\f1bc"; } +.bi-box-arrow-in-left::before { content: "\f1bd"; } +.bi-box-arrow-in-right::before { content: "\f1be"; } +.bi-box-arrow-in-up-left::before { content: "\f1bf"; } +.bi-box-arrow-in-up-right::before { content: "\f1c0"; } +.bi-box-arrow-in-up::before { content: "\f1c1"; } +.bi-box-arrow-left::before { content: "\f1c2"; } +.bi-box-arrow-right::before { content: "\f1c3"; } +.bi-box-arrow-up-left::before { content: "\f1c4"; } +.bi-box-arrow-up-right::before { content: "\f1c5"; } +.bi-box-arrow-up::before { content: "\f1c6"; } +.bi-box-seam::before { content: "\f1c7"; } +.bi-box::before { content: "\f1c8"; } +.bi-braces::before { content: "\f1c9"; } +.bi-bricks::before { content: "\f1ca"; } +.bi-briefcase-fill::before { content: "\f1cb"; } +.bi-briefcase::before { content: "\f1cc"; } +.bi-brightness-alt-high-fill::before { content: "\f1cd"; } +.bi-brightness-alt-high::before { content: "\f1ce"; } +.bi-brightness-alt-low-fill::before { content: "\f1cf"; } +.bi-brightness-alt-low::before { content: "\f1d0"; } +.bi-brightness-high-fill::before { content: "\f1d1"; } +.bi-brightness-high::before { content: "\f1d2"; } +.bi-brightness-low-fill::before { content: "\f1d3"; } +.bi-brightness-low::before { content: "\f1d4"; } +.bi-broadcast-pin::before { content: "\f1d5"; } +.bi-broadcast::before { content: "\f1d6"; } +.bi-brush-fill::before { content: "\f1d7"; } +.bi-brush::before { content: "\f1d8"; } +.bi-bucket-fill::before { content: "\f1d9"; } +.bi-bucket::before { content: "\f1da"; } +.bi-bug-fill::before { content: "\f1db"; } +.bi-bug::before { content: "\f1dc"; } +.bi-building::before { content: "\f1dd"; } +.bi-bullseye::before { content: "\f1de"; } +.bi-calculator-fill::before { content: "\f1df"; } +.bi-calculator::before { content: "\f1e0"; } +.bi-calendar-check-fill::before { content: "\f1e1"; } +.bi-calendar-check::before { content: "\f1e2"; } +.bi-calendar-date-fill::before { content: "\f1e3"; } +.bi-calendar-date::before { content: "\f1e4"; } +.bi-calendar-day-fill::before { content: "\f1e5"; } +.bi-calendar-day::before { content: "\f1e6"; } +.bi-calendar-event-fill::before { content: "\f1e7"; } +.bi-calendar-event::before { content: "\f1e8"; } +.bi-calendar-fill::before { content: "\f1e9"; } +.bi-calendar-minus-fill::before { content: "\f1ea"; } +.bi-calendar-minus::before { content: "\f1eb"; } +.bi-calendar-month-fill::before { content: "\f1ec"; } +.bi-calendar-month::before { content: "\f1ed"; } +.bi-calendar-plus-fill::before { content: "\f1ee"; } +.bi-calendar-plus::before { content: "\f1ef"; } +.bi-calendar-range-fill::before { content: "\f1f0"; } +.bi-calendar-range::before { content: "\f1f1"; } +.bi-calendar-week-fill::before { content: "\f1f2"; } +.bi-calendar-week::before { content: "\f1f3"; } +.bi-calendar-x-fill::before { content: "\f1f4"; } +.bi-calendar-x::before { content: "\f1f5"; } +.bi-calendar::before { content: "\f1f6"; } +.bi-calendar2-check-fill::before { content: "\f1f7"; } +.bi-calendar2-check::before { content: "\f1f8"; } +.bi-calendar2-date-fill::before { content: "\f1f9"; } +.bi-calendar2-date::before { content: "\f1fa"; } +.bi-calendar2-day-fill::before { content: "\f1fb"; } +.bi-calendar2-day::before { content: "\f1fc"; } +.bi-calendar2-event-fill::before { content: "\f1fd"; } +.bi-calendar2-event::before { content: "\f1fe"; } +.bi-calendar2-fill::before { content: "\f1ff"; } +.bi-calendar2-minus-fill::before { content: "\f200"; } +.bi-calendar2-minus::before { content: "\f201"; } +.bi-calendar2-month-fill::before { content: "\f202"; } +.bi-calendar2-month::before { content: "\f203"; } +.bi-calendar2-plus-fill::before { content: "\f204"; } +.bi-calendar2-plus::before { content: "\f205"; } +.bi-calendar2-range-fill::before { content: "\f206"; } +.bi-calendar2-range::before { content: "\f207"; } +.bi-calendar2-week-fill::before { content: "\f208"; } +.bi-calendar2-week::before { content: "\f209"; } +.bi-calendar2-x-fill::before { content: "\f20a"; } +.bi-calendar2-x::before { content: "\f20b"; } +.bi-calendar2::before { content: "\f20c"; } +.bi-calendar3-event-fill::before { content: "\f20d"; } +.bi-calendar3-event::before { content: "\f20e"; } +.bi-calendar3-fill::before { content: "\f20f"; } +.bi-calendar3-range-fill::before { content: "\f210"; } +.bi-calendar3-range::before { content: "\f211"; } +.bi-calendar3-week-fill::before { content: "\f212"; } +.bi-calendar3-week::before { content: "\f213"; } +.bi-calendar3::before { content: "\f214"; } +.bi-calendar4-event::before { content: "\f215"; } +.bi-calendar4-range::before { content: "\f216"; } +.bi-calendar4-week::before { content: "\f217"; } +.bi-calendar4::before { content: "\f218"; } +.bi-camera-fill::before { content: "\f219"; } +.bi-camera-reels-fill::before { content: "\f21a"; } +.bi-camera-reels::before { content: "\f21b"; } +.bi-camera-video-fill::before { content: "\f21c"; } +.bi-camera-video-off-fill::before { content: "\f21d"; } +.bi-camera-video-off::before { content: "\f21e"; } +.bi-camera-video::before { content: "\f21f"; } +.bi-camera::before { content: "\f220"; } +.bi-camera2::before { content: "\f221"; } +.bi-capslock-fill::before { content: "\f222"; } +.bi-capslock::before { content: "\f223"; } +.bi-card-checklist::before { content: "\f224"; } +.bi-card-heading::before { content: "\f225"; } +.bi-card-image::before { content: "\f226"; } +.bi-card-list::before { content: "\f227"; } +.bi-card-text::before { content: "\f228"; } +.bi-caret-down-fill::before { content: "\f229"; } +.bi-caret-down-square-fill::before { content: "\f22a"; } +.bi-caret-down-square::before { content: "\f22b"; } +.bi-caret-down::before { content: "\f22c"; } +.bi-caret-left-fill::before { content: "\f22d"; } +.bi-caret-left-square-fill::before { content: "\f22e"; } +.bi-caret-left-square::before { content: "\f22f"; } +.bi-caret-left::before { content: "\f230"; } +.bi-caret-right-fill::before { content: "\f231"; } +.bi-caret-right-square-fill::before { content: "\f232"; } +.bi-caret-right-square::before { content: "\f233"; } +.bi-caret-right::before { content: "\f234"; } +.bi-caret-up-fill::before { content: "\f235"; } +.bi-caret-up-square-fill::before { content: "\f236"; } +.bi-caret-up-square::before { content: "\f237"; } +.bi-caret-up::before { content: "\f238"; } +.bi-cart-check-fill::before { content: "\f239"; } +.bi-cart-check::before { content: "\f23a"; } +.bi-cart-dash-fill::before { content: "\f23b"; } +.bi-cart-dash::before { content: "\f23c"; } +.bi-cart-fill::before { content: "\f23d"; } +.bi-cart-plus-fill::before { content: "\f23e"; } +.bi-cart-plus::before { content: "\f23f"; } +.bi-cart-x-fill::before { content: "\f240"; } +.bi-cart-x::before { content: "\f241"; } +.bi-cart::before { content: "\f242"; } +.bi-cart2::before { content: "\f243"; } +.bi-cart3::before { content: "\f244"; } +.bi-cart4::before { content: "\f245"; } +.bi-cash-stack::before { content: "\f246"; } +.bi-cash::before { content: "\f247"; } +.bi-cast::before { content: "\f248"; } +.bi-chat-dots-fill::before { content: "\f249"; } +.bi-chat-dots::before { content: "\f24a"; } +.bi-chat-fill::before { content: "\f24b"; } +.bi-chat-left-dots-fill::before { content: "\f24c"; } +.bi-chat-left-dots::before { content: "\f24d"; } +.bi-chat-left-fill::before { content: "\f24e"; } +.bi-chat-left-quote-fill::before { content: "\f24f"; } +.bi-chat-left-quote::before { content: "\f250"; } +.bi-chat-left-text-fill::before { content: "\f251"; } +.bi-chat-left-text::before { content: "\f252"; } +.bi-chat-left::before { content: "\f253"; } +.bi-chat-quote-fill::before { content: "\f254"; } +.bi-chat-quote::before { content: "\f255"; } +.bi-chat-right-dots-fill::before { content: "\f256"; } +.bi-chat-right-dots::before { content: "\f257"; } +.bi-chat-right-fill::before { content: "\f258"; } +.bi-chat-right-quote-fill::before { content: "\f259"; } +.bi-chat-right-quote::before { content: "\f25a"; } +.bi-chat-right-text-fill::before { content: "\f25b"; } +.bi-chat-right-text::before { content: "\f25c"; } +.bi-chat-right::before { content: "\f25d"; } +.bi-chat-square-dots-fill::before { content: "\f25e"; } +.bi-chat-square-dots::before { content: "\f25f"; } +.bi-chat-square-fill::before { content: "\f260"; } +.bi-chat-square-quote-fill::before { content: "\f261"; } +.bi-chat-square-quote::before { content: "\f262"; } +.bi-chat-square-text-fill::before { content: "\f263"; } +.bi-chat-square-text::before { content: "\f264"; } +.bi-chat-square::before { content: "\f265"; } +.bi-chat-text-fill::before { content: "\f266"; } +.bi-chat-text::before { content: "\f267"; } +.bi-chat::before { content: "\f268"; } +.bi-check-all::before { content: "\f269"; } +.bi-check-circle-fill::before { content: "\f26a"; } +.bi-check-circle::before { content: "\f26b"; } +.bi-check-square-fill::before { content: "\f26c"; } +.bi-check-square::before { content: "\f26d"; } +.bi-check::before { content: "\f26e"; } +.bi-check2-all::before { content: "\f26f"; } +.bi-check2-circle::before { content: "\f270"; } +.bi-check2-square::before { content: "\f271"; } +.bi-check2::before { content: "\f272"; } +.bi-chevron-bar-contract::before { content: "\f273"; } +.bi-chevron-bar-down::before { content: "\f274"; } +.bi-chevron-bar-expand::before { content: "\f275"; } +.bi-chevron-bar-left::before { content: "\f276"; } +.bi-chevron-bar-right::before { content: "\f277"; } +.bi-chevron-bar-up::before { content: "\f278"; } +.bi-chevron-compact-down::before { content: "\f279"; } +.bi-chevron-compact-left::before { content: "\f27a"; } +.bi-chevron-compact-right::before { content: "\f27b"; } +.bi-chevron-compact-up::before { content: "\f27c"; } +.bi-chevron-contract::before { content: "\f27d"; } +.bi-chevron-double-down::before { content: "\f27e"; } +.bi-chevron-double-left::before { content: "\f27f"; } +.bi-chevron-double-right::before { content: "\f280"; } +.bi-chevron-double-up::before { content: "\f281"; } +.bi-chevron-down::before { content: "\f282"; } +.bi-chevron-expand::before { content: "\f283"; } +.bi-chevron-left::before { content: "\f284"; } +.bi-chevron-right::before { content: "\f285"; } +.bi-chevron-up::before { content: "\f286"; } +.bi-circle-fill::before { content: "\f287"; } +.bi-circle-half::before { content: "\f288"; } +.bi-circle-square::before { content: "\f289"; } +.bi-circle::before { content: "\f28a"; } +.bi-clipboard-check::before { content: "\f28b"; } +.bi-clipboard-data::before { content: "\f28c"; } +.bi-clipboard-minus::before { content: "\f28d"; } +.bi-clipboard-plus::before { content: "\f28e"; } +.bi-clipboard-x::before { content: "\f28f"; } +.bi-clipboard::before { content: "\f290"; } +.bi-clock-fill::before { content: "\f291"; } +.bi-clock-history::before { content: "\f292"; } +.bi-clock::before { content: "\f293"; } +.bi-cloud-arrow-down-fill::before { content: "\f294"; } +.bi-cloud-arrow-down::before { content: "\f295"; } +.bi-cloud-arrow-up-fill::before { content: "\f296"; } +.bi-cloud-arrow-up::before { content: "\f297"; } +.bi-cloud-check-fill::before { content: "\f298"; } +.bi-cloud-check::before { content: "\f299"; } +.bi-cloud-download-fill::before { content: "\f29a"; } +.bi-cloud-download::before { content: "\f29b"; } +.bi-cloud-drizzle-fill::before { content: "\f29c"; } +.bi-cloud-drizzle::before { content: "\f29d"; } +.bi-cloud-fill::before { content: "\f29e"; } +.bi-cloud-fog-fill::before { content: "\f29f"; } +.bi-cloud-fog::before { content: "\f2a0"; } +.bi-cloud-fog2-fill::before { content: "\f2a1"; } +.bi-cloud-fog2::before { content: "\f2a2"; } +.bi-cloud-hail-fill::before { content: "\f2a3"; } +.bi-cloud-hail::before { content: "\f2a4"; } +.bi-cloud-haze-1::before { content: "\f2a5"; } +.bi-cloud-haze-fill::before { content: "\f2a6"; } +.bi-cloud-haze::before { content: "\f2a7"; } +.bi-cloud-haze2-fill::before { content: "\f2a8"; } +.bi-cloud-lightning-fill::before { content: "\f2a9"; } +.bi-cloud-lightning-rain-fill::before { content: "\f2aa"; } +.bi-cloud-lightning-rain::before { content: "\f2ab"; } +.bi-cloud-lightning::before { content: "\f2ac"; } +.bi-cloud-minus-fill::before { content: "\f2ad"; } +.bi-cloud-minus::before { content: "\f2ae"; } +.bi-cloud-moon-fill::before { content: "\f2af"; } +.bi-cloud-moon::before { content: "\f2b0"; } +.bi-cloud-plus-fill::before { content: "\f2b1"; } +.bi-cloud-plus::before { content: "\f2b2"; } +.bi-cloud-rain-fill::before { content: "\f2b3"; } +.bi-cloud-rain-heavy-fill::before { content: "\f2b4"; } +.bi-cloud-rain-heavy::before { content: "\f2b5"; } +.bi-cloud-rain::before { content: "\f2b6"; } +.bi-cloud-slash-fill::before { content: "\f2b7"; } +.bi-cloud-slash::before { content: "\f2b8"; } +.bi-cloud-sleet-fill::before { content: "\f2b9"; } +.bi-cloud-sleet::before { content: "\f2ba"; } +.bi-cloud-snow-fill::before { content: "\f2bb"; } +.bi-cloud-snow::before { content: "\f2bc"; } +.bi-cloud-sun-fill::before { content: "\f2bd"; } +.bi-cloud-sun::before { content: "\f2be"; } +.bi-cloud-upload-fill::before { content: "\f2bf"; } +.bi-cloud-upload::before { content: "\f2c0"; } +.bi-cloud::before { content: "\f2c1"; } +.bi-clouds-fill::before { content: "\f2c2"; } +.bi-clouds::before { content: "\f2c3"; } +.bi-cloudy-fill::before { content: "\f2c4"; } +.bi-cloudy::before { content: "\f2c5"; } +.bi-code-slash::before { content: "\f2c6"; } +.bi-code-square::before { content: "\f2c7"; } +.bi-code::before { content: "\f2c8"; } +.bi-collection-fill::before { content: "\f2c9"; } +.bi-collection-play-fill::before { content: "\f2ca"; } +.bi-collection-play::before { content: "\f2cb"; } +.bi-collection::before { content: "\f2cc"; } +.bi-columns-gap::before { content: "\f2cd"; } +.bi-columns::before { content: "\f2ce"; } +.bi-command::before { content: "\f2cf"; } +.bi-compass-fill::before { content: "\f2d0"; } +.bi-compass::before { content: "\f2d1"; } +.bi-cone-striped::before { content: "\f2d2"; } +.bi-cone::before { content: "\f2d3"; } +.bi-controller::before { content: "\f2d4"; } +.bi-cpu-fill::before { content: "\f2d5"; } +.bi-cpu::before { content: "\f2d6"; } +.bi-credit-card-2-back-fill::before { content: "\f2d7"; } +.bi-credit-card-2-back::before { content: "\f2d8"; } +.bi-credit-card-2-front-fill::before { content: "\f2d9"; } +.bi-credit-card-2-front::before { content: "\f2da"; } +.bi-credit-card-fill::before { content: "\f2db"; } +.bi-credit-card::before { content: "\f2dc"; } +.bi-crop::before { content: "\f2dd"; } +.bi-cup-fill::before { content: "\f2de"; } +.bi-cup-straw::before { content: "\f2df"; } +.bi-cup::before { content: "\f2e0"; } +.bi-cursor-fill::before { content: "\f2e1"; } +.bi-cursor-text::before { content: "\f2e2"; } +.bi-cursor::before { content: "\f2e3"; } +.bi-dash-circle-dotted::before { content: "\f2e4"; } +.bi-dash-circle-fill::before { content: "\f2e5"; } +.bi-dash-circle::before { content: "\f2e6"; } +.bi-dash-square-dotted::before { content: "\f2e7"; } +.bi-dash-square-fill::before { content: "\f2e8"; } +.bi-dash-square::before { content: "\f2e9"; } +.bi-dash::before { content: "\f2ea"; } +.bi-diagram-2-fill::before { content: "\f2eb"; } +.bi-diagram-2::before { content: "\f2ec"; } +.bi-diagram-3-fill::before { content: "\f2ed"; } +.bi-diagram-3::before { content: "\f2ee"; } +.bi-diamond-fill::before { content: "\f2ef"; } +.bi-diamond-half::before { content: "\f2f0"; } +.bi-diamond::before { content: "\f2f1"; } +.bi-dice-1-fill::before { content: "\f2f2"; } +.bi-dice-1::before { content: "\f2f3"; } +.bi-dice-2-fill::before { content: "\f2f4"; } +.bi-dice-2::before { content: "\f2f5"; } +.bi-dice-3-fill::before { content: "\f2f6"; } +.bi-dice-3::before { content: "\f2f7"; } +.bi-dice-4-fill::before { content: "\f2f8"; } +.bi-dice-4::before { content: "\f2f9"; } +.bi-dice-5-fill::before { content: "\f2fa"; } +.bi-dice-5::before { content: "\f2fb"; } +.bi-dice-6-fill::before { content: "\f2fc"; } +.bi-dice-6::before { content: "\f2fd"; } +.bi-disc-fill::before { content: "\f2fe"; } +.bi-disc::before { content: "\f2ff"; } +.bi-discord::before { content: "\f300"; } +.bi-display-fill::before { content: "\f301"; } +.bi-display::before { content: "\f302"; } +.bi-distribute-horizontal::before { content: "\f303"; } +.bi-distribute-vertical::before { content: "\f304"; } +.bi-door-closed-fill::before { content: "\f305"; } +.bi-door-closed::before { content: "\f306"; } +.bi-door-open-fill::before { content: "\f307"; } +.bi-door-open::before { content: "\f308"; } +.bi-dot::before { content: "\f309"; } +.bi-download::before { content: "\f30a"; } +.bi-droplet-fill::before { content: "\f30b"; } +.bi-droplet-half::before { content: "\f30c"; } +.bi-droplet::before { content: "\f30d"; } +.bi-earbuds::before { content: "\f30e"; } +.bi-easel-fill::before { content: "\f30f"; } +.bi-easel::before { content: "\f310"; } +.bi-egg-fill::before { content: "\f311"; } +.bi-egg-fried::before { content: "\f312"; } +.bi-egg::before { content: "\f313"; } +.bi-eject-fill::before { content: "\f314"; } +.bi-eject::before { content: "\f315"; } +.bi-emoji-angry-fill::before { content: "\f316"; } +.bi-emoji-angry::before { content: "\f317"; } +.bi-emoji-dizzy-fill::before { content: "\f318"; } +.bi-emoji-dizzy::before { content: "\f319"; } +.bi-emoji-expressionless-fill::before { content: "\f31a"; } +.bi-emoji-expressionless::before { content: "\f31b"; } +.bi-emoji-frown-fill::before { content: "\f31c"; } +.bi-emoji-frown::before { content: "\f31d"; } +.bi-emoji-heart-eyes-fill::before { content: "\f31e"; } +.bi-emoji-heart-eyes::before { content: "\f31f"; } +.bi-emoji-laughing-fill::before { content: "\f320"; } +.bi-emoji-laughing::before { content: "\f321"; } +.bi-emoji-neutral-fill::before { content: "\f322"; } +.bi-emoji-neutral::before { content: "\f323"; } +.bi-emoji-smile-fill::before { content: "\f324"; } +.bi-emoji-smile-upside-down-fill::before { content: "\f325"; } +.bi-emoji-smile-upside-down::before { content: "\f326"; } +.bi-emoji-smile::before { content: "\f327"; } +.bi-emoji-sunglasses-fill::before { content: "\f328"; } +.bi-emoji-sunglasses::before { content: "\f329"; } +.bi-emoji-wink-fill::before { content: "\f32a"; } +.bi-emoji-wink::before { content: "\f32b"; } +.bi-envelope-fill::before { content: "\f32c"; } +.bi-envelope-open-fill::before { content: "\f32d"; } +.bi-envelope-open::before { content: "\f32e"; } +.bi-envelope::before { content: "\f32f"; } +.bi-eraser-fill::before { content: "\f330"; } +.bi-eraser::before { content: "\f331"; } +.bi-exclamation-circle-fill::before { content: "\f332"; } +.bi-exclamation-circle::before { content: "\f333"; } +.bi-exclamation-diamond-fill::before { content: "\f334"; } +.bi-exclamation-diamond::before { content: "\f335"; } +.bi-exclamation-octagon-fill::before { content: "\f336"; } +.bi-exclamation-octagon::before { content: "\f337"; } +.bi-exclamation-square-fill::before { content: "\f338"; } +.bi-exclamation-square::before { content: "\f339"; } +.bi-exclamation-triangle-fill::before { content: "\f33a"; } +.bi-exclamation-triangle::before { content: "\f33b"; } +.bi-exclamation::before { content: "\f33c"; } +.bi-exclude::before { content: "\f33d"; } +.bi-eye-fill::before { content: "\f33e"; } +.bi-eye-slash-fill::before { content: "\f33f"; } +.bi-eye-slash::before { content: "\f340"; } +.bi-eye::before { content: "\f341"; } +.bi-eyedropper::before { content: "\f342"; } +.bi-eyeglasses::before { content: "\f343"; } +.bi-facebook::before { content: "\f344"; } +.bi-file-arrow-down-fill::before { content: "\f345"; } +.bi-file-arrow-down::before { content: "\f346"; } +.bi-file-arrow-up-fill::before { content: "\f347"; } +.bi-file-arrow-up::before { content: "\f348"; } +.bi-file-bar-graph-fill::before { content: "\f349"; } +.bi-file-bar-graph::before { content: "\f34a"; } +.bi-file-binary-fill::before { content: "\f34b"; } +.bi-file-binary::before { content: "\f34c"; } +.bi-file-break-fill::before { content: "\f34d"; } +.bi-file-break::before { content: "\f34e"; } +.bi-file-check-fill::before { content: "\f34f"; } +.bi-file-check::before { content: "\f350"; } +.bi-file-code-fill::before { content: "\f351"; } +.bi-file-code::before { content: "\f352"; } +.bi-file-diff-fill::before { content: "\f353"; } +.bi-file-diff::before { content: "\f354"; } +.bi-file-earmark-arrow-down-fill::before { content: "\f355"; } +.bi-file-earmark-arrow-down::before { content: "\f356"; } +.bi-file-earmark-arrow-up-fill::before { content: "\f357"; } +.bi-file-earmark-arrow-up::before { content: "\f358"; } +.bi-file-earmark-bar-graph-fill::before { content: "\f359"; } +.bi-file-earmark-bar-graph::before { content: "\f35a"; } +.bi-file-earmark-binary-fill::before { content: "\f35b"; } +.bi-file-earmark-binary::before { content: "\f35c"; } +.bi-file-earmark-break-fill::before { content: "\f35d"; } +.bi-file-earmark-break::before { content: "\f35e"; } +.bi-file-earmark-check-fill::before { content: "\f35f"; } +.bi-file-earmark-check::before { content: "\f360"; } +.bi-file-earmark-code-fill::before { content: "\f361"; } +.bi-file-earmark-code::before { content: "\f362"; } +.bi-file-earmark-diff-fill::before { content: "\f363"; } +.bi-file-earmark-diff::before { content: "\f364"; } +.bi-file-earmark-easel-fill::before { content: "\f365"; } +.bi-file-earmark-easel::before { content: "\f366"; } +.bi-file-earmark-excel-fill::before { content: "\f367"; } +.bi-file-earmark-excel::before { content: "\f368"; } +.bi-file-earmark-fill::before { content: "\f369"; } +.bi-file-earmark-font-fill::before { content: "\f36a"; } +.bi-file-earmark-font::before { content: "\f36b"; } +.bi-file-earmark-image-fill::before { content: "\f36c"; } +.bi-file-earmark-image::before { content: "\f36d"; } +.bi-file-earmark-lock-fill::before { content: "\f36e"; } +.bi-file-earmark-lock::before { content: "\f36f"; } +.bi-file-earmark-lock2-fill::before { content: "\f370"; } +.bi-file-earmark-lock2::before { content: "\f371"; } +.bi-file-earmark-medical-fill::before { content: "\f372"; } +.bi-file-earmark-medical::before { content: "\f373"; } +.bi-file-earmark-minus-fill::before { content: "\f374"; } +.bi-file-earmark-minus::before { content: "\f375"; } +.bi-file-earmark-music-fill::before { content: "\f376"; } +.bi-file-earmark-music::before { content: "\f377"; } +.bi-file-earmark-person-fill::before { content: "\f378"; } +.bi-file-earmark-person::before { content: "\f379"; } +.bi-file-earmark-play-fill::before { content: "\f37a"; } +.bi-file-earmark-play::before { content: "\f37b"; } +.bi-file-earmark-plus-fill::before { content: "\f37c"; } +.bi-file-earmark-plus::before { content: "\f37d"; } +.bi-file-earmark-post-fill::before { content: "\f37e"; } +.bi-file-earmark-post::before { content: "\f37f"; } +.bi-file-earmark-ppt-fill::before { content: "\f380"; } +.bi-file-earmark-ppt::before { content: "\f381"; } +.bi-file-earmark-richtext-fill::before { content: "\f382"; } +.bi-file-earmark-richtext::before { content: "\f383"; } +.bi-file-earmark-ruled-fill::before { content: "\f384"; } +.bi-file-earmark-ruled::before { content: "\f385"; } +.bi-file-earmark-slides-fill::before { content: "\f386"; } +.bi-file-earmark-slides::before { content: "\f387"; } +.bi-file-earmark-spreadsheet-fill::before { content: "\f388"; } +.bi-file-earmark-spreadsheet::before { content: "\f389"; } +.bi-file-earmark-text-fill::before { content: "\f38a"; } +.bi-file-earmark-text::before { content: "\f38b"; } +.bi-file-earmark-word-fill::before { content: "\f38c"; } +.bi-file-earmark-word::before { content: "\f38d"; } +.bi-file-earmark-x-fill::before { content: "\f38e"; } +.bi-file-earmark-x::before { content: "\f38f"; } +.bi-file-earmark-zip-fill::before { content: "\f390"; } +.bi-file-earmark-zip::before { content: "\f391"; } +.bi-file-earmark::before { content: "\f392"; } +.bi-file-easel-fill::before { content: "\f393"; } +.bi-file-easel::before { content: "\f394"; } +.bi-file-excel-fill::before { content: "\f395"; } +.bi-file-excel::before { content: "\f396"; } +.bi-file-fill::before { content: "\f397"; } +.bi-file-font-fill::before { content: "\f398"; } +.bi-file-font::before { content: "\f399"; } +.bi-file-image-fill::before { content: "\f39a"; } +.bi-file-image::before { content: "\f39b"; } +.bi-file-lock-fill::before { content: "\f39c"; } +.bi-file-lock::before { content: "\f39d"; } +.bi-file-lock2-fill::before { content: "\f39e"; } +.bi-file-lock2::before { content: "\f39f"; } +.bi-file-medical-fill::before { content: "\f3a0"; } +.bi-file-medical::before { content: "\f3a1"; } +.bi-file-minus-fill::before { content: "\f3a2"; } +.bi-file-minus::before { content: "\f3a3"; } +.bi-file-music-fill::before { content: "\f3a4"; } +.bi-file-music::before { content: "\f3a5"; } +.bi-file-person-fill::before { content: "\f3a6"; } +.bi-file-person::before { content: "\f3a7"; } +.bi-file-play-fill::before { content: "\f3a8"; } +.bi-file-play::before { content: "\f3a9"; } +.bi-file-plus-fill::before { content: "\f3aa"; } +.bi-file-plus::before { content: "\f3ab"; } +.bi-file-post-fill::before { content: "\f3ac"; } +.bi-file-post::before { content: "\f3ad"; } +.bi-file-ppt-fill::before { content: "\f3ae"; } +.bi-file-ppt::before { content: "\f3af"; } +.bi-file-richtext-fill::before { content: "\f3b0"; } +.bi-file-richtext::before { content: "\f3b1"; } +.bi-file-ruled-fill::before { content: "\f3b2"; } +.bi-file-ruled::before { content: "\f3b3"; } +.bi-file-slides-fill::before { content: "\f3b4"; } +.bi-file-slides::before { content: "\f3b5"; } +.bi-file-spreadsheet-fill::before { content: "\f3b6"; } +.bi-file-spreadsheet::before { content: "\f3b7"; } +.bi-file-text-fill::before { content: "\f3b8"; } +.bi-file-text::before { content: "\f3b9"; } +.bi-file-word-fill::before { content: "\f3ba"; } +.bi-file-word::before { content: "\f3bb"; } +.bi-file-x-fill::before { content: "\f3bc"; } +.bi-file-x::before { content: "\f3bd"; } +.bi-file-zip-fill::before { content: "\f3be"; } +.bi-file-zip::before { content: "\f3bf"; } +.bi-file::before { content: "\f3c0"; } +.bi-files-alt::before { content: "\f3c1"; } +.bi-files::before { content: "\f3c2"; } +.bi-film::before { content: "\f3c3"; } +.bi-filter-circle-fill::before { content: "\f3c4"; } +.bi-filter-circle::before { content: "\f3c5"; } +.bi-filter-left::before { content: "\f3c6"; } +.bi-filter-right::before { content: "\f3c7"; } +.bi-filter-square-fill::before { content: "\f3c8"; } +.bi-filter-square::before { content: "\f3c9"; } +.bi-filter::before { content: "\f3ca"; } +.bi-flag-fill::before { content: "\f3cb"; } +.bi-flag::before { content: "\f3cc"; } +.bi-flower1::before { content: "\f3cd"; } +.bi-flower2::before { content: "\f3ce"; } +.bi-flower3::before { content: "\f3cf"; } +.bi-folder-check::before { content: "\f3d0"; } +.bi-folder-fill::before { content: "\f3d1"; } +.bi-folder-minus::before { content: "\f3d2"; } +.bi-folder-plus::before { content: "\f3d3"; } +.bi-folder-symlink-fill::before { content: "\f3d4"; } +.bi-folder-symlink::before { content: "\f3d5"; } +.bi-folder-x::before { content: "\f3d6"; } +.bi-folder::before { content: "\f3d7"; } +.bi-folder2-open::before { content: "\f3d8"; } +.bi-folder2::before { content: "\f3d9"; } +.bi-fonts::before { content: "\f3da"; } +.bi-forward-fill::before { content: "\f3db"; } +.bi-forward::before { content: "\f3dc"; } +.bi-front::before { content: "\f3dd"; } +.bi-fullscreen-exit::before { content: "\f3de"; } +.bi-fullscreen::before { content: "\f3df"; } +.bi-funnel-fill::before { content: "\f3e0"; } +.bi-funnel::before { content: "\f3e1"; } +.bi-gear-fill::before { content: "\f3e2"; } +.bi-gear-wide-connected::before { content: "\f3e3"; } +.bi-gear-wide::before { content: "\f3e4"; } +.bi-gear::before { content: "\f3e5"; } +.bi-gem::before { content: "\f3e6"; } +.bi-geo-alt-fill::before { content: "\f3e7"; } +.bi-geo-alt::before { content: "\f3e8"; } +.bi-geo-fill::before { content: "\f3e9"; } +.bi-geo::before { content: "\f3ea"; } +.bi-gift-fill::before { content: "\f3eb"; } +.bi-gift::before { content: "\f3ec"; } +.bi-github::before { content: "\f3ed"; } +.bi-globe::before { content: "\f3ee"; } +.bi-globe2::before { content: "\f3ef"; } +.bi-google::before { content: "\f3f0"; } +.bi-graph-down::before { content: "\f3f1"; } +.bi-graph-up::before { content: "\f3f2"; } +.bi-grid-1x2-fill::before { content: "\f3f3"; } +.bi-grid-1x2::before { content: "\f3f4"; } +.bi-grid-3x2-gap-fill::before { content: "\f3f5"; } +.bi-grid-3x2-gap::before { content: "\f3f6"; } +.bi-grid-3x2::before { content: "\f3f7"; } +.bi-grid-3x3-gap-fill::before { content: "\f3f8"; } +.bi-grid-3x3-gap::before { content: "\f3f9"; } +.bi-grid-3x3::before { content: "\f3fa"; } +.bi-grid-fill::before { content: "\f3fb"; } +.bi-grid::before { content: "\f3fc"; } +.bi-grip-horizontal::before { content: "\f3fd"; } +.bi-grip-vertical::before { content: "\f3fe"; } +.bi-hammer::before { content: "\f3ff"; } +.bi-hand-index-fill::before { content: "\f400"; } +.bi-hand-index-thumb-fill::before { content: "\f401"; } +.bi-hand-index-thumb::before { content: "\f402"; } +.bi-hand-index::before { content: "\f403"; } +.bi-hand-thumbs-down-fill::before { content: "\f404"; } +.bi-hand-thumbs-down::before { content: "\f405"; } +.bi-hand-thumbs-up-fill::before { content: "\f406"; } +.bi-hand-thumbs-up::before { content: "\f407"; } +.bi-handbag-fill::before { content: "\f408"; } +.bi-handbag::before { content: "\f409"; } +.bi-hash::before { content: "\f40a"; } +.bi-hdd-fill::before { content: "\f40b"; } +.bi-hdd-network-fill::before { content: "\f40c"; } +.bi-hdd-network::before { content: "\f40d"; } +.bi-hdd-rack-fill::before { content: "\f40e"; } +.bi-hdd-rack::before { content: "\f40f"; } +.bi-hdd-stack-fill::before { content: "\f410"; } +.bi-hdd-stack::before { content: "\f411"; } +.bi-hdd::before { content: "\f412"; } +.bi-headphones::before { content: "\f413"; } +.bi-headset::before { content: "\f414"; } +.bi-heart-fill::before { content: "\f415"; } +.bi-heart-half::before { content: "\f416"; } +.bi-heart::before { content: "\f417"; } +.bi-heptagon-fill::before { content: "\f418"; } +.bi-heptagon-half::before { content: "\f419"; } +.bi-heptagon::before { content: "\f41a"; } +.bi-hexagon-fill::before { content: "\f41b"; } +.bi-hexagon-half::before { content: "\f41c"; } +.bi-hexagon::before { content: "\f41d"; } +.bi-hourglass-bottom::before { content: "\f41e"; } +.bi-hourglass-split::before { content: "\f41f"; } +.bi-hourglass-top::before { content: "\f420"; } +.bi-hourglass::before { content: "\f421"; } +.bi-house-door-fill::before { content: "\f422"; } +.bi-house-door::before { content: "\f423"; } +.bi-house-fill::before { content: "\f424"; } +.bi-house::before { content: "\f425"; } +.bi-hr::before { content: "\f426"; } +.bi-hurricane::before { content: "\f427"; } +.bi-image-alt::before { content: "\f428"; } +.bi-image-fill::before { content: "\f429"; } +.bi-image::before { content: "\f42a"; } +.bi-images::before { content: "\f42b"; } +.bi-inbox-fill::before { content: "\f42c"; } +.bi-inbox::before { content: "\f42d"; } +.bi-inboxes-fill::before { content: "\f42e"; } +.bi-inboxes::before { content: "\f42f"; } +.bi-info-circle-fill::before { content: "\f430"; } +.bi-info-circle::before { content: "\f431"; } +.bi-info-square-fill::before { content: "\f432"; } +.bi-info-square::before { content: "\f433"; } +.bi-info::before { content: "\f434"; } +.bi-input-cursor-text::before { content: "\f435"; } +.bi-input-cursor::before { content: "\f436"; } +.bi-instagram::before { content: "\f437"; } +.bi-intersect::before { content: "\f438"; } +.bi-journal-album::before { content: "\f439"; } +.bi-journal-arrow-down::before { content: "\f43a"; } +.bi-journal-arrow-up::before { content: "\f43b"; } +.bi-journal-bookmark-fill::before { content: "\f43c"; } +.bi-journal-bookmark::before { content: "\f43d"; } +.bi-journal-check::before { content: "\f43e"; } +.bi-journal-code::before { content: "\f43f"; } +.bi-journal-medical::before { content: "\f440"; } +.bi-journal-minus::before { content: "\f441"; } +.bi-journal-plus::before { content: "\f442"; } +.bi-journal-richtext::before { content: "\f443"; } +.bi-journal-text::before { content: "\f444"; } +.bi-journal-x::before { content: "\f445"; } +.bi-journal::before { content: "\f446"; } +.bi-journals::before { content: "\f447"; } +.bi-joystick::before { content: "\f448"; } +.bi-justify-left::before { content: "\f449"; } +.bi-justify-right::before { content: "\f44a"; } +.bi-justify::before { content: "\f44b"; } +.bi-kanban-fill::before { content: "\f44c"; } +.bi-kanban::before { content: "\f44d"; } +.bi-key-fill::before { content: "\f44e"; } +.bi-key::before { content: "\f44f"; } +.bi-keyboard-fill::before { content: "\f450"; } +.bi-keyboard::before { content: "\f451"; } +.bi-ladder::before { content: "\f452"; } +.bi-lamp-fill::before { content: "\f453"; } +.bi-lamp::before { content: "\f454"; } +.bi-laptop-fill::before { content: "\f455"; } +.bi-laptop::before { content: "\f456"; } +.bi-layer-backward::before { content: "\f457"; } +.bi-layer-forward::before { content: "\f458"; } +.bi-layers-fill::before { content: "\f459"; } +.bi-layers-half::before { content: "\f45a"; } +.bi-layers::before { content: "\f45b"; } +.bi-layout-sidebar-inset-reverse::before { content: "\f45c"; } +.bi-layout-sidebar-inset::before { content: "\f45d"; } +.bi-layout-sidebar-reverse::before { content: "\f45e"; } +.bi-layout-sidebar::before { content: "\f45f"; } +.bi-layout-split::before { content: "\f460"; } +.bi-layout-text-sidebar-reverse::before { content: "\f461"; } +.bi-layout-text-sidebar::before { content: "\f462"; } +.bi-layout-text-window-reverse::before { content: "\f463"; } +.bi-layout-text-window::before { content: "\f464"; } +.bi-layout-three-columns::before { content: "\f465"; } +.bi-layout-wtf::before { content: "\f466"; } +.bi-life-preserver::before { content: "\f467"; } +.bi-lightbulb-fill::before { content: "\f468"; } +.bi-lightbulb-off-fill::before { content: "\f469"; } +.bi-lightbulb-off::before { content: "\f46a"; } +.bi-lightbulb::before { content: "\f46b"; } +.bi-lightning-charge-fill::before { content: "\f46c"; } +.bi-lightning-charge::before { content: "\f46d"; } +.bi-lightning-fill::before { content: "\f46e"; } +.bi-lightning::before { content: "\f46f"; } +.bi-link-45deg::before { content: "\f470"; } +.bi-link::before { content: "\f471"; } +.bi-linkedin::before { content: "\f472"; } +.bi-list-check::before { content: "\f473"; } +.bi-list-nested::before { content: "\f474"; } +.bi-list-ol::before { content: "\f475"; } +.bi-list-stars::before { content: "\f476"; } +.bi-list-task::before { content: "\f477"; } +.bi-list-ul::before { content: "\f478"; } +.bi-list::before { content: "\f479"; } +.bi-lock-fill::before { content: "\f47a"; } +.bi-lock::before { content: "\f47b"; } +.bi-mailbox::before { content: "\f47c"; } +.bi-mailbox2::before { content: "\f47d"; } +.bi-map-fill::before { content: "\f47e"; } +.bi-map::before { content: "\f47f"; } +.bi-markdown-fill::before { content: "\f480"; } +.bi-markdown::before { content: "\f481"; } +.bi-mask::before { content: "\f482"; } +.bi-megaphone-fill::before { content: "\f483"; } +.bi-megaphone::before { content: "\f484"; } +.bi-menu-app-fill::before { content: "\f485"; } +.bi-menu-app::before { content: "\f486"; } +.bi-menu-button-fill::before { content: "\f487"; } +.bi-menu-button-wide-fill::before { content: "\f488"; } +.bi-menu-button-wide::before { content: "\f489"; } +.bi-menu-button::before { content: "\f48a"; } +.bi-menu-down::before { content: "\f48b"; } +.bi-menu-up::before { content: "\f48c"; } +.bi-mic-fill::before { content: "\f48d"; } +.bi-mic-mute-fill::before { content: "\f48e"; } +.bi-mic-mute::before { content: "\f48f"; } +.bi-mic::before { content: "\f490"; } +.bi-minecart-loaded::before { content: "\f491"; } +.bi-minecart::before { content: "\f492"; } +.bi-moisture::before { content: "\f493"; } +.bi-moon-fill::before { content: "\f494"; } +.bi-moon-stars-fill::before { content: "\f495"; } +.bi-moon-stars::before { content: "\f496"; } +.bi-moon::before { content: "\f497"; } +.bi-mouse-fill::before { content: "\f498"; } +.bi-mouse::before { content: "\f499"; } +.bi-mouse2-fill::before { content: "\f49a"; } +.bi-mouse2::before { content: "\f49b"; } +.bi-mouse3-fill::before { content: "\f49c"; } +.bi-mouse3::before { content: "\f49d"; } +.bi-music-note-beamed::before { content: "\f49e"; } +.bi-music-note-list::before { content: "\f49f"; } +.bi-music-note::before { content: "\f4a0"; } +.bi-music-player-fill::before { content: "\f4a1"; } +.bi-music-player::before { content: "\f4a2"; } +.bi-newspaper::before { content: "\f4a3"; } +.bi-node-minus-fill::before { content: "\f4a4"; } +.bi-node-minus::before { content: "\f4a5"; } +.bi-node-plus-fill::before { content: "\f4a6"; } +.bi-node-plus::before { content: "\f4a7"; } +.bi-nut-fill::before { content: "\f4a8"; } +.bi-nut::before { content: "\f4a9"; } +.bi-octagon-fill::before { content: "\f4aa"; } +.bi-octagon-half::before { content: "\f4ab"; } +.bi-octagon::before { content: "\f4ac"; } +.bi-option::before { content: "\f4ad"; } +.bi-outlet::before { content: "\f4ae"; } +.bi-paint-bucket::before { content: "\f4af"; } +.bi-palette-fill::before { content: "\f4b0"; } +.bi-palette::before { content: "\f4b1"; } +.bi-palette2::before { content: "\f4b2"; } +.bi-paperclip::before { content: "\f4b3"; } +.bi-paragraph::before { content: "\f4b4"; } +.bi-patch-check-fill::before { content: "\f4b5"; } +.bi-patch-check::before { content: "\f4b6"; } +.bi-patch-exclamation-fill::before { content: "\f4b7"; } +.bi-patch-exclamation::before { content: "\f4b8"; } +.bi-patch-minus-fill::before { content: "\f4b9"; } +.bi-patch-minus::before { content: "\f4ba"; } +.bi-patch-plus-fill::before { content: "\f4bb"; } +.bi-patch-plus::before { content: "\f4bc"; } +.bi-patch-question-fill::before { content: "\f4bd"; } +.bi-patch-question::before { content: "\f4be"; } +.bi-pause-btn-fill::before { content: "\f4bf"; } +.bi-pause-btn::before { content: "\f4c0"; } +.bi-pause-circle-fill::before { content: "\f4c1"; } +.bi-pause-circle::before { content: "\f4c2"; } +.bi-pause-fill::before { content: "\f4c3"; } +.bi-pause::before { content: "\f4c4"; } +.bi-peace-fill::before { content: "\f4c5"; } +.bi-peace::before { content: "\f4c6"; } +.bi-pen-fill::before { content: "\f4c7"; } +.bi-pen::before { content: "\f4c8"; } +.bi-pencil-fill::before { content: "\f4c9"; } +.bi-pencil-square::before { content: "\f4ca"; } +.bi-pencil::before { content: "\f4cb"; } +.bi-pentagon-fill::before { content: "\f4cc"; } +.bi-pentagon-half::before { content: "\f4cd"; } +.bi-pentagon::before { content: "\f4ce"; } +.bi-people-fill::before { content: "\f4cf"; } +.bi-people::before { content: "\f4d0"; } +.bi-percent::before { content: "\f4d1"; } +.bi-person-badge-fill::before { content: "\f4d2"; } +.bi-person-badge::before { content: "\f4d3"; } +.bi-person-bounding-box::before { content: "\f4d4"; } +.bi-person-check-fill::before { content: "\f4d5"; } +.bi-person-check::before { content: "\f4d6"; } +.bi-person-circle::before { content: "\f4d7"; } +.bi-person-dash-fill::before { content: "\f4d8"; } +.bi-person-dash::before { content: "\f4d9"; } +.bi-person-fill::before { content: "\f4da"; } +.bi-person-lines-fill::before { content: "\f4db"; } +.bi-person-plus-fill::before { content: "\f4dc"; } +.bi-person-plus::before { content: "\f4dd"; } +.bi-person-square::before { content: "\f4de"; } +.bi-person-x-fill::before { content: "\f4df"; } +.bi-person-x::before { content: "\f4e0"; } +.bi-person::before { content: "\f4e1"; } +.bi-phone-fill::before { content: "\f4e2"; } +.bi-phone-landscape-fill::before { content: "\f4e3"; } +.bi-phone-landscape::before { content: "\f4e4"; } +.bi-phone-vibrate-fill::before { content: "\f4e5"; } +.bi-phone-vibrate::before { content: "\f4e6"; } +.bi-phone::before { content: "\f4e7"; } +.bi-pie-chart-fill::before { content: "\f4e8"; } +.bi-pie-chart::before { content: "\f4e9"; } +.bi-pin-angle-fill::before { content: "\f4ea"; } +.bi-pin-angle::before { content: "\f4eb"; } +.bi-pin-fill::before { content: "\f4ec"; } +.bi-pin::before { content: "\f4ed"; } +.bi-pip-fill::before { content: "\f4ee"; } +.bi-pip::before { content: "\f4ef"; } +.bi-play-btn-fill::before { content: "\f4f0"; } +.bi-play-btn::before { content: "\f4f1"; } +.bi-play-circle-fill::before { content: "\f4f2"; } +.bi-play-circle::before { content: "\f4f3"; } +.bi-play-fill::before { content: "\f4f4"; } +.bi-play::before { content: "\f4f5"; } +.bi-plug-fill::before { content: "\f4f6"; } +.bi-plug::before { content: "\f4f7"; } +.bi-plus-circle-dotted::before { content: "\f4f8"; } +.bi-plus-circle-fill::before { content: "\f4f9"; } +.bi-plus-circle::before { content: "\f4fa"; } +.bi-plus-square-dotted::before { content: "\f4fb"; } +.bi-plus-square-fill::before { content: "\f4fc"; } +.bi-plus-square::before { content: "\f4fd"; } +.bi-plus::before { content: "\f4fe"; } +.bi-power::before { content: "\f4ff"; } +.bi-printer-fill::before { content: "\f500"; } +.bi-printer::before { content: "\f501"; } +.bi-puzzle-fill::before { content: "\f502"; } +.bi-puzzle::before { content: "\f503"; } +.bi-question-circle-fill::before { content: "\f504"; } +.bi-question-circle::before { content: "\f505"; } +.bi-question-diamond-fill::before { content: "\f506"; } +.bi-question-diamond::before { content: "\f507"; } +.bi-question-octagon-fill::before { content: "\f508"; } +.bi-question-octagon::before { content: "\f509"; } +.bi-question-square-fill::before { content: "\f50a"; } +.bi-question-square::before { content: "\f50b"; } +.bi-question::before { content: "\f50c"; } +.bi-rainbow::before { content: "\f50d"; } +.bi-receipt-cutoff::before { content: "\f50e"; } +.bi-receipt::before { content: "\f50f"; } +.bi-reception-0::before { content: "\f510"; } +.bi-reception-1::before { content: "\f511"; } +.bi-reception-2::before { content: "\f512"; } +.bi-reception-3::before { content: "\f513"; } +.bi-reception-4::before { content: "\f514"; } +.bi-record-btn-fill::before { content: "\f515"; } +.bi-record-btn::before { content: "\f516"; } +.bi-record-circle-fill::before { content: "\f517"; } +.bi-record-circle::before { content: "\f518"; } +.bi-record-fill::before { content: "\f519"; } +.bi-record::before { content: "\f51a"; } +.bi-record2-fill::before { content: "\f51b"; } +.bi-record2::before { content: "\f51c"; } +.bi-reply-all-fill::before { content: "\f51d"; } +.bi-reply-all::before { content: "\f51e"; } +.bi-reply-fill::before { content: "\f51f"; } +.bi-reply::before { content: "\f520"; } +.bi-rss-fill::before { content: "\f521"; } +.bi-rss::before { content: "\f522"; } +.bi-rulers::before { content: "\f523"; } +.bi-save-fill::before { content: "\f524"; } +.bi-save::before { content: "\f525"; } +.bi-save2-fill::before { content: "\f526"; } +.bi-save2::before { content: "\f527"; } +.bi-scissors::before { content: "\f528"; } +.bi-screwdriver::before { content: "\f529"; } +.bi-search::before { content: "\f52a"; } +.bi-segmented-nav::before { content: "\f52b"; } +.bi-server::before { content: "\f52c"; } +.bi-share-fill::before { content: "\f52d"; } +.bi-share::before { content: "\f52e"; } +.bi-shield-check::before { content: "\f52f"; } +.bi-shield-exclamation::before { content: "\f530"; } +.bi-shield-fill-check::before { content: "\f531"; } +.bi-shield-fill-exclamation::before { content: "\f532"; } +.bi-shield-fill-minus::before { content: "\f533"; } +.bi-shield-fill-plus::before { content: "\f534"; } +.bi-shield-fill-x::before { content: "\f535"; } +.bi-shield-fill::before { content: "\f536"; } +.bi-shield-lock-fill::before { content: "\f537"; } +.bi-shield-lock::before { content: "\f538"; } +.bi-shield-minus::before { content: "\f539"; } +.bi-shield-plus::before { content: "\f53a"; } +.bi-shield-shaded::before { content: "\f53b"; } +.bi-shield-slash-fill::before { content: "\f53c"; } +.bi-shield-slash::before { content: "\f53d"; } +.bi-shield-x::before { content: "\f53e"; } +.bi-shield::before { content: "\f53f"; } +.bi-shift-fill::before { content: "\f540"; } +.bi-shift::before { content: "\f541"; } +.bi-shop-window::before { content: "\f542"; } +.bi-shop::before { content: "\f543"; } +.bi-shuffle::before { content: "\f544"; } +.bi-signpost-2-fill::before { content: "\f545"; } +.bi-signpost-2::before { content: "\f546"; } +.bi-signpost-fill::before { content: "\f547"; } +.bi-signpost-split-fill::before { content: "\f548"; } +.bi-signpost-split::before { content: "\f549"; } +.bi-signpost::before { content: "\f54a"; } +.bi-sim-fill::before { content: "\f54b"; } +.bi-sim::before { content: "\f54c"; } +.bi-skip-backward-btn-fill::before { content: "\f54d"; } +.bi-skip-backward-btn::before { content: "\f54e"; } +.bi-skip-backward-circle-fill::before { content: "\f54f"; } +.bi-skip-backward-circle::before { content: "\f550"; } +.bi-skip-backward-fill::before { content: "\f551"; } +.bi-skip-backward::before { content: "\f552"; } +.bi-skip-end-btn-fill::before { content: "\f553"; } +.bi-skip-end-btn::before { content: "\f554"; } +.bi-skip-end-circle-fill::before { content: "\f555"; } +.bi-skip-end-circle::before { content: "\f556"; } +.bi-skip-end-fill::before { content: "\f557"; } +.bi-skip-end::before { content: "\f558"; } +.bi-skip-forward-btn-fill::before { content: "\f559"; } +.bi-skip-forward-btn::before { content: "\f55a"; } +.bi-skip-forward-circle-fill::before { content: "\f55b"; } +.bi-skip-forward-circle::before { content: "\f55c"; } +.bi-skip-forward-fill::before { content: "\f55d"; } +.bi-skip-forward::before { content: "\f55e"; } +.bi-skip-start-btn-fill::before { content: "\f55f"; } +.bi-skip-start-btn::before { content: "\f560"; } +.bi-skip-start-circle-fill::before { content: "\f561"; } +.bi-skip-start-circle::before { content: "\f562"; } +.bi-skip-start-fill::before { content: "\f563"; } +.bi-skip-start::before { content: "\f564"; } +.bi-slack::before { content: "\f565"; } +.bi-slash-circle-fill::before { content: "\f566"; } +.bi-slash-circle::before { content: "\f567"; } +.bi-slash-square-fill::before { content: "\f568"; } +.bi-slash-square::before { content: "\f569"; } +.bi-slash::before { content: "\f56a"; } +.bi-sliders::before { content: "\f56b"; } +.bi-smartwatch::before { content: "\f56c"; } +.bi-snow::before { content: "\f56d"; } +.bi-snow2::before { content: "\f56e"; } +.bi-snow3::before { content: "\f56f"; } +.bi-sort-alpha-down-alt::before { content: "\f570"; } +.bi-sort-alpha-down::before { content: "\f571"; } +.bi-sort-alpha-up-alt::before { content: "\f572"; } +.bi-sort-alpha-up::before { content: "\f573"; } +.bi-sort-down-alt::before { content: "\f574"; } +.bi-sort-down::before { content: "\f575"; } +.bi-sort-numeric-down-alt::before { content: "\f576"; } +.bi-sort-numeric-down::before { content: "\f577"; } +.bi-sort-numeric-up-alt::before { content: "\f578"; } +.bi-sort-numeric-up::before { content: "\f579"; } +.bi-sort-up-alt::before { content: "\f57a"; } +.bi-sort-up::before { content: "\f57b"; } +.bi-soundwave::before { content: "\f57c"; } +.bi-speaker-fill::before { content: "\f57d"; } +.bi-speaker::before { content: "\f57e"; } +.bi-speedometer::before { content: "\f57f"; } +.bi-speedometer2::before { content: "\f580"; } +.bi-spellcheck::before { content: "\f581"; } +.bi-square-fill::before { content: "\f582"; } +.bi-square-half::before { content: "\f583"; } +.bi-square::before { content: "\f584"; } +.bi-stack::before { content: "\f585"; } +.bi-star-fill::before { content: "\f586"; } +.bi-star-half::before { content: "\f587"; } +.bi-star::before { content: "\f588"; } +.bi-stars::before { content: "\f589"; } +.bi-stickies-fill::before { content: "\f58a"; } +.bi-stickies::before { content: "\f58b"; } +.bi-sticky-fill::before { content: "\f58c"; } +.bi-sticky::before { content: "\f58d"; } +.bi-stop-btn-fill::before { content: "\f58e"; } +.bi-stop-btn::before { content: "\f58f"; } +.bi-stop-circle-fill::before { content: "\f590"; } +.bi-stop-circle::before { content: "\f591"; } +.bi-stop-fill::before { content: "\f592"; } +.bi-stop::before { content: "\f593"; } +.bi-stoplights-fill::before { content: "\f594"; } +.bi-stoplights::before { content: "\f595"; } +.bi-stopwatch-fill::before { content: "\f596"; } +.bi-stopwatch::before { content: "\f597"; } +.bi-subtract::before { content: "\f598"; } +.bi-suit-club-fill::before { content: "\f599"; } +.bi-suit-club::before { content: "\f59a"; } +.bi-suit-diamond-fill::before { content: "\f59b"; } +.bi-suit-diamond::before { content: "\f59c"; } +.bi-suit-heart-fill::before { content: "\f59d"; } +.bi-suit-heart::before { content: "\f59e"; } +.bi-suit-spade-fill::before { content: "\f59f"; } +.bi-suit-spade::before { content: "\f5a0"; } +.bi-sun-fill::before { content: "\f5a1"; } +.bi-sun::before { content: "\f5a2"; } +.bi-sunglasses::before { content: "\f5a3"; } +.bi-sunrise-fill::before { content: "\f5a4"; } +.bi-sunrise::before { content: "\f5a5"; } +.bi-sunset-fill::before { content: "\f5a6"; } +.bi-sunset::before { content: "\f5a7"; } +.bi-symmetry-horizontal::before { content: "\f5a8"; } +.bi-symmetry-vertical::before { content: "\f5a9"; } +.bi-table::before { content: "\f5aa"; } +.bi-tablet-fill::before { content: "\f5ab"; } +.bi-tablet-landscape-fill::before { content: "\f5ac"; } +.bi-tablet-landscape::before { content: "\f5ad"; } +.bi-tablet::before { content: "\f5ae"; } +.bi-tag-fill::before { content: "\f5af"; } +.bi-tag::before { content: "\f5b0"; } +.bi-tags-fill::before { content: "\f5b1"; } +.bi-tags::before { content: "\f5b2"; } +.bi-telegram::before { content: "\f5b3"; } +.bi-telephone-fill::before { content: "\f5b4"; } +.bi-telephone-forward-fill::before { content: "\f5b5"; } +.bi-telephone-forward::before { content: "\f5b6"; } +.bi-telephone-inbound-fill::before { content: "\f5b7"; } +.bi-telephone-inbound::before { content: "\f5b8"; } +.bi-telephone-minus-fill::before { content: "\f5b9"; } +.bi-telephone-minus::before { content: "\f5ba"; } +.bi-telephone-outbound-fill::before { content: "\f5bb"; } +.bi-telephone-outbound::before { content: "\f5bc"; } +.bi-telephone-plus-fill::before { content: "\f5bd"; } +.bi-telephone-plus::before { content: "\f5be"; } +.bi-telephone-x-fill::before { content: "\f5bf"; } +.bi-telephone-x::before { content: "\f5c0"; } +.bi-telephone::before { content: "\f5c1"; } +.bi-terminal-fill::before { content: "\f5c2"; } +.bi-terminal::before { content: "\f5c3"; } +.bi-text-center::before { content: "\f5c4"; } +.bi-text-indent-left::before { content: "\f5c5"; } +.bi-text-indent-right::before { content: "\f5c6"; } +.bi-text-left::before { content: "\f5c7"; } +.bi-text-paragraph::before { content: "\f5c8"; } +.bi-text-right::before { content: "\f5c9"; } +.bi-textarea-resize::before { content: "\f5ca"; } +.bi-textarea-t::before { content: "\f5cb"; } +.bi-textarea::before { content: "\f5cc"; } +.bi-thermometer-half::before { content: "\f5cd"; } +.bi-thermometer-high::before { content: "\f5ce"; } +.bi-thermometer-low::before { content: "\f5cf"; } +.bi-thermometer-snow::before { content: "\f5d0"; } +.bi-thermometer-sun::before { content: "\f5d1"; } +.bi-thermometer::before { content: "\f5d2"; } +.bi-three-dots-vertical::before { content: "\f5d3"; } +.bi-three-dots::before { content: "\f5d4"; } +.bi-toggle-off::before { content: "\f5d5"; } +.bi-toggle-on::before { content: "\f5d6"; } +.bi-toggle2-off::before { content: "\f5d7"; } +.bi-toggle2-on::before { content: "\f5d8"; } +.bi-toggles::before { content: "\f5d9"; } +.bi-toggles2::before { content: "\f5da"; } +.bi-tools::before { content: "\f5db"; } +.bi-tornado::before { content: "\f5dc"; } +.bi-trash-fill::before { content: "\f5dd"; } +.bi-trash::before { content: "\f5de"; } +.bi-trash2-fill::before { content: "\f5df"; } +.bi-trash2::before { content: "\f5e0"; } +.bi-tree-fill::before { content: "\f5e1"; } +.bi-tree::before { content: "\f5e2"; } +.bi-triangle-fill::before { content: "\f5e3"; } +.bi-triangle-half::before { content: "\f5e4"; } +.bi-triangle::before { content: "\f5e5"; } +.bi-trophy-fill::before { content: "\f5e6"; } +.bi-trophy::before { content: "\f5e7"; } +.bi-tropical-storm::before { content: "\f5e8"; } +.bi-truck-flatbed::before { content: "\f5e9"; } +.bi-truck::before { content: "\f5ea"; } +.bi-tsunami::before { content: "\f5eb"; } +.bi-tv-fill::before { content: "\f5ec"; } +.bi-tv::before { content: "\f5ed"; } +.bi-twitch::before { content: "\f5ee"; } +.bi-twitter::before { content: "\f5ef"; } +.bi-type-bold::before { content: "\f5f0"; } +.bi-type-h1::before { content: "\f5f1"; } +.bi-type-h2::before { content: "\f5f2"; } +.bi-type-h3::before { content: "\f5f3"; } +.bi-type-italic::before { content: "\f5f4"; } +.bi-type-strikethrough::before { content: "\f5f5"; } +.bi-type-underline::before { content: "\f5f6"; } +.bi-type::before { content: "\f5f7"; } +.bi-ui-checks-grid::before { content: "\f5f8"; } +.bi-ui-checks::before { content: "\f5f9"; } +.bi-ui-radios-grid::before { content: "\f5fa"; } +.bi-ui-radios::before { content: "\f5fb"; } +.bi-umbrella-fill::before { content: "\f5fc"; } +.bi-umbrella::before { content: "\f5fd"; } +.bi-union::before { content: "\f5fe"; } +.bi-unlock-fill::before { content: "\f5ff"; } +.bi-unlock::before { content: "\f600"; } +.bi-upc-scan::before { content: "\f601"; } +.bi-upc::before { content: "\f602"; } +.bi-upload::before { content: "\f603"; } +.bi-vector-pen::before { content: "\f604"; } +.bi-view-list::before { content: "\f605"; } +.bi-view-stacked::before { content: "\f606"; } +.bi-vinyl-fill::before { content: "\f607"; } +.bi-vinyl::before { content: "\f608"; } +.bi-voicemail::before { content: "\f609"; } +.bi-volume-down-fill::before { content: "\f60a"; } +.bi-volume-down::before { content: "\f60b"; } +.bi-volume-mute-fill::before { content: "\f60c"; } +.bi-volume-mute::before { content: "\f60d"; } +.bi-volume-off-fill::before { content: "\f60e"; } +.bi-volume-off::before { content: "\f60f"; } +.bi-volume-up-fill::before { content: "\f610"; } +.bi-volume-up::before { content: "\f611"; } +.bi-vr::before { content: "\f612"; } +.bi-wallet-fill::before { content: "\f613"; } +.bi-wallet::before { content: "\f614"; } +.bi-wallet2::before { content: "\f615"; } +.bi-watch::before { content: "\f616"; } +.bi-water::before { content: "\f617"; } +.bi-whatsapp::before { content: "\f618"; } +.bi-wifi-1::before { content: "\f619"; } +.bi-wifi-2::before { content: "\f61a"; } +.bi-wifi-off::before { content: "\f61b"; } +.bi-wifi::before { content: "\f61c"; } +.bi-wind::before { content: "\f61d"; } +.bi-window-dock::before { content: "\f61e"; } +.bi-window-sidebar::before { content: "\f61f"; } +.bi-window::before { content: "\f620"; } +.bi-wrench::before { content: "\f621"; } +.bi-x-circle-fill::before { content: "\f622"; } +.bi-x-circle::before { content: "\f623"; } +.bi-x-diamond-fill::before { content: "\f624"; } +.bi-x-diamond::before { content: "\f625"; } +.bi-x-octagon-fill::before { content: "\f626"; } +.bi-x-octagon::before { content: "\f627"; } +.bi-x-square-fill::before { content: "\f628"; } +.bi-x-square::before { content: "\f629"; } +.bi-x::before { content: "\f62a"; } +.bi-youtube::before { content: "\f62b"; } +.bi-zoom-in::before { content: "\f62c"; } +.bi-zoom-out::before { content: "\f62d"; } +.bi-bank::before { content: "\f62e"; } +.bi-bank2::before { content: "\f62f"; } +.bi-bell-slash-fill::before { content: "\f630"; } +.bi-bell-slash::before { content: "\f631"; } +.bi-cash-coin::before { content: "\f632"; } +.bi-check-lg::before { content: "\f633"; } +.bi-coin::before { content: "\f634"; } +.bi-currency-bitcoin::before { content: "\f635"; } +.bi-currency-dollar::before { content: "\f636"; } +.bi-currency-euro::before { content: "\f637"; } +.bi-currency-exchange::before { content: "\f638"; } +.bi-currency-pound::before { content: "\f639"; } +.bi-currency-yen::before { content: "\f63a"; } +.bi-dash-lg::before { content: "\f63b"; } +.bi-exclamation-lg::before { content: "\f63c"; } +.bi-file-earmark-pdf-fill::before { content: "\f63d"; } +.bi-file-earmark-pdf::before { content: "\f63e"; } +.bi-file-pdf-fill::before { content: "\f63f"; } +.bi-file-pdf::before { content: "\f640"; } +.bi-gender-ambiguous::before { content: "\f641"; } +.bi-gender-female::before { content: "\f642"; } +.bi-gender-male::before { content: "\f643"; } +.bi-gender-trans::before { content: "\f644"; } +.bi-headset-vr::before { content: "\f645"; } +.bi-info-lg::before { content: "\f646"; } +.bi-mastodon::before { content: "\f647"; } +.bi-messenger::before { content: "\f648"; } +.bi-piggy-bank-fill::before { content: "\f649"; } +.bi-piggy-bank::before { content: "\f64a"; } +.bi-pin-map-fill::before { content: "\f64b"; } +.bi-pin-map::before { content: "\f64c"; } +.bi-plus-lg::before { content: "\f64d"; } +.bi-question-lg::before { content: "\f64e"; } +.bi-recycle::before { content: "\f64f"; } +.bi-reddit::before { content: "\f650"; } +.bi-safe-fill::before { content: "\f651"; } +.bi-safe2-fill::before { content: "\f652"; } +.bi-safe2::before { content: "\f653"; } +.bi-sd-card-fill::before { content: "\f654"; } +.bi-sd-card::before { content: "\f655"; } +.bi-skype::before { content: "\f656"; } +.bi-slash-lg::before { content: "\f657"; } +.bi-translate::before { content: "\f658"; } +.bi-x-lg::before { content: "\f659"; } +.bi-safe::before { content: "\f65a"; } +.bi-apple::before { content: "\f65b"; } +.bi-microsoft::before { content: "\f65d"; } +.bi-windows::before { content: "\f65e"; } +.bi-behance::before { content: "\f65c"; } +.bi-dribbble::before { content: "\f65f"; } +.bi-line::before { content: "\f660"; } +.bi-medium::before { content: "\f661"; } +.bi-paypal::before { content: "\f662"; } +.bi-pinterest::before { content: "\f663"; } +.bi-signal::before { content: "\f664"; } +.bi-snapchat::before { content: "\f665"; } +.bi-spotify::before { content: "\f666"; } +.bi-stack-overflow::before { content: "\f667"; } +.bi-strava::before { content: "\f668"; } +.bi-wordpress::before { content: "\f669"; } +.bi-vimeo::before { content: "\f66a"; } +.bi-activity::before { content: "\f66b"; } +.bi-easel2-fill::before { content: "\f66c"; } +.bi-easel2::before { content: "\f66d"; } +.bi-easel3-fill::before { content: "\f66e"; } +.bi-easel3::before { content: "\f66f"; } +.bi-fan::before { content: "\f670"; } +.bi-fingerprint::before { content: "\f671"; } +.bi-graph-down-arrow::before { content: "\f672"; } +.bi-graph-up-arrow::before { content: "\f673"; } +.bi-hypnotize::before { content: "\f674"; } +.bi-magic::before { content: "\f675"; } +.bi-person-rolodex::before { content: "\f676"; } +.bi-person-video::before { content: "\f677"; } +.bi-person-video2::before { content: "\f678"; } +.bi-person-video3::before { content: "\f679"; } +.bi-person-workspace::before { content: "\f67a"; } +.bi-radioactive::before { content: "\f67b"; } +.bi-webcam-fill::before { content: "\f67c"; } +.bi-webcam::before { content: "\f67d"; } +.bi-yin-yang::before { content: "\f67e"; } +.bi-bandaid-fill::before { content: "\f680"; } +.bi-bandaid::before { content: "\f681"; } +.bi-bluetooth::before { content: "\f682"; } +.bi-body-text::before { content: "\f683"; } +.bi-boombox::before { content: "\f684"; } +.bi-boxes::before { content: "\f685"; } +.bi-dpad-fill::before { content: "\f686"; } +.bi-dpad::before { content: "\f687"; } +.bi-ear-fill::before { content: "\f688"; } +.bi-ear::before { content: "\f689"; } +.bi-envelope-check-1::before { content: "\f68a"; } +.bi-envelope-check-fill::before { content: "\f68b"; } +.bi-envelope-check::before { content: "\f68c"; } +.bi-envelope-dash-1::before { content: "\f68d"; } +.bi-envelope-dash-fill::before { content: "\f68e"; } +.bi-envelope-dash::before { content: "\f68f"; } +.bi-envelope-exclamation-1::before { content: "\f690"; } +.bi-envelope-exclamation-fill::before { content: "\f691"; } +.bi-envelope-exclamation::before { content: "\f692"; } +.bi-envelope-plus-fill::before { content: "\f693"; } +.bi-envelope-plus::before { content: "\f694"; } +.bi-envelope-slash-1::before { content: "\f695"; } +.bi-envelope-slash-fill::before { content: "\f696"; } +.bi-envelope-slash::before { content: "\f697"; } +.bi-envelope-x-1::before { content: "\f698"; } +.bi-envelope-x-fill::before { content: "\f699"; } +.bi-envelope-x::before { content: "\f69a"; } +.bi-explicit-fill::before { content: "\f69b"; } +.bi-explicit::before { content: "\f69c"; } +.bi-git::before { content: "\f69d"; } +.bi-infinity::before { content: "\f69e"; } +.bi-list-columns-reverse::before { content: "\f69f"; } +.bi-list-columns::before { content: "\f6a0"; } +.bi-meta::before { content: "\f6a1"; } +.bi-mortorboard-fill::before { content: "\f6a2"; } +.bi-mortorboard::before { content: "\f6a3"; } +.bi-nintendo-switch::before { content: "\f6a4"; } +.bi-pc-display-horizontal::before { content: "\f6a5"; } +.bi-pc-display::before { content: "\f6a6"; } +.bi-pc-horizontal::before { content: "\f6a7"; } +.bi-pc::before { content: "\f6a8"; } +.bi-playstation::before { content: "\f6a9"; } +.bi-plus-slash-minus::before { content: "\f6aa"; } +.bi-projector-fill::before { content: "\f6ab"; } +.bi-projector::before { content: "\f6ac"; } +.bi-qr-code-scan::before { content: "\f6ad"; } +.bi-qr-code::before { content: "\f6ae"; } +.bi-quora::before { content: "\f6af"; } +.bi-quote::before { content: "\f6b0"; } +.bi-robot::before { content: "\f6b1"; } +.bi-send-check-fill::before { content: "\f6b2"; } +.bi-send-check::before { content: "\f6b3"; } +.bi-send-dash-fill::before { content: "\f6b4"; } +.bi-send-dash::before { content: "\f6b5"; } +.bi-send-exclamation-1::before { content: "\f6b6"; } +.bi-send-exclamation-fill::before { content: "\f6b7"; } +.bi-send-exclamation::before { content: "\f6b8"; } +.bi-send-fill::before { content: "\f6b9"; } +.bi-send-plus-fill::before { content: "\f6ba"; } +.bi-send-plus::before { content: "\f6bb"; } +.bi-send-slash-fill::before { content: "\f6bc"; } +.bi-send-slash::before { content: "\f6bd"; } +.bi-send-x-fill::before { content: "\f6be"; } +.bi-send-x::before { content: "\f6bf"; } +.bi-send::before { content: "\f6c0"; } +.bi-steam::before { content: "\f6c1"; } +.bi-terminal-dash-1::before { content: "\f6c2"; } +.bi-terminal-dash::before { content: "\f6c3"; } +.bi-terminal-plus::before { content: "\f6c4"; } +.bi-terminal-split::before { content: "\f6c5"; } +.bi-ticket-detailed-fill::before { content: "\f6c6"; } +.bi-ticket-detailed::before { content: "\f6c7"; } +.bi-ticket-fill::before { content: "\f6c8"; } +.bi-ticket-perforated-fill::before { content: "\f6c9"; } +.bi-ticket-perforated::before { content: "\f6ca"; } +.bi-ticket::before { content: "\f6cb"; } +.bi-tiktok::before { content: "\f6cc"; } +.bi-window-dash::before { content: "\f6cd"; } +.bi-window-desktop::before { content: "\f6ce"; } +.bi-window-fullscreen::before { content: "\f6cf"; } +.bi-window-plus::before { content: "\f6d0"; } +.bi-window-split::before { content: "\f6d1"; } +.bi-window-stack::before { content: "\f6d2"; } +.bi-window-x::before { content: "\f6d3"; } +.bi-xbox::before { content: "\f6d4"; } +.bi-ethernet::before { content: "\f6d5"; } +.bi-hdmi-fill::before { content: "\f6d6"; } +.bi-hdmi::before { content: "\f6d7"; } +.bi-usb-c-fill::before { content: "\f6d8"; } +.bi-usb-c::before { content: "\f6d9"; } +.bi-usb-fill::before { content: "\f6da"; } +.bi-usb-plug-fill::before { content: "\f6db"; } +.bi-usb-plug::before { content: "\f6dc"; } +.bi-usb-symbol::before { content: "\f6dd"; } +.bi-usb::before { content: "\f6de"; } +.bi-boombox-fill::before { content: "\f6df"; } +.bi-displayport-1::before { content: "\f6e0"; } +.bi-displayport::before { content: "\f6e1"; } +.bi-gpu-card::before { content: "\f6e2"; } +.bi-memory::before { content: "\f6e3"; } +.bi-modem-fill::before { content: "\f6e4"; } +.bi-modem::before { content: "\f6e5"; } +.bi-motherboard-fill::before { content: "\f6e6"; } +.bi-motherboard::before { content: "\f6e7"; } +.bi-optical-audio-fill::before { content: "\f6e8"; } +.bi-optical-audio::before { content: "\f6e9"; } +.bi-pci-card::before { content: "\f6ea"; } +.bi-router-fill::before { content: "\f6eb"; } +.bi-router::before { content: "\f6ec"; } +.bi-ssd-fill::before { content: "\f6ed"; } +.bi-ssd::before { content: "\f6ee"; } +.bi-thunderbolt-fill::before { content: "\f6ef"; } +.bi-thunderbolt::before { content: "\f6f0"; } +.bi-usb-drive-fill::before { content: "\f6f1"; } +.bi-usb-drive::before { content: "\f6f2"; } +.bi-usb-micro-fill::before { content: "\f6f3"; } +.bi-usb-micro::before { content: "\f6f4"; } +.bi-usb-mini-fill::before { content: "\f6f5"; } +.bi-usb-mini::before { content: "\f6f6"; } +.bi-cloud-haze2::before { content: "\f6f7"; } +.bi-device-hdd-fill::before { content: "\f6f8"; } +.bi-device-hdd::before { content: "\f6f9"; } +.bi-device-ssd-fill::before { content: "\f6fa"; } +.bi-device-ssd::before { content: "\f6fb"; } +.bi-displayport-fill::before { content: "\f6fc"; } +.bi-mortarboard-fill::before { content: "\f6fd"; } +.bi-mortarboard::before { content: "\f6fe"; } +.bi-terminal-x::before { content: "\f6ff"; } +.bi-arrow-through-heart-fill::before { content: "\f700"; } +.bi-arrow-through-heart::before { content: "\f701"; } +.bi-badge-sd-fill::before { content: "\f702"; } +.bi-badge-sd::before { content: "\f703"; } +.bi-bag-heart-fill::before { content: "\f704"; } +.bi-bag-heart::before { content: "\f705"; } +.bi-balloon-fill::before { content: "\f706"; } +.bi-balloon-heart-fill::before { content: "\f707"; } +.bi-balloon-heart::before { content: "\f708"; } +.bi-balloon::before { content: "\f709"; } +.bi-box2-fill::before { content: "\f70a"; } +.bi-box2-heart-fill::before { content: "\f70b"; } +.bi-box2-heart::before { content: "\f70c"; } +.bi-box2::before { content: "\f70d"; } +.bi-braces-asterisk::before { content: "\f70e"; } +.bi-calendar-heart-fill::before { content: "\f70f"; } +.bi-calendar-heart::before { content: "\f710"; } +.bi-calendar2-heart-fill::before { content: "\f711"; } +.bi-calendar2-heart::before { content: "\f712"; } +.bi-chat-heart-fill::before { content: "\f713"; } +.bi-chat-heart::before { content: "\f714"; } +.bi-chat-left-heart-fill::before { content: "\f715"; } +.bi-chat-left-heart::before { content: "\f716"; } +.bi-chat-right-heart-fill::before { content: "\f717"; } +.bi-chat-right-heart::before { content: "\f718"; } +.bi-chat-square-heart-fill::before { content: "\f719"; } +.bi-chat-square-heart::before { content: "\f71a"; } +.bi-clipboard-check-fill::before { content: "\f71b"; } +.bi-clipboard-data-fill::before { content: "\f71c"; } +.bi-clipboard-fill::before { content: "\f71d"; } +.bi-clipboard-heart-fill::before { content: "\f71e"; } +.bi-clipboard-heart::before { content: "\f71f"; } +.bi-clipboard-minus-fill::before { content: "\f720"; } +.bi-clipboard-plus-fill::before { content: "\f721"; } +.bi-clipboard-pulse::before { content: "\f722"; } +.bi-clipboard-x-fill::before { content: "\f723"; } +.bi-clipboard2-check-fill::before { content: "\f724"; } +.bi-clipboard2-check::before { content: "\f725"; } +.bi-clipboard2-data-fill::before { content: "\f726"; } +.bi-clipboard2-data::before { content: "\f727"; } +.bi-clipboard2-fill::before { content: "\f728"; } +.bi-clipboard2-heart-fill::before { content: "\f729"; } +.bi-clipboard2-heart::before { content: "\f72a"; } +.bi-clipboard2-minus-fill::before { content: "\f72b"; } +.bi-clipboard2-minus::before { content: "\f72c"; } +.bi-clipboard2-plus-fill::before { content: "\f72d"; } +.bi-clipboard2-plus::before { content: "\f72e"; } +.bi-clipboard2-pulse-fill::before { content: "\f72f"; } +.bi-clipboard2-pulse::before { content: "\f730"; } +.bi-clipboard2-x-fill::before { content: "\f731"; } +.bi-clipboard2-x::before { content: "\f732"; } +.bi-clipboard2::before { content: "\f733"; } +.bi-emoji-kiss-fill::before { content: "\f734"; } +.bi-emoji-kiss::before { content: "\f735"; } +.bi-envelope-heart-fill::before { content: "\f736"; } +.bi-envelope-heart::before { content: "\f737"; } +.bi-envelope-open-heart-fill::before { content: "\f738"; } +.bi-envelope-open-heart::before { content: "\f739"; } +.bi-envelope-paper-fill::before { content: "\f73a"; } +.bi-envelope-paper-heart-fill::before { content: "\f73b"; } +.bi-envelope-paper-heart::before { content: "\f73c"; } +.bi-envelope-paper::before { content: "\f73d"; } +.bi-filetype-aac::before { content: "\f73e"; } +.bi-filetype-ai::before { content: "\f73f"; } +.bi-filetype-bmp::before { content: "\f740"; } +.bi-filetype-cs::before { content: "\f741"; } +.bi-filetype-css::before { content: "\f742"; } +.bi-filetype-csv::before { content: "\f743"; } +.bi-filetype-doc::before { content: "\f744"; } +.bi-filetype-docx::before { content: "\f745"; } +.bi-filetype-exe::before { content: "\f746"; } +.bi-filetype-gif::before { content: "\f747"; } +.bi-filetype-heic::before { content: "\f748"; } +.bi-filetype-html::before { content: "\f749"; } +.bi-filetype-java::before { content: "\f74a"; } +.bi-filetype-jpg::before { content: "\f74b"; } +.bi-filetype-js::before { content: "\f74c"; } +.bi-filetype-jsx::before { content: "\f74d"; } +.bi-filetype-key::before { content: "\f74e"; } +.bi-filetype-m4p::before { content: "\f74f"; } +.bi-filetype-md::before { content: "\f750"; } +.bi-filetype-mdx::before { content: "\f751"; } +.bi-filetype-mov::before { content: "\f752"; } +.bi-filetype-mp3::before { content: "\f753"; } +.bi-filetype-mp4::before { content: "\f754"; } +.bi-filetype-otf::before { content: "\f755"; } +.bi-filetype-pdf::before { content: "\f756"; } +.bi-filetype-php::before { content: "\f757"; } +.bi-filetype-png::before { content: "\f758"; } +.bi-filetype-ppt-1::before { content: "\f759"; } +.bi-filetype-ppt::before { content: "\f75a"; } +.bi-filetype-psd::before { content: "\f75b"; } +.bi-filetype-py::before { content: "\f75c"; } +.bi-filetype-raw::before { content: "\f75d"; } +.bi-filetype-rb::before { content: "\f75e"; } +.bi-filetype-sass::before { content: "\f75f"; } +.bi-filetype-scss::before { content: "\f760"; } +.bi-filetype-sh::before { content: "\f761"; } +.bi-filetype-svg::before { content: "\f762"; } +.bi-filetype-tiff::before { content: "\f763"; } +.bi-filetype-tsx::before { content: "\f764"; } +.bi-filetype-ttf::before { content: "\f765"; } +.bi-filetype-txt::before { content: "\f766"; } +.bi-filetype-wav::before { content: "\f767"; } +.bi-filetype-woff::before { content: "\f768"; } +.bi-filetype-xls-1::before { content: "\f769"; } +.bi-filetype-xls::before { content: "\f76a"; } +.bi-filetype-xml::before { content: "\f76b"; } +.bi-filetype-yml::before { content: "\f76c"; } +.bi-heart-arrow::before { content: "\f76d"; } +.bi-heart-pulse-fill::before { content: "\f76e"; } +.bi-heart-pulse::before { content: "\f76f"; } +.bi-heartbreak-fill::before { content: "\f770"; } +.bi-heartbreak::before { content: "\f771"; } +.bi-hearts::before { content: "\f772"; } +.bi-hospital-fill::before { content: "\f773"; } +.bi-hospital::before { content: "\f774"; } +.bi-house-heart-fill::before { content: "\f775"; } +.bi-house-heart::before { content: "\f776"; } +.bi-incognito::before { content: "\f777"; } +.bi-magnet-fill::before { content: "\f778"; } +.bi-magnet::before { content: "\f779"; } +.bi-person-heart::before { content: "\f77a"; } +.bi-person-hearts::before { content: "\f77b"; } +.bi-phone-flip::before { content: "\f77c"; } +.bi-plugin::before { content: "\f77d"; } +.bi-postage-fill::before { content: "\f77e"; } +.bi-postage-heart-fill::before { content: "\f77f"; } +.bi-postage-heart::before { content: "\f780"; } +.bi-postage::before { content: "\f781"; } +.bi-postcard-fill::before { content: "\f782"; } +.bi-postcard-heart-fill::before { content: "\f783"; } +.bi-postcard-heart::before { content: "\f784"; } +.bi-postcard::before { content: "\f785"; } +.bi-search-heart-fill::before { content: "\f786"; } +.bi-search-heart::before { content: "\f787"; } +.bi-sliders2-vertical::before { content: "\f788"; } +.bi-sliders2::before { content: "\f789"; } +.bi-trash3-fill::before { content: "\f78a"; } +.bi-trash3::before { content: "\f78b"; } +.bi-valentine::before { content: "\f78c"; } +.bi-valentine2::before { content: "\f78d"; } +.bi-wrench-adjustable-circle-fill::before { content: "\f78e"; } +.bi-wrench-adjustable-circle::before { content: "\f78f"; } +.bi-wrench-adjustable::before { content: "\f790"; } +.bi-filetype-json::before { content: "\f791"; } +.bi-filetype-pptx::before { content: "\f792"; } +.bi-filetype-xlsx::before { content: "\f793"; } +.bi-1-circle-1::before { content: "\f794"; } +.bi-1-circle-fill-1::before { content: "\f795"; } +.bi-1-circle-fill::before { content: "\f796"; } +.bi-1-circle::before { content: "\f797"; } +.bi-1-square-fill::before { content: "\f798"; } +.bi-1-square::before { content: "\f799"; } +.bi-2-circle-1::before { content: "\f79a"; } +.bi-2-circle-fill-1::before { content: "\f79b"; } +.bi-2-circle-fill::before { content: "\f79c"; } +.bi-2-circle::before { content: "\f79d"; } +.bi-2-square-fill::before { content: "\f79e"; } +.bi-2-square::before { content: "\f79f"; } +.bi-3-circle-1::before { content: "\f7a0"; } +.bi-3-circle-fill-1::before { content: "\f7a1"; } +.bi-3-circle-fill::before { content: "\f7a2"; } +.bi-3-circle::before { content: "\f7a3"; } +.bi-3-square-fill::before { content: "\f7a4"; } +.bi-3-square::before { content: "\f7a5"; } +.bi-4-circle-1::before { content: "\f7a6"; } +.bi-4-circle-fill-1::before { content: "\f7a7"; } +.bi-4-circle-fill::before { content: "\f7a8"; } +.bi-4-circle::before { content: "\f7a9"; } +.bi-4-square-fill::before { content: "\f7aa"; } +.bi-4-square::before { content: "\f7ab"; } +.bi-5-circle-1::before { content: "\f7ac"; } +.bi-5-circle-fill-1::before { content: "\f7ad"; } +.bi-5-circle-fill::before { content: "\f7ae"; } +.bi-5-circle::before { content: "\f7af"; } +.bi-5-square-fill::before { content: "\f7b0"; } +.bi-5-square::before { content: "\f7b1"; } +.bi-6-circle-1::before { content: "\f7b2"; } +.bi-6-circle-fill-1::before { content: "\f7b3"; } +.bi-6-circle-fill::before { content: "\f7b4"; } +.bi-6-circle::before { content: "\f7b5"; } +.bi-6-square-fill::before { content: "\f7b6"; } +.bi-6-square::before { content: "\f7b7"; } +.bi-7-circle-1::before { content: "\f7b8"; } +.bi-7-circle-fill-1::before { content: "\f7b9"; } +.bi-7-circle-fill::before { content: "\f7ba"; } +.bi-7-circle::before { content: "\f7bb"; } +.bi-7-square-fill::before { content: "\f7bc"; } +.bi-7-square::before { content: "\f7bd"; } +.bi-8-circle-1::before { content: "\f7be"; } +.bi-8-circle-fill-1::before { content: "\f7bf"; } +.bi-8-circle-fill::before { content: "\f7c0"; } +.bi-8-circle::before { content: "\f7c1"; } +.bi-8-square-fill::before { content: "\f7c2"; } +.bi-8-square::before { content: "\f7c3"; } +.bi-9-circle-1::before { content: "\f7c4"; } +.bi-9-circle-fill-1::before { content: "\f7c5"; } +.bi-9-circle-fill::before { content: "\f7c6"; } +.bi-9-circle::before { content: "\f7c7"; } +.bi-9-square-fill::before { content: "\f7c8"; } +.bi-9-square::before { content: "\f7c9"; } +.bi-airplane-engines-fill::before { content: "\f7ca"; } +.bi-airplane-engines::before { content: "\f7cb"; } +.bi-airplane-fill::before { content: "\f7cc"; } +.bi-airplane::before { content: "\f7cd"; } +.bi-alexa::before { content: "\f7ce"; } +.bi-alipay::before { content: "\f7cf"; } +.bi-android::before { content: "\f7d0"; } +.bi-android2::before { content: "\f7d1"; } +.bi-box-fill::before { content: "\f7d2"; } +.bi-box-seam-fill::before { content: "\f7d3"; } +.bi-browser-chrome::before { content: "\f7d4"; } +.bi-browser-edge::before { content: "\f7d5"; } +.bi-browser-firefox::before { content: "\f7d6"; } +.bi-browser-safari::before { content: "\f7d7"; } +.bi-c-circle-1::before { content: "\f7d8"; } +.bi-c-circle-fill-1::before { content: "\f7d9"; } +.bi-c-circle-fill::before { content: "\f7da"; } +.bi-c-circle::before { content: "\f7db"; } +.bi-c-square-fill::before { content: "\f7dc"; } +.bi-c-square::before { content: "\f7dd"; } +.bi-capsule-pill::before { content: "\f7de"; } +.bi-capsule::before { content: "\f7df"; } +.bi-car-front-fill::before { content: "\f7e0"; } +.bi-car-front::before { content: "\f7e1"; } +.bi-cassette-fill::before { content: "\f7e2"; } +.bi-cassette::before { content: "\f7e3"; } +.bi-cc-circle-1::before { content: "\f7e4"; } +.bi-cc-circle-fill-1::before { content: "\f7e5"; } +.bi-cc-circle-fill::before { content: "\f7e6"; } +.bi-cc-circle::before { content: "\f7e7"; } +.bi-cc-square-fill::before { content: "\f7e8"; } +.bi-cc-square::before { content: "\f7e9"; } +.bi-cup-hot-fill::before { content: "\f7ea"; } +.bi-cup-hot::before { content: "\f7eb"; } +.bi-currency-rupee::before { content: "\f7ec"; } +.bi-dropbox::before { content: "\f7ed"; } +.bi-escape::before { content: "\f7ee"; } +.bi-fast-forward-btn-fill::before { content: "\f7ef"; } +.bi-fast-forward-btn::before { content: "\f7f0"; } +.bi-fast-forward-circle-fill::before { content: "\f7f1"; } +.bi-fast-forward-circle::before { content: "\f7f2"; } +.bi-fast-forward-fill::before { content: "\f7f3"; } +.bi-fast-forward::before { content: "\f7f4"; } +.bi-filetype-sql::before { content: "\f7f5"; } +.bi-fire::before { content: "\f7f6"; } +.bi-google-play::before { content: "\f7f7"; } +.bi-h-circle-1::before { content: "\f7f8"; } +.bi-h-circle-fill-1::before { content: "\f7f9"; } +.bi-h-circle-fill::before { content: "\f7fa"; } +.bi-h-circle::before { content: "\f7fb"; } +.bi-h-square-fill::before { content: "\f7fc"; } +.bi-h-square::before { content: "\f7fd"; } +.bi-indent::before { content: "\f7fe"; } +.bi-lungs-fill::before { content: "\f7ff"; } +.bi-lungs::before { content: "\f800"; } +.bi-microsoft-teams::before { content: "\f801"; } +.bi-p-circle-1::before { content: "\f802"; } +.bi-p-circle-fill-1::before { content: "\f803"; } +.bi-p-circle-fill::before { content: "\f804"; } +.bi-p-circle::before { content: "\f805"; } +.bi-p-square-fill::before { content: "\f806"; } +.bi-p-square::before { content: "\f807"; } +.bi-pass-fill::before { content: "\f808"; } +.bi-pass::before { content: "\f809"; } +.bi-prescription::before { content: "\f80a"; } +.bi-prescription2::before { content: "\f80b"; } +.bi-r-circle-1::before { content: "\f80c"; } +.bi-r-circle-fill-1::before { content: "\f80d"; } +.bi-r-circle-fill::before { content: "\f80e"; } +.bi-r-circle::before { content: "\f80f"; } +.bi-r-square-fill::before { content: "\f810"; } +.bi-r-square::before { content: "\f811"; } +.bi-repeat-1::before { content: "\f812"; } +.bi-repeat::before { content: "\f813"; } +.bi-rewind-btn-fill::before { content: "\f814"; } +.bi-rewind-btn::before { content: "\f815"; } +.bi-rewind-circle-fill::before { content: "\f816"; } +.bi-rewind-circle::before { content: "\f817"; } +.bi-rewind-fill::before { content: "\f818"; } +.bi-rewind::before { content: "\f819"; } +.bi-train-freight-front-fill::before { content: "\f81a"; } +.bi-train-freight-front::before { content: "\f81b"; } +.bi-train-front-fill::before { content: "\f81c"; } +.bi-train-front::before { content: "\f81d"; } +.bi-train-lightrail-front-fill::before { content: "\f81e"; } +.bi-train-lightrail-front::before { content: "\f81f"; } +.bi-truck-front-fill::before { content: "\f820"; } +.bi-truck-front::before { content: "\f821"; } +.bi-ubuntu::before { content: "\f822"; } +.bi-unindent::before { content: "\f823"; } +.bi-unity::before { content: "\f824"; } +.bi-universal-access-circle::before { content: "\f825"; } +.bi-universal-access::before { content: "\f826"; } +.bi-virus::before { content: "\f827"; } +.bi-virus2::before { content: "\f828"; } +.bi-wechat::before { content: "\f829"; } +.bi-yelp::before { content: "\f82a"; } +.bi-sign-stop-fill::before { content: "\f82b"; } +.bi-sign-stop-lights-fill::before { content: "\f82c"; } +.bi-sign-stop-lights::before { content: "\f82d"; } +.bi-sign-stop::before { content: "\f82e"; } +.bi-sign-turn-left-fill::before { content: "\f82f"; } +.bi-sign-turn-left::before { content: "\f830"; } +.bi-sign-turn-right-fill::before { content: "\f831"; } +.bi-sign-turn-right::before { content: "\f832"; } +.bi-sign-turn-slight-left-fill::before { content: "\f833"; } +.bi-sign-turn-slight-left::before { content: "\f834"; } +.bi-sign-turn-slight-right-fill::before { content: "\f835"; } +.bi-sign-turn-slight-right::before { content: "\f836"; } +.bi-sign-yield-fill::before { content: "\f837"; } +.bi-sign-yield::before { content: "\f838"; } +.bi-ev-station-fill::before { content: "\f839"; } +.bi-ev-station::before { content: "\f83a"; } +.bi-fuel-pump-diesel-fill::before { content: "\f83b"; } +.bi-fuel-pump-diesel::before { content: "\f83c"; } +.bi-fuel-pump-fill::before { content: "\f83d"; } +.bi-fuel-pump::before { content: "\f83e"; } +.bi-0-circle-fill::before { content: "\f83f"; } +.bi-0-circle::before { content: "\f840"; } +.bi-0-square-fill::before { content: "\f841"; } +.bi-0-square::before { content: "\f842"; } +.bi-rocket-fill::before { content: "\f843"; } +.bi-rocket-takeoff-fill::before { content: "\f844"; } +.bi-rocket-takeoff::before { content: "\f845"; } +.bi-rocket::before { content: "\f846"; } +.bi-stripe::before { content: "\f847"; } +.bi-subscript::before { content: "\f848"; } +.bi-superscript::before { content: "\f849"; } +.bi-trello::before { content: "\f84a"; } +.bi-envelope-at-fill::before { content: "\f84b"; } +.bi-envelope-at::before { content: "\f84c"; } +.bi-regex::before { content: "\f84d"; } +.bi-text-wrap::before { content: "\f84e"; } +.bi-sign-dead-end-fill::before { content: "\f84f"; } +.bi-sign-dead-end::before { content: "\f850"; } +.bi-sign-do-not-enter-fill::before { content: "\f851"; } +.bi-sign-do-not-enter::before { content: "\f852"; } +.bi-sign-intersection-fill::before { content: "\f853"; } +.bi-sign-intersection-side-fill::before { content: "\f854"; } +.bi-sign-intersection-side::before { content: "\f855"; } +.bi-sign-intersection-t-fill::before { content: "\f856"; } +.bi-sign-intersection-t::before { content: "\f857"; } +.bi-sign-intersection-y-fill::before { content: "\f858"; } +.bi-sign-intersection-y::before { content: "\f859"; } +.bi-sign-intersection::before { content: "\f85a"; } +.bi-sign-merge-left-fill::before { content: "\f85b"; } +.bi-sign-merge-left::before { content: "\f85c"; } +.bi-sign-merge-right-fill::before { content: "\f85d"; } +.bi-sign-merge-right::before { content: "\f85e"; } +.bi-sign-no-left-turn-fill::before { content: "\f85f"; } +.bi-sign-no-left-turn::before { content: "\f860"; } +.bi-sign-no-parking-fill::before { content: "\f861"; } +.bi-sign-no-parking::before { content: "\f862"; } +.bi-sign-no-right-turn-fill::before { content: "\f863"; } +.bi-sign-no-right-turn::before { content: "\f864"; } +.bi-sign-railroad-fill::before { content: "\f865"; } +.bi-sign-railroad::before { content: "\f866"; } +.bi-building-add::before { content: "\f867"; } +.bi-building-check::before { content: "\f868"; } +.bi-building-dash::before { content: "\f869"; } +.bi-building-down::before { content: "\f86a"; } +.bi-building-exclamation::before { content: "\f86b"; } +.bi-building-fill-add::before { content: "\f86c"; } +.bi-building-fill-check::before { content: "\f86d"; } +.bi-building-fill-dash::before { content: "\f86e"; } +.bi-building-fill-down::before { content: "\f86f"; } +.bi-building-fill-exclamation::before { content: "\f870"; } +.bi-building-fill-gear::before { content: "\f871"; } +.bi-building-fill-lock::before { content: "\f872"; } +.bi-building-fill-slash::before { content: "\f873"; } +.bi-building-fill-up::before { content: "\f874"; } +.bi-building-fill-x::before { content: "\f875"; } +.bi-building-fill::before { content: "\f876"; } +.bi-building-gear::before { content: "\f877"; } +.bi-building-lock::before { content: "\f878"; } +.bi-building-slash::before { content: "\f879"; } +.bi-building-up::before { content: "\f87a"; } +.bi-building-x::before { content: "\f87b"; } +.bi-buildings-fill::before { content: "\f87c"; } +.bi-buildings::before { content: "\f87d"; } +.bi-bus-front-fill::before { content: "\f87e"; } +.bi-bus-front::before { content: "\f87f"; } +.bi-ev-front-fill::before { content: "\f880"; } +.bi-ev-front::before { content: "\f881"; } +.bi-globe-americas::before { content: "\f882"; } +.bi-globe-asia-australia::before { content: "\f883"; } +.bi-globe-central-south-asia::before { content: "\f884"; } +.bi-globe-europe-africa::before { content: "\f885"; } +.bi-house-add-fill::before { content: "\f886"; } +.bi-house-add::before { content: "\f887"; } +.bi-house-check-fill::before { content: "\f888"; } +.bi-house-check::before { content: "\f889"; } +.bi-house-dash-fill::before { content: "\f88a"; } +.bi-house-dash::before { content: "\f88b"; } +.bi-house-down-fill::before { content: "\f88c"; } +.bi-house-down::before { content: "\f88d"; } +.bi-house-exclamation-fill::before { content: "\f88e"; } +.bi-house-exclamation::before { content: "\f88f"; } +.bi-house-gear-fill::before { content: "\f890"; } +.bi-house-gear::before { content: "\f891"; } +.bi-house-lock-fill::before { content: "\f892"; } +.bi-house-lock::before { content: "\f893"; } +.bi-house-slash-fill::before { content: "\f894"; } +.bi-house-slash::before { content: "\f895"; } +.bi-house-up-fill::before { content: "\f896"; } +.bi-house-up::before { content: "\f897"; } +.bi-house-x-fill::before { content: "\f898"; } +.bi-house-x::before { content: "\f899"; } +.bi-person-add::before { content: "\f89a"; } +.bi-person-down::before { content: "\f89b"; } +.bi-person-exclamation::before { content: "\f89c"; } +.bi-person-fill-add::before { content: "\f89d"; } +.bi-person-fill-check::before { content: "\f89e"; } +.bi-person-fill-dash::before { content: "\f89f"; } +.bi-person-fill-down::before { content: "\f8a0"; } +.bi-person-fill-exclamation::before { content: "\f8a1"; } +.bi-person-fill-gear::before { content: "\f8a2"; } +.bi-person-fill-lock::before { content: "\f8a3"; } +.bi-person-fill-slash::before { content: "\f8a4"; } +.bi-person-fill-up::before { content: "\f8a5"; } +.bi-person-fill-x::before { content: "\f8a6"; } +.bi-person-gear::before { content: "\f8a7"; } +.bi-person-lock::before { content: "\f8a8"; } +.bi-person-slash::before { content: "\f8a9"; } +.bi-person-up::before { content: "\f8aa"; } +.bi-scooter::before { content: "\f8ab"; } +.bi-taxi-front-fill::before { content: "\f8ac"; } +.bi-taxi-front::before { content: "\f8ad"; } +.bi-amd::before { content: "\f8ae"; } +.bi-database-add::before { content: "\f8af"; } +.bi-database-check::before { content: "\f8b0"; } +.bi-database-dash::before { content: "\f8b1"; } +.bi-database-down::before { content: "\f8b2"; } +.bi-database-exclamation::before { content: "\f8b3"; } +.bi-database-fill-add::before { content: "\f8b4"; } +.bi-database-fill-check::before { content: "\f8b5"; } +.bi-database-fill-dash::before { content: "\f8b6"; } +.bi-database-fill-down::before { content: "\f8b7"; } +.bi-database-fill-exclamation::before { content: "\f8b8"; } +.bi-database-fill-gear::before { content: "\f8b9"; } +.bi-database-fill-lock::before { content: "\f8ba"; } +.bi-database-fill-slash::before { content: "\f8bb"; } +.bi-database-fill-up::before { content: "\f8bc"; } +.bi-database-fill-x::before { content: "\f8bd"; } +.bi-database-fill::before { content: "\f8be"; } +.bi-database-gear::before { content: "\f8bf"; } +.bi-database-lock::before { content: "\f8c0"; } +.bi-database-slash::before { content: "\f8c1"; } +.bi-database-up::before { content: "\f8c2"; } +.bi-database-x::before { content: "\f8c3"; } +.bi-database::before { content: "\f8c4"; } +.bi-houses-fill::before { content: "\f8c5"; } +.bi-houses::before { content: "\f8c6"; } +.bi-nvidia::before { content: "\f8c7"; } +.bi-person-vcard-fill::before { content: "\f8c8"; } +.bi-person-vcard::before { content: "\f8c9"; } +.bi-sina-weibo::before { content: "\f8ca"; } +.bi-tencent-qq::before { content: "\f8cb"; } +.bi-wikipedia::before { content: "\f8cc"; } diff --git a/site_libs/bootstrap/bootstrap-icons.woff b/site_libs/bootstrap/bootstrap-icons.woff new file mode 100644 index 00000000..18d21d45 Binary files /dev/null and b/site_libs/bootstrap/bootstrap-icons.woff differ diff --git a/site_libs/bootstrap/bootstrap.min.css b/site_libs/bootstrap/bootstrap.min.css new file mode 100644 index 00000000..7a2c054c --- /dev/null +++ b/site_libs/bootstrap/bootstrap.min.css @@ -0,0 +1,10 @@ +ļ»æ/*! + * Bootstrap v5.1.3 (https://getbootstrap.com/) + * Copyright 2011-2021 The Bootstrap Authors + * Copyright 2011-2021 Twitter, Inc. + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) + */@import"https://fonts.googleapis.com/css2?family=Source+Sans+Pro:wght@300;400;700&display=swap";:root{--bs-blue: #2780e3;--bs-indigo: #6610f2;--bs-purple: #613d7c;--bs-pink: #e83e8c;--bs-red: #ff0039;--bs-orange: #f0ad4e;--bs-yellow: #ff7518;--bs-green: #3fb618;--bs-teal: #20c997;--bs-cyan: #9954bb;--bs-white: #fff;--bs-gray: #6c757d;--bs-gray-dark: #373a3c;--bs-gray-100: #f8f9fa;--bs-gray-200: #e9ecef;--bs-gray-300: #dee2e6;--bs-gray-400: #ced4da;--bs-gray-500: #adb5bd;--bs-gray-600: #6c757d;--bs-gray-700: #495057;--bs-gray-800: #373a3c;--bs-gray-900: #212529;--bs-default: #373a3c;--bs-primary: #2780e3;--bs-secondary: #373a3c;--bs-success: #3fb618;--bs-info: #9954bb;--bs-warning: #ff7518;--bs-danger: #ff0039;--bs-light: #f8f9fa;--bs-dark: #373a3c;--bs-default-rgb: 55, 58, 60;--bs-primary-rgb: 39, 128, 227;--bs-secondary-rgb: 55, 58, 60;--bs-success-rgb: 63, 182, 24;--bs-info-rgb: 153, 84, 187;--bs-warning-rgb: 255, 117, 24;--bs-danger-rgb: 255, 0, 57;--bs-light-rgb: 248, 249, 250;--bs-dark-rgb: 55, 58, 60;--bs-white-rgb: 255, 255, 255;--bs-black-rgb: 0, 0, 0;--bs-body-color-rgb: 55, 58, 60;--bs-body-bg-rgb: 255, 255, 255;--bs-font-sans-serif: "Source Sans Pro", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol";--bs-font-monospace: SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;--bs-gradient: linear-gradient(180deg, rgba(255, 255, 255, 0.15), rgba(255, 255, 255, 0));--bs-root-font-size: 1em;--bs-body-font-family: var(--bs-font-sans-serif);--bs-body-font-size: 1rem;--bs-body-font-weight: 400;--bs-body-line-height: 1.7;--bs-body-color: #373a3c;--bs-body-bg: #fff}*,*::before,*::after{box-sizing:border-box}:root{font-size:var(--bs-root-font-size)}body{margin:0;font-family:var(--bs-body-font-family);font-size:var(--bs-body-font-size);font-weight:var(--bs-body-font-weight);line-height:var(--bs-body-line-height);color:var(--bs-body-color);text-align:var(--bs-body-text-align);background-color:var(--bs-body-bg);-webkit-text-size-adjust:100%;-webkit-tap-highlight-color:rgba(0,0,0,0)}hr{margin:1rem 0;color:inherit;background-color:currentColor;border:0;opacity:.25}hr:not([size]){height:1px}h6,.h6,h5,.h5,h4,.h4,h3,.h3,h2,.h2,h1,.h1{margin-top:0;margin-bottom:.5rem;font-weight:400;line-height:1.2}h1,.h1{font-size:calc(1.325rem + 0.9vw)}@media(min-width: 1200px){h1,.h1{font-size:2rem}}h2,.h2{font-size:calc(1.29rem + 0.48vw)}@media(min-width: 1200px){h2,.h2{font-size:1.65rem}}h3,.h3{font-size:calc(1.27rem + 0.24vw)}@media(min-width: 1200px){h3,.h3{font-size:1.45rem}}h4,.h4{font-size:1.25rem}h5,.h5{font-size:1.1rem}h6,.h6{font-size:1rem}p{margin-top:0;margin-bottom:1rem}abbr[title],abbr[data-bs-original-title]{text-decoration:underline dotted;-webkit-text-decoration:underline dotted;-moz-text-decoration:underline dotted;-ms-text-decoration:underline dotted;-o-text-decoration:underline dotted;cursor:help;text-decoration-skip-ink:none}address{margin-bottom:1rem;font-style:normal;line-height:inherit}ol,ul{padding-left:2rem}ol,ul,dl{margin-top:0;margin-bottom:1rem}ol ol,ul ul,ol ul,ul ol{margin-bottom:0}dt{font-weight:700}dd{margin-bottom:.5rem;margin-left:0}blockquote{margin:0 0 1rem;padding:.625rem 1.25rem;border-left:.25rem solid #e9ecef}blockquote p:last-child,blockquote ul:last-child,blockquote ol:last-child{margin-bottom:0}b,strong{font-weight:bolder}small,.small{font-size:0.875em}mark,.mark{padding:.2em;background-color:#fcf8e3}sub,sup{position:relative;font-size:0.75em;line-height:0;vertical-align:baseline}sub{bottom:-0.25em}sup{top:-0.5em}a{color:#2780e3;text-decoration:underline;-webkit-text-decoration:underline;-moz-text-decoration:underline;-ms-text-decoration:underline;-o-text-decoration:underline}a:hover{color:#1f66b6}a:not([href]):not([class]),a:not([href]):not([class]):hover{color:inherit;text-decoration:none}pre,code,kbd,samp{font-family:var(--bs-font-monospace);font-size:1em;direction:ltr /* rtl:ignore */;unicode-bidi:bidi-override}pre{display:block;margin-top:0;margin-bottom:1rem;overflow:auto;font-size:0.875em;color:#000;background-color:#f7f7f7;padding:.5rem;border:1px solid #dee2e6}pre code{background-color:rgba(0,0,0,0);font-size:inherit;color:inherit;word-break:normal}code{font-size:0.875em;color:#9753b8;background-color:#f7f7f7;padding:.125rem .25rem;word-wrap:break-word}a>code{color:inherit}kbd{padding:.4rem .4rem;font-size:0.875em;color:#fff;background-color:#212529}kbd kbd{padding:0;font-size:1em;font-weight:700}figure{margin:0 0 1rem}img,svg{vertical-align:middle}table{caption-side:bottom;border-collapse:collapse}caption{padding-top:.5rem;padding-bottom:.5rem;color:#6c757d;text-align:left}th{text-align:inherit;text-align:-webkit-match-parent}thead,tbody,tfoot,tr,td,th{border-color:inherit;border-style:solid;border-width:0}label{display:inline-block}button{border-radius:0}button:focus:not(:focus-visible){outline:0}input,button,select,optgroup,textarea{margin:0;font-family:inherit;font-size:inherit;line-height:inherit}button,select{text-transform:none}[role=button]{cursor:pointer}select{word-wrap:normal}select:disabled{opacity:1}[list]::-webkit-calendar-picker-indicator{display:none}button,[type=button],[type=reset],[type=submit]{-webkit-appearance:button}button:not(:disabled),[type=button]:not(:disabled),[type=reset]:not(:disabled),[type=submit]:not(:disabled){cursor:pointer}::-moz-focus-inner{padding:0;border-style:none}textarea{resize:vertical}fieldset{min-width:0;padding:0;margin:0;border:0}legend{float:left;width:100%;padding:0;margin-bottom:.5rem;font-size:calc(1.275rem + 0.3vw);line-height:inherit}@media(min-width: 1200px){legend{font-size:1.5rem}}legend+*{clear:left}::-webkit-datetime-edit-fields-wrapper,::-webkit-datetime-edit-text,::-webkit-datetime-edit-minute,::-webkit-datetime-edit-hour-field,::-webkit-datetime-edit-day-field,::-webkit-datetime-edit-month-field,::-webkit-datetime-edit-year-field{padding:0}::-webkit-inner-spin-button{height:auto}[type=search]{outline-offset:-2px;-webkit-appearance:textfield}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-color-swatch-wrapper{padding:0}::file-selector-button{font:inherit}::-webkit-file-upload-button{font:inherit;-webkit-appearance:button}output{display:inline-block}iframe{border:0}summary{display:list-item;cursor:pointer}progress{vertical-align:baseline}[hidden]{display:none !important}.lead{font-size:1.25rem;font-weight:300}.display-1{font-size:calc(1.625rem + 4.5vw);font-weight:300;line-height:1.2}@media(min-width: 1200px){.display-1{font-size:5rem}}.display-2{font-size:calc(1.575rem + 3.9vw);font-weight:300;line-height:1.2}@media(min-width: 1200px){.display-2{font-size:4.5rem}}.display-3{font-size:calc(1.525rem + 3.3vw);font-weight:300;line-height:1.2}@media(min-width: 1200px){.display-3{font-size:4rem}}.display-4{font-size:calc(1.475rem + 2.7vw);font-weight:300;line-height:1.2}@media(min-width: 1200px){.display-4{font-size:3.5rem}}.display-5{font-size:calc(1.425rem + 2.1vw);font-weight:300;line-height:1.2}@media(min-width: 1200px){.display-5{font-size:3rem}}.display-6{font-size:calc(1.375rem + 1.5vw);font-weight:300;line-height:1.2}@media(min-width: 1200px){.display-6{font-size:2.5rem}}.list-unstyled{padding-left:0;list-style:none}.list-inline{padding-left:0;list-style:none}.list-inline-item{display:inline-block}.list-inline-item:not(:last-child){margin-right:.5rem}.initialism{font-size:0.875em;text-transform:uppercase}.blockquote{margin-bottom:1rem;font-size:1.25rem}.blockquote>:last-child{margin-bottom:0}.blockquote-footer{margin-top:-1rem;margin-bottom:1rem;font-size:0.875em;color:#6c757d}.blockquote-footer::before{content:"ā€”Ā "}.img-fluid{max-width:100%;height:auto}.img-thumbnail{padding:.25rem;background-color:#fff;border:1px solid #dee2e6;max-width:100%;height:auto}.figure{display:inline-block}.figure-img{margin-bottom:.5rem;line-height:1}.figure-caption{font-size:0.875em;color:#6c757d}.grid{display:grid;grid-template-rows:repeat(var(--bs-rows, 1), 1fr);grid-template-columns:repeat(var(--bs-columns, 12), 1fr);gap:var(--bs-gap, 1.5rem)}.grid .g-col-1{grid-column:auto/span 1}.grid .g-col-2{grid-column:auto/span 2}.grid .g-col-3{grid-column:auto/span 3}.grid .g-col-4{grid-column:auto/span 4}.grid .g-col-5{grid-column:auto/span 5}.grid .g-col-6{grid-column:auto/span 6}.grid .g-col-7{grid-column:auto/span 7}.grid .g-col-8{grid-column:auto/span 8}.grid .g-col-9{grid-column:auto/span 9}.grid .g-col-10{grid-column:auto/span 10}.grid .g-col-11{grid-column:auto/span 11}.grid .g-col-12{grid-column:auto/span 12}.grid .g-start-1{grid-column-start:1}.grid .g-start-2{grid-column-start:2}.grid .g-start-3{grid-column-start:3}.grid .g-start-4{grid-column-start:4}.grid .g-start-5{grid-column-start:5}.grid .g-start-6{grid-column-start:6}.grid .g-start-7{grid-column-start:7}.grid .g-start-8{grid-column-start:8}.grid .g-start-9{grid-column-start:9}.grid .g-start-10{grid-column-start:10}.grid .g-start-11{grid-column-start:11}@media(min-width: 576px){.grid .g-col-sm-1{grid-column:auto/span 1}.grid .g-col-sm-2{grid-column:auto/span 2}.grid .g-col-sm-3{grid-column:auto/span 3}.grid .g-col-sm-4{grid-column:auto/span 4}.grid .g-col-sm-5{grid-column:auto/span 5}.grid .g-col-sm-6{grid-column:auto/span 6}.grid .g-col-sm-7{grid-column:auto/span 7}.grid .g-col-sm-8{grid-column:auto/span 8}.grid .g-col-sm-9{grid-column:auto/span 9}.grid .g-col-sm-10{grid-column:auto/span 10}.grid .g-col-sm-11{grid-column:auto/span 11}.grid .g-col-sm-12{grid-column:auto/span 12}.grid .g-start-sm-1{grid-column-start:1}.grid .g-start-sm-2{grid-column-start:2}.grid .g-start-sm-3{grid-column-start:3}.grid .g-start-sm-4{grid-column-start:4}.grid .g-start-sm-5{grid-column-start:5}.grid .g-start-sm-6{grid-column-start:6}.grid .g-start-sm-7{grid-column-start:7}.grid .g-start-sm-8{grid-column-start:8}.grid .g-start-sm-9{grid-column-start:9}.grid .g-start-sm-10{grid-column-start:10}.grid .g-start-sm-11{grid-column-start:11}}@media(min-width: 768px){.grid .g-col-md-1{grid-column:auto/span 1}.grid .g-col-md-2{grid-column:auto/span 2}.grid .g-col-md-3{grid-column:auto/span 3}.grid .g-col-md-4{grid-column:auto/span 4}.grid .g-col-md-5{grid-column:auto/span 5}.grid .g-col-md-6{grid-column:auto/span 6}.grid .g-col-md-7{grid-column:auto/span 7}.grid .g-col-md-8{grid-column:auto/span 8}.grid .g-col-md-9{grid-column:auto/span 9}.grid .g-col-md-10{grid-column:auto/span 10}.grid .g-col-md-11{grid-column:auto/span 11}.grid .g-col-md-12{grid-column:auto/span 12}.grid .g-start-md-1{grid-column-start:1}.grid .g-start-md-2{grid-column-start:2}.grid .g-start-md-3{grid-column-start:3}.grid .g-start-md-4{grid-column-start:4}.grid .g-start-md-5{grid-column-start:5}.grid .g-start-md-6{grid-column-start:6}.grid .g-start-md-7{grid-column-start:7}.grid .g-start-md-8{grid-column-start:8}.grid .g-start-md-9{grid-column-start:9}.grid .g-start-md-10{grid-column-start:10}.grid .g-start-md-11{grid-column-start:11}}@media(min-width: 992px){.grid .g-col-lg-1{grid-column:auto/span 1}.grid .g-col-lg-2{grid-column:auto/span 2}.grid .g-col-lg-3{grid-column:auto/span 3}.grid .g-col-lg-4{grid-column:auto/span 4}.grid .g-col-lg-5{grid-column:auto/span 5}.grid .g-col-lg-6{grid-column:auto/span 6}.grid .g-col-lg-7{grid-column:auto/span 7}.grid .g-col-lg-8{grid-column:auto/span 8}.grid .g-col-lg-9{grid-column:auto/span 9}.grid .g-col-lg-10{grid-column:auto/span 10}.grid .g-col-lg-11{grid-column:auto/span 11}.grid .g-col-lg-12{grid-column:auto/span 12}.grid .g-start-lg-1{grid-column-start:1}.grid .g-start-lg-2{grid-column-start:2}.grid .g-start-lg-3{grid-column-start:3}.grid .g-start-lg-4{grid-column-start:4}.grid .g-start-lg-5{grid-column-start:5}.grid .g-start-lg-6{grid-column-start:6}.grid .g-start-lg-7{grid-column-start:7}.grid .g-start-lg-8{grid-column-start:8}.grid .g-start-lg-9{grid-column-start:9}.grid .g-start-lg-10{grid-column-start:10}.grid .g-start-lg-11{grid-column-start:11}}@media(min-width: 1200px){.grid .g-col-xl-1{grid-column:auto/span 1}.grid .g-col-xl-2{grid-column:auto/span 2}.grid .g-col-xl-3{grid-column:auto/span 3}.grid .g-col-xl-4{grid-column:auto/span 4}.grid .g-col-xl-5{grid-column:auto/span 5}.grid .g-col-xl-6{grid-column:auto/span 6}.grid .g-col-xl-7{grid-column:auto/span 7}.grid .g-col-xl-8{grid-column:auto/span 8}.grid .g-col-xl-9{grid-column:auto/span 9}.grid .g-col-xl-10{grid-column:auto/span 10}.grid .g-col-xl-11{grid-column:auto/span 11}.grid .g-col-xl-12{grid-column:auto/span 12}.grid .g-start-xl-1{grid-column-start:1}.grid .g-start-xl-2{grid-column-start:2}.grid .g-start-xl-3{grid-column-start:3}.grid .g-start-xl-4{grid-column-start:4}.grid .g-start-xl-5{grid-column-start:5}.grid .g-start-xl-6{grid-column-start:6}.grid .g-start-xl-7{grid-column-start:7}.grid .g-start-xl-8{grid-column-start:8}.grid .g-start-xl-9{grid-column-start:9}.grid .g-start-xl-10{grid-column-start:10}.grid .g-start-xl-11{grid-column-start:11}}@media(min-width: 1400px){.grid .g-col-xxl-1{grid-column:auto/span 1}.grid .g-col-xxl-2{grid-column:auto/span 2}.grid .g-col-xxl-3{grid-column:auto/span 3}.grid .g-col-xxl-4{grid-column:auto/span 4}.grid .g-col-xxl-5{grid-column:auto/span 5}.grid .g-col-xxl-6{grid-column:auto/span 6}.grid .g-col-xxl-7{grid-column:auto/span 7}.grid .g-col-xxl-8{grid-column:auto/span 8}.grid .g-col-xxl-9{grid-column:auto/span 9}.grid .g-col-xxl-10{grid-column:auto/span 10}.grid .g-col-xxl-11{grid-column:auto/span 11}.grid .g-col-xxl-12{grid-column:auto/span 12}.grid .g-start-xxl-1{grid-column-start:1}.grid .g-start-xxl-2{grid-column-start:2}.grid .g-start-xxl-3{grid-column-start:3}.grid .g-start-xxl-4{grid-column-start:4}.grid .g-start-xxl-5{grid-column-start:5}.grid .g-start-xxl-6{grid-column-start:6}.grid .g-start-xxl-7{grid-column-start:7}.grid .g-start-xxl-8{grid-column-start:8}.grid .g-start-xxl-9{grid-column-start:9}.grid .g-start-xxl-10{grid-column-start:10}.grid .g-start-xxl-11{grid-column-start:11}}.table{--bs-table-bg: transparent;--bs-table-accent-bg: transparent;--bs-table-striped-color: #373a3c;--bs-table-striped-bg: rgba(0, 0, 0, 0.05);--bs-table-active-color: #373a3c;--bs-table-active-bg: rgba(0, 0, 0, 0.1);--bs-table-hover-color: #373a3c;--bs-table-hover-bg: rgba(0, 0, 0, 0.075);width:100%;margin-bottom:1rem;color:#373a3c;vertical-align:top;border-color:#dee2e6}.table>:not(caption)>*>*{padding:.5rem .5rem;background-color:var(--bs-table-bg);border-bottom-width:1px;box-shadow:inset 0 0 0 9999px var(--bs-table-accent-bg)}.table>tbody{vertical-align:inherit}.table>thead{vertical-align:bottom}.table>:not(:first-child){border-top:2px solid #b6babc}.caption-top{caption-side:top}.table-sm>:not(caption)>*>*{padding:.25rem .25rem}.table-bordered>:not(caption)>*{border-width:1px 0}.table-bordered>:not(caption)>*>*{border-width:0 1px}.table-borderless>:not(caption)>*>*{border-bottom-width:0}.table-borderless>:not(:first-child){border-top-width:0}.table-striped>tbody>tr:nth-of-type(odd)>*{--bs-table-accent-bg: var(--bs-table-striped-bg);color:var(--bs-table-striped-color)}.table-active{--bs-table-accent-bg: var(--bs-table-active-bg);color:var(--bs-table-active-color)}.table-hover>tbody>tr:hover>*{--bs-table-accent-bg: var(--bs-table-hover-bg);color:var(--bs-table-hover-color)}.table-primary{--bs-table-bg: #d4e6f9;--bs-table-striped-bg: #c9dbed;--bs-table-striped-color: #000;--bs-table-active-bg: #bfcfe0;--bs-table-active-color: #000;--bs-table-hover-bg: #c4d5e6;--bs-table-hover-color: #000;color:#000;border-color:#bfcfe0}.table-secondary{--bs-table-bg: #d7d8d8;--bs-table-striped-bg: #cccdcd;--bs-table-striped-color: #000;--bs-table-active-bg: #c2c2c2;--bs-table-active-color: #000;--bs-table-hover-bg: #c7c8c8;--bs-table-hover-color: #000;color:#000;border-color:#c2c2c2}.table-success{--bs-table-bg: #d9f0d1;--bs-table-striped-bg: #cee4c7;--bs-table-striped-color: #000;--bs-table-active-bg: #c3d8bc;--bs-table-active-color: #000;--bs-table-hover-bg: #c9dec1;--bs-table-hover-color: #000;color:#000;border-color:#c3d8bc}.table-info{--bs-table-bg: #ebddf1;--bs-table-striped-bg: #dfd2e5;--bs-table-striped-color: #000;--bs-table-active-bg: #d4c7d9;--bs-table-active-color: #000;--bs-table-hover-bg: #d9ccdf;--bs-table-hover-color: #000;color:#000;border-color:#d4c7d9}.table-warning{--bs-table-bg: #ffe3d1;--bs-table-striped-bg: #f2d8c7;--bs-table-striped-color: #000;--bs-table-active-bg: #e6ccbc;--bs-table-active-color: #000;--bs-table-hover-bg: #ecd2c1;--bs-table-hover-color: #000;color:#000;border-color:#e6ccbc}.table-danger{--bs-table-bg: #ffccd7;--bs-table-striped-bg: #f2c2cc;--bs-table-striped-color: #000;--bs-table-active-bg: #e6b8c2;--bs-table-active-color: #000;--bs-table-hover-bg: #ecbdc7;--bs-table-hover-color: #000;color:#000;border-color:#e6b8c2}.table-light{--bs-table-bg: #f8f9fa;--bs-table-striped-bg: #ecedee;--bs-table-striped-color: #000;--bs-table-active-bg: #dfe0e1;--bs-table-active-color: #000;--bs-table-hover-bg: #e5e6e7;--bs-table-hover-color: #000;color:#000;border-color:#dfe0e1}.table-dark{--bs-table-bg: #373a3c;--bs-table-striped-bg: #414446;--bs-table-striped-color: #fff;--bs-table-active-bg: #4b4e50;--bs-table-active-color: #fff;--bs-table-hover-bg: #46494b;--bs-table-hover-color: #fff;color:#fff;border-color:#4b4e50}.table-responsive{overflow-x:auto;-webkit-overflow-scrolling:touch}@media(max-width: 575.98px){.table-responsive-sm{overflow-x:auto;-webkit-overflow-scrolling:touch}}@media(max-width: 767.98px){.table-responsive-md{overflow-x:auto;-webkit-overflow-scrolling:touch}}@media(max-width: 991.98px){.table-responsive-lg{overflow-x:auto;-webkit-overflow-scrolling:touch}}@media(max-width: 1199.98px){.table-responsive-xl{overflow-x:auto;-webkit-overflow-scrolling:touch}}@media(max-width: 1399.98px){.table-responsive-xxl{overflow-x:auto;-webkit-overflow-scrolling:touch}}.form-label,.shiny-input-container .control-label{margin-bottom:.5rem}.col-form-label{padding-top:calc(0.375rem + 1px);padding-bottom:calc(0.375rem + 1px);margin-bottom:0;font-size:inherit;line-height:1.5}.col-form-label-lg{padding-top:calc(0.5rem + 1px);padding-bottom:calc(0.5rem + 1px);font-size:1.25rem}.col-form-label-sm{padding-top:calc(0.25rem + 1px);padding-bottom:calc(0.25rem + 1px);font-size:0.875rem}.form-text{margin-top:.25rem;font-size:0.875em;color:#6c757d}.form-control{display:block;width:100%;padding:.375rem .75rem;font-size:1rem;font-weight:400;line-height:1.5;color:#373a3c;background-color:#fff;background-clip:padding-box;border:1px solid #ced4da;appearance:none;-webkit-appearance:none;-moz-appearance:none;-ms-appearance:none;-o-appearance:none;border-radius:0;transition:border-color .15s ease-in-out,box-shadow .15s ease-in-out}@media(prefers-reduced-motion: reduce){.form-control{transition:none}}.form-control[type=file]{overflow:hidden}.form-control[type=file]:not(:disabled):not([readonly]){cursor:pointer}.form-control:focus{color:#373a3c;background-color:#fff;border-color:#93c0f1;outline:0;box-shadow:0 0 0 .25rem rgba(39,128,227,.25)}.form-control::-webkit-date-and-time-value{height:1.5em}.form-control::placeholder{color:#6c757d;opacity:1}.form-control:disabled,.form-control[readonly]{background-color:#e9ecef;opacity:1}.form-control::file-selector-button{padding:.375rem .75rem;margin:-0.375rem -0.75rem;margin-inline-end:.75rem;color:#373a3c;background-color:#e9ecef;pointer-events:none;border-color:inherit;border-style:solid;border-width:0;border-inline-end-width:1px;border-radius:0;transition:color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out}@media(prefers-reduced-motion: reduce){.form-control::file-selector-button{transition:none}}.form-control:hover:not(:disabled):not([readonly])::file-selector-button{background-color:#dde0e3}.form-control::-webkit-file-upload-button{padding:.375rem .75rem;margin:-0.375rem -0.75rem;margin-inline-end:.75rem;color:#373a3c;background-color:#e9ecef;pointer-events:none;border-color:inherit;border-style:solid;border-width:0;border-inline-end-width:1px;border-radius:0;transition:color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out}@media(prefers-reduced-motion: reduce){.form-control::-webkit-file-upload-button{transition:none}}.form-control:hover:not(:disabled):not([readonly])::-webkit-file-upload-button{background-color:#dde0e3}.form-control-plaintext{display:block;width:100%;padding:.375rem 0;margin-bottom:0;line-height:1.5;color:#373a3c;background-color:rgba(0,0,0,0);border:solid rgba(0,0,0,0);border-width:1px 0}.form-control-plaintext.form-control-sm,.form-control-plaintext.form-control-lg{padding-right:0;padding-left:0}.form-control-sm{min-height:calc(1.5em + 0.5rem + 2px);padding:.25rem .5rem;font-size:0.875rem}.form-control-sm::file-selector-button{padding:.25rem .5rem;margin:-0.25rem -0.5rem;margin-inline-end:.5rem}.form-control-sm::-webkit-file-upload-button{padding:.25rem .5rem;margin:-0.25rem -0.5rem;margin-inline-end:.5rem}.form-control-lg{min-height:calc(1.5em + 1rem + 2px);padding:.5rem 1rem;font-size:1.25rem}.form-control-lg::file-selector-button{padding:.5rem 1rem;margin:-0.5rem -1rem;margin-inline-end:1rem}.form-control-lg::-webkit-file-upload-button{padding:.5rem 1rem;margin:-0.5rem -1rem;margin-inline-end:1rem}textarea.form-control{min-height:calc(1.5em + 0.75rem + 2px)}textarea.form-control-sm{min-height:calc(1.5em + 0.5rem + 2px)}textarea.form-control-lg{min-height:calc(1.5em + 1rem + 2px)}.form-control-color{width:3rem;height:auto;padding:.375rem}.form-control-color:not(:disabled):not([readonly]){cursor:pointer}.form-control-color::-moz-color-swatch{height:1.5em}.form-control-color::-webkit-color-swatch{height:1.5em}.form-select{display:block;width:100%;padding:.375rem 2.25rem .375rem .75rem;-moz-padding-start:calc(0.75rem - 3px);font-size:1rem;font-weight:400;line-height:1.5;color:#373a3c;background-color:#fff;background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3e%3cpath fill='none' stroke='%23373a3c' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M2 5l6 6 6-6'/%3e%3c/svg%3e");background-repeat:no-repeat;background-position:right .75rem center;background-size:16px 12px;border:1px solid #ced4da;border-radius:0;transition:border-color .15s ease-in-out,box-shadow .15s ease-in-out;appearance:none;-webkit-appearance:none;-moz-appearance:none;-ms-appearance:none;-o-appearance:none}@media(prefers-reduced-motion: reduce){.form-select{transition:none}}.form-select:focus{border-color:#93c0f1;outline:0;box-shadow:0 0 0 .25rem rgba(39,128,227,.25)}.form-select[multiple],.form-select[size]:not([size="1"]){padding-right:.75rem;background-image:none}.form-select:disabled{background-color:#e9ecef}.form-select:-moz-focusring{color:rgba(0,0,0,0);text-shadow:0 0 0 #373a3c}.form-select-sm{padding-top:.25rem;padding-bottom:.25rem;padding-left:.5rem;font-size:0.875rem}.form-select-lg{padding-top:.5rem;padding-bottom:.5rem;padding-left:1rem;font-size:1.25rem}.form-check,.shiny-input-container .checkbox,.shiny-input-container .radio{display:block;min-height:1.5rem;padding-left:0;margin-bottom:.125rem}.form-check .form-check-input,.form-check .shiny-input-container .checkbox input,.form-check .shiny-input-container .radio input,.shiny-input-container .checkbox .form-check-input,.shiny-input-container .checkbox .shiny-input-container .checkbox input,.shiny-input-container .checkbox .shiny-input-container .radio input,.shiny-input-container .radio .form-check-input,.shiny-input-container .radio .shiny-input-container .checkbox input,.shiny-input-container .radio .shiny-input-container .radio input{float:left;margin-left:0}.form-check-input,.shiny-input-container .checkbox input,.shiny-input-container .checkbox-inline input,.shiny-input-container .radio input,.shiny-input-container .radio-inline input{width:1em;height:1em;margin-top:.35em;vertical-align:top;background-color:#fff;background-repeat:no-repeat;background-position:center;background-size:contain;border:1px solid rgba(0,0,0,.25);appearance:none;-webkit-appearance:none;-moz-appearance:none;-ms-appearance:none;-o-appearance:none;color-adjust:exact;-webkit-print-color-adjust:exact}.form-check-input[type=radio],.shiny-input-container .checkbox input[type=radio],.shiny-input-container .checkbox-inline input[type=radio],.shiny-input-container .radio input[type=radio],.shiny-input-container .radio-inline input[type=radio]{border-radius:50%}.form-check-input:active,.shiny-input-container .checkbox input:active,.shiny-input-container .checkbox-inline input:active,.shiny-input-container .radio input:active,.shiny-input-container .radio-inline input:active{filter:brightness(90%)}.form-check-input:focus,.shiny-input-container .checkbox input:focus,.shiny-input-container .checkbox-inline input:focus,.shiny-input-container .radio input:focus,.shiny-input-container .radio-inline input:focus{border-color:#93c0f1;outline:0;box-shadow:0 0 0 .25rem rgba(39,128,227,.25)}.form-check-input:checked,.shiny-input-container .checkbox input:checked,.shiny-input-container .checkbox-inline input:checked,.shiny-input-container .radio input:checked,.shiny-input-container .radio-inline input:checked{background-color:#2780e3;border-color:#2780e3}.form-check-input:checked[type=checkbox],.shiny-input-container .checkbox input:checked[type=checkbox],.shiny-input-container .checkbox-inline input:checked[type=checkbox],.shiny-input-container .radio input:checked[type=checkbox],.shiny-input-container .radio-inline input:checked[type=checkbox]{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20'%3e%3cpath fill='none' stroke='%23fff' stroke-linecap='round' stroke-linejoin='round' stroke-width='3' d='M6 10l3 3l6-6'/%3e%3c/svg%3e")}.form-check-input:checked[type=radio],.shiny-input-container .checkbox input:checked[type=radio],.shiny-input-container .checkbox-inline input:checked[type=radio],.shiny-input-container .radio input:checked[type=radio],.shiny-input-container .radio-inline input:checked[type=radio]{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='2' fill='%23fff'/%3e%3c/svg%3e")}.form-check-input[type=checkbox]:indeterminate,.shiny-input-container .checkbox input[type=checkbox]:indeterminate,.shiny-input-container .checkbox-inline input[type=checkbox]:indeterminate,.shiny-input-container .radio input[type=checkbox]:indeterminate,.shiny-input-container .radio-inline input[type=checkbox]:indeterminate{background-color:#2780e3;border-color:#2780e3;background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20'%3e%3cpath fill='none' stroke='%23fff' stroke-linecap='round' stroke-linejoin='round' stroke-width='3' d='M6 10h8'/%3e%3c/svg%3e")}.form-check-input:disabled,.shiny-input-container .checkbox input:disabled,.shiny-input-container .checkbox-inline input:disabled,.shiny-input-container .radio input:disabled,.shiny-input-container .radio-inline input:disabled{pointer-events:none;filter:none;opacity:.5}.form-check-input[disabled]~.form-check-label,.form-check-input[disabled]~span,.form-check-input:disabled~.form-check-label,.form-check-input:disabled~span,.shiny-input-container .checkbox input[disabled]~.form-check-label,.shiny-input-container .checkbox input[disabled]~span,.shiny-input-container .checkbox input:disabled~.form-check-label,.shiny-input-container .checkbox input:disabled~span,.shiny-input-container .checkbox-inline input[disabled]~.form-check-label,.shiny-input-container .checkbox-inline input[disabled]~span,.shiny-input-container .checkbox-inline input:disabled~.form-check-label,.shiny-input-container .checkbox-inline input:disabled~span,.shiny-input-container .radio input[disabled]~.form-check-label,.shiny-input-container .radio input[disabled]~span,.shiny-input-container .radio input:disabled~.form-check-label,.shiny-input-container .radio input:disabled~span,.shiny-input-container .radio-inline input[disabled]~.form-check-label,.shiny-input-container .radio-inline input[disabled]~span,.shiny-input-container .radio-inline input:disabled~.form-check-label,.shiny-input-container .radio-inline input:disabled~span{opacity:.5}.form-check-label,.shiny-input-container .checkbox label,.shiny-input-container .checkbox-inline label,.shiny-input-container .radio label,.shiny-input-container .radio-inline label{cursor:pointer}.form-switch{padding-left:2.5em}.form-switch .form-check-input{width:2em;margin-left:-2.5em;background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='3' fill='rgba%280, 0, 0, 0.25%29'/%3e%3c/svg%3e");background-position:left center;transition:background-position .15s ease-in-out}@media(prefers-reduced-motion: reduce){.form-switch .form-check-input{transition:none}}.form-switch .form-check-input:focus{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='3' fill='%2393c0f1'/%3e%3c/svg%3e")}.form-switch .form-check-input:checked{background-position:right center;background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='3' fill='%23fff'/%3e%3c/svg%3e")}.form-check-inline,.shiny-input-container .checkbox-inline,.shiny-input-container .radio-inline{display:inline-block;margin-right:1rem}.btn-check{position:absolute;clip:rect(0, 0, 0, 0);pointer-events:none}.btn-check[disabled]+.btn,.btn-check:disabled+.btn{pointer-events:none;filter:none;opacity:.65}.form-range{width:100%;height:1.5rem;padding:0;background-color:rgba(0,0,0,0);appearance:none;-webkit-appearance:none;-moz-appearance:none;-ms-appearance:none;-o-appearance:none}.form-range:focus{outline:0}.form-range:focus::-webkit-slider-thumb{box-shadow:0 0 0 1px #fff,0 0 0 .25rem rgba(39,128,227,.25)}.form-range:focus::-moz-range-thumb{box-shadow:0 0 0 1px #fff,0 0 0 .25rem rgba(39,128,227,.25)}.form-range::-moz-focus-outer{border:0}.form-range::-webkit-slider-thumb{width:1rem;height:1rem;margin-top:-0.25rem;background-color:#2780e3;border:0;transition:background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;appearance:none;-webkit-appearance:none;-moz-appearance:none;-ms-appearance:none;-o-appearance:none}@media(prefers-reduced-motion: reduce){.form-range::-webkit-slider-thumb{transition:none}}.form-range::-webkit-slider-thumb:active{background-color:#bed9f7}.form-range::-webkit-slider-runnable-track{width:100%;height:.5rem;color:rgba(0,0,0,0);cursor:pointer;background-color:#dee2e6;border-color:rgba(0,0,0,0)}.form-range::-moz-range-thumb{width:1rem;height:1rem;background-color:#2780e3;border:0;transition:background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;appearance:none;-webkit-appearance:none;-moz-appearance:none;-ms-appearance:none;-o-appearance:none}@media(prefers-reduced-motion: reduce){.form-range::-moz-range-thumb{transition:none}}.form-range::-moz-range-thumb:active{background-color:#bed9f7}.form-range::-moz-range-track{width:100%;height:.5rem;color:rgba(0,0,0,0);cursor:pointer;background-color:#dee2e6;border-color:rgba(0,0,0,0)}.form-range:disabled{pointer-events:none}.form-range:disabled::-webkit-slider-thumb{background-color:#adb5bd}.form-range:disabled::-moz-range-thumb{background-color:#adb5bd}.form-floating{position:relative}.form-floating>.form-control,.form-floating>.form-select{height:calc(3.5rem + 2px);line-height:1.25}.form-floating>label{position:absolute;top:0;left:0;height:100%;padding:1rem .75rem;pointer-events:none;border:1px solid rgba(0,0,0,0);transform-origin:0 0;transition:opacity .1s ease-in-out,transform .1s ease-in-out}@media(prefers-reduced-motion: reduce){.form-floating>label{transition:none}}.form-floating>.form-control{padding:1rem .75rem}.form-floating>.form-control::placeholder{color:rgba(0,0,0,0)}.form-floating>.form-control:focus,.form-floating>.form-control:not(:placeholder-shown){padding-top:1.625rem;padding-bottom:.625rem}.form-floating>.form-control:-webkit-autofill{padding-top:1.625rem;padding-bottom:.625rem}.form-floating>.form-select{padding-top:1.625rem;padding-bottom:.625rem}.form-floating>.form-control:focus~label,.form-floating>.form-control:not(:placeholder-shown)~label,.form-floating>.form-select~label{opacity:.65;transform:scale(0.85) translateY(-0.5rem) translateX(0.15rem)}.form-floating>.form-control:-webkit-autofill~label{opacity:.65;transform:scale(0.85) translateY(-0.5rem) translateX(0.15rem)}.input-group{position:relative;display:flex;display:-webkit-flex;flex-wrap:wrap;-webkit-flex-wrap:wrap;align-items:stretch;-webkit-align-items:stretch;width:100%}.input-group>.form-control,.input-group>.form-select{position:relative;flex:1 1 auto;-webkit-flex:1 1 auto;width:1%;min-width:0}.input-group>.form-control:focus,.input-group>.form-select:focus{z-index:3}.input-group .btn{position:relative;z-index:2}.input-group .btn:focus{z-index:3}.input-group-text{display:flex;display:-webkit-flex;align-items:center;-webkit-align-items:center;padding:.375rem .75rem;font-size:1rem;font-weight:400;line-height:1.5;color:#373a3c;text-align:center;white-space:nowrap;background-color:#e9ecef;border:1px solid #ced4da}.input-group-lg>.form-control,.input-group-lg>.form-select,.input-group-lg>.input-group-text,.input-group-lg>.btn{padding:.5rem 1rem;font-size:1.25rem}.input-group-sm>.form-control,.input-group-sm>.form-select,.input-group-sm>.input-group-text,.input-group-sm>.btn{padding:.25rem .5rem;font-size:0.875rem}.input-group-lg>.form-select,.input-group-sm>.form-select{padding-right:3rem}.input-group>:not(:first-child):not(.dropdown-menu):not(.valid-tooltip):not(.valid-feedback):not(.invalid-tooltip):not(.invalid-feedback){margin-left:-1px}.valid-feedback{display:none;width:100%;margin-top:.25rem;font-size:0.875em;color:#3fb618}.valid-tooltip{position:absolute;top:100%;z-index:5;display:none;max-width:100%;padding:.25rem .5rem;margin-top:.1rem;font-size:0.875rem;color:#fff;background-color:rgba(63,182,24,.9)}.was-validated :valid~.valid-feedback,.was-validated :valid~.valid-tooltip,.is-valid~.valid-feedback,.is-valid~.valid-tooltip{display:block}.was-validated .form-control:valid,.form-control.is-valid{border-color:#3fb618;padding-right:calc(1.5em + 0.75rem);background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 8 8'%3e%3cpath fill='%233fb618' d='M2.3 6.73L.6 4.53c-.4-1.04.46-1.4 1.1-.8l1.1 1.4 3.4-3.8c.6-.63 1.6-.27 1.2.7l-4 4.6c-.43.5-.8.4-1.1.1z'/%3e%3c/svg%3e");background-repeat:no-repeat;background-position:right calc(0.375em + 0.1875rem) center;background-size:calc(0.75em + 0.375rem) calc(0.75em + 0.375rem)}.was-validated .form-control:valid:focus,.form-control.is-valid:focus{border-color:#3fb618;box-shadow:0 0 0 .25rem rgba(63,182,24,.25)}.was-validated textarea.form-control:valid,textarea.form-control.is-valid{padding-right:calc(1.5em + 0.75rem);background-position:top calc(0.375em + 0.1875rem) right calc(0.375em + 0.1875rem)}.was-validated .form-select:valid,.form-select.is-valid{border-color:#3fb618}.was-validated .form-select:valid:not([multiple]):not([size]),.was-validated .form-select:valid:not([multiple])[size="1"],.form-select.is-valid:not([multiple]):not([size]),.form-select.is-valid:not([multiple])[size="1"]{padding-right:4.125rem;background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3e%3cpath fill='none' stroke='%23373a3c' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M2 5l6 6 6-6'/%3e%3c/svg%3e"),url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 8 8'%3e%3cpath fill='%233fb618' d='M2.3 6.73L.6 4.53c-.4-1.04.46-1.4 1.1-.8l1.1 1.4 3.4-3.8c.6-.63 1.6-.27 1.2.7l-4 4.6c-.43.5-.8.4-1.1.1z'/%3e%3c/svg%3e");background-position:right .75rem center,center right 2.25rem;background-size:16px 12px,calc(0.75em + 0.375rem) calc(0.75em + 0.375rem)}.was-validated .form-select:valid:focus,.form-select.is-valid:focus{border-color:#3fb618;box-shadow:0 0 0 .25rem rgba(63,182,24,.25)}.was-validated .form-check-input:valid,.form-check-input.is-valid{border-color:#3fb618}.was-validated .form-check-input:valid:checked,.form-check-input.is-valid:checked{background-color:#3fb618}.was-validated .form-check-input:valid:focus,.form-check-input.is-valid:focus{box-shadow:0 0 0 .25rem rgba(63,182,24,.25)}.was-validated .form-check-input:valid~.form-check-label,.form-check-input.is-valid~.form-check-label{color:#3fb618}.form-check-inline .form-check-input~.valid-feedback{margin-left:.5em}.was-validated .input-group .form-control:valid,.input-group .form-control.is-valid,.was-validated .input-group .form-select:valid,.input-group .form-select.is-valid{z-index:1}.was-validated .input-group .form-control:valid:focus,.input-group .form-control.is-valid:focus,.was-validated .input-group .form-select:valid:focus,.input-group .form-select.is-valid:focus{z-index:3}.invalid-feedback{display:none;width:100%;margin-top:.25rem;font-size:0.875em;color:#ff0039}.invalid-tooltip{position:absolute;top:100%;z-index:5;display:none;max-width:100%;padding:.25rem .5rem;margin-top:.1rem;font-size:0.875rem;color:#fff;background-color:rgba(255,0,57,.9)}.was-validated :invalid~.invalid-feedback,.was-validated :invalid~.invalid-tooltip,.is-invalid~.invalid-feedback,.is-invalid~.invalid-tooltip{display:block}.was-validated .form-control:invalid,.form-control.is-invalid{border-color:#ff0039;padding-right:calc(1.5em + 0.75rem);background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 12' width='12' height='12' fill='none' stroke='%23ff0039'%3e%3ccircle cx='6' cy='6' r='4.5'/%3e%3cpath stroke-linejoin='round' d='M5.8 3.6h.4L6 6.5z'/%3e%3ccircle cx='6' cy='8.2' r='.6' fill='%23ff0039' stroke='none'/%3e%3c/svg%3e");background-repeat:no-repeat;background-position:right calc(0.375em + 0.1875rem) center;background-size:calc(0.75em + 0.375rem) calc(0.75em + 0.375rem)}.was-validated .form-control:invalid:focus,.form-control.is-invalid:focus{border-color:#ff0039;box-shadow:0 0 0 .25rem rgba(255,0,57,.25)}.was-validated textarea.form-control:invalid,textarea.form-control.is-invalid{padding-right:calc(1.5em + 0.75rem);background-position:top calc(0.375em + 0.1875rem) right calc(0.375em + 0.1875rem)}.was-validated .form-select:invalid,.form-select.is-invalid{border-color:#ff0039}.was-validated .form-select:invalid:not([multiple]):not([size]),.was-validated .form-select:invalid:not([multiple])[size="1"],.form-select.is-invalid:not([multiple]):not([size]),.form-select.is-invalid:not([multiple])[size="1"]{padding-right:4.125rem;background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3e%3cpath fill='none' stroke='%23373a3c' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M2 5l6 6 6-6'/%3e%3c/svg%3e"),url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 12' width='12' height='12' fill='none' stroke='%23ff0039'%3e%3ccircle cx='6' cy='6' r='4.5'/%3e%3cpath stroke-linejoin='round' d='M5.8 3.6h.4L6 6.5z'/%3e%3ccircle cx='6' cy='8.2' r='.6' fill='%23ff0039' stroke='none'/%3e%3c/svg%3e");background-position:right .75rem center,center right 2.25rem;background-size:16px 12px,calc(0.75em + 0.375rem) calc(0.75em + 0.375rem)}.was-validated .form-select:invalid:focus,.form-select.is-invalid:focus{border-color:#ff0039;box-shadow:0 0 0 .25rem rgba(255,0,57,.25)}.was-validated .form-check-input:invalid,.form-check-input.is-invalid{border-color:#ff0039}.was-validated .form-check-input:invalid:checked,.form-check-input.is-invalid:checked{background-color:#ff0039}.was-validated .form-check-input:invalid:focus,.form-check-input.is-invalid:focus{box-shadow:0 0 0 .25rem rgba(255,0,57,.25)}.was-validated .form-check-input:invalid~.form-check-label,.form-check-input.is-invalid~.form-check-label{color:#ff0039}.form-check-inline .form-check-input~.invalid-feedback{margin-left:.5em}.was-validated .input-group .form-control:invalid,.input-group .form-control.is-invalid,.was-validated .input-group .form-select:invalid,.input-group .form-select.is-invalid{z-index:2}.was-validated .input-group .form-control:invalid:focus,.input-group .form-control.is-invalid:focus,.was-validated .input-group .form-select:invalid:focus,.input-group .form-select.is-invalid:focus{z-index:3}.btn{display:inline-block;font-weight:400;line-height:1.5;color:#373a3c;text-align:center;text-decoration:none;-webkit-text-decoration:none;-moz-text-decoration:none;-ms-text-decoration:none;-o-text-decoration:none;vertical-align:middle;cursor:pointer;user-select:none;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;-o-user-select:none;background-color:rgba(0,0,0,0);border:1px solid rgba(0,0,0,0);padding:.375rem .75rem;font-size:1rem;border-radius:0;transition:color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out}@media(prefers-reduced-motion: reduce){.btn{transition:none}}.btn:hover{color:#373a3c}.btn-check:focus+.btn,.btn:focus{outline:0;box-shadow:0 0 0 .25rem rgba(39,128,227,.25)}.btn:disabled,.btn.disabled,fieldset:disabled .btn{pointer-events:none;opacity:.65}.btn-default{color:#fff;background-color:#373a3c;border-color:#373a3c}.btn-default:hover{color:#fff;background-color:#2f3133;border-color:#2c2e30}.btn-check:focus+.btn-default,.btn-default:focus{color:#fff;background-color:#2f3133;border-color:#2c2e30;box-shadow:0 0 0 .25rem rgba(85,88,89,.5)}.btn-check:checked+.btn-default,.btn-check:active+.btn-default,.btn-default:active,.btn-default.active,.show>.btn-default.dropdown-toggle{color:#fff;background-color:#2c2e30;border-color:#292c2d}.btn-check:checked+.btn-default:focus,.btn-check:active+.btn-default:focus,.btn-default:active:focus,.btn-default.active:focus,.show>.btn-default.dropdown-toggle:focus{box-shadow:0 0 0 .25rem rgba(85,88,89,.5)}.btn-default:disabled,.btn-default.disabled{color:#fff;background-color:#373a3c;border-color:#373a3c}.btn-primary{color:#fff;background-color:#2780e3;border-color:#2780e3}.btn-primary:hover{color:#fff;background-color:#216dc1;border-color:#1f66b6}.btn-check:focus+.btn-primary,.btn-primary:focus{color:#fff;background-color:#216dc1;border-color:#1f66b6;box-shadow:0 0 0 .25rem rgba(71,147,231,.5)}.btn-check:checked+.btn-primary,.btn-check:active+.btn-primary,.btn-primary:active,.btn-primary.active,.show>.btn-primary.dropdown-toggle{color:#fff;background-color:#1f66b6;border-color:#1d60aa}.btn-check:checked+.btn-primary:focus,.btn-check:active+.btn-primary:focus,.btn-primary:active:focus,.btn-primary.active:focus,.show>.btn-primary.dropdown-toggle:focus{box-shadow:0 0 0 .25rem rgba(71,147,231,.5)}.btn-primary:disabled,.btn-primary.disabled{color:#fff;background-color:#2780e3;border-color:#2780e3}.btn-secondary{color:#fff;background-color:#373a3c;border-color:#373a3c}.btn-secondary:hover{color:#fff;background-color:#2f3133;border-color:#2c2e30}.btn-check:focus+.btn-secondary,.btn-secondary:focus{color:#fff;background-color:#2f3133;border-color:#2c2e30;box-shadow:0 0 0 .25rem rgba(85,88,89,.5)}.btn-check:checked+.btn-secondary,.btn-check:active+.btn-secondary,.btn-secondary:active,.btn-secondary.active,.show>.btn-secondary.dropdown-toggle{color:#fff;background-color:#2c2e30;border-color:#292c2d}.btn-check:checked+.btn-secondary:focus,.btn-check:active+.btn-secondary:focus,.btn-secondary:active:focus,.btn-secondary.active:focus,.show>.btn-secondary.dropdown-toggle:focus{box-shadow:0 0 0 .25rem rgba(85,88,89,.5)}.btn-secondary:disabled,.btn-secondary.disabled{color:#fff;background-color:#373a3c;border-color:#373a3c}.btn-success{color:#fff;background-color:#3fb618;border-color:#3fb618}.btn-success:hover{color:#fff;background-color:#369b14;border-color:#329213}.btn-check:focus+.btn-success,.btn-success:focus{color:#fff;background-color:#369b14;border-color:#329213;box-shadow:0 0 0 .25rem rgba(92,193,59,.5)}.btn-check:checked+.btn-success,.btn-check:active+.btn-success,.btn-success:active,.btn-success.active,.show>.btn-success.dropdown-toggle{color:#fff;background-color:#329213;border-color:#2f8912}.btn-check:checked+.btn-success:focus,.btn-check:active+.btn-success:focus,.btn-success:active:focus,.btn-success.active:focus,.show>.btn-success.dropdown-toggle:focus{box-shadow:0 0 0 .25rem rgba(92,193,59,.5)}.btn-success:disabled,.btn-success.disabled{color:#fff;background-color:#3fb618;border-color:#3fb618}.btn-info{color:#fff;background-color:#9954bb;border-color:#9954bb}.btn-info:hover{color:#fff;background-color:#82479f;border-color:#7a4396}.btn-check:focus+.btn-info,.btn-info:focus{color:#fff;background-color:#82479f;border-color:#7a4396;box-shadow:0 0 0 .25rem rgba(168,110,197,.5)}.btn-check:checked+.btn-info,.btn-check:active+.btn-info,.btn-info:active,.btn-info.active,.show>.btn-info.dropdown-toggle{color:#fff;background-color:#7a4396;border-color:#733f8c}.btn-check:checked+.btn-info:focus,.btn-check:active+.btn-info:focus,.btn-info:active:focus,.btn-info.active:focus,.show>.btn-info.dropdown-toggle:focus{box-shadow:0 0 0 .25rem rgba(168,110,197,.5)}.btn-info:disabled,.btn-info.disabled{color:#fff;background-color:#9954bb;border-color:#9954bb}.btn-warning{color:#fff;background-color:#ff7518;border-color:#ff7518}.btn-warning:hover{color:#fff;background-color:#d96314;border-color:#cc5e13}.btn-check:focus+.btn-warning,.btn-warning:focus{color:#fff;background-color:#d96314;border-color:#cc5e13;box-shadow:0 0 0 .25rem rgba(255,138,59,.5)}.btn-check:checked+.btn-warning,.btn-check:active+.btn-warning,.btn-warning:active,.btn-warning.active,.show>.btn-warning.dropdown-toggle{color:#fff;background-color:#cc5e13;border-color:#bf5812}.btn-check:checked+.btn-warning:focus,.btn-check:active+.btn-warning:focus,.btn-warning:active:focus,.btn-warning.active:focus,.show>.btn-warning.dropdown-toggle:focus{box-shadow:0 0 0 .25rem rgba(255,138,59,.5)}.btn-warning:disabled,.btn-warning.disabled{color:#fff;background-color:#ff7518;border-color:#ff7518}.btn-danger{color:#fff;background-color:#ff0039;border-color:#ff0039}.btn-danger:hover{color:#fff;background-color:#d90030;border-color:#cc002e}.btn-check:focus+.btn-danger,.btn-danger:focus{color:#fff;background-color:#d90030;border-color:#cc002e;box-shadow:0 0 0 .25rem rgba(255,38,87,.5)}.btn-check:checked+.btn-danger,.btn-check:active+.btn-danger,.btn-danger:active,.btn-danger.active,.show>.btn-danger.dropdown-toggle{color:#fff;background-color:#cc002e;border-color:#bf002b}.btn-check:checked+.btn-danger:focus,.btn-check:active+.btn-danger:focus,.btn-danger:active:focus,.btn-danger.active:focus,.show>.btn-danger.dropdown-toggle:focus{box-shadow:0 0 0 .25rem rgba(255,38,87,.5)}.btn-danger:disabled,.btn-danger.disabled{color:#fff;background-color:#ff0039;border-color:#ff0039}.btn-light{color:#000;background-color:#f8f9fa;border-color:#f8f9fa}.btn-light:hover{color:#000;background-color:#f9fafb;border-color:#f9fafb}.btn-check:focus+.btn-light,.btn-light:focus{color:#000;background-color:#f9fafb;border-color:#f9fafb;box-shadow:0 0 0 .25rem rgba(211,212,213,.5)}.btn-check:checked+.btn-light,.btn-check:active+.btn-light,.btn-light:active,.btn-light.active,.show>.btn-light.dropdown-toggle{color:#000;background-color:#f9fafb;border-color:#f9fafb}.btn-check:checked+.btn-light:focus,.btn-check:active+.btn-light:focus,.btn-light:active:focus,.btn-light.active:focus,.show>.btn-light.dropdown-toggle:focus{box-shadow:0 0 0 .25rem rgba(211,212,213,.5)}.btn-light:disabled,.btn-light.disabled{color:#000;background-color:#f8f9fa;border-color:#f8f9fa}.btn-dark{color:#fff;background-color:#373a3c;border-color:#373a3c}.btn-dark:hover{color:#fff;background-color:#2f3133;border-color:#2c2e30}.btn-check:focus+.btn-dark,.btn-dark:focus{color:#fff;background-color:#2f3133;border-color:#2c2e30;box-shadow:0 0 0 .25rem rgba(85,88,89,.5)}.btn-check:checked+.btn-dark,.btn-check:active+.btn-dark,.btn-dark:active,.btn-dark.active,.show>.btn-dark.dropdown-toggle{color:#fff;background-color:#2c2e30;border-color:#292c2d}.btn-check:checked+.btn-dark:focus,.btn-check:active+.btn-dark:focus,.btn-dark:active:focus,.btn-dark.active:focus,.show>.btn-dark.dropdown-toggle:focus{box-shadow:0 0 0 .25rem rgba(85,88,89,.5)}.btn-dark:disabled,.btn-dark.disabled{color:#fff;background-color:#373a3c;border-color:#373a3c}.btn-outline-default{color:#373a3c;border-color:#373a3c;background-color:rgba(0,0,0,0)}.btn-outline-default:hover{color:#fff;background-color:#373a3c;border-color:#373a3c}.btn-check:focus+.btn-outline-default,.btn-outline-default:focus{box-shadow:0 0 0 .25rem rgba(55,58,60,.5)}.btn-check:checked+.btn-outline-default,.btn-check:active+.btn-outline-default,.btn-outline-default:active,.btn-outline-default.active,.btn-outline-default.dropdown-toggle.show{color:#fff;background-color:#373a3c;border-color:#373a3c}.btn-check:checked+.btn-outline-default:focus,.btn-check:active+.btn-outline-default:focus,.btn-outline-default:active:focus,.btn-outline-default.active:focus,.btn-outline-default.dropdown-toggle.show:focus{box-shadow:0 0 0 .25rem rgba(55,58,60,.5)}.btn-outline-default:disabled,.btn-outline-default.disabled{color:#373a3c;background-color:rgba(0,0,0,0)}.btn-outline-primary{color:#2780e3;border-color:#2780e3;background-color:rgba(0,0,0,0)}.btn-outline-primary:hover{color:#fff;background-color:#2780e3;border-color:#2780e3}.btn-check:focus+.btn-outline-primary,.btn-outline-primary:focus{box-shadow:0 0 0 .25rem rgba(39,128,227,.5)}.btn-check:checked+.btn-outline-primary,.btn-check:active+.btn-outline-primary,.btn-outline-primary:active,.btn-outline-primary.active,.btn-outline-primary.dropdown-toggle.show{color:#fff;background-color:#2780e3;border-color:#2780e3}.btn-check:checked+.btn-outline-primary:focus,.btn-check:active+.btn-outline-primary:focus,.btn-outline-primary:active:focus,.btn-outline-primary.active:focus,.btn-outline-primary.dropdown-toggle.show:focus{box-shadow:0 0 0 .25rem rgba(39,128,227,.5)}.btn-outline-primary:disabled,.btn-outline-primary.disabled{color:#2780e3;background-color:rgba(0,0,0,0)}.btn-outline-secondary{color:#373a3c;border-color:#373a3c;background-color:rgba(0,0,0,0)}.btn-outline-secondary:hover{color:#fff;background-color:#373a3c;border-color:#373a3c}.btn-check:focus+.btn-outline-secondary,.btn-outline-secondary:focus{box-shadow:0 0 0 .25rem rgba(55,58,60,.5)}.btn-check:checked+.btn-outline-secondary,.btn-check:active+.btn-outline-secondary,.btn-outline-secondary:active,.btn-outline-secondary.active,.btn-outline-secondary.dropdown-toggle.show{color:#fff;background-color:#373a3c;border-color:#373a3c}.btn-check:checked+.btn-outline-secondary:focus,.btn-check:active+.btn-outline-secondary:focus,.btn-outline-secondary:active:focus,.btn-outline-secondary.active:focus,.btn-outline-secondary.dropdown-toggle.show:focus{box-shadow:0 0 0 .25rem rgba(55,58,60,.5)}.btn-outline-secondary:disabled,.btn-outline-secondary.disabled{color:#373a3c;background-color:rgba(0,0,0,0)}.btn-outline-success{color:#3fb618;border-color:#3fb618;background-color:rgba(0,0,0,0)}.btn-outline-success:hover{color:#fff;background-color:#3fb618;border-color:#3fb618}.btn-check:focus+.btn-outline-success,.btn-outline-success:focus{box-shadow:0 0 0 .25rem rgba(63,182,24,.5)}.btn-check:checked+.btn-outline-success,.btn-check:active+.btn-outline-success,.btn-outline-success:active,.btn-outline-success.active,.btn-outline-success.dropdown-toggle.show{color:#fff;background-color:#3fb618;border-color:#3fb618}.btn-check:checked+.btn-outline-success:focus,.btn-check:active+.btn-outline-success:focus,.btn-outline-success:active:focus,.btn-outline-success.active:focus,.btn-outline-success.dropdown-toggle.show:focus{box-shadow:0 0 0 .25rem rgba(63,182,24,.5)}.btn-outline-success:disabled,.btn-outline-success.disabled{color:#3fb618;background-color:rgba(0,0,0,0)}.btn-outline-info{color:#9954bb;border-color:#9954bb;background-color:rgba(0,0,0,0)}.btn-outline-info:hover{color:#fff;background-color:#9954bb;border-color:#9954bb}.btn-check:focus+.btn-outline-info,.btn-outline-info:focus{box-shadow:0 0 0 .25rem rgba(153,84,187,.5)}.btn-check:checked+.btn-outline-info,.btn-check:active+.btn-outline-info,.btn-outline-info:active,.btn-outline-info.active,.btn-outline-info.dropdown-toggle.show{color:#fff;background-color:#9954bb;border-color:#9954bb}.btn-check:checked+.btn-outline-info:focus,.btn-check:active+.btn-outline-info:focus,.btn-outline-info:active:focus,.btn-outline-info.active:focus,.btn-outline-info.dropdown-toggle.show:focus{box-shadow:0 0 0 .25rem rgba(153,84,187,.5)}.btn-outline-info:disabled,.btn-outline-info.disabled{color:#9954bb;background-color:rgba(0,0,0,0)}.btn-outline-warning{color:#ff7518;border-color:#ff7518;background-color:rgba(0,0,0,0)}.btn-outline-warning:hover{color:#fff;background-color:#ff7518;border-color:#ff7518}.btn-check:focus+.btn-outline-warning,.btn-outline-warning:focus{box-shadow:0 0 0 .25rem rgba(255,117,24,.5)}.btn-check:checked+.btn-outline-warning,.btn-check:active+.btn-outline-warning,.btn-outline-warning:active,.btn-outline-warning.active,.btn-outline-warning.dropdown-toggle.show{color:#fff;background-color:#ff7518;border-color:#ff7518}.btn-check:checked+.btn-outline-warning:focus,.btn-check:active+.btn-outline-warning:focus,.btn-outline-warning:active:focus,.btn-outline-warning.active:focus,.btn-outline-warning.dropdown-toggle.show:focus{box-shadow:0 0 0 .25rem rgba(255,117,24,.5)}.btn-outline-warning:disabled,.btn-outline-warning.disabled{color:#ff7518;background-color:rgba(0,0,0,0)}.btn-outline-danger{color:#ff0039;border-color:#ff0039;background-color:rgba(0,0,0,0)}.btn-outline-danger:hover{color:#fff;background-color:#ff0039;border-color:#ff0039}.btn-check:focus+.btn-outline-danger,.btn-outline-danger:focus{box-shadow:0 0 0 .25rem rgba(255,0,57,.5)}.btn-check:checked+.btn-outline-danger,.btn-check:active+.btn-outline-danger,.btn-outline-danger:active,.btn-outline-danger.active,.btn-outline-danger.dropdown-toggle.show{color:#fff;background-color:#ff0039;border-color:#ff0039}.btn-check:checked+.btn-outline-danger:focus,.btn-check:active+.btn-outline-danger:focus,.btn-outline-danger:active:focus,.btn-outline-danger.active:focus,.btn-outline-danger.dropdown-toggle.show:focus{box-shadow:0 0 0 .25rem rgba(255,0,57,.5)}.btn-outline-danger:disabled,.btn-outline-danger.disabled{color:#ff0039;background-color:rgba(0,0,0,0)}.btn-outline-light{color:#f8f9fa;border-color:#f8f9fa;background-color:rgba(0,0,0,0)}.btn-outline-light:hover{color:#000;background-color:#f8f9fa;border-color:#f8f9fa}.btn-check:focus+.btn-outline-light,.btn-outline-light:focus{box-shadow:0 0 0 .25rem rgba(248,249,250,.5)}.btn-check:checked+.btn-outline-light,.btn-check:active+.btn-outline-light,.btn-outline-light:active,.btn-outline-light.active,.btn-outline-light.dropdown-toggle.show{color:#000;background-color:#f8f9fa;border-color:#f8f9fa}.btn-check:checked+.btn-outline-light:focus,.btn-check:active+.btn-outline-light:focus,.btn-outline-light:active:focus,.btn-outline-light.active:focus,.btn-outline-light.dropdown-toggle.show:focus{box-shadow:0 0 0 .25rem rgba(248,249,250,.5)}.btn-outline-light:disabled,.btn-outline-light.disabled{color:#f8f9fa;background-color:rgba(0,0,0,0)}.btn-outline-dark{color:#373a3c;border-color:#373a3c;background-color:rgba(0,0,0,0)}.btn-outline-dark:hover{color:#fff;background-color:#373a3c;border-color:#373a3c}.btn-check:focus+.btn-outline-dark,.btn-outline-dark:focus{box-shadow:0 0 0 .25rem rgba(55,58,60,.5)}.btn-check:checked+.btn-outline-dark,.btn-check:active+.btn-outline-dark,.btn-outline-dark:active,.btn-outline-dark.active,.btn-outline-dark.dropdown-toggle.show{color:#fff;background-color:#373a3c;border-color:#373a3c}.btn-check:checked+.btn-outline-dark:focus,.btn-check:active+.btn-outline-dark:focus,.btn-outline-dark:active:focus,.btn-outline-dark.active:focus,.btn-outline-dark.dropdown-toggle.show:focus{box-shadow:0 0 0 .25rem rgba(55,58,60,.5)}.btn-outline-dark:disabled,.btn-outline-dark.disabled{color:#373a3c;background-color:rgba(0,0,0,0)}.btn-link{font-weight:400;color:#2780e3;text-decoration:underline;-webkit-text-decoration:underline;-moz-text-decoration:underline;-ms-text-decoration:underline;-o-text-decoration:underline}.btn-link:hover{color:#1f66b6}.btn-link:disabled,.btn-link.disabled{color:#6c757d}.btn-lg,.btn-group-lg>.btn{padding:.5rem 1rem;font-size:1.25rem;border-radius:0}.btn-sm,.btn-group-sm>.btn{padding:.25rem .5rem;font-size:0.875rem;border-radius:0}.fade{transition:opacity .15s linear}@media(prefers-reduced-motion: reduce){.fade{transition:none}}.fade:not(.show){opacity:0}.collapse:not(.show){display:none}.collapsing{height:0;overflow:hidden;transition:height .2s ease}@media(prefers-reduced-motion: reduce){.collapsing{transition:none}}.collapsing.collapse-horizontal{width:0;height:auto;transition:width .35s ease}@media(prefers-reduced-motion: reduce){.collapsing.collapse-horizontal{transition:none}}.dropup,.dropend,.dropdown,.dropstart{position:relative}.dropdown-toggle{white-space:nowrap}.dropdown-toggle::after{display:inline-block;margin-left:.255em;vertical-align:.255em;content:"";border-top:.3em solid;border-right:.3em solid rgba(0,0,0,0);border-bottom:0;border-left:.3em solid rgba(0,0,0,0)}.dropdown-toggle:empty::after{margin-left:0}.dropdown-menu{position:absolute;z-index:1000;display:none;min-width:10rem;padding:.5rem 0;margin:0;font-size:1rem;color:#373a3c;text-align:left;list-style:none;background-color:#fff;background-clip:padding-box;border:1px solid rgba(0,0,0,.15)}.dropdown-menu[data-bs-popper]{top:100%;left:0;margin-top:.125rem}.dropdown-menu-start{--bs-position: start}.dropdown-menu-start[data-bs-popper]{right:auto;left:0}.dropdown-menu-end{--bs-position: end}.dropdown-menu-end[data-bs-popper]{right:0;left:auto}@media(min-width: 576px){.dropdown-menu-sm-start{--bs-position: start}.dropdown-menu-sm-start[data-bs-popper]{right:auto;left:0}.dropdown-menu-sm-end{--bs-position: end}.dropdown-menu-sm-end[data-bs-popper]{right:0;left:auto}}@media(min-width: 768px){.dropdown-menu-md-start{--bs-position: start}.dropdown-menu-md-start[data-bs-popper]{right:auto;left:0}.dropdown-menu-md-end{--bs-position: end}.dropdown-menu-md-end[data-bs-popper]{right:0;left:auto}}@media(min-width: 992px){.dropdown-menu-lg-start{--bs-position: start}.dropdown-menu-lg-start[data-bs-popper]{right:auto;left:0}.dropdown-menu-lg-end{--bs-position: end}.dropdown-menu-lg-end[data-bs-popper]{right:0;left:auto}}@media(min-width: 1200px){.dropdown-menu-xl-start{--bs-position: start}.dropdown-menu-xl-start[data-bs-popper]{right:auto;left:0}.dropdown-menu-xl-end{--bs-position: end}.dropdown-menu-xl-end[data-bs-popper]{right:0;left:auto}}@media(min-width: 1400px){.dropdown-menu-xxl-start{--bs-position: start}.dropdown-menu-xxl-start[data-bs-popper]{right:auto;left:0}.dropdown-menu-xxl-end{--bs-position: end}.dropdown-menu-xxl-end[data-bs-popper]{right:0;left:auto}}.dropup .dropdown-menu[data-bs-popper]{top:auto;bottom:100%;margin-top:0;margin-bottom:.125rem}.dropup .dropdown-toggle::after{display:inline-block;margin-left:.255em;vertical-align:.255em;content:"";border-top:0;border-right:.3em solid rgba(0,0,0,0);border-bottom:.3em solid;border-left:.3em solid rgba(0,0,0,0)}.dropup .dropdown-toggle:empty::after{margin-left:0}.dropend .dropdown-menu[data-bs-popper]{top:0;right:auto;left:100%;margin-top:0;margin-left:.125rem}.dropend .dropdown-toggle::after{display:inline-block;margin-left:.255em;vertical-align:.255em;content:"";border-top:.3em solid rgba(0,0,0,0);border-right:0;border-bottom:.3em solid rgba(0,0,0,0);border-left:.3em solid}.dropend .dropdown-toggle:empty::after{margin-left:0}.dropend .dropdown-toggle::after{vertical-align:0}.dropstart .dropdown-menu[data-bs-popper]{top:0;right:100%;left:auto;margin-top:0;margin-right:.125rem}.dropstart .dropdown-toggle::after{display:inline-block;margin-left:.255em;vertical-align:.255em;content:""}.dropstart .dropdown-toggle::after{display:none}.dropstart .dropdown-toggle::before{display:inline-block;margin-right:.255em;vertical-align:.255em;content:"";border-top:.3em solid rgba(0,0,0,0);border-right:.3em solid;border-bottom:.3em solid rgba(0,0,0,0)}.dropstart .dropdown-toggle:empty::after{margin-left:0}.dropstart .dropdown-toggle::before{vertical-align:0}.dropdown-divider{height:0;margin:.5rem 0;overflow:hidden;border-top:1px solid rgba(0,0,0,.15)}.dropdown-item{display:block;width:100%;padding:.25rem 1rem;clear:both;font-weight:400;color:#212529;text-align:inherit;text-decoration:none;-webkit-text-decoration:none;-moz-text-decoration:none;-ms-text-decoration:none;-o-text-decoration:none;white-space:nowrap;background-color:rgba(0,0,0,0);border:0}.dropdown-item:hover,.dropdown-item:focus{color:#1e2125;background-color:#e9ecef}.dropdown-item.active,.dropdown-item:active{color:#fff;text-decoration:none;background-color:#2780e3}.dropdown-item.disabled,.dropdown-item:disabled{color:#adb5bd;pointer-events:none;background-color:rgba(0,0,0,0)}.dropdown-menu.show{display:block}.dropdown-header{display:block;padding:.5rem 1rem;margin-bottom:0;font-size:0.875rem;color:#6c757d;white-space:nowrap}.dropdown-item-text{display:block;padding:.25rem 1rem;color:#212529}.dropdown-menu-dark{color:#dee2e6;background-color:#373a3c;border-color:rgba(0,0,0,.15)}.dropdown-menu-dark .dropdown-item{color:#dee2e6}.dropdown-menu-dark .dropdown-item:hover,.dropdown-menu-dark .dropdown-item:focus{color:#fff;background-color:rgba(255,255,255,.15)}.dropdown-menu-dark .dropdown-item.active,.dropdown-menu-dark .dropdown-item:active{color:#fff;background-color:#2780e3}.dropdown-menu-dark .dropdown-item.disabled,.dropdown-menu-dark .dropdown-item:disabled{color:#adb5bd}.dropdown-menu-dark .dropdown-divider{border-color:rgba(0,0,0,.15)}.dropdown-menu-dark .dropdown-item-text{color:#dee2e6}.dropdown-menu-dark .dropdown-header{color:#adb5bd}.btn-group,.btn-group-vertical{position:relative;display:inline-flex;vertical-align:middle}.btn-group>.btn,.btn-group-vertical>.btn{position:relative;flex:1 1 auto;-webkit-flex:1 1 auto}.btn-group>.btn-check:checked+.btn,.btn-group>.btn-check:focus+.btn,.btn-group>.btn:hover,.btn-group>.btn:focus,.btn-group>.btn:active,.btn-group>.btn.active,.btn-group-vertical>.btn-check:checked+.btn,.btn-group-vertical>.btn-check:focus+.btn,.btn-group-vertical>.btn:hover,.btn-group-vertical>.btn:focus,.btn-group-vertical>.btn:active,.btn-group-vertical>.btn.active{z-index:1}.btn-toolbar{display:flex;display:-webkit-flex;flex-wrap:wrap;-webkit-flex-wrap:wrap;justify-content:flex-start;-webkit-justify-content:flex-start}.btn-toolbar .input-group{width:auto}.btn-group>.btn:not(:first-child),.btn-group>.btn-group:not(:first-child){margin-left:-1px}.dropdown-toggle-split{padding-right:.5625rem;padding-left:.5625rem}.dropdown-toggle-split::after,.dropup .dropdown-toggle-split::after,.dropend .dropdown-toggle-split::after{margin-left:0}.dropstart .dropdown-toggle-split::before{margin-right:0}.btn-sm+.dropdown-toggle-split,.btn-group-sm>.btn+.dropdown-toggle-split{padding-right:.375rem;padding-left:.375rem}.btn-lg+.dropdown-toggle-split,.btn-group-lg>.btn+.dropdown-toggle-split{padding-right:.75rem;padding-left:.75rem}.btn-group-vertical{flex-direction:column;-webkit-flex-direction:column;align-items:flex-start;-webkit-align-items:flex-start;justify-content:center;-webkit-justify-content:center}.btn-group-vertical>.btn,.btn-group-vertical>.btn-group{width:100%}.btn-group-vertical>.btn:not(:first-child),.btn-group-vertical>.btn-group:not(:first-child){margin-top:-1px}.nav{display:flex;display:-webkit-flex;flex-wrap:wrap;-webkit-flex-wrap:wrap;padding-left:0;margin-bottom:0;list-style:none}.nav-link{display:block;padding:.5rem 1rem;color:#2780e3;text-decoration:none;-webkit-text-decoration:none;-moz-text-decoration:none;-ms-text-decoration:none;-o-text-decoration:none;transition:color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out}@media(prefers-reduced-motion: reduce){.nav-link{transition:none}}.nav-link:hover,.nav-link:focus{color:#1f66b6}.nav-link.disabled{color:#6c757d;pointer-events:none;cursor:default}.nav-tabs{border-bottom:1px solid #dee2e6}.nav-tabs .nav-link{margin-bottom:-1px;background:none;border:1px solid rgba(0,0,0,0)}.nav-tabs .nav-link:hover,.nav-tabs .nav-link:focus{border-color:#e9ecef #e9ecef #dee2e6;isolation:isolate}.nav-tabs .nav-link.disabled{color:#6c757d;background-color:rgba(0,0,0,0);border-color:rgba(0,0,0,0)}.nav-tabs .nav-link.active,.nav-tabs .nav-item.show .nav-link{color:#495057;background-color:#fff;border-color:#dee2e6 #dee2e6 #fff}.nav-tabs .dropdown-menu{margin-top:-1px}.nav-pills .nav-link{background:none;border:0}.nav-pills .nav-link.active,.nav-pills .show>.nav-link{color:#fff;background-color:#2780e3}.nav-fill>.nav-link,.nav-fill .nav-item{flex:1 1 auto;-webkit-flex:1 1 auto;text-align:center}.nav-justified>.nav-link,.nav-justified .nav-item{flex-basis:0;-webkit-flex-basis:0;flex-grow:1;-webkit-flex-grow:1;text-align:center}.nav-fill .nav-item .nav-link,.nav-justified .nav-item .nav-link{width:100%}.tab-content>.tab-pane{display:none}.tab-content>.active{display:block}.navbar{position:relative;display:flex;display:-webkit-flex;flex-wrap:wrap;-webkit-flex-wrap:wrap;align-items:center;-webkit-align-items:center;justify-content:space-between;-webkit-justify-content:space-between;padding-top:.5rem;padding-bottom:.5rem}.navbar>.container-xxl,.navbar>.container-xl,.navbar>.container-lg,.navbar>.container-md,.navbar>.container-sm,.navbar>.container,.navbar>.container-fluid{display:flex;display:-webkit-flex;flex-wrap:inherit;-webkit-flex-wrap:inherit;align-items:center;-webkit-align-items:center;justify-content:space-between;-webkit-justify-content:space-between}.navbar-brand{padding-top:.3125rem;padding-bottom:.3125rem;margin-right:1rem;font-size:1.25rem;text-decoration:none;-webkit-text-decoration:none;-moz-text-decoration:none;-ms-text-decoration:none;-o-text-decoration:none;white-space:nowrap}.navbar-nav{display:flex;display:-webkit-flex;flex-direction:column;-webkit-flex-direction:column;padding-left:0;margin-bottom:0;list-style:none}.navbar-nav .nav-link{padding-right:0;padding-left:0}.navbar-nav .dropdown-menu{position:static}.navbar-text{padding-top:.5rem;padding-bottom:.5rem}.navbar-collapse{flex-basis:100%;-webkit-flex-basis:100%;flex-grow:1;-webkit-flex-grow:1;align-items:center;-webkit-align-items:center}.navbar-toggler{padding:.25 0;font-size:1.25rem;line-height:1;background-color:rgba(0,0,0,0);border:1px solid rgba(0,0,0,0);transition:box-shadow .15s ease-in-out}@media(prefers-reduced-motion: reduce){.navbar-toggler{transition:none}}.navbar-toggler:hover{text-decoration:none}.navbar-toggler:focus{text-decoration:none;outline:0;box-shadow:0 0 0 .25rem}.navbar-toggler-icon{display:inline-block;width:1.5em;height:1.5em;vertical-align:middle;background-repeat:no-repeat;background-position:center;background-size:100%}.navbar-nav-scroll{max-height:var(--bs-scroll-height, 75vh);overflow-y:auto}@media(min-width: 576px){.navbar-expand-sm{flex-wrap:nowrap;-webkit-flex-wrap:nowrap;justify-content:flex-start;-webkit-justify-content:flex-start}.navbar-expand-sm .navbar-nav{flex-direction:row;-webkit-flex-direction:row}.navbar-expand-sm .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-sm .navbar-nav .nav-link{padding-right:.5rem;padding-left:.5rem}.navbar-expand-sm .navbar-nav-scroll{overflow:visible}.navbar-expand-sm .navbar-collapse{display:flex !important;display:-webkit-flex !important;flex-basis:auto;-webkit-flex-basis:auto}.navbar-expand-sm .navbar-toggler{display:none}.navbar-expand-sm .offcanvas-header{display:none}.navbar-expand-sm .offcanvas{position:inherit;bottom:0;z-index:1000;flex-grow:1;-webkit-flex-grow:1;visibility:visible !important;background-color:rgba(0,0,0,0);border-right:0;border-left:0;transition:none;transform:none}.navbar-expand-sm .offcanvas-top,.navbar-expand-sm .offcanvas-bottom{height:auto;border-top:0;border-bottom:0}.navbar-expand-sm .offcanvas-body{display:flex;display:-webkit-flex;flex-grow:0;-webkit-flex-grow:0;padding:0;overflow-y:visible}}@media(min-width: 768px){.navbar-expand-md{flex-wrap:nowrap;-webkit-flex-wrap:nowrap;justify-content:flex-start;-webkit-justify-content:flex-start}.navbar-expand-md .navbar-nav{flex-direction:row;-webkit-flex-direction:row}.navbar-expand-md .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-md .navbar-nav .nav-link{padding-right:.5rem;padding-left:.5rem}.navbar-expand-md .navbar-nav-scroll{overflow:visible}.navbar-expand-md .navbar-collapse{display:flex !important;display:-webkit-flex !important;flex-basis:auto;-webkit-flex-basis:auto}.navbar-expand-md .navbar-toggler{display:none}.navbar-expand-md .offcanvas-header{display:none}.navbar-expand-md .offcanvas{position:inherit;bottom:0;z-index:1000;flex-grow:1;-webkit-flex-grow:1;visibility:visible !important;background-color:rgba(0,0,0,0);border-right:0;border-left:0;transition:none;transform:none}.navbar-expand-md .offcanvas-top,.navbar-expand-md .offcanvas-bottom{height:auto;border-top:0;border-bottom:0}.navbar-expand-md .offcanvas-body{display:flex;display:-webkit-flex;flex-grow:0;-webkit-flex-grow:0;padding:0;overflow-y:visible}}@media(min-width: 992px){.navbar-expand-lg{flex-wrap:nowrap;-webkit-flex-wrap:nowrap;justify-content:flex-start;-webkit-justify-content:flex-start}.navbar-expand-lg .navbar-nav{flex-direction:row;-webkit-flex-direction:row}.navbar-expand-lg .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-lg .navbar-nav .nav-link{padding-right:.5rem;padding-left:.5rem}.navbar-expand-lg .navbar-nav-scroll{overflow:visible}.navbar-expand-lg .navbar-collapse{display:flex !important;display:-webkit-flex !important;flex-basis:auto;-webkit-flex-basis:auto}.navbar-expand-lg .navbar-toggler{display:none}.navbar-expand-lg .offcanvas-header{display:none}.navbar-expand-lg .offcanvas{position:inherit;bottom:0;z-index:1000;flex-grow:1;-webkit-flex-grow:1;visibility:visible !important;background-color:rgba(0,0,0,0);border-right:0;border-left:0;transition:none;transform:none}.navbar-expand-lg .offcanvas-top,.navbar-expand-lg .offcanvas-bottom{height:auto;border-top:0;border-bottom:0}.navbar-expand-lg .offcanvas-body{display:flex;display:-webkit-flex;flex-grow:0;-webkit-flex-grow:0;padding:0;overflow-y:visible}}@media(min-width: 1200px){.navbar-expand-xl{flex-wrap:nowrap;-webkit-flex-wrap:nowrap;justify-content:flex-start;-webkit-justify-content:flex-start}.navbar-expand-xl .navbar-nav{flex-direction:row;-webkit-flex-direction:row}.navbar-expand-xl .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-xl .navbar-nav .nav-link{padding-right:.5rem;padding-left:.5rem}.navbar-expand-xl .navbar-nav-scroll{overflow:visible}.navbar-expand-xl .navbar-collapse{display:flex !important;display:-webkit-flex !important;flex-basis:auto;-webkit-flex-basis:auto}.navbar-expand-xl .navbar-toggler{display:none}.navbar-expand-xl .offcanvas-header{display:none}.navbar-expand-xl .offcanvas{position:inherit;bottom:0;z-index:1000;flex-grow:1;-webkit-flex-grow:1;visibility:visible !important;background-color:rgba(0,0,0,0);border-right:0;border-left:0;transition:none;transform:none}.navbar-expand-xl .offcanvas-top,.navbar-expand-xl .offcanvas-bottom{height:auto;border-top:0;border-bottom:0}.navbar-expand-xl .offcanvas-body{display:flex;display:-webkit-flex;flex-grow:0;-webkit-flex-grow:0;padding:0;overflow-y:visible}}@media(min-width: 1400px){.navbar-expand-xxl{flex-wrap:nowrap;-webkit-flex-wrap:nowrap;justify-content:flex-start;-webkit-justify-content:flex-start}.navbar-expand-xxl .navbar-nav{flex-direction:row;-webkit-flex-direction:row}.navbar-expand-xxl .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-xxl .navbar-nav .nav-link{padding-right:.5rem;padding-left:.5rem}.navbar-expand-xxl .navbar-nav-scroll{overflow:visible}.navbar-expand-xxl .navbar-collapse{display:flex !important;display:-webkit-flex !important;flex-basis:auto;-webkit-flex-basis:auto}.navbar-expand-xxl .navbar-toggler{display:none}.navbar-expand-xxl .offcanvas-header{display:none}.navbar-expand-xxl .offcanvas{position:inherit;bottom:0;z-index:1000;flex-grow:1;-webkit-flex-grow:1;visibility:visible !important;background-color:rgba(0,0,0,0);border-right:0;border-left:0;transition:none;transform:none}.navbar-expand-xxl .offcanvas-top,.navbar-expand-xxl .offcanvas-bottom{height:auto;border-top:0;border-bottom:0}.navbar-expand-xxl .offcanvas-body{display:flex;display:-webkit-flex;flex-grow:0;-webkit-flex-grow:0;padding:0;overflow-y:visible}}.navbar-expand{flex-wrap:nowrap;-webkit-flex-wrap:nowrap;justify-content:flex-start;-webkit-justify-content:flex-start}.navbar-expand .navbar-nav{flex-direction:row;-webkit-flex-direction:row}.navbar-expand .navbar-nav .dropdown-menu{position:absolute}.navbar-expand .navbar-nav .nav-link{padding-right:.5rem;padding-left:.5rem}.navbar-expand .navbar-nav-scroll{overflow:visible}.navbar-expand .navbar-collapse{display:flex !important;display:-webkit-flex !important;flex-basis:auto;-webkit-flex-basis:auto}.navbar-expand .navbar-toggler{display:none}.navbar-expand .offcanvas-header{display:none}.navbar-expand .offcanvas{position:inherit;bottom:0;z-index:1000;flex-grow:1;-webkit-flex-grow:1;visibility:visible !important;background-color:rgba(0,0,0,0);border-right:0;border-left:0;transition:none;transform:none}.navbar-expand .offcanvas-top,.navbar-expand .offcanvas-bottom{height:auto;border-top:0;border-bottom:0}.navbar-expand .offcanvas-body{display:flex;display:-webkit-flex;flex-grow:0;-webkit-flex-grow:0;padding:0;overflow-y:visible}.navbar-light{background-color:#2780e3}.navbar-light .navbar-brand{color:#fdfeff}.navbar-light .navbar-brand:hover,.navbar-light .navbar-brand:focus{color:#fdfeff}.navbar-light .navbar-nav .nav-link{color:#fdfeff}.navbar-light .navbar-nav .nav-link:hover,.navbar-light .navbar-nav .nav-link:focus{color:rgba(253,254,255,.8)}.navbar-light .navbar-nav .nav-link.disabled{color:rgba(253,254,255,.75)}.navbar-light .navbar-nav .show>.nav-link,.navbar-light .navbar-nav .nav-link.active{color:#fdfeff}.navbar-light .navbar-toggler{color:#fdfeff;border-color:rgba(253,254,255,0)}.navbar-light .navbar-toggler-icon{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 30 30'%3e%3cpath stroke='%23fdfeff' stroke-linecap='round' stroke-miterlimit='10' stroke-width='2' d='M4 7h22M4 15h22M4 23h22'/%3e%3c/svg%3e")}.navbar-light .navbar-text{color:#fdfeff}.navbar-light .navbar-text a,.navbar-light .navbar-text a:hover,.navbar-light .navbar-text a:focus{color:#fdfeff}.navbar-dark{background-color:#2780e3}.navbar-dark .navbar-brand{color:#fdfeff}.navbar-dark .navbar-brand:hover,.navbar-dark .navbar-brand:focus{color:#fdfeff}.navbar-dark .navbar-nav .nav-link{color:#fdfeff}.navbar-dark .navbar-nav .nav-link:hover,.navbar-dark .navbar-nav .nav-link:focus{color:rgba(253,254,255,.8)}.navbar-dark .navbar-nav .nav-link.disabled{color:rgba(253,254,255,.75)}.navbar-dark .navbar-nav .show>.nav-link,.navbar-dark .navbar-nav .active>.nav-link,.navbar-dark .navbar-nav .nav-link.active{color:#fdfeff}.navbar-dark .navbar-toggler{color:#fdfeff;border-color:rgba(253,254,255,0)}.navbar-dark .navbar-toggler-icon{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 30 30'%3e%3cpath stroke='%23fdfeff' stroke-linecap='round' stroke-miterlimit='10' stroke-width='2' d='M4 7h22M4 15h22M4 23h22'/%3e%3c/svg%3e")}.navbar-dark .navbar-text{color:#fdfeff}.navbar-dark .navbar-text a,.navbar-dark .navbar-text a:hover,.navbar-dark .navbar-text a:focus{color:#fdfeff}.card{position:relative;display:flex;display:-webkit-flex;flex-direction:column;-webkit-flex-direction:column;min-width:0;word-wrap:break-word;background-color:#fff;background-clip:border-box;border:1px solid rgba(0,0,0,.125)}.card>hr{margin-right:0;margin-left:0}.card>.list-group{border-top:inherit;border-bottom:inherit}.card>.list-group:first-child{border-top-width:0}.card>.list-group:last-child{border-bottom-width:0}.card>.card-header+.list-group,.card>.list-group+.card-footer{border-top:0}.card-body{flex:1 1 auto;-webkit-flex:1 1 auto;padding:1rem 1rem}.card-title{margin-bottom:.5rem}.card-subtitle{margin-top:-0.25rem;margin-bottom:0}.card-text:last-child{margin-bottom:0}.card-link+.card-link{margin-left:1rem}.card-header{padding:.5rem 1rem;margin-bottom:0;background-color:#adb5bd;border-bottom:1px solid rgba(0,0,0,.125)}.card-footer{padding:.5rem 1rem;background-color:#adb5bd;border-top:1px solid rgba(0,0,0,.125)}.card-header-tabs{margin-right:-0.5rem;margin-bottom:-0.5rem;margin-left:-0.5rem;border-bottom:0}.card-header-pills{margin-right:-0.5rem;margin-left:-0.5rem}.card-img-overlay{position:absolute;top:0;right:0;bottom:0;left:0;padding:1rem}.card-img,.card-img-top,.card-img-bottom{width:100%}.card-group>.card{margin-bottom:.75rem}@media(min-width: 576px){.card-group{display:flex;display:-webkit-flex;flex-flow:row wrap;-webkit-flex-flow:row wrap}.card-group>.card{flex:1 0 0%;-webkit-flex:1 0 0%;margin-bottom:0}.card-group>.card+.card{margin-left:0;border-left:0}}.accordion-button{position:relative;display:flex;display:-webkit-flex;align-items:center;-webkit-align-items:center;width:100%;padding:1rem 1.25rem;font-size:1rem;color:#373a3c;text-align:left;background-color:#fff;border:0;overflow-anchor:none;transition:color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out,border-radius .15s ease}@media(prefers-reduced-motion: reduce){.accordion-button{transition:none}}.accordion-button:not(.collapsed){color:#2373cc;background-color:#e9f2fc;box-shadow:inset 0 -1px 0 rgba(0,0,0,.125)}.accordion-button:not(.collapsed)::after{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%232373cc'%3e%3cpath fill-rule='evenodd' d='M1.646 4.646a.5.5 0 0 1 .708 0L8 10.293l5.646-5.647a.5.5 0 0 1 .708.708l-6 6a.5.5 0 0 1-.708 0l-6-6a.5.5 0 0 1 0-.708z'/%3e%3c/svg%3e");transform:rotate(-180deg)}.accordion-button::after{flex-shrink:0;-webkit-flex-shrink:0;width:1.25rem;height:1.25rem;margin-left:auto;content:"";background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23373a3c'%3e%3cpath fill-rule='evenodd' d='M1.646 4.646a.5.5 0 0 1 .708 0L8 10.293l5.646-5.647a.5.5 0 0 1 .708.708l-6 6a.5.5 0 0 1-.708 0l-6-6a.5.5 0 0 1 0-.708z'/%3e%3c/svg%3e");background-repeat:no-repeat;background-size:1.25rem;transition:transform .2s ease-in-out}@media(prefers-reduced-motion: reduce){.accordion-button::after{transition:none}}.accordion-button:hover{z-index:2}.accordion-button:focus{z-index:3;border-color:#93c0f1;outline:0;box-shadow:0 0 0 .25rem rgba(39,128,227,.25)}.accordion-header{margin-bottom:0}.accordion-item{background-color:#fff;border:1px solid rgba(0,0,0,.125)}.accordion-item:not(:first-of-type){border-top:0}.accordion-body{padding:1rem 1.25rem}.accordion-flush .accordion-collapse{border-width:0}.accordion-flush .accordion-item{border-right:0;border-left:0}.accordion-flush .accordion-item:first-child{border-top:0}.accordion-flush .accordion-item:last-child{border-bottom:0}.breadcrumb{display:flex;display:-webkit-flex;flex-wrap:wrap;-webkit-flex-wrap:wrap;padding:0 0;margin-bottom:1rem;list-style:none}.breadcrumb-item+.breadcrumb-item{padding-left:.5rem}.breadcrumb-item+.breadcrumb-item::before{float:left;padding-right:.5rem;color:#6c757d;content:var(--bs-breadcrumb-divider, ">") /* rtl: var(--bs-breadcrumb-divider, ">") */}.breadcrumb-item.active{color:#6c757d}.pagination{display:flex;display:-webkit-flex;padding-left:0;list-style:none}.page-link{position:relative;display:block;color:#2780e3;text-decoration:none;-webkit-text-decoration:none;-moz-text-decoration:none;-ms-text-decoration:none;-o-text-decoration:none;background-color:#fff;border:1px solid #dee2e6;transition:color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out}@media(prefers-reduced-motion: reduce){.page-link{transition:none}}.page-link:hover{z-index:2;color:#1f66b6;background-color:#e9ecef;border-color:#dee2e6}.page-link:focus{z-index:3;color:#1f66b6;background-color:#e9ecef;outline:0;box-shadow:0 0 0 .25rem rgba(39,128,227,.25)}.page-item:not(:first-child) .page-link{margin-left:-1px}.page-item.active .page-link{z-index:3;color:#fff;background-color:#2780e3;border-color:#2780e3}.page-item.disabled .page-link{color:#6c757d;pointer-events:none;background-color:#fff;border-color:#dee2e6}.page-link{padding:.375rem .75rem}.pagination-lg .page-link{padding:.75rem 1.5rem;font-size:1.25rem}.pagination-sm .page-link{padding:.25rem .5rem;font-size:0.875rem}.badge{display:inline-block;padding:.35em .65em;font-size:0.75em;font-weight:700;line-height:1;color:#fff;text-align:center;white-space:nowrap;vertical-align:baseline}.badge:empty{display:none}.btn .badge{position:relative;top:-1px}.alert{position:relative;padding:1rem 1rem;margin-bottom:1rem;border:0 solid rgba(0,0,0,0)}.alert-heading{color:inherit}.alert-link{font-weight:700}.alert-dismissible{padding-right:3rem}.alert-dismissible .btn-close{position:absolute;top:0;right:0;z-index:2;padding:1.25rem 1rem}.alert-default{color:#212324;background-color:#d7d8d8;border-color:#c3c4c5}.alert-default .alert-link{color:#1a1c1d}.alert-primary{color:#174d88;background-color:#d4e6f9;border-color:#bed9f7}.alert-primary .alert-link{color:#123e6d}.alert-secondary{color:#212324;background-color:#d7d8d8;border-color:#c3c4c5}.alert-secondary .alert-link{color:#1a1c1d}.alert-success{color:#266d0e;background-color:#d9f0d1;border-color:#c5e9ba}.alert-success .alert-link{color:#1e570b}.alert-info{color:#5c3270;background-color:#ebddf1;border-color:#e0cceb}.alert-info .alert-link{color:#4a285a}.alert-warning{color:#99460e;background-color:#ffe3d1;border-color:#ffd6ba}.alert-warning .alert-link{color:#7a380b}.alert-danger{color:#902;background-color:#ffccd7;border-color:#ffb3c4}.alert-danger .alert-link{color:#7a001b}.alert-light{color:#959596;background-color:#fefefe;border-color:#fdfdfe}.alert-light .alert-link{color:#777778}.alert-dark{color:#212324;background-color:#d7d8d8;border-color:#c3c4c5}.alert-dark .alert-link{color:#1a1c1d}@keyframes progress-bar-stripes{0%{background-position-x:.5rem}}.progress{display:flex;display:-webkit-flex;height:.5rem;overflow:hidden;font-size:0.75rem;background-color:#e9ecef}.progress-bar{display:flex;display:-webkit-flex;flex-direction:column;-webkit-flex-direction:column;justify-content:center;-webkit-justify-content:center;overflow:hidden;color:#fff;text-align:center;white-space:nowrap;background-color:#2780e3;transition:width .6s ease}@media(prefers-reduced-motion: reduce){.progress-bar{transition:none}}.progress-bar-striped{background-image:linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);background-size:.5rem .5rem}.progress-bar-animated{animation:1s linear infinite progress-bar-stripes}@media(prefers-reduced-motion: reduce){.progress-bar-animated{animation:none}}.list-group{display:flex;display:-webkit-flex;flex-direction:column;-webkit-flex-direction:column;padding-left:0;margin-bottom:0}.list-group-numbered{list-style-type:none;counter-reset:section}.list-group-numbered>li::before{content:counters(section, ".") ". ";counter-increment:section}.list-group-item-action{width:100%;color:#495057;text-align:inherit}.list-group-item-action:hover,.list-group-item-action:focus{z-index:1;color:#495057;text-decoration:none;background-color:#f8f9fa}.list-group-item-action:active{color:#373a3c;background-color:#e9ecef}.list-group-item{position:relative;display:block;padding:.5rem 1rem;color:#212529;text-decoration:none;-webkit-text-decoration:none;-moz-text-decoration:none;-ms-text-decoration:none;-o-text-decoration:none;background-color:#fff;border:1px solid rgba(0,0,0,.125)}.list-group-item.disabled,.list-group-item:disabled{color:#6c757d;pointer-events:none;background-color:#fff}.list-group-item.active{z-index:2;color:#fff;background-color:#2780e3;border-color:#2780e3}.list-group-item+.list-group-item{border-top-width:0}.list-group-item+.list-group-item.active{margin-top:-1px;border-top-width:1px}.list-group-horizontal{flex-direction:row;-webkit-flex-direction:row}.list-group-horizontal>.list-group-item.active{margin-top:0}.list-group-horizontal>.list-group-item+.list-group-item{border-top-width:1px;border-left-width:0}.list-group-horizontal>.list-group-item+.list-group-item.active{margin-left:-1px;border-left-width:1px}@media(min-width: 576px){.list-group-horizontal-sm{flex-direction:row;-webkit-flex-direction:row}.list-group-horizontal-sm>.list-group-item.active{margin-top:0}.list-group-horizontal-sm>.list-group-item+.list-group-item{border-top-width:1px;border-left-width:0}.list-group-horizontal-sm>.list-group-item+.list-group-item.active{margin-left:-1px;border-left-width:1px}}@media(min-width: 768px){.list-group-horizontal-md{flex-direction:row;-webkit-flex-direction:row}.list-group-horizontal-md>.list-group-item.active{margin-top:0}.list-group-horizontal-md>.list-group-item+.list-group-item{border-top-width:1px;border-left-width:0}.list-group-horizontal-md>.list-group-item+.list-group-item.active{margin-left:-1px;border-left-width:1px}}@media(min-width: 992px){.list-group-horizontal-lg{flex-direction:row;-webkit-flex-direction:row}.list-group-horizontal-lg>.list-group-item.active{margin-top:0}.list-group-horizontal-lg>.list-group-item+.list-group-item{border-top-width:1px;border-left-width:0}.list-group-horizontal-lg>.list-group-item+.list-group-item.active{margin-left:-1px;border-left-width:1px}}@media(min-width: 1200px){.list-group-horizontal-xl{flex-direction:row;-webkit-flex-direction:row}.list-group-horizontal-xl>.list-group-item.active{margin-top:0}.list-group-horizontal-xl>.list-group-item+.list-group-item{border-top-width:1px;border-left-width:0}.list-group-horizontal-xl>.list-group-item+.list-group-item.active{margin-left:-1px;border-left-width:1px}}@media(min-width: 1400px){.list-group-horizontal-xxl{flex-direction:row;-webkit-flex-direction:row}.list-group-horizontal-xxl>.list-group-item.active{margin-top:0}.list-group-horizontal-xxl>.list-group-item+.list-group-item{border-top-width:1px;border-left-width:0}.list-group-horizontal-xxl>.list-group-item+.list-group-item.active{margin-left:-1px;border-left-width:1px}}.list-group-flush>.list-group-item{border-width:0 0 1px}.list-group-flush>.list-group-item:last-child{border-bottom-width:0}.list-group-item-default{color:#212324;background-color:#d7d8d8}.list-group-item-default.list-group-item-action:hover,.list-group-item-default.list-group-item-action:focus{color:#212324;background-color:#c2c2c2}.list-group-item-default.list-group-item-action.active{color:#fff;background-color:#212324;border-color:#212324}.list-group-item-primary{color:#174d88;background-color:#d4e6f9}.list-group-item-primary.list-group-item-action:hover,.list-group-item-primary.list-group-item-action:focus{color:#174d88;background-color:#bfcfe0}.list-group-item-primary.list-group-item-action.active{color:#fff;background-color:#174d88;border-color:#174d88}.list-group-item-secondary{color:#212324;background-color:#d7d8d8}.list-group-item-secondary.list-group-item-action:hover,.list-group-item-secondary.list-group-item-action:focus{color:#212324;background-color:#c2c2c2}.list-group-item-secondary.list-group-item-action.active{color:#fff;background-color:#212324;border-color:#212324}.list-group-item-success{color:#266d0e;background-color:#d9f0d1}.list-group-item-success.list-group-item-action:hover,.list-group-item-success.list-group-item-action:focus{color:#266d0e;background-color:#c3d8bc}.list-group-item-success.list-group-item-action.active{color:#fff;background-color:#266d0e;border-color:#266d0e}.list-group-item-info{color:#5c3270;background-color:#ebddf1}.list-group-item-info.list-group-item-action:hover,.list-group-item-info.list-group-item-action:focus{color:#5c3270;background-color:#d4c7d9}.list-group-item-info.list-group-item-action.active{color:#fff;background-color:#5c3270;border-color:#5c3270}.list-group-item-warning{color:#99460e;background-color:#ffe3d1}.list-group-item-warning.list-group-item-action:hover,.list-group-item-warning.list-group-item-action:focus{color:#99460e;background-color:#e6ccbc}.list-group-item-warning.list-group-item-action.active{color:#fff;background-color:#99460e;border-color:#99460e}.list-group-item-danger{color:#902;background-color:#ffccd7}.list-group-item-danger.list-group-item-action:hover,.list-group-item-danger.list-group-item-action:focus{color:#902;background-color:#e6b8c2}.list-group-item-danger.list-group-item-action.active{color:#fff;background-color:#902;border-color:#902}.list-group-item-light{color:#959596;background-color:#fefefe}.list-group-item-light.list-group-item-action:hover,.list-group-item-light.list-group-item-action:focus{color:#959596;background-color:#e5e5e5}.list-group-item-light.list-group-item-action.active{color:#fff;background-color:#959596;border-color:#959596}.list-group-item-dark{color:#212324;background-color:#d7d8d8}.list-group-item-dark.list-group-item-action:hover,.list-group-item-dark.list-group-item-action:focus{color:#212324;background-color:#c2c2c2}.list-group-item-dark.list-group-item-action.active{color:#fff;background-color:#212324;border-color:#212324}.btn-close{box-sizing:content-box;width:1em;height:1em;padding:.25em .25em;color:#000;background:rgba(0,0,0,0) url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23000'%3e%3cpath d='M.293.293a1 1 0 011.414 0L8 6.586 14.293.293a1 1 0 111.414 1.414L9.414 8l6.293 6.293a1 1 0 01-1.414 1.414L8 9.414l-6.293 6.293a1 1 0 01-1.414-1.414L6.586 8 .293 1.707a1 1 0 010-1.414z'/%3e%3c/svg%3e") center/1em auto no-repeat;border:0;opacity:.5}.btn-close:hover{color:#000;text-decoration:none;opacity:.75}.btn-close:focus{outline:0;box-shadow:0 0 0 .25rem rgba(39,128,227,.25);opacity:1}.btn-close:disabled,.btn-close.disabled{pointer-events:none;user-select:none;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;-o-user-select:none;opacity:.25}.btn-close-white{filter:invert(1) grayscale(100%) brightness(200%)}.toast{width:350px;max-width:100%;font-size:0.875rem;pointer-events:auto;background-color:rgba(255,255,255,.85);background-clip:padding-box;border:1px solid rgba(0,0,0,.1);box-shadow:0 .5rem 1rem rgba(0,0,0,.15)}.toast.showing{opacity:0}.toast:not(.show){display:none}.toast-container{width:max-content;width:-webkit-max-content;width:-moz-max-content;width:-ms-max-content;width:-o-max-content;max-width:100%;pointer-events:none}.toast-container>:not(:last-child){margin-bottom:.75rem}.toast-header{display:flex;display:-webkit-flex;align-items:center;-webkit-align-items:center;padding:.5rem .75rem;color:#6c757d;background-color:rgba(255,255,255,.85);background-clip:padding-box;border-bottom:1px solid rgba(0,0,0,.05)}.toast-header .btn-close{margin-right:-0.375rem;margin-left:.75rem}.toast-body{padding:.75rem;word-wrap:break-word}.modal{position:fixed;top:0;left:0;z-index:1055;display:none;width:100%;height:100%;overflow-x:hidden;overflow-y:auto;outline:0}.modal-dialog{position:relative;width:auto;margin:.5rem;pointer-events:none}.modal.fade .modal-dialog{transition:transform .3s ease-out;transform:translate(0, -50px)}@media(prefers-reduced-motion: reduce){.modal.fade .modal-dialog{transition:none}}.modal.show .modal-dialog{transform:none}.modal.modal-static .modal-dialog{transform:scale(1.02)}.modal-dialog-scrollable{height:calc(100% - 1rem)}.modal-dialog-scrollable .modal-content{max-height:100%;overflow:hidden}.modal-dialog-scrollable .modal-body{overflow-y:auto}.modal-dialog-centered{display:flex;display:-webkit-flex;align-items:center;-webkit-align-items:center;min-height:calc(100% - 1rem)}.modal-content{position:relative;display:flex;display:-webkit-flex;flex-direction:column;-webkit-flex-direction:column;width:100%;pointer-events:auto;background-color:#fff;background-clip:padding-box;border:1px solid rgba(0,0,0,.2);outline:0}.modal-backdrop{position:fixed;top:0;left:0;z-index:1050;width:100vw;height:100vh;background-color:#000}.modal-backdrop.fade{opacity:0}.modal-backdrop.show{opacity:.5}.modal-header{display:flex;display:-webkit-flex;flex-shrink:0;-webkit-flex-shrink:0;align-items:center;-webkit-align-items:center;justify-content:space-between;-webkit-justify-content:space-between;padding:1rem 1rem;border-bottom:1px solid #dee2e6}.modal-header .btn-close{padding:.5rem .5rem;margin:-0.5rem -0.5rem -0.5rem auto}.modal-title{margin-bottom:0;line-height:1.5}.modal-body{position:relative;flex:1 1 auto;-webkit-flex:1 1 auto;padding:1rem}.modal-footer{display:flex;display:-webkit-flex;flex-wrap:wrap;-webkit-flex-wrap:wrap;flex-shrink:0;-webkit-flex-shrink:0;align-items:center;-webkit-align-items:center;justify-content:flex-end;-webkit-justify-content:flex-end;padding:.75rem;border-top:1px solid #dee2e6}.modal-footer>*{margin:.25rem}@media(min-width: 576px){.modal-dialog{max-width:500px;margin:1.75rem auto}.modal-dialog-scrollable{height:calc(100% - 3.5rem)}.modal-dialog-centered{min-height:calc(100% - 3.5rem)}.modal-sm{max-width:300px}}@media(min-width: 992px){.modal-lg,.modal-xl{max-width:800px}}@media(min-width: 1200px){.modal-xl{max-width:1140px}}.modal-fullscreen{width:100vw;max-width:none;height:100%;margin:0}.modal-fullscreen .modal-content{height:100%;border:0}.modal-fullscreen .modal-body{overflow-y:auto}@media(max-width: 575.98px){.modal-fullscreen-sm-down{width:100vw;max-width:none;height:100%;margin:0}.modal-fullscreen-sm-down .modal-content{height:100%;border:0}.modal-fullscreen-sm-down .modal-body{overflow-y:auto}}@media(max-width: 767.98px){.modal-fullscreen-md-down{width:100vw;max-width:none;height:100%;margin:0}.modal-fullscreen-md-down .modal-content{height:100%;border:0}.modal-fullscreen-md-down .modal-body{overflow-y:auto}}@media(max-width: 991.98px){.modal-fullscreen-lg-down{width:100vw;max-width:none;height:100%;margin:0}.modal-fullscreen-lg-down .modal-content{height:100%;border:0}.modal-fullscreen-lg-down .modal-body{overflow-y:auto}}@media(max-width: 1199.98px){.modal-fullscreen-xl-down{width:100vw;max-width:none;height:100%;margin:0}.modal-fullscreen-xl-down .modal-content{height:100%;border:0}.modal-fullscreen-xl-down .modal-body{overflow-y:auto}}@media(max-width: 1399.98px){.modal-fullscreen-xxl-down{width:100vw;max-width:none;height:100%;margin:0}.modal-fullscreen-xxl-down .modal-content{height:100%;border:0}.modal-fullscreen-xxl-down .modal-body{overflow-y:auto}}.tooltip{position:absolute;z-index:1080;display:block;margin:0;font-family:var(--bs-font-sans-serif);font-style:normal;font-weight:400;line-height:1.7;text-align:left;text-align:start;text-decoration:none;text-shadow:none;text-transform:none;letter-spacing:normal;word-break:normal;word-spacing:normal;white-space:normal;line-break:auto;font-size:0.875rem;word-wrap:break-word;opacity:0}.tooltip.show{opacity:.9}.tooltip .tooltip-arrow{position:absolute;display:block;width:.8rem;height:.4rem}.tooltip .tooltip-arrow::before{position:absolute;content:"";border-color:rgba(0,0,0,0);border-style:solid}.bs-tooltip-top,.bs-tooltip-auto[data-popper-placement^=top]{padding:.4rem 0}.bs-tooltip-top .tooltip-arrow,.bs-tooltip-auto[data-popper-placement^=top] .tooltip-arrow{bottom:0}.bs-tooltip-top .tooltip-arrow::before,.bs-tooltip-auto[data-popper-placement^=top] .tooltip-arrow::before{top:-1px;border-width:.4rem .4rem 0;border-top-color:#000}.bs-tooltip-end,.bs-tooltip-auto[data-popper-placement^=right]{padding:0 .4rem}.bs-tooltip-end .tooltip-arrow,.bs-tooltip-auto[data-popper-placement^=right] .tooltip-arrow{left:0;width:.4rem;height:.8rem}.bs-tooltip-end .tooltip-arrow::before,.bs-tooltip-auto[data-popper-placement^=right] .tooltip-arrow::before{right:-1px;border-width:.4rem .4rem .4rem 0;border-right-color:#000}.bs-tooltip-bottom,.bs-tooltip-auto[data-popper-placement^=bottom]{padding:.4rem 0}.bs-tooltip-bottom .tooltip-arrow,.bs-tooltip-auto[data-popper-placement^=bottom] .tooltip-arrow{top:0}.bs-tooltip-bottom .tooltip-arrow::before,.bs-tooltip-auto[data-popper-placement^=bottom] .tooltip-arrow::before{bottom:-1px;border-width:0 .4rem .4rem;border-bottom-color:#000}.bs-tooltip-start,.bs-tooltip-auto[data-popper-placement^=left]{padding:0 .4rem}.bs-tooltip-start .tooltip-arrow,.bs-tooltip-auto[data-popper-placement^=left] .tooltip-arrow{right:0;width:.4rem;height:.8rem}.bs-tooltip-start .tooltip-arrow::before,.bs-tooltip-auto[data-popper-placement^=left] .tooltip-arrow::before{left:-1px;border-width:.4rem 0 .4rem .4rem;border-left-color:#000}.tooltip-inner{max-width:200px;padding:.25rem .5rem;color:#fff;text-align:center;background-color:#000}.popover{position:absolute;top:0;left:0 /* rtl:ignore */;z-index:1070;display:block;max-width:276px;font-family:var(--bs-font-sans-serif);font-style:normal;font-weight:400;line-height:1.7;text-align:left;text-align:start;text-decoration:none;text-shadow:none;text-transform:none;letter-spacing:normal;word-break:normal;word-spacing:normal;white-space:normal;line-break:auto;font-size:0.875rem;word-wrap:break-word;background-color:#fff;background-clip:padding-box;border:1px solid rgba(0,0,0,.2)}.popover .popover-arrow{position:absolute;display:block;width:1rem;height:.5rem}.popover .popover-arrow::before,.popover .popover-arrow::after{position:absolute;display:block;content:"";border-color:rgba(0,0,0,0);border-style:solid}.bs-popover-top>.popover-arrow,.bs-popover-auto[data-popper-placement^=top]>.popover-arrow{bottom:calc(-0.5rem - 1px)}.bs-popover-top>.popover-arrow::before,.bs-popover-auto[data-popper-placement^=top]>.popover-arrow::before{bottom:0;border-width:.5rem .5rem 0;border-top-color:rgba(0,0,0,.25)}.bs-popover-top>.popover-arrow::after,.bs-popover-auto[data-popper-placement^=top]>.popover-arrow::after{bottom:1px;border-width:.5rem .5rem 0;border-top-color:#fff}.bs-popover-end>.popover-arrow,.bs-popover-auto[data-popper-placement^=right]>.popover-arrow{left:calc(-0.5rem - 1px);width:.5rem;height:1rem}.bs-popover-end>.popover-arrow::before,.bs-popover-auto[data-popper-placement^=right]>.popover-arrow::before{left:0;border-width:.5rem .5rem .5rem 0;border-right-color:rgba(0,0,0,.25)}.bs-popover-end>.popover-arrow::after,.bs-popover-auto[data-popper-placement^=right]>.popover-arrow::after{left:1px;border-width:.5rem .5rem .5rem 0;border-right-color:#fff}.bs-popover-bottom>.popover-arrow,.bs-popover-auto[data-popper-placement^=bottom]>.popover-arrow{top:calc(-0.5rem - 1px)}.bs-popover-bottom>.popover-arrow::before,.bs-popover-auto[data-popper-placement^=bottom]>.popover-arrow::before{top:0;border-width:0 .5rem .5rem .5rem;border-bottom-color:rgba(0,0,0,.25)}.bs-popover-bottom>.popover-arrow::after,.bs-popover-auto[data-popper-placement^=bottom]>.popover-arrow::after{top:1px;border-width:0 .5rem .5rem .5rem;border-bottom-color:#fff}.bs-popover-bottom .popover-header::before,.bs-popover-auto[data-popper-placement^=bottom] .popover-header::before{position:absolute;top:0;left:50%;display:block;width:1rem;margin-left:-0.5rem;content:"";border-bottom:1px solid #f0f0f0}.bs-popover-start>.popover-arrow,.bs-popover-auto[data-popper-placement^=left]>.popover-arrow{right:calc(-0.5rem - 1px);width:.5rem;height:1rem}.bs-popover-start>.popover-arrow::before,.bs-popover-auto[data-popper-placement^=left]>.popover-arrow::before{right:0;border-width:.5rem 0 .5rem .5rem;border-left-color:rgba(0,0,0,.25)}.bs-popover-start>.popover-arrow::after,.bs-popover-auto[data-popper-placement^=left]>.popover-arrow::after{right:1px;border-width:.5rem 0 .5rem .5rem;border-left-color:#fff}.popover-header{padding:.5rem 1rem;margin-bottom:0;font-size:1rem;background-color:#f0f0f0;border-bottom:1px solid rgba(0,0,0,.2)}.popover-header:empty{display:none}.popover-body{padding:1rem 1rem;color:#373a3c}.carousel{position:relative}.carousel.pointer-event{touch-action:pan-y;-webkit-touch-action:pan-y;-moz-touch-action:pan-y;-ms-touch-action:pan-y;-o-touch-action:pan-y}.carousel-inner{position:relative;width:100%;overflow:hidden}.carousel-inner::after{display:block;clear:both;content:""}.carousel-item{position:relative;display:none;float:left;width:100%;margin-right:-100%;backface-visibility:hidden;-webkit-backface-visibility:hidden;-moz-backface-visibility:hidden;-ms-backface-visibility:hidden;-o-backface-visibility:hidden;transition:transform .6s ease-in-out}@media(prefers-reduced-motion: reduce){.carousel-item{transition:none}}.carousel-item.active,.carousel-item-next,.carousel-item-prev{display:block}.carousel-item-next:not(.carousel-item-start),.active.carousel-item-end{transform:translateX(100%)}.carousel-item-prev:not(.carousel-item-end),.active.carousel-item-start{transform:translateX(-100%)}.carousel-fade .carousel-item{opacity:0;transition-property:opacity;transform:none}.carousel-fade .carousel-item.active,.carousel-fade .carousel-item-next.carousel-item-start,.carousel-fade .carousel-item-prev.carousel-item-end{z-index:1;opacity:1}.carousel-fade .active.carousel-item-start,.carousel-fade .active.carousel-item-end{z-index:0;opacity:0;transition:opacity 0s .6s}@media(prefers-reduced-motion: reduce){.carousel-fade .active.carousel-item-start,.carousel-fade .active.carousel-item-end{transition:none}}.carousel-control-prev,.carousel-control-next{position:absolute;top:0;bottom:0;z-index:1;display:flex;display:-webkit-flex;align-items:center;-webkit-align-items:center;justify-content:center;-webkit-justify-content:center;width:15%;padding:0;color:#fff;text-align:center;background:none;border:0;opacity:.5;transition:opacity .15s ease}@media(prefers-reduced-motion: reduce){.carousel-control-prev,.carousel-control-next{transition:none}}.carousel-control-prev:hover,.carousel-control-prev:focus,.carousel-control-next:hover,.carousel-control-next:focus{color:#fff;text-decoration:none;outline:0;opacity:.9}.carousel-control-prev{left:0}.carousel-control-next{right:0}.carousel-control-prev-icon,.carousel-control-next-icon{display:inline-block;width:2rem;height:2rem;background-repeat:no-repeat;background-position:50%;background-size:100% 100%}.carousel-control-prev-icon{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23fff'%3e%3cpath d='M11.354 1.646a.5.5 0 0 1 0 .708L5.707 8l5.647 5.646a.5.5 0 0 1-.708.708l-6-6a.5.5 0 0 1 0-.708l6-6a.5.5 0 0 1 .708 0z'/%3e%3c/svg%3e")}.carousel-control-next-icon{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23fff'%3e%3cpath d='M4.646 1.646a.5.5 0 0 1 .708 0l6 6a.5.5 0 0 1 0 .708l-6 6a.5.5 0 0 1-.708-.708L10.293 8 4.646 2.354a.5.5 0 0 1 0-.708z'/%3e%3c/svg%3e")}.carousel-indicators{position:absolute;right:0;bottom:0;left:0;z-index:2;display:flex;display:-webkit-flex;justify-content:center;-webkit-justify-content:center;padding:0;margin-right:15%;margin-bottom:1rem;margin-left:15%;list-style:none}.carousel-indicators [data-bs-target]{box-sizing:content-box;flex:0 1 auto;-webkit-flex:0 1 auto;width:30px;height:3px;padding:0;margin-right:3px;margin-left:3px;text-indent:-999px;cursor:pointer;background-color:#fff;background-clip:padding-box;border:0;border-top:10px solid rgba(0,0,0,0);border-bottom:10px solid rgba(0,0,0,0);opacity:.5;transition:opacity .6s ease}@media(prefers-reduced-motion: reduce){.carousel-indicators [data-bs-target]{transition:none}}.carousel-indicators .active{opacity:1}.carousel-caption{position:absolute;right:15%;bottom:1.25rem;left:15%;padding-top:1.25rem;padding-bottom:1.25rem;color:#fff;text-align:center}.carousel-dark .carousel-control-prev-icon,.carousel-dark .carousel-control-next-icon{filter:invert(1) grayscale(100)}.carousel-dark .carousel-indicators [data-bs-target]{background-color:#000}.carousel-dark .carousel-caption{color:#000}@keyframes spinner-border{to{transform:rotate(360deg) /* rtl:ignore */}}.spinner-border{display:inline-block;width:2rem;height:2rem;vertical-align:-0.125em;border:.25em solid currentColor;border-right-color:rgba(0,0,0,0);border-radius:50%;animation:.75s linear infinite spinner-border}.spinner-border-sm{width:1rem;height:1rem;border-width:.2em}@keyframes spinner-grow{0%{transform:scale(0)}50%{opacity:1;transform:none}}.spinner-grow{display:inline-block;width:2rem;height:2rem;vertical-align:-0.125em;background-color:currentColor;border-radius:50%;opacity:0;animation:.75s linear infinite spinner-grow}.spinner-grow-sm{width:1rem;height:1rem}@media(prefers-reduced-motion: reduce){.spinner-border,.spinner-grow{animation-duration:1.5s;-webkit-animation-duration:1.5s;-moz-animation-duration:1.5s;-ms-animation-duration:1.5s;-o-animation-duration:1.5s}}.offcanvas{position:fixed;bottom:0;z-index:1045;display:flex;display:-webkit-flex;flex-direction:column;-webkit-flex-direction:column;max-width:100%;visibility:hidden;background-color:#fff;background-clip:padding-box;outline:0;transition:transform .3s ease-in-out}@media(prefers-reduced-motion: reduce){.offcanvas{transition:none}}.offcanvas-backdrop{position:fixed;top:0;left:0;z-index:1040;width:100vw;height:100vh;background-color:#000}.offcanvas-backdrop.fade{opacity:0}.offcanvas-backdrop.show{opacity:.5}.offcanvas-header{display:flex;display:-webkit-flex;align-items:center;-webkit-align-items:center;justify-content:space-between;-webkit-justify-content:space-between;padding:1rem 1rem}.offcanvas-header .btn-close{padding:.5rem .5rem;margin-top:-0.5rem;margin-right:-0.5rem;margin-bottom:-0.5rem}.offcanvas-title{margin-bottom:0;line-height:1.5}.offcanvas-body{flex-grow:1;-webkit-flex-grow:1;padding:1rem 1rem;overflow-y:auto}.offcanvas-start{top:0;left:0;width:400px;border-right:1px solid rgba(0,0,0,.2);transform:translateX(-100%)}.offcanvas-end{top:0;right:0;width:400px;border-left:1px solid rgba(0,0,0,.2);transform:translateX(100%)}.offcanvas-top{top:0;right:0;left:0;height:30vh;max-height:100%;border-bottom:1px solid rgba(0,0,0,.2);transform:translateY(-100%)}.offcanvas-bottom{right:0;left:0;height:30vh;max-height:100%;border-top:1px solid rgba(0,0,0,.2);transform:translateY(100%)}.offcanvas.show{transform:none}.placeholder{display:inline-block;min-height:1em;vertical-align:middle;cursor:wait;background-color:currentColor;opacity:.5}.placeholder.btn::before{display:inline-block;content:""}.placeholder-xs{min-height:.6em}.placeholder-sm{min-height:.8em}.placeholder-lg{min-height:1.2em}.placeholder-glow .placeholder{animation:placeholder-glow 2s ease-in-out infinite}@keyframes placeholder-glow{50%{opacity:.2}}.placeholder-wave{mask-image:linear-gradient(130deg, #000 55%, rgba(0, 0, 0, 0.8) 75%, #000 95%);-webkit-mask-image:linear-gradient(130deg, #000 55%, rgba(0, 0, 0, 0.8) 75%, #000 95%);mask-size:200% 100%;-webkit-mask-size:200% 100%;animation:placeholder-wave 2s linear infinite}@keyframes placeholder-wave{100%{mask-position:-200% 0%;-webkit-mask-position:-200% 0%}}.clearfix::after{display:block;clear:both;content:""}.link-default{color:#373a3c}.link-default:hover,.link-default:focus{color:#2c2e30}.link-primary{color:#2780e3}.link-primary:hover,.link-primary:focus{color:#1f66b6}.link-secondary{color:#373a3c}.link-secondary:hover,.link-secondary:focus{color:#2c2e30}.link-success{color:#3fb618}.link-success:hover,.link-success:focus{color:#329213}.link-info{color:#9954bb}.link-info:hover,.link-info:focus{color:#7a4396}.link-warning{color:#ff7518}.link-warning:hover,.link-warning:focus{color:#cc5e13}.link-danger{color:#ff0039}.link-danger:hover,.link-danger:focus{color:#cc002e}.link-light{color:#f8f9fa}.link-light:hover,.link-light:focus{color:#f9fafb}.link-dark{color:#373a3c}.link-dark:hover,.link-dark:focus{color:#2c2e30}.ratio{position:relative;width:100%}.ratio::before{display:block;padding-top:var(--bs-aspect-ratio);content:""}.ratio>*{position:absolute;top:0;left:0;width:100%;height:100%}.ratio-1x1{--bs-aspect-ratio: 100%}.ratio-4x3{--bs-aspect-ratio: 75%}.ratio-16x9{--bs-aspect-ratio: 56.25%}.ratio-21x9{--bs-aspect-ratio: 42.8571428571%}.fixed-top{position:fixed;top:0;right:0;left:0;z-index:1030}.fixed-bottom{position:fixed;right:0;bottom:0;left:0;z-index:1030}.sticky-top{position:sticky;top:0;z-index:1020}@media(min-width: 576px){.sticky-sm-top{position:sticky;top:0;z-index:1020}}@media(min-width: 768px){.sticky-md-top{position:sticky;top:0;z-index:1020}}@media(min-width: 992px){.sticky-lg-top{position:sticky;top:0;z-index:1020}}@media(min-width: 1200px){.sticky-xl-top{position:sticky;top:0;z-index:1020}}@media(min-width: 1400px){.sticky-xxl-top{position:sticky;top:0;z-index:1020}}.hstack{display:flex;display:-webkit-flex;flex-direction:row;-webkit-flex-direction:row;align-items:center;-webkit-align-items:center;align-self:stretch;-webkit-align-self:stretch}.vstack{display:flex;display:-webkit-flex;flex:1 1 auto;-webkit-flex:1 1 auto;flex-direction:column;-webkit-flex-direction:column;align-self:stretch;-webkit-align-self:stretch}.visually-hidden,.visually-hidden-focusable:not(:focus):not(:focus-within){position:absolute !important;width:1px !important;height:1px !important;padding:0 !important;margin:-1px !important;overflow:hidden !important;clip:rect(0, 0, 0, 0) !important;white-space:nowrap !important;border:0 !important}.stretched-link::after{position:absolute;top:0;right:0;bottom:0;left:0;z-index:1;content:""}.text-truncate{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.vr{display:inline-block;align-self:stretch;-webkit-align-self:stretch;width:1px;min-height:1em;background-color:currentColor;opacity:.25}.align-baseline{vertical-align:baseline !important}.align-top{vertical-align:top !important}.align-middle{vertical-align:middle !important}.align-bottom{vertical-align:bottom !important}.align-text-bottom{vertical-align:text-bottom !important}.align-text-top{vertical-align:text-top !important}.float-start{float:left !important}.float-end{float:right !important}.float-none{float:none !important}.opacity-0{opacity:0 !important}.opacity-25{opacity:.25 !important}.opacity-50{opacity:.5 !important}.opacity-75{opacity:.75 !important}.opacity-100{opacity:1 !important}.overflow-auto{overflow:auto !important}.overflow-hidden{overflow:hidden !important}.overflow-visible{overflow:visible !important}.overflow-scroll{overflow:scroll !important}.d-inline{display:inline !important}.d-inline-block{display:inline-block !important}.d-block{display:block !important}.d-grid{display:grid !important}.d-table{display:table !important}.d-table-row{display:table-row !important}.d-table-cell{display:table-cell !important}.d-flex{display:flex !important}.d-inline-flex{display:inline-flex !important}.d-none{display:none !important}.shadow{box-shadow:0 .5rem 1rem rgba(0,0,0,.15) !important}.shadow-sm{box-shadow:0 .125rem .25rem rgba(0,0,0,.075) !important}.shadow-lg{box-shadow:0 1rem 3rem rgba(0,0,0,.175) !important}.shadow-none{box-shadow:none !important}.position-static{position:static !important}.position-relative{position:relative !important}.position-absolute{position:absolute !important}.position-fixed{position:fixed !important}.position-sticky{position:sticky !important}.top-0{top:0 !important}.top-50{top:50% !important}.top-100{top:100% !important}.bottom-0{bottom:0 !important}.bottom-50{bottom:50% !important}.bottom-100{bottom:100% !important}.start-0{left:0 !important}.start-50{left:50% !important}.start-100{left:100% !important}.end-0{right:0 !important}.end-50{right:50% !important}.end-100{right:100% !important}.translate-middle{transform:translate(-50%, -50%) !important}.translate-middle-x{transform:translateX(-50%) !important}.translate-middle-y{transform:translateY(-50%) !important}.border{border:1px solid #dee2e6 !important}.border-0{border:0 !important}.border-top{border-top:1px solid #dee2e6 !important}.border-top-0{border-top:0 !important}.border-end{border-right:1px solid #dee2e6 !important}.border-end-0{border-right:0 !important}.border-bottom{border-bottom:1px solid #dee2e6 !important}.border-bottom-0{border-bottom:0 !important}.border-start{border-left:1px solid #dee2e6 !important}.border-start-0{border-left:0 !important}.border-default{border-color:#373a3c !important}.border-primary{border-color:#2780e3 !important}.border-secondary{border-color:#373a3c !important}.border-success{border-color:#3fb618 !important}.border-info{border-color:#9954bb !important}.border-warning{border-color:#ff7518 !important}.border-danger{border-color:#ff0039 !important}.border-light{border-color:#f8f9fa !important}.border-dark{border-color:#373a3c !important}.border-white{border-color:#fff !important}.border-1{border-width:1px !important}.border-2{border-width:2px !important}.border-3{border-width:3px !important}.border-4{border-width:4px !important}.border-5{border-width:5px !important}.w-25{width:25% !important}.w-50{width:50% !important}.w-75{width:75% !important}.w-100{width:100% !important}.w-auto{width:auto !important}.mw-100{max-width:100% !important}.vw-100{width:100vw !important}.min-vw-100{min-width:100vw !important}.h-25{height:25% !important}.h-50{height:50% !important}.h-75{height:75% !important}.h-100{height:100% !important}.h-auto{height:auto !important}.mh-100{max-height:100% !important}.vh-100{height:100vh !important}.min-vh-100{min-height:100vh !important}.flex-fill{flex:1 1 auto !important}.flex-row{flex-direction:row !important}.flex-column{flex-direction:column !important}.flex-row-reverse{flex-direction:row-reverse !important}.flex-column-reverse{flex-direction:column-reverse !important}.flex-grow-0{flex-grow:0 !important}.flex-grow-1{flex-grow:1 !important}.flex-shrink-0{flex-shrink:0 !important}.flex-shrink-1{flex-shrink:1 !important}.flex-wrap{flex-wrap:wrap !important}.flex-nowrap{flex-wrap:nowrap !important}.flex-wrap-reverse{flex-wrap:wrap-reverse !important}.gap-0{gap:0 !important}.gap-1{gap:.25rem !important}.gap-2{gap:.5rem !important}.gap-3{gap:1rem !important}.gap-4{gap:1.5rem !important}.gap-5{gap:3rem !important}.justify-content-start{justify-content:flex-start !important}.justify-content-end{justify-content:flex-end !important}.justify-content-center{justify-content:center !important}.justify-content-between{justify-content:space-between !important}.justify-content-around{justify-content:space-around !important}.justify-content-evenly{justify-content:space-evenly !important}.align-items-start{align-items:flex-start !important}.align-items-end{align-items:flex-end !important}.align-items-center{align-items:center !important}.align-items-baseline{align-items:baseline !important}.align-items-stretch{align-items:stretch !important}.align-content-start{align-content:flex-start !important}.align-content-end{align-content:flex-end !important}.align-content-center{align-content:center !important}.align-content-between{align-content:space-between !important}.align-content-around{align-content:space-around !important}.align-content-stretch{align-content:stretch !important}.align-self-auto{align-self:auto !important}.align-self-start{align-self:flex-start !important}.align-self-end{align-self:flex-end !important}.align-self-center{align-self:center !important}.align-self-baseline{align-self:baseline !important}.align-self-stretch{align-self:stretch !important}.order-first{order:-1 !important}.order-0{order:0 !important}.order-1{order:1 !important}.order-2{order:2 !important}.order-3{order:3 !important}.order-4{order:4 !important}.order-5{order:5 !important}.order-last{order:6 !important}.m-0{margin:0 !important}.m-1{margin:.25rem !important}.m-2{margin:.5rem !important}.m-3{margin:1rem !important}.m-4{margin:1.5rem !important}.m-5{margin:3rem !important}.m-auto{margin:auto !important}.mx-0{margin-right:0 !important;margin-left:0 !important}.mx-1{margin-right:.25rem !important;margin-left:.25rem !important}.mx-2{margin-right:.5rem !important;margin-left:.5rem !important}.mx-3{margin-right:1rem !important;margin-left:1rem !important}.mx-4{margin-right:1.5rem !important;margin-left:1.5rem !important}.mx-5{margin-right:3rem !important;margin-left:3rem !important}.mx-auto{margin-right:auto !important;margin-left:auto !important}.my-0{margin-top:0 !important;margin-bottom:0 !important}.my-1{margin-top:.25rem !important;margin-bottom:.25rem !important}.my-2{margin-top:.5rem !important;margin-bottom:.5rem !important}.my-3{margin-top:1rem !important;margin-bottom:1rem !important}.my-4{margin-top:1.5rem !important;margin-bottom:1.5rem !important}.my-5{margin-top:3rem !important;margin-bottom:3rem !important}.my-auto{margin-top:auto !important;margin-bottom:auto !important}.mt-0{margin-top:0 !important}.mt-1{margin-top:.25rem !important}.mt-2{margin-top:.5rem !important}.mt-3{margin-top:1rem !important}.mt-4{margin-top:1.5rem !important}.mt-5{margin-top:3rem !important}.mt-auto{margin-top:auto !important}.me-0{margin-right:0 !important}.me-1{margin-right:.25rem !important}.me-2{margin-right:.5rem !important}.me-3{margin-right:1rem !important}.me-4{margin-right:1.5rem !important}.me-5{margin-right:3rem !important}.me-auto{margin-right:auto !important}.mb-0{margin-bottom:0 !important}.mb-1{margin-bottom:.25rem !important}.mb-2{margin-bottom:.5rem !important}.mb-3{margin-bottom:1rem !important}.mb-4{margin-bottom:1.5rem !important}.mb-5{margin-bottom:3rem !important}.mb-auto{margin-bottom:auto !important}.ms-0{margin-left:0 !important}.ms-1{margin-left:.25rem !important}.ms-2{margin-left:.5rem !important}.ms-3{margin-left:1rem !important}.ms-4{margin-left:1.5rem !important}.ms-5{margin-left:3rem !important}.ms-auto{margin-left:auto !important}.p-0{padding:0 !important}.p-1{padding:.25rem !important}.p-2{padding:.5rem !important}.p-3{padding:1rem !important}.p-4{padding:1.5rem !important}.p-5{padding:3rem !important}.px-0{padding-right:0 !important;padding-left:0 !important}.px-1{padding-right:.25rem !important;padding-left:.25rem !important}.px-2{padding-right:.5rem !important;padding-left:.5rem !important}.px-3{padding-right:1rem !important;padding-left:1rem !important}.px-4{padding-right:1.5rem !important;padding-left:1.5rem !important}.px-5{padding-right:3rem !important;padding-left:3rem !important}.py-0{padding-top:0 !important;padding-bottom:0 !important}.py-1{padding-top:.25rem !important;padding-bottom:.25rem !important}.py-2{padding-top:.5rem !important;padding-bottom:.5rem !important}.py-3{padding-top:1rem !important;padding-bottom:1rem !important}.py-4{padding-top:1.5rem !important;padding-bottom:1.5rem !important}.py-5{padding-top:3rem !important;padding-bottom:3rem !important}.pt-0{padding-top:0 !important}.pt-1{padding-top:.25rem !important}.pt-2{padding-top:.5rem !important}.pt-3{padding-top:1rem !important}.pt-4{padding-top:1.5rem !important}.pt-5{padding-top:3rem !important}.pe-0{padding-right:0 !important}.pe-1{padding-right:.25rem !important}.pe-2{padding-right:.5rem !important}.pe-3{padding-right:1rem !important}.pe-4{padding-right:1.5rem !important}.pe-5{padding-right:3rem !important}.pb-0{padding-bottom:0 !important}.pb-1{padding-bottom:.25rem !important}.pb-2{padding-bottom:.5rem !important}.pb-3{padding-bottom:1rem !important}.pb-4{padding-bottom:1.5rem !important}.pb-5{padding-bottom:3rem !important}.ps-0{padding-left:0 !important}.ps-1{padding-left:.25rem !important}.ps-2{padding-left:.5rem !important}.ps-3{padding-left:1rem !important}.ps-4{padding-left:1.5rem !important}.ps-5{padding-left:3rem !important}.font-monospace{font-family:var(--bs-font-monospace) !important}.fs-1{font-size:calc(1.325rem + 0.9vw) !important}.fs-2{font-size:calc(1.29rem + 0.48vw) !important}.fs-3{font-size:calc(1.27rem + 0.24vw) !important}.fs-4{font-size:1.25rem !important}.fs-5{font-size:1.1rem !important}.fs-6{font-size:1rem !important}.fst-italic{font-style:italic !important}.fst-normal{font-style:normal !important}.fw-light{font-weight:300 !important}.fw-lighter{font-weight:lighter !important}.fw-normal{font-weight:400 !important}.fw-bold{font-weight:700 !important}.fw-bolder{font-weight:bolder !important}.lh-1{line-height:1 !important}.lh-sm{line-height:1.25 !important}.lh-base{line-height:1.7 !important}.lh-lg{line-height:2 !important}.text-start{text-align:left !important}.text-end{text-align:right !important}.text-center{text-align:center !important}.text-decoration-none{text-decoration:none !important}.text-decoration-underline{text-decoration:underline !important}.text-decoration-line-through{text-decoration:line-through !important}.text-lowercase{text-transform:lowercase !important}.text-uppercase{text-transform:uppercase !important}.text-capitalize{text-transform:capitalize !important}.text-wrap{white-space:normal !important}.text-nowrap{white-space:nowrap !important}.text-break{word-wrap:break-word !important;word-break:break-word !important}.text-default{--bs-text-opacity: 1;color:rgba(var(--bs-default-rgb), var(--bs-text-opacity)) !important}.text-primary{--bs-text-opacity: 1;color:rgba(var(--bs-primary-rgb), var(--bs-text-opacity)) !important}.text-secondary{--bs-text-opacity: 1;color:rgba(var(--bs-secondary-rgb), var(--bs-text-opacity)) !important}.text-success{--bs-text-opacity: 1;color:rgba(var(--bs-success-rgb), var(--bs-text-opacity)) !important}.text-info{--bs-text-opacity: 1;color:rgba(var(--bs-info-rgb), var(--bs-text-opacity)) !important}.text-warning{--bs-text-opacity: 1;color:rgba(var(--bs-warning-rgb), var(--bs-text-opacity)) !important}.text-danger{--bs-text-opacity: 1;color:rgba(var(--bs-danger-rgb), var(--bs-text-opacity)) !important}.text-light{--bs-text-opacity: 1;color:rgba(var(--bs-light-rgb), var(--bs-text-opacity)) !important}.text-dark{--bs-text-opacity: 1;color:rgba(var(--bs-dark-rgb), var(--bs-text-opacity)) !important}.text-black{--bs-text-opacity: 1;color:rgba(var(--bs-black-rgb), var(--bs-text-opacity)) !important}.text-white{--bs-text-opacity: 1;color:rgba(var(--bs-white-rgb), var(--bs-text-opacity)) !important}.text-body{--bs-text-opacity: 1;color:rgba(var(--bs-body-color-rgb), var(--bs-text-opacity)) !important}.text-muted{--bs-text-opacity: 1;color:#6c757d !important}.text-black-50{--bs-text-opacity: 1;color:rgba(0,0,0,.5) !important}.text-white-50{--bs-text-opacity: 1;color:rgba(255,255,255,.5) !important}.text-reset{--bs-text-opacity: 1;color:inherit !important}.text-opacity-25{--bs-text-opacity: 0.25}.text-opacity-50{--bs-text-opacity: 0.5}.text-opacity-75{--bs-text-opacity: 0.75}.text-opacity-100{--bs-text-opacity: 1}.bg-default{--bs-bg-opacity: 1;background-color:rgba(var(--bs-default-rgb), var(--bs-bg-opacity)) !important}.bg-primary{--bs-bg-opacity: 1;background-color:rgba(var(--bs-primary-rgb), var(--bs-bg-opacity)) !important}.bg-secondary{--bs-bg-opacity: 1;background-color:rgba(var(--bs-secondary-rgb), var(--bs-bg-opacity)) !important}.bg-success{--bs-bg-opacity: 1;background-color:rgba(var(--bs-success-rgb), var(--bs-bg-opacity)) !important}.bg-info{--bs-bg-opacity: 1;background-color:rgba(var(--bs-info-rgb), var(--bs-bg-opacity)) !important}.bg-warning{--bs-bg-opacity: 1;background-color:rgba(var(--bs-warning-rgb), var(--bs-bg-opacity)) !important}.bg-danger{--bs-bg-opacity: 1;background-color:rgba(var(--bs-danger-rgb), var(--bs-bg-opacity)) !important}.bg-light{--bs-bg-opacity: 1;background-color:rgba(var(--bs-light-rgb), var(--bs-bg-opacity)) !important}.bg-dark{--bs-bg-opacity: 1;background-color:rgba(var(--bs-dark-rgb), var(--bs-bg-opacity)) !important}.bg-black{--bs-bg-opacity: 1;background-color:rgba(var(--bs-black-rgb), var(--bs-bg-opacity)) !important}.bg-white{--bs-bg-opacity: 1;background-color:rgba(var(--bs-white-rgb), var(--bs-bg-opacity)) !important}.bg-body{--bs-bg-opacity: 1;background-color:rgba(var(--bs-body-bg-rgb), var(--bs-bg-opacity)) !important}.bg-transparent{--bs-bg-opacity: 1;background-color:rgba(0,0,0,0) !important}.bg-opacity-10{--bs-bg-opacity: 0.1}.bg-opacity-25{--bs-bg-opacity: 0.25}.bg-opacity-50{--bs-bg-opacity: 0.5}.bg-opacity-75{--bs-bg-opacity: 0.75}.bg-opacity-100{--bs-bg-opacity: 1}.bg-gradient{background-image:var(--bs-gradient) !important}.user-select-all{user-select:all !important}.user-select-auto{user-select:auto !important}.user-select-none{user-select:none !important}.pe-none{pointer-events:none !important}.pe-auto{pointer-events:auto !important}.rounded{border-radius:.25rem !important}.rounded-0{border-radius:0 !important}.rounded-1{border-radius:.2em !important}.rounded-2{border-radius:.25rem !important}.rounded-3{border-radius:.3rem !important}.rounded-circle{border-radius:50% !important}.rounded-pill{border-radius:50rem !important}.rounded-top{border-top-left-radius:.25rem !important;border-top-right-radius:.25rem !important}.rounded-end{border-top-right-radius:.25rem !important;border-bottom-right-radius:.25rem !important}.rounded-bottom{border-bottom-right-radius:.25rem !important;border-bottom-left-radius:.25rem !important}.rounded-start{border-bottom-left-radius:.25rem !important;border-top-left-radius:.25rem !important}.visible{visibility:visible !important}.invisible{visibility:hidden !important}@media(min-width: 576px){.float-sm-start{float:left !important}.float-sm-end{float:right !important}.float-sm-none{float:none !important}.d-sm-inline{display:inline !important}.d-sm-inline-block{display:inline-block !important}.d-sm-block{display:block !important}.d-sm-grid{display:grid !important}.d-sm-table{display:table !important}.d-sm-table-row{display:table-row !important}.d-sm-table-cell{display:table-cell !important}.d-sm-flex{display:flex !important}.d-sm-inline-flex{display:inline-flex !important}.d-sm-none{display:none !important}.flex-sm-fill{flex:1 1 auto !important}.flex-sm-row{flex-direction:row !important}.flex-sm-column{flex-direction:column !important}.flex-sm-row-reverse{flex-direction:row-reverse !important}.flex-sm-column-reverse{flex-direction:column-reverse !important}.flex-sm-grow-0{flex-grow:0 !important}.flex-sm-grow-1{flex-grow:1 !important}.flex-sm-shrink-0{flex-shrink:0 !important}.flex-sm-shrink-1{flex-shrink:1 !important}.flex-sm-wrap{flex-wrap:wrap !important}.flex-sm-nowrap{flex-wrap:nowrap !important}.flex-sm-wrap-reverse{flex-wrap:wrap-reverse !important}.gap-sm-0{gap:0 !important}.gap-sm-1{gap:.25rem !important}.gap-sm-2{gap:.5rem !important}.gap-sm-3{gap:1rem !important}.gap-sm-4{gap:1.5rem !important}.gap-sm-5{gap:3rem !important}.justify-content-sm-start{justify-content:flex-start !important}.justify-content-sm-end{justify-content:flex-end !important}.justify-content-sm-center{justify-content:center !important}.justify-content-sm-between{justify-content:space-between !important}.justify-content-sm-around{justify-content:space-around !important}.justify-content-sm-evenly{justify-content:space-evenly !important}.align-items-sm-start{align-items:flex-start !important}.align-items-sm-end{align-items:flex-end !important}.align-items-sm-center{align-items:center !important}.align-items-sm-baseline{align-items:baseline !important}.align-items-sm-stretch{align-items:stretch !important}.align-content-sm-start{align-content:flex-start !important}.align-content-sm-end{align-content:flex-end !important}.align-content-sm-center{align-content:center !important}.align-content-sm-between{align-content:space-between !important}.align-content-sm-around{align-content:space-around !important}.align-content-sm-stretch{align-content:stretch !important}.align-self-sm-auto{align-self:auto !important}.align-self-sm-start{align-self:flex-start !important}.align-self-sm-end{align-self:flex-end !important}.align-self-sm-center{align-self:center !important}.align-self-sm-baseline{align-self:baseline !important}.align-self-sm-stretch{align-self:stretch !important}.order-sm-first{order:-1 !important}.order-sm-0{order:0 !important}.order-sm-1{order:1 !important}.order-sm-2{order:2 !important}.order-sm-3{order:3 !important}.order-sm-4{order:4 !important}.order-sm-5{order:5 !important}.order-sm-last{order:6 !important}.m-sm-0{margin:0 !important}.m-sm-1{margin:.25rem !important}.m-sm-2{margin:.5rem !important}.m-sm-3{margin:1rem !important}.m-sm-4{margin:1.5rem !important}.m-sm-5{margin:3rem !important}.m-sm-auto{margin:auto !important}.mx-sm-0{margin-right:0 !important;margin-left:0 !important}.mx-sm-1{margin-right:.25rem !important;margin-left:.25rem !important}.mx-sm-2{margin-right:.5rem !important;margin-left:.5rem !important}.mx-sm-3{margin-right:1rem !important;margin-left:1rem !important}.mx-sm-4{margin-right:1.5rem !important;margin-left:1.5rem !important}.mx-sm-5{margin-right:3rem !important;margin-left:3rem !important}.mx-sm-auto{margin-right:auto !important;margin-left:auto !important}.my-sm-0{margin-top:0 !important;margin-bottom:0 !important}.my-sm-1{margin-top:.25rem !important;margin-bottom:.25rem !important}.my-sm-2{margin-top:.5rem !important;margin-bottom:.5rem !important}.my-sm-3{margin-top:1rem !important;margin-bottom:1rem !important}.my-sm-4{margin-top:1.5rem !important;margin-bottom:1.5rem !important}.my-sm-5{margin-top:3rem !important;margin-bottom:3rem !important}.my-sm-auto{margin-top:auto !important;margin-bottom:auto !important}.mt-sm-0{margin-top:0 !important}.mt-sm-1{margin-top:.25rem !important}.mt-sm-2{margin-top:.5rem !important}.mt-sm-3{margin-top:1rem !important}.mt-sm-4{margin-top:1.5rem !important}.mt-sm-5{margin-top:3rem !important}.mt-sm-auto{margin-top:auto !important}.me-sm-0{margin-right:0 !important}.me-sm-1{margin-right:.25rem !important}.me-sm-2{margin-right:.5rem !important}.me-sm-3{margin-right:1rem !important}.me-sm-4{margin-right:1.5rem !important}.me-sm-5{margin-right:3rem !important}.me-sm-auto{margin-right:auto !important}.mb-sm-0{margin-bottom:0 !important}.mb-sm-1{margin-bottom:.25rem !important}.mb-sm-2{margin-bottom:.5rem !important}.mb-sm-3{margin-bottom:1rem !important}.mb-sm-4{margin-bottom:1.5rem !important}.mb-sm-5{margin-bottom:3rem !important}.mb-sm-auto{margin-bottom:auto !important}.ms-sm-0{margin-left:0 !important}.ms-sm-1{margin-left:.25rem !important}.ms-sm-2{margin-left:.5rem !important}.ms-sm-3{margin-left:1rem !important}.ms-sm-4{margin-left:1.5rem !important}.ms-sm-5{margin-left:3rem !important}.ms-sm-auto{margin-left:auto !important}.p-sm-0{padding:0 !important}.p-sm-1{padding:.25rem !important}.p-sm-2{padding:.5rem !important}.p-sm-3{padding:1rem !important}.p-sm-4{padding:1.5rem !important}.p-sm-5{padding:3rem !important}.px-sm-0{padding-right:0 !important;padding-left:0 !important}.px-sm-1{padding-right:.25rem !important;padding-left:.25rem !important}.px-sm-2{padding-right:.5rem !important;padding-left:.5rem !important}.px-sm-3{padding-right:1rem !important;padding-left:1rem !important}.px-sm-4{padding-right:1.5rem !important;padding-left:1.5rem !important}.px-sm-5{padding-right:3rem !important;padding-left:3rem !important}.py-sm-0{padding-top:0 !important;padding-bottom:0 !important}.py-sm-1{padding-top:.25rem !important;padding-bottom:.25rem !important}.py-sm-2{padding-top:.5rem !important;padding-bottom:.5rem !important}.py-sm-3{padding-top:1rem !important;padding-bottom:1rem !important}.py-sm-4{padding-top:1.5rem !important;padding-bottom:1.5rem !important}.py-sm-5{padding-top:3rem !important;padding-bottom:3rem !important}.pt-sm-0{padding-top:0 !important}.pt-sm-1{padding-top:.25rem !important}.pt-sm-2{padding-top:.5rem !important}.pt-sm-3{padding-top:1rem !important}.pt-sm-4{padding-top:1.5rem !important}.pt-sm-5{padding-top:3rem !important}.pe-sm-0{padding-right:0 !important}.pe-sm-1{padding-right:.25rem !important}.pe-sm-2{padding-right:.5rem !important}.pe-sm-3{padding-right:1rem !important}.pe-sm-4{padding-right:1.5rem !important}.pe-sm-5{padding-right:3rem !important}.pb-sm-0{padding-bottom:0 !important}.pb-sm-1{padding-bottom:.25rem !important}.pb-sm-2{padding-bottom:.5rem !important}.pb-sm-3{padding-bottom:1rem !important}.pb-sm-4{padding-bottom:1.5rem !important}.pb-sm-5{padding-bottom:3rem !important}.ps-sm-0{padding-left:0 !important}.ps-sm-1{padding-left:.25rem !important}.ps-sm-2{padding-left:.5rem !important}.ps-sm-3{padding-left:1rem !important}.ps-sm-4{padding-left:1.5rem !important}.ps-sm-5{padding-left:3rem !important}.text-sm-start{text-align:left !important}.text-sm-end{text-align:right !important}.text-sm-center{text-align:center !important}}@media(min-width: 768px){.float-md-start{float:left !important}.float-md-end{float:right !important}.float-md-none{float:none !important}.d-md-inline{display:inline !important}.d-md-inline-block{display:inline-block !important}.d-md-block{display:block !important}.d-md-grid{display:grid !important}.d-md-table{display:table !important}.d-md-table-row{display:table-row !important}.d-md-table-cell{display:table-cell !important}.d-md-flex{display:flex !important}.d-md-inline-flex{display:inline-flex !important}.d-md-none{display:none !important}.flex-md-fill{flex:1 1 auto !important}.flex-md-row{flex-direction:row !important}.flex-md-column{flex-direction:column !important}.flex-md-row-reverse{flex-direction:row-reverse !important}.flex-md-column-reverse{flex-direction:column-reverse !important}.flex-md-grow-0{flex-grow:0 !important}.flex-md-grow-1{flex-grow:1 !important}.flex-md-shrink-0{flex-shrink:0 !important}.flex-md-shrink-1{flex-shrink:1 !important}.flex-md-wrap{flex-wrap:wrap !important}.flex-md-nowrap{flex-wrap:nowrap !important}.flex-md-wrap-reverse{flex-wrap:wrap-reverse !important}.gap-md-0{gap:0 !important}.gap-md-1{gap:.25rem !important}.gap-md-2{gap:.5rem !important}.gap-md-3{gap:1rem !important}.gap-md-4{gap:1.5rem !important}.gap-md-5{gap:3rem !important}.justify-content-md-start{justify-content:flex-start !important}.justify-content-md-end{justify-content:flex-end !important}.justify-content-md-center{justify-content:center !important}.justify-content-md-between{justify-content:space-between !important}.justify-content-md-around{justify-content:space-around !important}.justify-content-md-evenly{justify-content:space-evenly !important}.align-items-md-start{align-items:flex-start !important}.align-items-md-end{align-items:flex-end !important}.align-items-md-center{align-items:center !important}.align-items-md-baseline{align-items:baseline !important}.align-items-md-stretch{align-items:stretch !important}.align-content-md-start{align-content:flex-start !important}.align-content-md-end{align-content:flex-end !important}.align-content-md-center{align-content:center !important}.align-content-md-between{align-content:space-between !important}.align-content-md-around{align-content:space-around !important}.align-content-md-stretch{align-content:stretch !important}.align-self-md-auto{align-self:auto !important}.align-self-md-start{align-self:flex-start !important}.align-self-md-end{align-self:flex-end !important}.align-self-md-center{align-self:center !important}.align-self-md-baseline{align-self:baseline !important}.align-self-md-stretch{align-self:stretch !important}.order-md-first{order:-1 !important}.order-md-0{order:0 !important}.order-md-1{order:1 !important}.order-md-2{order:2 !important}.order-md-3{order:3 !important}.order-md-4{order:4 !important}.order-md-5{order:5 !important}.order-md-last{order:6 !important}.m-md-0{margin:0 !important}.m-md-1{margin:.25rem !important}.m-md-2{margin:.5rem !important}.m-md-3{margin:1rem !important}.m-md-4{margin:1.5rem !important}.m-md-5{margin:3rem !important}.m-md-auto{margin:auto !important}.mx-md-0{margin-right:0 !important;margin-left:0 !important}.mx-md-1{margin-right:.25rem !important;margin-left:.25rem !important}.mx-md-2{margin-right:.5rem !important;margin-left:.5rem !important}.mx-md-3{margin-right:1rem !important;margin-left:1rem !important}.mx-md-4{margin-right:1.5rem !important;margin-left:1.5rem !important}.mx-md-5{margin-right:3rem !important;margin-left:3rem !important}.mx-md-auto{margin-right:auto !important;margin-left:auto !important}.my-md-0{margin-top:0 !important;margin-bottom:0 !important}.my-md-1{margin-top:.25rem !important;margin-bottom:.25rem !important}.my-md-2{margin-top:.5rem !important;margin-bottom:.5rem !important}.my-md-3{margin-top:1rem !important;margin-bottom:1rem !important}.my-md-4{margin-top:1.5rem !important;margin-bottom:1.5rem !important}.my-md-5{margin-top:3rem !important;margin-bottom:3rem !important}.my-md-auto{margin-top:auto !important;margin-bottom:auto !important}.mt-md-0{margin-top:0 !important}.mt-md-1{margin-top:.25rem !important}.mt-md-2{margin-top:.5rem !important}.mt-md-3{margin-top:1rem !important}.mt-md-4{margin-top:1.5rem !important}.mt-md-5{margin-top:3rem !important}.mt-md-auto{margin-top:auto !important}.me-md-0{margin-right:0 !important}.me-md-1{margin-right:.25rem !important}.me-md-2{margin-right:.5rem !important}.me-md-3{margin-right:1rem !important}.me-md-4{margin-right:1.5rem !important}.me-md-5{margin-right:3rem !important}.me-md-auto{margin-right:auto !important}.mb-md-0{margin-bottom:0 !important}.mb-md-1{margin-bottom:.25rem !important}.mb-md-2{margin-bottom:.5rem !important}.mb-md-3{margin-bottom:1rem !important}.mb-md-4{margin-bottom:1.5rem !important}.mb-md-5{margin-bottom:3rem !important}.mb-md-auto{margin-bottom:auto !important}.ms-md-0{margin-left:0 !important}.ms-md-1{margin-left:.25rem !important}.ms-md-2{margin-left:.5rem !important}.ms-md-3{margin-left:1rem !important}.ms-md-4{margin-left:1.5rem !important}.ms-md-5{margin-left:3rem !important}.ms-md-auto{margin-left:auto !important}.p-md-0{padding:0 !important}.p-md-1{padding:.25rem !important}.p-md-2{padding:.5rem !important}.p-md-3{padding:1rem !important}.p-md-4{padding:1.5rem !important}.p-md-5{padding:3rem !important}.px-md-0{padding-right:0 !important;padding-left:0 !important}.px-md-1{padding-right:.25rem !important;padding-left:.25rem !important}.px-md-2{padding-right:.5rem !important;padding-left:.5rem !important}.px-md-3{padding-right:1rem !important;padding-left:1rem !important}.px-md-4{padding-right:1.5rem !important;padding-left:1.5rem !important}.px-md-5{padding-right:3rem !important;padding-left:3rem !important}.py-md-0{padding-top:0 !important;padding-bottom:0 !important}.py-md-1{padding-top:.25rem !important;padding-bottom:.25rem !important}.py-md-2{padding-top:.5rem !important;padding-bottom:.5rem !important}.py-md-3{padding-top:1rem !important;padding-bottom:1rem !important}.py-md-4{padding-top:1.5rem !important;padding-bottom:1.5rem !important}.py-md-5{padding-top:3rem !important;padding-bottom:3rem !important}.pt-md-0{padding-top:0 !important}.pt-md-1{padding-top:.25rem !important}.pt-md-2{padding-top:.5rem !important}.pt-md-3{padding-top:1rem !important}.pt-md-4{padding-top:1.5rem !important}.pt-md-5{padding-top:3rem !important}.pe-md-0{padding-right:0 !important}.pe-md-1{padding-right:.25rem !important}.pe-md-2{padding-right:.5rem !important}.pe-md-3{padding-right:1rem !important}.pe-md-4{padding-right:1.5rem !important}.pe-md-5{padding-right:3rem !important}.pb-md-0{padding-bottom:0 !important}.pb-md-1{padding-bottom:.25rem !important}.pb-md-2{padding-bottom:.5rem !important}.pb-md-3{padding-bottom:1rem !important}.pb-md-4{padding-bottom:1.5rem !important}.pb-md-5{padding-bottom:3rem !important}.ps-md-0{padding-left:0 !important}.ps-md-1{padding-left:.25rem !important}.ps-md-2{padding-left:.5rem !important}.ps-md-3{padding-left:1rem !important}.ps-md-4{padding-left:1.5rem !important}.ps-md-5{padding-left:3rem !important}.text-md-start{text-align:left !important}.text-md-end{text-align:right !important}.text-md-center{text-align:center !important}}@media(min-width: 992px){.float-lg-start{float:left !important}.float-lg-end{float:right !important}.float-lg-none{float:none !important}.d-lg-inline{display:inline !important}.d-lg-inline-block{display:inline-block !important}.d-lg-block{display:block !important}.d-lg-grid{display:grid !important}.d-lg-table{display:table !important}.d-lg-table-row{display:table-row !important}.d-lg-table-cell{display:table-cell !important}.d-lg-flex{display:flex !important}.d-lg-inline-flex{display:inline-flex !important}.d-lg-none{display:none !important}.flex-lg-fill{flex:1 1 auto !important}.flex-lg-row{flex-direction:row !important}.flex-lg-column{flex-direction:column !important}.flex-lg-row-reverse{flex-direction:row-reverse !important}.flex-lg-column-reverse{flex-direction:column-reverse !important}.flex-lg-grow-0{flex-grow:0 !important}.flex-lg-grow-1{flex-grow:1 !important}.flex-lg-shrink-0{flex-shrink:0 !important}.flex-lg-shrink-1{flex-shrink:1 !important}.flex-lg-wrap{flex-wrap:wrap !important}.flex-lg-nowrap{flex-wrap:nowrap !important}.flex-lg-wrap-reverse{flex-wrap:wrap-reverse !important}.gap-lg-0{gap:0 !important}.gap-lg-1{gap:.25rem !important}.gap-lg-2{gap:.5rem !important}.gap-lg-3{gap:1rem !important}.gap-lg-4{gap:1.5rem !important}.gap-lg-5{gap:3rem !important}.justify-content-lg-start{justify-content:flex-start !important}.justify-content-lg-end{justify-content:flex-end !important}.justify-content-lg-center{justify-content:center !important}.justify-content-lg-between{justify-content:space-between !important}.justify-content-lg-around{justify-content:space-around !important}.justify-content-lg-evenly{justify-content:space-evenly !important}.align-items-lg-start{align-items:flex-start !important}.align-items-lg-end{align-items:flex-end !important}.align-items-lg-center{align-items:center !important}.align-items-lg-baseline{align-items:baseline !important}.align-items-lg-stretch{align-items:stretch !important}.align-content-lg-start{align-content:flex-start !important}.align-content-lg-end{align-content:flex-end !important}.align-content-lg-center{align-content:center !important}.align-content-lg-between{align-content:space-between !important}.align-content-lg-around{align-content:space-around !important}.align-content-lg-stretch{align-content:stretch !important}.align-self-lg-auto{align-self:auto !important}.align-self-lg-start{align-self:flex-start !important}.align-self-lg-end{align-self:flex-end !important}.align-self-lg-center{align-self:center !important}.align-self-lg-baseline{align-self:baseline !important}.align-self-lg-stretch{align-self:stretch !important}.order-lg-first{order:-1 !important}.order-lg-0{order:0 !important}.order-lg-1{order:1 !important}.order-lg-2{order:2 !important}.order-lg-3{order:3 !important}.order-lg-4{order:4 !important}.order-lg-5{order:5 !important}.order-lg-last{order:6 !important}.m-lg-0{margin:0 !important}.m-lg-1{margin:.25rem !important}.m-lg-2{margin:.5rem !important}.m-lg-3{margin:1rem !important}.m-lg-4{margin:1.5rem !important}.m-lg-5{margin:3rem !important}.m-lg-auto{margin:auto !important}.mx-lg-0{margin-right:0 !important;margin-left:0 !important}.mx-lg-1{margin-right:.25rem !important;margin-left:.25rem !important}.mx-lg-2{margin-right:.5rem !important;margin-left:.5rem !important}.mx-lg-3{margin-right:1rem !important;margin-left:1rem !important}.mx-lg-4{margin-right:1.5rem !important;margin-left:1.5rem !important}.mx-lg-5{margin-right:3rem !important;margin-left:3rem !important}.mx-lg-auto{margin-right:auto !important;margin-left:auto !important}.my-lg-0{margin-top:0 !important;margin-bottom:0 !important}.my-lg-1{margin-top:.25rem !important;margin-bottom:.25rem !important}.my-lg-2{margin-top:.5rem !important;margin-bottom:.5rem !important}.my-lg-3{margin-top:1rem !important;margin-bottom:1rem !important}.my-lg-4{margin-top:1.5rem !important;margin-bottom:1.5rem !important}.my-lg-5{margin-top:3rem !important;margin-bottom:3rem !important}.my-lg-auto{margin-top:auto !important;margin-bottom:auto !important}.mt-lg-0{margin-top:0 !important}.mt-lg-1{margin-top:.25rem !important}.mt-lg-2{margin-top:.5rem !important}.mt-lg-3{margin-top:1rem !important}.mt-lg-4{margin-top:1.5rem !important}.mt-lg-5{margin-top:3rem !important}.mt-lg-auto{margin-top:auto !important}.me-lg-0{margin-right:0 !important}.me-lg-1{margin-right:.25rem !important}.me-lg-2{margin-right:.5rem !important}.me-lg-3{margin-right:1rem !important}.me-lg-4{margin-right:1.5rem !important}.me-lg-5{margin-right:3rem !important}.me-lg-auto{margin-right:auto !important}.mb-lg-0{margin-bottom:0 !important}.mb-lg-1{margin-bottom:.25rem !important}.mb-lg-2{margin-bottom:.5rem !important}.mb-lg-3{margin-bottom:1rem !important}.mb-lg-4{margin-bottom:1.5rem !important}.mb-lg-5{margin-bottom:3rem !important}.mb-lg-auto{margin-bottom:auto !important}.ms-lg-0{margin-left:0 !important}.ms-lg-1{margin-left:.25rem !important}.ms-lg-2{margin-left:.5rem !important}.ms-lg-3{margin-left:1rem !important}.ms-lg-4{margin-left:1.5rem !important}.ms-lg-5{margin-left:3rem !important}.ms-lg-auto{margin-left:auto !important}.p-lg-0{padding:0 !important}.p-lg-1{padding:.25rem !important}.p-lg-2{padding:.5rem !important}.p-lg-3{padding:1rem !important}.p-lg-4{padding:1.5rem !important}.p-lg-5{padding:3rem !important}.px-lg-0{padding-right:0 !important;padding-left:0 !important}.px-lg-1{padding-right:.25rem !important;padding-left:.25rem !important}.px-lg-2{padding-right:.5rem !important;padding-left:.5rem !important}.px-lg-3{padding-right:1rem !important;padding-left:1rem !important}.px-lg-4{padding-right:1.5rem !important;padding-left:1.5rem !important}.px-lg-5{padding-right:3rem !important;padding-left:3rem !important}.py-lg-0{padding-top:0 !important;padding-bottom:0 !important}.py-lg-1{padding-top:.25rem !important;padding-bottom:.25rem !important}.py-lg-2{padding-top:.5rem !important;padding-bottom:.5rem !important}.py-lg-3{padding-top:1rem !important;padding-bottom:1rem !important}.py-lg-4{padding-top:1.5rem !important;padding-bottom:1.5rem !important}.py-lg-5{padding-top:3rem !important;padding-bottom:3rem !important}.pt-lg-0{padding-top:0 !important}.pt-lg-1{padding-top:.25rem !important}.pt-lg-2{padding-top:.5rem !important}.pt-lg-3{padding-top:1rem !important}.pt-lg-4{padding-top:1.5rem !important}.pt-lg-5{padding-top:3rem !important}.pe-lg-0{padding-right:0 !important}.pe-lg-1{padding-right:.25rem !important}.pe-lg-2{padding-right:.5rem !important}.pe-lg-3{padding-right:1rem !important}.pe-lg-4{padding-right:1.5rem !important}.pe-lg-5{padding-right:3rem !important}.pb-lg-0{padding-bottom:0 !important}.pb-lg-1{padding-bottom:.25rem !important}.pb-lg-2{padding-bottom:.5rem !important}.pb-lg-3{padding-bottom:1rem !important}.pb-lg-4{padding-bottom:1.5rem !important}.pb-lg-5{padding-bottom:3rem !important}.ps-lg-0{padding-left:0 !important}.ps-lg-1{padding-left:.25rem !important}.ps-lg-2{padding-left:.5rem !important}.ps-lg-3{padding-left:1rem !important}.ps-lg-4{padding-left:1.5rem !important}.ps-lg-5{padding-left:3rem !important}.text-lg-start{text-align:left !important}.text-lg-end{text-align:right !important}.text-lg-center{text-align:center !important}}@media(min-width: 1200px){.float-xl-start{float:left !important}.float-xl-end{float:right !important}.float-xl-none{float:none !important}.d-xl-inline{display:inline !important}.d-xl-inline-block{display:inline-block !important}.d-xl-block{display:block !important}.d-xl-grid{display:grid !important}.d-xl-table{display:table !important}.d-xl-table-row{display:table-row !important}.d-xl-table-cell{display:table-cell !important}.d-xl-flex{display:flex !important}.d-xl-inline-flex{display:inline-flex !important}.d-xl-none{display:none !important}.flex-xl-fill{flex:1 1 auto !important}.flex-xl-row{flex-direction:row !important}.flex-xl-column{flex-direction:column !important}.flex-xl-row-reverse{flex-direction:row-reverse !important}.flex-xl-column-reverse{flex-direction:column-reverse !important}.flex-xl-grow-0{flex-grow:0 !important}.flex-xl-grow-1{flex-grow:1 !important}.flex-xl-shrink-0{flex-shrink:0 !important}.flex-xl-shrink-1{flex-shrink:1 !important}.flex-xl-wrap{flex-wrap:wrap !important}.flex-xl-nowrap{flex-wrap:nowrap !important}.flex-xl-wrap-reverse{flex-wrap:wrap-reverse !important}.gap-xl-0{gap:0 !important}.gap-xl-1{gap:.25rem !important}.gap-xl-2{gap:.5rem !important}.gap-xl-3{gap:1rem !important}.gap-xl-4{gap:1.5rem !important}.gap-xl-5{gap:3rem !important}.justify-content-xl-start{justify-content:flex-start !important}.justify-content-xl-end{justify-content:flex-end !important}.justify-content-xl-center{justify-content:center !important}.justify-content-xl-between{justify-content:space-between !important}.justify-content-xl-around{justify-content:space-around !important}.justify-content-xl-evenly{justify-content:space-evenly !important}.align-items-xl-start{align-items:flex-start !important}.align-items-xl-end{align-items:flex-end !important}.align-items-xl-center{align-items:center !important}.align-items-xl-baseline{align-items:baseline !important}.align-items-xl-stretch{align-items:stretch !important}.align-content-xl-start{align-content:flex-start !important}.align-content-xl-end{align-content:flex-end !important}.align-content-xl-center{align-content:center !important}.align-content-xl-between{align-content:space-between !important}.align-content-xl-around{align-content:space-around !important}.align-content-xl-stretch{align-content:stretch !important}.align-self-xl-auto{align-self:auto !important}.align-self-xl-start{align-self:flex-start !important}.align-self-xl-end{align-self:flex-end !important}.align-self-xl-center{align-self:center !important}.align-self-xl-baseline{align-self:baseline !important}.align-self-xl-stretch{align-self:stretch !important}.order-xl-first{order:-1 !important}.order-xl-0{order:0 !important}.order-xl-1{order:1 !important}.order-xl-2{order:2 !important}.order-xl-3{order:3 !important}.order-xl-4{order:4 !important}.order-xl-5{order:5 !important}.order-xl-last{order:6 !important}.m-xl-0{margin:0 !important}.m-xl-1{margin:.25rem !important}.m-xl-2{margin:.5rem !important}.m-xl-3{margin:1rem !important}.m-xl-4{margin:1.5rem !important}.m-xl-5{margin:3rem !important}.m-xl-auto{margin:auto !important}.mx-xl-0{margin-right:0 !important;margin-left:0 !important}.mx-xl-1{margin-right:.25rem !important;margin-left:.25rem !important}.mx-xl-2{margin-right:.5rem !important;margin-left:.5rem !important}.mx-xl-3{margin-right:1rem !important;margin-left:1rem !important}.mx-xl-4{margin-right:1.5rem !important;margin-left:1.5rem !important}.mx-xl-5{margin-right:3rem !important;margin-left:3rem !important}.mx-xl-auto{margin-right:auto !important;margin-left:auto !important}.my-xl-0{margin-top:0 !important;margin-bottom:0 !important}.my-xl-1{margin-top:.25rem !important;margin-bottom:.25rem !important}.my-xl-2{margin-top:.5rem !important;margin-bottom:.5rem !important}.my-xl-3{margin-top:1rem !important;margin-bottom:1rem !important}.my-xl-4{margin-top:1.5rem !important;margin-bottom:1.5rem !important}.my-xl-5{margin-top:3rem !important;margin-bottom:3rem !important}.my-xl-auto{margin-top:auto !important;margin-bottom:auto !important}.mt-xl-0{margin-top:0 !important}.mt-xl-1{margin-top:.25rem !important}.mt-xl-2{margin-top:.5rem !important}.mt-xl-3{margin-top:1rem !important}.mt-xl-4{margin-top:1.5rem !important}.mt-xl-5{margin-top:3rem !important}.mt-xl-auto{margin-top:auto !important}.me-xl-0{margin-right:0 !important}.me-xl-1{margin-right:.25rem !important}.me-xl-2{margin-right:.5rem !important}.me-xl-3{margin-right:1rem !important}.me-xl-4{margin-right:1.5rem !important}.me-xl-5{margin-right:3rem !important}.me-xl-auto{margin-right:auto !important}.mb-xl-0{margin-bottom:0 !important}.mb-xl-1{margin-bottom:.25rem !important}.mb-xl-2{margin-bottom:.5rem !important}.mb-xl-3{margin-bottom:1rem !important}.mb-xl-4{margin-bottom:1.5rem !important}.mb-xl-5{margin-bottom:3rem !important}.mb-xl-auto{margin-bottom:auto !important}.ms-xl-0{margin-left:0 !important}.ms-xl-1{margin-left:.25rem !important}.ms-xl-2{margin-left:.5rem !important}.ms-xl-3{margin-left:1rem !important}.ms-xl-4{margin-left:1.5rem !important}.ms-xl-5{margin-left:3rem !important}.ms-xl-auto{margin-left:auto !important}.p-xl-0{padding:0 !important}.p-xl-1{padding:.25rem !important}.p-xl-2{padding:.5rem !important}.p-xl-3{padding:1rem !important}.p-xl-4{padding:1.5rem !important}.p-xl-5{padding:3rem !important}.px-xl-0{padding-right:0 !important;padding-left:0 !important}.px-xl-1{padding-right:.25rem !important;padding-left:.25rem !important}.px-xl-2{padding-right:.5rem !important;padding-left:.5rem !important}.px-xl-3{padding-right:1rem !important;padding-left:1rem !important}.px-xl-4{padding-right:1.5rem !important;padding-left:1.5rem !important}.px-xl-5{padding-right:3rem !important;padding-left:3rem !important}.py-xl-0{padding-top:0 !important;padding-bottom:0 !important}.py-xl-1{padding-top:.25rem !important;padding-bottom:.25rem !important}.py-xl-2{padding-top:.5rem !important;padding-bottom:.5rem !important}.py-xl-3{padding-top:1rem !important;padding-bottom:1rem !important}.py-xl-4{padding-top:1.5rem !important;padding-bottom:1.5rem !important}.py-xl-5{padding-top:3rem !important;padding-bottom:3rem !important}.pt-xl-0{padding-top:0 !important}.pt-xl-1{padding-top:.25rem !important}.pt-xl-2{padding-top:.5rem !important}.pt-xl-3{padding-top:1rem !important}.pt-xl-4{padding-top:1.5rem !important}.pt-xl-5{padding-top:3rem !important}.pe-xl-0{padding-right:0 !important}.pe-xl-1{padding-right:.25rem !important}.pe-xl-2{padding-right:.5rem !important}.pe-xl-3{padding-right:1rem !important}.pe-xl-4{padding-right:1.5rem !important}.pe-xl-5{padding-right:3rem !important}.pb-xl-0{padding-bottom:0 !important}.pb-xl-1{padding-bottom:.25rem !important}.pb-xl-2{padding-bottom:.5rem !important}.pb-xl-3{padding-bottom:1rem !important}.pb-xl-4{padding-bottom:1.5rem !important}.pb-xl-5{padding-bottom:3rem !important}.ps-xl-0{padding-left:0 !important}.ps-xl-1{padding-left:.25rem !important}.ps-xl-2{padding-left:.5rem !important}.ps-xl-3{padding-left:1rem !important}.ps-xl-4{padding-left:1.5rem !important}.ps-xl-5{padding-left:3rem !important}.text-xl-start{text-align:left !important}.text-xl-end{text-align:right !important}.text-xl-center{text-align:center !important}}@media(min-width: 1400px){.float-xxl-start{float:left !important}.float-xxl-end{float:right !important}.float-xxl-none{float:none !important}.d-xxl-inline{display:inline !important}.d-xxl-inline-block{display:inline-block !important}.d-xxl-block{display:block !important}.d-xxl-grid{display:grid !important}.d-xxl-table{display:table !important}.d-xxl-table-row{display:table-row !important}.d-xxl-table-cell{display:table-cell !important}.d-xxl-flex{display:flex !important}.d-xxl-inline-flex{display:inline-flex !important}.d-xxl-none{display:none !important}.flex-xxl-fill{flex:1 1 auto !important}.flex-xxl-row{flex-direction:row !important}.flex-xxl-column{flex-direction:column !important}.flex-xxl-row-reverse{flex-direction:row-reverse !important}.flex-xxl-column-reverse{flex-direction:column-reverse !important}.flex-xxl-grow-0{flex-grow:0 !important}.flex-xxl-grow-1{flex-grow:1 !important}.flex-xxl-shrink-0{flex-shrink:0 !important}.flex-xxl-shrink-1{flex-shrink:1 !important}.flex-xxl-wrap{flex-wrap:wrap !important}.flex-xxl-nowrap{flex-wrap:nowrap !important}.flex-xxl-wrap-reverse{flex-wrap:wrap-reverse !important}.gap-xxl-0{gap:0 !important}.gap-xxl-1{gap:.25rem !important}.gap-xxl-2{gap:.5rem !important}.gap-xxl-3{gap:1rem !important}.gap-xxl-4{gap:1.5rem !important}.gap-xxl-5{gap:3rem !important}.justify-content-xxl-start{justify-content:flex-start !important}.justify-content-xxl-end{justify-content:flex-end !important}.justify-content-xxl-center{justify-content:center !important}.justify-content-xxl-between{justify-content:space-between !important}.justify-content-xxl-around{justify-content:space-around !important}.justify-content-xxl-evenly{justify-content:space-evenly !important}.align-items-xxl-start{align-items:flex-start !important}.align-items-xxl-end{align-items:flex-end !important}.align-items-xxl-center{align-items:center !important}.align-items-xxl-baseline{align-items:baseline !important}.align-items-xxl-stretch{align-items:stretch !important}.align-content-xxl-start{align-content:flex-start !important}.align-content-xxl-end{align-content:flex-end !important}.align-content-xxl-center{align-content:center !important}.align-content-xxl-between{align-content:space-between !important}.align-content-xxl-around{align-content:space-around !important}.align-content-xxl-stretch{align-content:stretch !important}.align-self-xxl-auto{align-self:auto !important}.align-self-xxl-start{align-self:flex-start !important}.align-self-xxl-end{align-self:flex-end !important}.align-self-xxl-center{align-self:center !important}.align-self-xxl-baseline{align-self:baseline !important}.align-self-xxl-stretch{align-self:stretch !important}.order-xxl-first{order:-1 !important}.order-xxl-0{order:0 !important}.order-xxl-1{order:1 !important}.order-xxl-2{order:2 !important}.order-xxl-3{order:3 !important}.order-xxl-4{order:4 !important}.order-xxl-5{order:5 !important}.order-xxl-last{order:6 !important}.m-xxl-0{margin:0 !important}.m-xxl-1{margin:.25rem !important}.m-xxl-2{margin:.5rem !important}.m-xxl-3{margin:1rem !important}.m-xxl-4{margin:1.5rem !important}.m-xxl-5{margin:3rem !important}.m-xxl-auto{margin:auto !important}.mx-xxl-0{margin-right:0 !important;margin-left:0 !important}.mx-xxl-1{margin-right:.25rem !important;margin-left:.25rem !important}.mx-xxl-2{margin-right:.5rem !important;margin-left:.5rem !important}.mx-xxl-3{margin-right:1rem !important;margin-left:1rem !important}.mx-xxl-4{margin-right:1.5rem !important;margin-left:1.5rem !important}.mx-xxl-5{margin-right:3rem !important;margin-left:3rem !important}.mx-xxl-auto{margin-right:auto !important;margin-left:auto !important}.my-xxl-0{margin-top:0 !important;margin-bottom:0 !important}.my-xxl-1{margin-top:.25rem !important;margin-bottom:.25rem !important}.my-xxl-2{margin-top:.5rem !important;margin-bottom:.5rem !important}.my-xxl-3{margin-top:1rem !important;margin-bottom:1rem !important}.my-xxl-4{margin-top:1.5rem !important;margin-bottom:1.5rem !important}.my-xxl-5{margin-top:3rem !important;margin-bottom:3rem !important}.my-xxl-auto{margin-top:auto !important;margin-bottom:auto !important}.mt-xxl-0{margin-top:0 !important}.mt-xxl-1{margin-top:.25rem !important}.mt-xxl-2{margin-top:.5rem !important}.mt-xxl-3{margin-top:1rem !important}.mt-xxl-4{margin-top:1.5rem !important}.mt-xxl-5{margin-top:3rem !important}.mt-xxl-auto{margin-top:auto !important}.me-xxl-0{margin-right:0 !important}.me-xxl-1{margin-right:.25rem !important}.me-xxl-2{margin-right:.5rem !important}.me-xxl-3{margin-right:1rem !important}.me-xxl-4{margin-right:1.5rem !important}.me-xxl-5{margin-right:3rem !important}.me-xxl-auto{margin-right:auto !important}.mb-xxl-0{margin-bottom:0 !important}.mb-xxl-1{margin-bottom:.25rem !important}.mb-xxl-2{margin-bottom:.5rem !important}.mb-xxl-3{margin-bottom:1rem !important}.mb-xxl-4{margin-bottom:1.5rem !important}.mb-xxl-5{margin-bottom:3rem !important}.mb-xxl-auto{margin-bottom:auto !important}.ms-xxl-0{margin-left:0 !important}.ms-xxl-1{margin-left:.25rem !important}.ms-xxl-2{margin-left:.5rem !important}.ms-xxl-3{margin-left:1rem !important}.ms-xxl-4{margin-left:1.5rem !important}.ms-xxl-5{margin-left:3rem !important}.ms-xxl-auto{margin-left:auto !important}.p-xxl-0{padding:0 !important}.p-xxl-1{padding:.25rem !important}.p-xxl-2{padding:.5rem !important}.p-xxl-3{padding:1rem !important}.p-xxl-4{padding:1.5rem !important}.p-xxl-5{padding:3rem !important}.px-xxl-0{padding-right:0 !important;padding-left:0 !important}.px-xxl-1{padding-right:.25rem !important;padding-left:.25rem !important}.px-xxl-2{padding-right:.5rem !important;padding-left:.5rem !important}.px-xxl-3{padding-right:1rem !important;padding-left:1rem !important}.px-xxl-4{padding-right:1.5rem !important;padding-left:1.5rem !important}.px-xxl-5{padding-right:3rem !important;padding-left:3rem !important}.py-xxl-0{padding-top:0 !important;padding-bottom:0 !important}.py-xxl-1{padding-top:.25rem !important;padding-bottom:.25rem !important}.py-xxl-2{padding-top:.5rem !important;padding-bottom:.5rem !important}.py-xxl-3{padding-top:1rem !important;padding-bottom:1rem !important}.py-xxl-4{padding-top:1.5rem !important;padding-bottom:1.5rem !important}.py-xxl-5{padding-top:3rem !important;padding-bottom:3rem !important}.pt-xxl-0{padding-top:0 !important}.pt-xxl-1{padding-top:.25rem !important}.pt-xxl-2{padding-top:.5rem !important}.pt-xxl-3{padding-top:1rem !important}.pt-xxl-4{padding-top:1.5rem !important}.pt-xxl-5{padding-top:3rem !important}.pe-xxl-0{padding-right:0 !important}.pe-xxl-1{padding-right:.25rem !important}.pe-xxl-2{padding-right:.5rem !important}.pe-xxl-3{padding-right:1rem !important}.pe-xxl-4{padding-right:1.5rem !important}.pe-xxl-5{padding-right:3rem !important}.pb-xxl-0{padding-bottom:0 !important}.pb-xxl-1{padding-bottom:.25rem !important}.pb-xxl-2{padding-bottom:.5rem !important}.pb-xxl-3{padding-bottom:1rem !important}.pb-xxl-4{padding-bottom:1.5rem !important}.pb-xxl-5{padding-bottom:3rem !important}.ps-xxl-0{padding-left:0 !important}.ps-xxl-1{padding-left:.25rem !important}.ps-xxl-2{padding-left:.5rem !important}.ps-xxl-3{padding-left:1rem !important}.ps-xxl-4{padding-left:1.5rem !important}.ps-xxl-5{padding-left:3rem !important}.text-xxl-start{text-align:left !important}.text-xxl-end{text-align:right !important}.text-xxl-center{text-align:center !important}}.bg-default{color:#fff}.bg-primary{color:#fff}.bg-secondary{color:#fff}.bg-success{color:#fff}.bg-info{color:#fff}.bg-warning{color:#fff}.bg-danger{color:#fff}.bg-light{color:#000}.bg-dark{color:#fff}@media(min-width: 1200px){.fs-1{font-size:2rem !important}.fs-2{font-size:1.65rem !important}.fs-3{font-size:1.45rem !important}}@media print{.d-print-inline{display:inline !important}.d-print-inline-block{display:inline-block !important}.d-print-block{display:block !important}.d-print-grid{display:grid !important}.d-print-table{display:table !important}.d-print-table-row{display:table-row !important}.d-print-table-cell{display:table-cell !important}.d-print-flex{display:flex !important}.d-print-inline-flex{display:inline-flex !important}.d-print-none{display:none !important}}.quarto-container{min-height:calc(100vh - 132px)}footer.footer .nav-footer,#quarto-header>nav{padding-left:1em;padding-right:1em}nav[role=doc-toc]{padding-left:.5em}#quarto-content>*{padding-top:14px}@media(max-width: 991.98px){#quarto-content>*{padding-top:0}#quarto-content .subtitle{padding-top:14px}#quarto-content section:first-of-type h2:first-of-type,#quarto-content section:first-of-type .h2:first-of-type{margin-top:1rem}}.headroom-target,header.headroom{will-change:transform;transition:position 200ms linear;transition:all 200ms linear}header.headroom--pinned{transform:translateY(0%)}header.headroom--unpinned{transform:translateY(-100%)}.navbar-container{width:100%}.navbar-brand{overflow:hidden;text-overflow:ellipsis}.navbar-brand-container{max-width:calc(100% - 115px);min-width:0;display:flex;align-items:center}@media(min-width: 992px){.navbar-brand-container{margin-right:1em}}.navbar-brand.navbar-brand-logo{margin-right:4px;display:inline-flex}.navbar-toggler{flex-basis:content;flex-shrink:0}.navbar .navbar-toggler{order:-1;margin-right:.5em}.navbar-logo{max-height:24px;width:auto;padding-right:4px}nav .nav-item:not(.compact){padding-top:1px}nav .nav-link i,nav .dropdown-item i{padding-right:1px}.navbar-expand-lg .navbar-nav .nav-link{padding-left:.6rem;padding-right:.6rem}nav .nav-item.compact .nav-link{padding-left:.5rem;padding-right:.5rem;font-size:1.1rem}.navbar .quarto-navbar-tools div.dropdown{display:inline-block}.navbar .quarto-navbar-tools .quarto-navigation-tool{color:#fdfeff}.navbar .quarto-navbar-tools .quarto-navigation-tool:hover{color:#fdfeff}@media(max-width: 991.98px){.navbar .quarto-navbar-tools{margin-top:.25em;padding-top:.75em;display:block;color:solid #007ffd 1px;text-align:center;vertical-align:middle;margin-right:auto}}.navbar-nav .dropdown-menu{min-width:220px;font-size:.9rem}.navbar .navbar-nav .nav-link.dropdown-toggle::after{opacity:.75;vertical-align:.175em}.navbar ul.dropdown-menu{padding-top:0;padding-bottom:0}.navbar .dropdown-header{text-transform:uppercase;font-size:.8rem;padding:0 .5rem}.navbar .dropdown-item{padding:.4rem .5rem}.navbar .dropdown-item>i.bi{margin-left:.1rem;margin-right:.25em}.sidebar #quarto-search{margin-top:-1px}.sidebar #quarto-search svg.aa-SubmitIcon{width:16px;height:16px}.sidebar-navigation a{color:inherit}.sidebar-title{margin-top:.25rem;padding-bottom:.5rem;font-size:1.3rem;line-height:1.6rem;visibility:visible}.sidebar-title>a{font-size:inherit;text-decoration:none}.sidebar-title .sidebar-tools-main{margin-top:-6px}@media(max-width: 991.98px){#quarto-sidebar div.sidebar-header{padding-top:.2em}}.sidebar-header-stacked .sidebar-title{margin-top:.6rem}.sidebar-logo{max-width:90%;padding-bottom:.5rem}.sidebar-logo-link{text-decoration:none}.sidebar-navigation li a{text-decoration:none}.sidebar-navigation .quarto-navigation-tool{opacity:.7;font-size:.875rem}#quarto-sidebar>nav>.sidebar-tools-main{margin-left:14px}.sidebar-tools-main{display:inline-flex;margin-left:0px;order:2}.sidebar-tools-main:not(.tools-wide){vertical-align:middle}.sidebar-navigation .quarto-navigation-tool.dropdown-toggle::after{display:none}.sidebar.sidebar-navigation>*{padding-top:1em}.sidebar-item{margin-bottom:.2em}.sidebar-section{margin-top:.2em;padding-left:.5em;padding-bottom:.2em}.sidebar-item .sidebar-item-container{display:flex;justify-content:space-between}.sidebar-item-toggle:hover{cursor:pointer}.sidebar-item .sidebar-item-toggle .bi{font-size:.7rem;text-align:center}.sidebar-item .sidebar-item-toggle .bi-chevron-right::before{transition:transform 200ms ease}.sidebar-item .sidebar-item-toggle[aria-expanded=false] .bi-chevron-right::before{transform:none}.sidebar-item .sidebar-item-toggle[aria-expanded=true] .bi-chevron-right::before{transform:rotate(90deg)}.sidebar-navigation .sidebar-divider{margin-left:0;margin-right:0;margin-top:.5rem;margin-bottom:.5rem}@media(max-width: 991.98px){.quarto-secondary-nav{display:block}.quarto-secondary-nav button.quarto-search-button{padding-right:0em;padding-left:2em}.quarto-secondary-nav button.quarto-btn-toggle{margin-left:-0.75rem;margin-right:.15rem}.quarto-secondary-nav nav.quarto-page-breadcrumbs{display:flex;align-items:center;padding-right:1em;margin-left:-0.25em}.quarto-secondary-nav nav.quarto-page-breadcrumbs a{text-decoration:none}.quarto-secondary-nav nav.quarto-page-breadcrumbs ol.breadcrumb{margin-bottom:0}}@media(min-width: 992px){.quarto-secondary-nav{display:none}}.quarto-secondary-nav .quarto-btn-toggle{color:#595959}.quarto-secondary-nav[aria-expanded=false] .quarto-btn-toggle .bi-chevron-right::before{transform:none}.quarto-secondary-nav[aria-expanded=true] .quarto-btn-toggle .bi-chevron-right::before{transform:rotate(90deg)}.quarto-secondary-nav .quarto-btn-toggle .bi-chevron-right::before{transition:transform 200ms ease}.quarto-secondary-nav{cursor:pointer}.quarto-secondary-nav-title{margin-top:.3em;color:#595959;padding-top:4px}.quarto-secondary-nav nav.quarto-page-breadcrumbs{color:#595959}.quarto-secondary-nav nav.quarto-page-breadcrumbs a{color:#595959}.quarto-secondary-nav nav.quarto-page-breadcrumbs a:hover{color:rgba(27,88,157,.8)}.quarto-secondary-nav nav.quarto-page-breadcrumbs .breadcrumb-item::before{color:#8c8c8c}div.sidebar-item-container{color:#595959}div.sidebar-item-container:hover,div.sidebar-item-container:focus{color:rgba(27,88,157,.8)}div.sidebar-item-container.disabled{color:rgba(89,89,89,.75)}div.sidebar-item-container .active,div.sidebar-item-container .show>.nav-link,div.sidebar-item-container .sidebar-link>code{color:#1b589d}div.sidebar.sidebar-navigation.rollup.quarto-sidebar-toggle-contents,nav.sidebar.sidebar-navigation:not(.rollup){background-color:#fff}@media(max-width: 991.98px){.sidebar-navigation .sidebar-item a,.nav-page .nav-page-text,.sidebar-navigation{font-size:1rem}.sidebar-navigation ul.sidebar-section.depth1 .sidebar-section-item{font-size:1.1rem}.sidebar-logo{display:none}.sidebar.sidebar-navigation{position:static;border-bottom:1px solid #dee2e6}.sidebar.sidebar-navigation.collapsing{position:fixed;z-index:1000}.sidebar.sidebar-navigation.show{position:fixed;z-index:1000}.sidebar.sidebar-navigation{min-height:100%}nav.quarto-secondary-nav{background-color:#fff;border-bottom:1px solid #dee2e6}.sidebar .sidebar-footer{visibility:visible;padding-top:1rem;position:inherit}.sidebar-tools-collapse{display:block}}#quarto-sidebar{transition:width .15s ease-in}#quarto-sidebar>*{padding-right:1em}@media(max-width: 991.98px){#quarto-sidebar .sidebar-menu-container{white-space:nowrap;min-width:225px}#quarto-sidebar.show{transition:width .15s ease-out}}@media(min-width: 992px){#quarto-sidebar{display:flex;flex-direction:column}.nav-page .nav-page-text,.sidebar-navigation .sidebar-section .sidebar-item{font-size:.875rem}.sidebar-navigation .sidebar-item{font-size:.925rem}.sidebar.sidebar-navigation{display:block;position:sticky}.sidebar-search{width:100%}.sidebar .sidebar-footer{visibility:visible}}@media(max-width: 991.98px){#quarto-sidebar-glass{position:fixed;top:0;bottom:0;left:0;right:0;background-color:rgba(255,255,255,0);transition:background-color .15s ease-in;z-index:-1}#quarto-sidebar-glass.collapsing{z-index:1000}#quarto-sidebar-glass.show{transition:background-color .15s ease-out;background-color:rgba(102,102,102,.4);z-index:1000}}.sidebar .sidebar-footer{padding:.5rem 1rem;align-self:flex-end;color:#6c757d;width:100%}.quarto-page-breadcrumbs .breadcrumb-item+.breadcrumb-item,.quarto-page-breadcrumbs .breadcrumb-item{padding-right:.33em;padding-left:0}.quarto-page-breadcrumbs .breadcrumb-item::before{padding-right:.33em}.quarto-sidebar-footer{font-size:.875em}.sidebar-section .bi-chevron-right{vertical-align:middle}.sidebar-section .bi-chevron-right::before{font-size:.9em}.notransition{-webkit-transition:none !important;-moz-transition:none !important;-o-transition:none !important;transition:none !important}.btn:focus:not(:focus-visible){box-shadow:none}.page-navigation{display:flex;justify-content:space-between}.nav-page{padding-bottom:.75em}.nav-page .bi{font-size:1.8rem;vertical-align:middle}.nav-page .nav-page-text{padding-left:.25em;padding-right:.25em}.nav-page a{color:#6c757d;text-decoration:none;display:flex;align-items:center}.nav-page a:hover{color:#1f66b6}.toc-actions{display:flex}.toc-actions p{margin-block-start:0;margin-block-end:0}.toc-actions a{text-decoration:none;color:inherit;font-weight:400}.toc-actions a:hover{color:#1f66b6}.toc-actions .action-links{margin-left:4px}.sidebar nav[role=doc-toc] .toc-actions .bi{margin-left:-4px;font-size:.7rem;color:#6c757d}.sidebar nav[role=doc-toc] .toc-actions .bi:before{padding-top:3px}#quarto-margin-sidebar .toc-actions .bi:before{margin-top:.3rem;font-size:.7rem;color:#6c757d;vertical-align:top}.sidebar nav[role=doc-toc] .toc-actions>div:first-of-type{margin-top:-3px}#quarto-margin-sidebar .toc-actions p,.sidebar nav[role=doc-toc] .toc-actions p{font-size:.875rem}.nav-footer .toc-actions{padding-bottom:.5em;padding-top:.5em}.nav-footer .toc-actions :first-child{margin-left:auto}.nav-footer .toc-actions :last-child{margin-right:auto}.nav-footer .toc-actions .action-links{display:flex}.nav-footer .toc-actions .action-links p{padding-right:1.5em}.nav-footer .toc-actions .action-links p:last-of-type{padding-right:0}.nav-footer{display:flex;flex-direction:row;flex-wrap:wrap;justify-content:space-between;align-items:baseline;text-align:center;padding-top:.5rem;padding-bottom:.5rem;background-color:#fff}body.nav-fixed{padding-top:64px}.nav-footer-contents{color:#6c757d;margin-top:.25rem}.nav-footer{min-height:3.5em;color:#757575}.nav-footer a{color:#757575}.nav-footer .nav-footer-left{font-size:.825em}.nav-footer .nav-footer-center{font-size:.825em}.nav-footer .nav-footer-right{font-size:.825em}.nav-footer-left .footer-items,.nav-footer-center .footer-items,.nav-footer-right .footer-items{display:inline-flex;padding-top:.3em;padding-bottom:.3em;margin-bottom:0em}.nav-footer-left .footer-items .nav-link,.nav-footer-center .footer-items .nav-link,.nav-footer-right .footer-items .nav-link{padding-left:.6em;padding-right:.6em}.nav-footer-left{flex:1 1 0px;text-align:left}.nav-footer-right{flex:1 1 0px;text-align:right}.nav-footer-center{flex:1 1 0px;min-height:3em;text-align:center}.nav-footer-center .footer-items{justify-content:center}@media(max-width: 767.98px){.nav-footer-center{margin-top:3em}}.navbar .quarto-reader-toggle.reader .quarto-reader-toggle-btn{background-color:#fdfeff;border-radius:3px}.quarto-reader-toggle.reader.quarto-navigation-tool .quarto-reader-toggle-btn{background-color:#595959;border-radius:3px}.quarto-reader-toggle .quarto-reader-toggle-btn{display:inline-flex;padding-left:.2em;padding-right:.2em;margin-left:-0.2em;margin-right:-0.2em;text-align:center}.navbar .quarto-reader-toggle:not(.reader) .bi::before{background-image:url('data:image/svg+xml,')}.navbar .quarto-reader-toggle.reader .bi::before{background-image:url('data:image/svg+xml,')}.sidebar-navigation .quarto-reader-toggle:not(.reader) .bi::before{background-image:url('data:image/svg+xml,')}.sidebar-navigation .quarto-reader-toggle.reader .bi::before{background-image:url('data:image/svg+xml,')}#quarto-back-to-top{display:none;position:fixed;bottom:50px;background-color:#fff;border-radius:.25rem;box-shadow:0 .2rem .5rem #6c757d,0 0 .05rem #6c757d;color:#6c757d;text-decoration:none;font-size:.9em;text-align:center;left:50%;padding:.4rem .8rem;transform:translate(-50%, 0)}.aa-DetachedOverlay ul.aa-List,#quarto-search-results ul.aa-List{list-style:none;padding-left:0}.aa-DetachedOverlay .aa-Panel,#quarto-search-results .aa-Panel{background-color:#fff;position:absolute;z-index:2000}#quarto-search-results .aa-Panel{max-width:400px}#quarto-search input{font-size:.925rem}@media(min-width: 992px){.navbar #quarto-search{margin-left:.25rem;order:999}}@media(max-width: 991.98px){#quarto-sidebar .sidebar-search{display:none}}#quarto-sidebar .sidebar-search .aa-Autocomplete{width:100%}.navbar .aa-Autocomplete .aa-Form{width:180px}.navbar #quarto-search.type-overlay .aa-Autocomplete{width:40px}.navbar #quarto-search.type-overlay .aa-Autocomplete .aa-Form{background-color:inherit;border:none}.navbar #quarto-search.type-overlay .aa-Autocomplete .aa-Form:focus-within{box-shadow:none;outline:none}.navbar #quarto-search.type-overlay .aa-Autocomplete .aa-Form .aa-InputWrapper{display:none}.navbar #quarto-search.type-overlay .aa-Autocomplete .aa-Form .aa-InputWrapper:focus-within{display:inherit}.navbar #quarto-search.type-overlay .aa-Autocomplete .aa-Form .aa-Label svg,.navbar #quarto-search.type-overlay .aa-Autocomplete .aa-Form .aa-LoadingIndicator svg{width:26px;height:26px;color:#fdfeff;opacity:1}.navbar #quarto-search.type-overlay .aa-Autocomplete svg.aa-SubmitIcon{width:26px;height:26px;color:#fdfeff;opacity:1}.aa-Autocomplete .aa-Form,.aa-DetachedFormContainer .aa-Form{align-items:center;background-color:#fff;border:1px solid #ced4da;border-radius:.25rem;color:#373a3c;display:flex;line-height:1em;margin:0;position:relative;width:100%}.aa-Autocomplete .aa-Form:focus-within,.aa-DetachedFormContainer .aa-Form:focus-within{box-shadow:rgba(39,128,227,.6) 0 0 0 1px;outline:currentColor none medium}.aa-Autocomplete .aa-Form .aa-InputWrapperPrefix,.aa-DetachedFormContainer .aa-Form .aa-InputWrapperPrefix{align-items:center;display:flex;flex-shrink:0;order:1}.aa-Autocomplete .aa-Form .aa-InputWrapperPrefix .aa-Label,.aa-Autocomplete .aa-Form .aa-InputWrapperPrefix .aa-LoadingIndicator,.aa-DetachedFormContainer .aa-Form .aa-InputWrapperPrefix .aa-Label,.aa-DetachedFormContainer .aa-Form .aa-InputWrapperPrefix .aa-LoadingIndicator{cursor:initial;flex-shrink:0;padding:0;text-align:left}.aa-Autocomplete .aa-Form .aa-InputWrapperPrefix .aa-Label svg,.aa-Autocomplete .aa-Form .aa-InputWrapperPrefix .aa-LoadingIndicator svg,.aa-DetachedFormContainer .aa-Form .aa-InputWrapperPrefix .aa-Label svg,.aa-DetachedFormContainer .aa-Form .aa-InputWrapperPrefix .aa-LoadingIndicator svg{color:#373a3c;opacity:.5}.aa-Autocomplete .aa-Form .aa-InputWrapperPrefix .aa-SubmitButton,.aa-DetachedFormContainer .aa-Form .aa-InputWrapperPrefix .aa-SubmitButton{appearance:none;background:none;border:0;margin:0}.aa-Autocomplete .aa-Form .aa-InputWrapperPrefix .aa-LoadingIndicator,.aa-DetachedFormContainer .aa-Form .aa-InputWrapperPrefix .aa-LoadingIndicator{align-items:center;display:flex;justify-content:center}.aa-Autocomplete .aa-Form .aa-InputWrapperPrefix .aa-LoadingIndicator[hidden],.aa-DetachedFormContainer .aa-Form .aa-InputWrapperPrefix .aa-LoadingIndicator[hidden]{display:none}.aa-Autocomplete .aa-Form .aa-InputWrapper,.aa-DetachedFormContainer .aa-Form .aa-InputWrapper{order:3;position:relative;width:100%}.aa-Autocomplete .aa-Form .aa-InputWrapper .aa-Input,.aa-DetachedFormContainer .aa-Form .aa-InputWrapper .aa-Input{appearance:none;background:none;border:0;color:#373a3c;font:inherit;height:calc(1.5em + .1rem + 2px);padding:0;width:100%}.aa-Autocomplete .aa-Form .aa-InputWrapper .aa-Input::placeholder,.aa-DetachedFormContainer .aa-Form .aa-InputWrapper .aa-Input::placeholder{color:#373a3c;opacity:.8}.aa-Autocomplete .aa-Form .aa-InputWrapper .aa-Input:focus,.aa-DetachedFormContainer .aa-Form .aa-InputWrapper .aa-Input:focus{border-color:none;box-shadow:none;outline:none}.aa-Autocomplete .aa-Form .aa-InputWrapper .aa-Input::-webkit-search-decoration,.aa-Autocomplete .aa-Form .aa-InputWrapper .aa-Input::-webkit-search-cancel-button,.aa-Autocomplete .aa-Form .aa-InputWrapper .aa-Input::-webkit-search-results-button,.aa-Autocomplete .aa-Form .aa-InputWrapper .aa-Input::-webkit-search-results-decoration,.aa-DetachedFormContainer .aa-Form .aa-InputWrapper .aa-Input::-webkit-search-decoration,.aa-DetachedFormContainer .aa-Form .aa-InputWrapper .aa-Input::-webkit-search-cancel-button,.aa-DetachedFormContainer .aa-Form .aa-InputWrapper .aa-Input::-webkit-search-results-button,.aa-DetachedFormContainer .aa-Form .aa-InputWrapper .aa-Input::-webkit-search-results-decoration{display:none}.aa-Autocomplete .aa-Form .aa-InputWrapperSuffix,.aa-DetachedFormContainer .aa-Form .aa-InputWrapperSuffix{align-items:center;display:flex;order:4}.aa-Autocomplete .aa-Form .aa-InputWrapperSuffix .aa-ClearButton,.aa-DetachedFormContainer .aa-Form .aa-InputWrapperSuffix .aa-ClearButton{align-items:center;background:none;border:0;color:#373a3c;opacity:.8;cursor:pointer;display:flex;margin:0;width:calc(1.5em + .1rem + 2px)}.aa-Autocomplete .aa-Form .aa-InputWrapperSuffix .aa-ClearButton:hover,.aa-Autocomplete .aa-Form .aa-InputWrapperSuffix .aa-ClearButton:focus,.aa-DetachedFormContainer .aa-Form .aa-InputWrapperSuffix .aa-ClearButton:hover,.aa-DetachedFormContainer .aa-Form .aa-InputWrapperSuffix .aa-ClearButton:focus{color:#373a3c;opacity:.8}.aa-Autocomplete .aa-Form .aa-InputWrapperSuffix .aa-ClearButton[hidden],.aa-DetachedFormContainer .aa-Form .aa-InputWrapperSuffix .aa-ClearButton[hidden]{display:none}.aa-Autocomplete .aa-Form .aa-InputWrapperSuffix .aa-ClearButton svg,.aa-DetachedFormContainer .aa-Form .aa-InputWrapperSuffix .aa-ClearButton svg{width:calc(1.5em + 0.75rem + 2px)}.aa-Autocomplete .aa-Form .aa-InputWrapperSuffix .aa-CopyButton,.aa-DetachedFormContainer .aa-Form .aa-InputWrapperSuffix .aa-CopyButton{border:none;align-items:center;background:none;color:#373a3c;opacity:.4;font-size:.7rem;cursor:pointer;display:none;margin:0;width:calc(1em + .1rem + 2px)}.aa-Autocomplete .aa-Form .aa-InputWrapperSuffix .aa-CopyButton:hover,.aa-Autocomplete .aa-Form .aa-InputWrapperSuffix .aa-CopyButton:focus,.aa-DetachedFormContainer .aa-Form .aa-InputWrapperSuffix .aa-CopyButton:hover,.aa-DetachedFormContainer .aa-Form .aa-InputWrapperSuffix .aa-CopyButton:focus{color:#373a3c;opacity:.8}.aa-Autocomplete .aa-Form .aa-InputWrapperSuffix .aa-CopyButton[hidden],.aa-DetachedFormContainer .aa-Form .aa-InputWrapperSuffix .aa-CopyButton[hidden]{display:none}.aa-PanelLayout:empty{display:none}.quarto-search-no-results.no-query{display:none}.aa-Source:has(.no-query){display:none}#quarto-search-results .aa-Panel{border:solid #ced4da 1px}#quarto-search-results .aa-SourceNoResults{width:398px}.aa-DetachedOverlay .aa-Panel,#quarto-search-results .aa-Panel{max-height:65vh;overflow-y:auto;font-size:.925rem}.aa-DetachedOverlay .aa-SourceNoResults,#quarto-search-results .aa-SourceNoResults{height:60px;display:flex;justify-content:center;align-items:center}.aa-DetachedOverlay .search-error,#quarto-search-results .search-error{padding-top:10px;padding-left:20px;padding-right:20px;cursor:default}.aa-DetachedOverlay .search-error .search-error-title,#quarto-search-results .search-error .search-error-title{font-size:1.1rem;margin-bottom:.5rem}.aa-DetachedOverlay .search-error .search-error-title .search-error-icon,#quarto-search-results .search-error .search-error-title .search-error-icon{margin-right:8px}.aa-DetachedOverlay .search-error .search-error-text,#quarto-search-results .search-error .search-error-text{font-weight:300}.aa-DetachedOverlay .search-result-text,#quarto-search-results .search-result-text{font-weight:300;overflow:hidden;text-overflow:ellipsis;display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;line-height:1.2rem;max-height:2.4rem}.aa-DetachedOverlay .aa-SourceHeader .search-result-header,#quarto-search-results .aa-SourceHeader .search-result-header{font-size:.875rem;background-color:#f2f2f2;padding-left:14px;padding-bottom:4px;padding-top:4px}.aa-DetachedOverlay .aa-SourceHeader .search-result-header-no-results,#quarto-search-results .aa-SourceHeader .search-result-header-no-results{display:none}.aa-DetachedOverlay .aa-SourceFooter .algolia-search-logo,#quarto-search-results .aa-SourceFooter .algolia-search-logo{width:110px;opacity:.85;margin:8px;float:right}.aa-DetachedOverlay .search-result-section,#quarto-search-results .search-result-section{font-size:.925em}.aa-DetachedOverlay a.search-result-link,#quarto-search-results a.search-result-link{color:inherit;text-decoration:none}.aa-DetachedOverlay li.aa-Item[aria-selected=true] .search-item,#quarto-search-results li.aa-Item[aria-selected=true] .search-item{background-color:#2780e3}.aa-DetachedOverlay li.aa-Item[aria-selected=true] .search-item.search-result-more,.aa-DetachedOverlay li.aa-Item[aria-selected=true] .search-item .search-result-section,.aa-DetachedOverlay li.aa-Item[aria-selected=true] .search-item .search-result-text,.aa-DetachedOverlay li.aa-Item[aria-selected=true] .search-item .search-result-title-container,.aa-DetachedOverlay li.aa-Item[aria-selected=true] .search-item .search-result-text-container,#quarto-search-results li.aa-Item[aria-selected=true] .search-item.search-result-more,#quarto-search-results li.aa-Item[aria-selected=true] .search-item .search-result-section,#quarto-search-results li.aa-Item[aria-selected=true] .search-item .search-result-text,#quarto-search-results li.aa-Item[aria-selected=true] .search-item .search-result-title-container,#quarto-search-results li.aa-Item[aria-selected=true] .search-item .search-result-text-container{color:#fff;background-color:#2780e3}.aa-DetachedOverlay li.aa-Item[aria-selected=true] .search-item mark.search-match,.aa-DetachedOverlay li.aa-Item[aria-selected=true] .search-item .search-match.mark,#quarto-search-results li.aa-Item[aria-selected=true] .search-item mark.search-match,#quarto-search-results li.aa-Item[aria-selected=true] .search-item .search-match.mark{color:#fff;background-color:#4b95e8}.aa-DetachedOverlay li.aa-Item[aria-selected=false] .search-item,#quarto-search-results li.aa-Item[aria-selected=false] .search-item{background-color:#fff}.aa-DetachedOverlay li.aa-Item[aria-selected=false] .search-item.search-result-more,.aa-DetachedOverlay li.aa-Item[aria-selected=false] .search-item .search-result-section,.aa-DetachedOverlay li.aa-Item[aria-selected=false] .search-item .search-result-text,.aa-DetachedOverlay li.aa-Item[aria-selected=false] .search-item .search-result-title-container,.aa-DetachedOverlay li.aa-Item[aria-selected=false] .search-item .search-result-text-container,#quarto-search-results li.aa-Item[aria-selected=false] .search-item.search-result-more,#quarto-search-results li.aa-Item[aria-selected=false] .search-item .search-result-section,#quarto-search-results li.aa-Item[aria-selected=false] .search-item .search-result-text,#quarto-search-results li.aa-Item[aria-selected=false] .search-item .search-result-title-container,#quarto-search-results li.aa-Item[aria-selected=false] .search-item .search-result-text-container{color:#373a3c}.aa-DetachedOverlay li.aa-Item[aria-selected=false] .search-item mark.search-match,.aa-DetachedOverlay li.aa-Item[aria-selected=false] .search-item .search-match.mark,#quarto-search-results li.aa-Item[aria-selected=false] .search-item mark.search-match,#quarto-search-results li.aa-Item[aria-selected=false] .search-item .search-match.mark{color:inherit;background-color:#e5effc}.aa-DetachedOverlay .aa-Item .search-result-doc:not(.document-selectable) .search-result-title-container,#quarto-search-results .aa-Item .search-result-doc:not(.document-selectable) .search-result-title-container{background-color:#fff;color:#373a3c}.aa-DetachedOverlay .aa-Item .search-result-doc:not(.document-selectable) .search-result-text-container,#quarto-search-results .aa-Item .search-result-doc:not(.document-selectable) .search-result-text-container{padding-top:0px}.aa-DetachedOverlay li.aa-Item .search-result-doc.document-selectable .search-result-text-container,#quarto-search-results li.aa-Item .search-result-doc.document-selectable .search-result-text-container{margin-top:-4px}.aa-DetachedOverlay .aa-Item,#quarto-search-results .aa-Item{cursor:pointer}.aa-DetachedOverlay .aa-Item .search-item,#quarto-search-results .aa-Item .search-item{border-left:none;border-right:none;border-top:none;background-color:#fff;border-color:#ced4da;color:#373a3c}.aa-DetachedOverlay .aa-Item .search-item p,#quarto-search-results .aa-Item .search-item p{margin-top:0;margin-bottom:0}.aa-DetachedOverlay .aa-Item .search-item i.bi,#quarto-search-results .aa-Item .search-item i.bi{padding-left:8px;padding-right:8px;font-size:1.3em}.aa-DetachedOverlay .aa-Item .search-item .search-result-title,#quarto-search-results .aa-Item .search-item .search-result-title{margin-top:.3em;margin-bottom:.1rem}.aa-DetachedOverlay .aa-Item .search-result-title-container,#quarto-search-results .aa-Item .search-result-title-container{font-size:1em;display:flex;padding:6px 4px 6px 4px}.aa-DetachedOverlay .aa-Item .search-result-text-container,#quarto-search-results .aa-Item .search-result-text-container{padding-bottom:8px;padding-right:8px;margin-left:44px}.aa-DetachedOverlay .aa-Item .search-result-doc-section,.aa-DetachedOverlay .aa-Item .search-result-more,#quarto-search-results .aa-Item .search-result-doc-section,#quarto-search-results .aa-Item .search-result-more{padding-top:8px;padding-bottom:8px;padding-left:44px}.aa-DetachedOverlay .aa-Item .search-result-more,#quarto-search-results .aa-Item .search-result-more{font-size:.8em;font-weight:400}.aa-DetachedOverlay .aa-Item .search-result-doc,#quarto-search-results .aa-Item .search-result-doc{border-top:1px solid #ced4da}.aa-DetachedSearchButton{background:none;border:none}.aa-DetachedSearchButton .aa-DetachedSearchButtonPlaceholder{display:none}.navbar .aa-DetachedSearchButton .aa-DetachedSearchButtonIcon{color:#fdfeff}.sidebar-tools-collapse #quarto-search,.sidebar-tools-main #quarto-search{display:inline}.sidebar-tools-collapse #quarto-search .aa-Autocomplete,.sidebar-tools-main #quarto-search .aa-Autocomplete{display:inline}.sidebar-tools-collapse #quarto-search .aa-DetachedSearchButton,.sidebar-tools-main #quarto-search .aa-DetachedSearchButton{padding-left:4px;padding-right:4px}.sidebar-tools-collapse #quarto-search .aa-DetachedSearchButton .aa-DetachedSearchButtonIcon,.sidebar-tools-main #quarto-search .aa-DetachedSearchButton .aa-DetachedSearchButtonIcon{color:#595959}.sidebar-tools-collapse #quarto-search .aa-DetachedSearchButton .aa-DetachedSearchButtonIcon .aa-SubmitIcon,.sidebar-tools-main #quarto-search .aa-DetachedSearchButton .aa-DetachedSearchButtonIcon .aa-SubmitIcon{margin-top:-3px}.aa-DetachedContainer{background:rgba(255,255,255,.65);width:90%;bottom:0;box-shadow:rgba(206,212,218,.6) 0 0 0 1px;outline:currentColor none medium;display:flex;flex-direction:column;left:0;margin:0;overflow:hidden;padding:0;position:fixed;right:0;top:0;z-index:1101}.aa-DetachedContainer::after{height:32px}.aa-DetachedContainer .aa-SourceHeader{margin:var(--aa-spacing-half) 0 var(--aa-spacing-half) 2px}.aa-DetachedContainer .aa-Panel{background-color:#fff;border-radius:0;box-shadow:none;flex-grow:1;margin:0;padding:0;position:relative}.aa-DetachedContainer .aa-PanelLayout{bottom:0;box-shadow:none;left:0;margin:0;max-height:none;overflow-y:auto;position:absolute;right:0;top:0;width:100%}.aa-DetachedFormContainer{background-color:#fff;border-bottom:1px solid #ced4da;display:flex;flex-direction:row;justify-content:space-between;margin:0;padding:.5em}.aa-DetachedCancelButton{background:none;font-size:.8em;border:0;border-radius:3px;color:#373a3c;cursor:pointer;margin:0 0 0 .5em;padding:0 .5em}.aa-DetachedCancelButton:hover,.aa-DetachedCancelButton:focus{box-shadow:rgba(39,128,227,.6) 0 0 0 1px;outline:currentColor none medium}.aa-DetachedContainer--modal{bottom:inherit;height:auto;margin:0 auto;position:absolute;top:100px;border-radius:6px;max-width:850px}@media(max-width: 575.98px){.aa-DetachedContainer--modal{width:100%;top:0px;border-radius:0px;border:none}}.aa-DetachedContainer--modal .aa-PanelLayout{max-height:var(--aa-detached-modal-max-height);padding-bottom:var(--aa-spacing-half);position:static}.aa-Detached{height:100vh;overflow:hidden}.aa-DetachedOverlay{background-color:rgba(55,58,60,.4);position:fixed;left:0;right:0;top:0;margin:0;padding:0;height:100vh;z-index:1100}.quarto-listing{padding-bottom:1em}.listing-pagination{padding-top:.5em}ul.pagination{float:right;padding-left:8px;padding-top:.5em}ul.pagination li{padding-right:.75em}ul.pagination li.disabled a,ul.pagination li.active a{color:#373a3c;text-decoration:none}ul.pagination li:last-of-type{padding-right:0}.listing-actions-group{display:flex}.quarto-listing-filter{margin-bottom:1em;width:200px;margin-left:auto}.quarto-listing-sort{margin-bottom:1em;margin-right:auto;width:auto}.quarto-listing-sort .input-group-text{font-size:.8em}.input-group-text{border-right:none}.quarto-listing-sort select.form-select{font-size:.8em}.listing-no-matching{text-align:center;padding-top:2em;padding-bottom:3em;font-size:1em}#quarto-margin-sidebar .quarto-listing-category{padding-top:0;font-size:1rem}#quarto-margin-sidebar .quarto-listing-category-title{cursor:pointer;font-weight:600;font-size:1rem}.quarto-listing-category .category{cursor:pointer}.quarto-listing-category .category.active{font-weight:600}.quarto-listing-category.category-cloud{display:flex;flex-wrap:wrap;align-items:baseline}.quarto-listing-category.category-cloud .category{padding-right:5px}.quarto-listing-category.category-cloud .category-cloud-1{font-size:.75em}.quarto-listing-category.category-cloud .category-cloud-2{font-size:.95em}.quarto-listing-category.category-cloud .category-cloud-3{font-size:1.15em}.quarto-listing-category.category-cloud .category-cloud-4{font-size:1.35em}.quarto-listing-category.category-cloud .category-cloud-5{font-size:1.55em}.quarto-listing-category.category-cloud .category-cloud-6{font-size:1.75em}.quarto-listing-category.category-cloud .category-cloud-7{font-size:1.95em}.quarto-listing-category.category-cloud .category-cloud-8{font-size:2.15em}.quarto-listing-category.category-cloud .category-cloud-9{font-size:2.35em}.quarto-listing-category.category-cloud .category-cloud-10{font-size:2.55em}.quarto-listing-cols-1{grid-template-columns:repeat(1, minmax(0, 1fr));gap:1.5em}@media(max-width: 767.98px){.quarto-listing-cols-1{grid-template-columns:repeat(1, minmax(0, 1fr));gap:1.5em}}@media(max-width: 575.98px){.quarto-listing-cols-1{grid-template-columns:minmax(0, 1fr);gap:1.5em}}.quarto-listing-cols-2{grid-template-columns:repeat(2, minmax(0, 1fr));gap:1.5em}@media(max-width: 767.98px){.quarto-listing-cols-2{grid-template-columns:repeat(2, minmax(0, 1fr));gap:1.5em}}@media(max-width: 575.98px){.quarto-listing-cols-2{grid-template-columns:minmax(0, 1fr);gap:1.5em}}.quarto-listing-cols-3{grid-template-columns:repeat(3, minmax(0, 1fr));gap:1.5em}@media(max-width: 767.98px){.quarto-listing-cols-3{grid-template-columns:repeat(2, minmax(0, 1fr));gap:1.5em}}@media(max-width: 575.98px){.quarto-listing-cols-3{grid-template-columns:minmax(0, 1fr);gap:1.5em}}.quarto-listing-cols-4{grid-template-columns:repeat(4, minmax(0, 1fr));gap:1.5em}@media(max-width: 767.98px){.quarto-listing-cols-4{grid-template-columns:repeat(2, minmax(0, 1fr));gap:1.5em}}@media(max-width: 575.98px){.quarto-listing-cols-4{grid-template-columns:minmax(0, 1fr);gap:1.5em}}.quarto-listing-cols-5{grid-template-columns:repeat(5, minmax(0, 1fr));gap:1.5em}@media(max-width: 767.98px){.quarto-listing-cols-5{grid-template-columns:repeat(2, minmax(0, 1fr));gap:1.5em}}@media(max-width: 575.98px){.quarto-listing-cols-5{grid-template-columns:minmax(0, 1fr);gap:1.5em}}.quarto-listing-cols-6{grid-template-columns:repeat(6, minmax(0, 1fr));gap:1.5em}@media(max-width: 767.98px){.quarto-listing-cols-6{grid-template-columns:repeat(2, minmax(0, 1fr));gap:1.5em}}@media(max-width: 575.98px){.quarto-listing-cols-6{grid-template-columns:minmax(0, 1fr);gap:1.5em}}.quarto-listing-cols-7{grid-template-columns:repeat(7, minmax(0, 1fr));gap:1.5em}@media(max-width: 767.98px){.quarto-listing-cols-7{grid-template-columns:repeat(2, minmax(0, 1fr));gap:1.5em}}@media(max-width: 575.98px){.quarto-listing-cols-7{grid-template-columns:minmax(0, 1fr);gap:1.5em}}.quarto-listing-cols-8{grid-template-columns:repeat(8, minmax(0, 1fr));gap:1.5em}@media(max-width: 767.98px){.quarto-listing-cols-8{grid-template-columns:repeat(2, minmax(0, 1fr));gap:1.5em}}@media(max-width: 575.98px){.quarto-listing-cols-8{grid-template-columns:minmax(0, 1fr);gap:1.5em}}.quarto-listing-cols-9{grid-template-columns:repeat(9, minmax(0, 1fr));gap:1.5em}@media(max-width: 767.98px){.quarto-listing-cols-9{grid-template-columns:repeat(2, minmax(0, 1fr));gap:1.5em}}@media(max-width: 575.98px){.quarto-listing-cols-9{grid-template-columns:minmax(0, 1fr);gap:1.5em}}.quarto-listing-cols-10{grid-template-columns:repeat(10, minmax(0, 1fr));gap:1.5em}@media(max-width: 767.98px){.quarto-listing-cols-10{grid-template-columns:repeat(2, minmax(0, 1fr));gap:1.5em}}@media(max-width: 575.98px){.quarto-listing-cols-10{grid-template-columns:minmax(0, 1fr);gap:1.5em}}.quarto-listing-cols-11{grid-template-columns:repeat(11, minmax(0, 1fr));gap:1.5em}@media(max-width: 767.98px){.quarto-listing-cols-11{grid-template-columns:repeat(2, minmax(0, 1fr));gap:1.5em}}@media(max-width: 575.98px){.quarto-listing-cols-11{grid-template-columns:minmax(0, 1fr);gap:1.5em}}.quarto-listing-cols-12{grid-template-columns:repeat(12, minmax(0, 1fr));gap:1.5em}@media(max-width: 767.98px){.quarto-listing-cols-12{grid-template-columns:repeat(2, minmax(0, 1fr));gap:1.5em}}@media(max-width: 575.98px){.quarto-listing-cols-12{grid-template-columns:minmax(0, 1fr);gap:1.5em}}.quarto-listing-grid{gap:1.5em}.quarto-grid-item.borderless{border:none}.quarto-grid-item.borderless .listing-categories .listing-category:last-of-type,.quarto-grid-item.borderless .listing-categories .listing-category:first-of-type{padding-left:0}.quarto-grid-item.borderless .listing-categories .listing-category{border:0}.quarto-grid-link{text-decoration:none;color:inherit}.quarto-grid-link:hover{text-decoration:none;color:inherit}.quarto-grid-item h5.title,.quarto-grid-item .title.h5{margin-top:0;margin-bottom:0}.quarto-grid-item .card-footer{display:flex;justify-content:space-between;font-size:.8em}.quarto-grid-item .card-footer p{margin-bottom:0}.quarto-grid-item p.card-img-top{margin-bottom:0}.quarto-grid-item p.card-img-top>img{object-fit:cover}.quarto-grid-item .card-other-values{margin-top:.5em;font-size:.8em}.quarto-grid-item .card-other-values tr{margin-bottom:.5em}.quarto-grid-item .card-other-values tr>td:first-of-type{font-weight:600;padding-right:1em;padding-left:1em;vertical-align:top}.quarto-grid-item div.post-contents{display:flex;flex-direction:column;text-decoration:none;height:100%}.quarto-grid-item .listing-item-img-placeholder{background-color:#adb5bd;flex-shrink:0}.quarto-grid-item .card-attribution{padding-top:1em;display:flex;gap:1em;text-transform:uppercase;color:#6c757d;font-weight:500;flex-grow:10;align-items:flex-end}.quarto-grid-item .description{padding-bottom:1em}.quarto-grid-item .card-attribution .date{align-self:flex-end}.quarto-grid-item .card-attribution.justify{justify-content:space-between}.quarto-grid-item .card-attribution.start{justify-content:flex-start}.quarto-grid-item .card-attribution.end{justify-content:flex-end}.quarto-grid-item .card-title{margin-bottom:.1em}.quarto-grid-item .card-subtitle{padding-top:.25em}.quarto-grid-item .card-text{font-size:.9em}.quarto-grid-item .listing-reading-time{padding-bottom:.25em}.quarto-grid-item .card-text-small{font-size:.8em}.quarto-grid-item .card-subtitle.subtitle{font-size:.9em;font-weight:600;padding-bottom:.5em}.quarto-grid-item .listing-categories{display:flex;flex-wrap:wrap;padding-bottom:5px}.quarto-grid-item .listing-categories .listing-category{color:#6c757d;border:solid 1px #dee2e6;border-radius:.25rem;text-transform:uppercase;font-size:.65em;padding-left:.5em;padding-right:.5em;padding-top:.15em;padding-bottom:.15em;cursor:pointer;margin-right:4px;margin-bottom:4px}.quarto-grid-item.card-right{text-align:right}.quarto-grid-item.card-right .listing-categories{justify-content:flex-end}.quarto-grid-item.card-left{text-align:left}.quarto-grid-item.card-center{text-align:center}.quarto-grid-item.card-center .listing-description{text-align:justify}.quarto-grid-item.card-center .listing-categories{justify-content:center}table.quarto-listing-table td.image{padding:0px}table.quarto-listing-table td.image img{width:100%;max-width:50px;object-fit:contain}table.quarto-listing-table a{text-decoration:none}table.quarto-listing-table th a{color:inherit}table.quarto-listing-table th a.asc:after{margin-bottom:-2px;margin-left:5px;display:inline-block;height:1rem;width:1rem;background-repeat:no-repeat;background-size:1rem 1rem;background-image:url('data:image/svg+xml,');content:""}table.quarto-listing-table th a.desc:after{margin-bottom:-2px;margin-left:5px;display:inline-block;height:1rem;width:1rem;background-repeat:no-repeat;background-size:1rem 1rem;background-image:url('data:image/svg+xml,');content:""}table.quarto-listing-table.table-hover td{cursor:pointer}.quarto-post.image-left{flex-direction:row}.quarto-post.image-right{flex-direction:row-reverse}@media(max-width: 767.98px){.quarto-post.image-right,.quarto-post.image-left{gap:0em;flex-direction:column}.quarto-post .metadata{padding-bottom:1em;order:2}.quarto-post .body{order:1}.quarto-post .thumbnail{order:3}}.list.quarto-listing-default div:last-of-type{border-bottom:none}@media(min-width: 992px){.quarto-listing-container-default{margin-right:2em}}div.quarto-post{display:flex;gap:2em;margin-bottom:1.5em;border-bottom:1px solid #dee2e6}@media(max-width: 767.98px){div.quarto-post{padding-bottom:1em}}div.quarto-post .metadata{flex-basis:20%;flex-grow:0;margin-top:.2em;flex-shrink:10}div.quarto-post .thumbnail{flex-basis:30%;flex-grow:0;flex-shrink:0}div.quarto-post .thumbnail img{margin-top:.4em;width:100%;object-fit:cover}div.quarto-post .body{flex-basis:45%;flex-grow:1;flex-shrink:0}div.quarto-post .body h3.listing-title,div.quarto-post .body .listing-title.h3{margin-top:0px;margin-bottom:0px;border-bottom:none}div.quarto-post .body .listing-subtitle{font-size:.875em;margin-bottom:.5em;margin-top:.2em}div.quarto-post .body .description{font-size:.9em}div.quarto-post a{color:#373a3c;display:flex;flex-direction:column;text-decoration:none}div.quarto-post a div.description{flex-shrink:0}div.quarto-post .metadata{display:flex;flex-direction:column;font-size:.8em;font-family:var(--bs-font-sans-serif);flex-basis:33%}div.quarto-post .listing-categories{display:flex;flex-wrap:wrap;padding-bottom:5px}div.quarto-post .listing-categories .listing-category{color:#6c757d;border:solid 1px #dee2e6;border-radius:.25rem;text-transform:uppercase;font-size:.65em;padding-left:.5em;padding-right:.5em;padding-top:.15em;padding-bottom:.15em;cursor:pointer;margin-right:4px;margin-bottom:4px}div.quarto-post .listing-description{margin-bottom:.5em}div.quarto-about-jolla{display:flex !important;flex-direction:column;align-items:center;margin-top:10%;padding-bottom:1em}div.quarto-about-jolla .about-image{object-fit:cover;margin-left:auto;margin-right:auto;margin-bottom:1.5em}div.quarto-about-jolla img.round{border-radius:50%}div.quarto-about-jolla img.rounded{border-radius:10px}div.quarto-about-jolla .quarto-title h1.title,div.quarto-about-jolla .quarto-title .title.h1{text-align:center}div.quarto-about-jolla .quarto-title .description{text-align:center}div.quarto-about-jolla h2,div.quarto-about-jolla .h2{border-bottom:none}div.quarto-about-jolla .about-sep{width:60%}div.quarto-about-jolla main{text-align:center}div.quarto-about-jolla .about-links{display:flex}@media(min-width: 992px){div.quarto-about-jolla .about-links{flex-direction:row;column-gap:.8em;row-gap:15px;flex-wrap:wrap}}@media(max-width: 991.98px){div.quarto-about-jolla .about-links{flex-direction:column;row-gap:1em;width:100%;padding-bottom:1.5em}}div.quarto-about-jolla .about-link{color:#686d71;text-decoration:none;border:solid 1px}@media(min-width: 992px){div.quarto-about-jolla .about-link{font-size:.8em;padding:.25em .5em;border-radius:4px}}@media(max-width: 991.98px){div.quarto-about-jolla .about-link{font-size:1.1em;padding:.5em .5em;text-align:center;border-radius:6px}}div.quarto-about-jolla .about-link:hover{color:#2780e3}div.quarto-about-jolla .about-link i.bi{margin-right:.15em}div.quarto-about-solana{display:flex !important;flex-direction:column;padding-top:3em !important;padding-bottom:1em}div.quarto-about-solana .about-entity{display:flex !important;align-items:start;justify-content:space-between}@media(min-width: 992px){div.quarto-about-solana .about-entity{flex-direction:row}}@media(max-width: 991.98px){div.quarto-about-solana .about-entity{flex-direction:column-reverse;align-items:center;text-align:center}}div.quarto-about-solana .about-entity .entity-contents{display:flex;flex-direction:column}@media(max-width: 767.98px){div.quarto-about-solana .about-entity .entity-contents{width:100%}}div.quarto-about-solana .about-entity .about-image{object-fit:cover}@media(max-width: 991.98px){div.quarto-about-solana .about-entity .about-image{margin-bottom:1.5em}}div.quarto-about-solana .about-entity img.round{border-radius:50%}div.quarto-about-solana .about-entity img.rounded{border-radius:10px}div.quarto-about-solana .about-entity .about-links{display:flex;justify-content:left;padding-bottom:1.2em}@media(min-width: 992px){div.quarto-about-solana .about-entity .about-links{flex-direction:row;column-gap:.8em;row-gap:15px;flex-wrap:wrap}}@media(max-width: 991.98px){div.quarto-about-solana .about-entity .about-links{flex-direction:column;row-gap:1em;width:100%;padding-bottom:1.5em}}div.quarto-about-solana .about-entity .about-link{color:#686d71;text-decoration:none;border:solid 1px}@media(min-width: 992px){div.quarto-about-solana .about-entity .about-link{font-size:.8em;padding:.25em .5em;border-radius:4px}}@media(max-width: 991.98px){div.quarto-about-solana .about-entity .about-link{font-size:1.1em;padding:.5em .5em;text-align:center;border-radius:6px}}div.quarto-about-solana .about-entity .about-link:hover{color:#2780e3}div.quarto-about-solana .about-entity .about-link i.bi{margin-right:.15em}div.quarto-about-solana .about-contents{padding-right:1.5em;flex-basis:0;flex-grow:1}div.quarto-about-solana .about-contents main.content{margin-top:0}div.quarto-about-solana .about-contents h2,div.quarto-about-solana .about-contents .h2{border-bottom:none}div.quarto-about-trestles{display:flex !important;flex-direction:row;padding-top:3em !important;padding-bottom:1em}@media(max-width: 991.98px){div.quarto-about-trestles{flex-direction:column;padding-top:0em !important}}div.quarto-about-trestles .about-entity{display:flex !important;flex-direction:column;align-items:center;text-align:center;padding-right:1em}@media(min-width: 992px){div.quarto-about-trestles .about-entity{flex:0 0 42%}}div.quarto-about-trestles .about-entity .about-image{object-fit:cover;margin-bottom:1.5em}div.quarto-about-trestles .about-entity img.round{border-radius:50%}div.quarto-about-trestles .about-entity img.rounded{border-radius:10px}div.quarto-about-trestles .about-entity .about-links{display:flex;justify-content:center}@media(min-width: 992px){div.quarto-about-trestles .about-entity .about-links{flex-direction:row;column-gap:.8em;row-gap:15px;flex-wrap:wrap}}@media(max-width: 991.98px){div.quarto-about-trestles .about-entity .about-links{flex-direction:column;row-gap:1em;width:100%;padding-bottom:1.5em}}div.quarto-about-trestles .about-entity .about-link{color:#686d71;text-decoration:none;border:solid 1px}@media(min-width: 992px){div.quarto-about-trestles .about-entity .about-link{font-size:.8em;padding:.25em .5em;border-radius:4px}}@media(max-width: 991.98px){div.quarto-about-trestles .about-entity .about-link{font-size:1.1em;padding:.5em .5em;text-align:center;border-radius:6px}}div.quarto-about-trestles .about-entity .about-link:hover{color:#2780e3}div.quarto-about-trestles .about-entity .about-link i.bi{margin-right:.15em}div.quarto-about-trestles .about-contents{flex-basis:0;flex-grow:1}div.quarto-about-trestles .about-contents h2,div.quarto-about-trestles .about-contents .h2{border-bottom:none}@media(min-width: 992px){div.quarto-about-trestles .about-contents{border-left:solid 1px #dee2e6;padding-left:1.5em}}div.quarto-about-trestles .about-contents main.content{margin-top:0}div.quarto-about-marquee{padding-bottom:1em}div.quarto-about-marquee .about-contents{display:flex;flex-direction:column}div.quarto-about-marquee .about-image{max-height:550px;margin-bottom:1.5em;object-fit:cover}div.quarto-about-marquee img.round{border-radius:50%}div.quarto-about-marquee img.rounded{border-radius:10px}div.quarto-about-marquee h2,div.quarto-about-marquee .h2{border-bottom:none}div.quarto-about-marquee .about-links{display:flex;justify-content:center;padding-top:1.5em}@media(min-width: 992px){div.quarto-about-marquee .about-links{flex-direction:row;column-gap:.8em;row-gap:15px;flex-wrap:wrap}}@media(max-width: 991.98px){div.quarto-about-marquee .about-links{flex-direction:column;row-gap:1em;width:100%;padding-bottom:1.5em}}div.quarto-about-marquee .about-link{color:#686d71;text-decoration:none;border:solid 1px}@media(min-width: 992px){div.quarto-about-marquee .about-link{font-size:.8em;padding:.25em .5em;border-radius:4px}}@media(max-width: 991.98px){div.quarto-about-marquee .about-link{font-size:1.1em;padding:.5em .5em;text-align:center;border-radius:6px}}div.quarto-about-marquee .about-link:hover{color:#2780e3}div.quarto-about-marquee .about-link i.bi{margin-right:.15em}@media(min-width: 992px){div.quarto-about-marquee .about-link{border:none}}div.quarto-about-broadside{display:flex;flex-direction:column;padding-bottom:1em}div.quarto-about-broadside .about-main{display:flex !important;padding-top:0 !important}@media(min-width: 992px){div.quarto-about-broadside .about-main{flex-direction:row;align-items:flex-start}}@media(max-width: 991.98px){div.quarto-about-broadside .about-main{flex-direction:column}}@media(max-width: 991.98px){div.quarto-about-broadside .about-main .about-entity{flex-shrink:0;width:100%;height:450px;margin-bottom:1.5em;background-size:cover;background-repeat:no-repeat}}@media(min-width: 992px){div.quarto-about-broadside .about-main .about-entity{flex:0 10 50%;margin-right:1.5em;width:100%;height:100%;background-size:100%;background-repeat:no-repeat}}div.quarto-about-broadside .about-main .about-contents{padding-top:14px;flex:0 0 50%}div.quarto-about-broadside h2,div.quarto-about-broadside .h2{border-bottom:none}div.quarto-about-broadside .about-sep{margin-top:1.5em;width:60%;align-self:center}div.quarto-about-broadside .about-links{display:flex;justify-content:center;column-gap:20px;padding-top:1.5em}@media(min-width: 992px){div.quarto-about-broadside .about-links{flex-direction:row;column-gap:.8em;row-gap:15px;flex-wrap:wrap}}@media(max-width: 991.98px){div.quarto-about-broadside .about-links{flex-direction:column;row-gap:1em;width:100%;padding-bottom:1.5em}}div.quarto-about-broadside .about-link{color:#686d71;text-decoration:none;border:solid 1px}@media(min-width: 992px){div.quarto-about-broadside .about-link{font-size:.8em;padding:.25em .5em;border-radius:4px}}@media(max-width: 991.98px){div.quarto-about-broadside .about-link{font-size:1.1em;padding:.5em .5em;text-align:center;border-radius:6px}}div.quarto-about-broadside .about-link:hover{color:#2780e3}div.quarto-about-broadside .about-link i.bi{margin-right:.15em}@media(min-width: 992px){div.quarto-about-broadside .about-link{border:none}}.tippy-box[data-theme~=quarto]{background-color:#fff;border:solid 1px #dee2e6;border-radius:.25rem;color:#373a3c;font-size:.875rem}.tippy-box[data-theme~=quarto]>.tippy-backdrop{background-color:#fff}.tippy-box[data-theme~=quarto]>.tippy-arrow:after,.tippy-box[data-theme~=quarto]>.tippy-svg-arrow:after{content:"";position:absolute;z-index:-1}.tippy-box[data-theme~=quarto]>.tippy-arrow:after{border-color:rgba(0,0,0,0);border-style:solid}.tippy-box[data-placement^=top]>.tippy-arrow:before{bottom:-6px}.tippy-box[data-placement^=bottom]>.tippy-arrow:before{top:-6px}.tippy-box[data-placement^=right]>.tippy-arrow:before{left:-6px}.tippy-box[data-placement^=left]>.tippy-arrow:before{right:-6px}.tippy-box[data-theme~=quarto][data-placement^=top]>.tippy-arrow:before{border-top-color:#fff}.tippy-box[data-theme~=quarto][data-placement^=top]>.tippy-arrow:after{border-top-color:#dee2e6;border-width:7px 7px 0;top:17px;left:1px}.tippy-box[data-theme~=quarto][data-placement^=top]>.tippy-svg-arrow>svg{top:16px}.tippy-box[data-theme~=quarto][data-placement^=top]>.tippy-svg-arrow:after{top:17px}.tippy-box[data-theme~=quarto][data-placement^=bottom]>.tippy-arrow:before{border-bottom-color:#fff;bottom:16px}.tippy-box[data-theme~=quarto][data-placement^=bottom]>.tippy-arrow:after{border-bottom-color:#dee2e6;border-width:0 7px 7px;bottom:17px;left:1px}.tippy-box[data-theme~=quarto][data-placement^=bottom]>.tippy-svg-arrow>svg{bottom:15px}.tippy-box[data-theme~=quarto][data-placement^=bottom]>.tippy-svg-arrow:after{bottom:17px}.tippy-box[data-theme~=quarto][data-placement^=left]>.tippy-arrow:before{border-left-color:#fff}.tippy-box[data-theme~=quarto][data-placement^=left]>.tippy-arrow:after{border-left-color:#dee2e6;border-width:7px 0 7px 7px;left:17px;top:1px}.tippy-box[data-theme~=quarto][data-placement^=left]>.tippy-svg-arrow>svg{left:11px}.tippy-box[data-theme~=quarto][data-placement^=left]>.tippy-svg-arrow:after{left:12px}.tippy-box[data-theme~=quarto][data-placement^=right]>.tippy-arrow:before{border-right-color:#fff;right:16px}.tippy-box[data-theme~=quarto][data-placement^=right]>.tippy-arrow:after{border-width:7px 7px 7px 0;right:17px;top:1px;border-right-color:#dee2e6}.tippy-box[data-theme~=quarto][data-placement^=right]>.tippy-svg-arrow>svg{right:11px}.tippy-box[data-theme~=quarto][data-placement^=right]>.tippy-svg-arrow:after{right:12px}.tippy-box[data-theme~=quarto]>.tippy-svg-arrow{fill:#373a3c}.tippy-box[data-theme~=quarto]>.tippy-svg-arrow:after{background-image:url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTYiIGhlaWdodD0iNiIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48cGF0aCBkPSJNMCA2czEuNzk2LS4wMTMgNC42Ny0zLjYxNUM1Ljg1MS45IDYuOTMuMDA2IDggMGMxLjA3LS4wMDYgMi4xNDguODg3IDMuMzQzIDIuMzg1QzE0LjIzMyA2LjAwNSAxNiA2IDE2IDZIMHoiIGZpbGw9InJnYmEoMCwgOCwgMTYsIDAuMikiLz48L3N2Zz4=);background-size:16px 6px;width:16px;height:6px}.top-right{position:absolute;top:1em;right:1em}.hidden{display:none !important}.zindex-bottom{z-index:-1 !important}.quarto-layout-panel{margin-bottom:1em}.quarto-layout-panel>figure{width:100%}.quarto-layout-panel>figure>figcaption,.quarto-layout-panel>.panel-caption{margin-top:10pt}.quarto-layout-panel>.table-caption{margin-top:0px}.table-caption p{margin-bottom:.5em}.quarto-layout-row{display:flex;flex-direction:row;align-items:flex-start}.quarto-layout-valign-top{align-items:flex-start}.quarto-layout-valign-bottom{align-items:flex-end}.quarto-layout-valign-center{align-items:center}.quarto-layout-cell{position:relative;margin-right:20px}.quarto-layout-cell:last-child{margin-right:0}.quarto-layout-cell figure,.quarto-layout-cell>p{margin:.2em}.quarto-layout-cell img{max-width:100%}.quarto-layout-cell .html-widget{width:100% !important}.quarto-layout-cell div figure p{margin:0}.quarto-layout-cell figure{display:inline-block;margin-inline-start:0;margin-inline-end:0}.quarto-layout-cell table{display:inline-table}.quarto-layout-cell-subref figcaption,figure .quarto-layout-row figure figcaption{text-align:center;font-style:italic}.quarto-figure{position:relative;margin-bottom:1em}.quarto-figure>figure{width:100%;margin-bottom:0}.quarto-figure-left>figure>p,.quarto-figure-left>figure>div{text-align:left}.quarto-figure-center>figure>p,.quarto-figure-center>figure>div{text-align:center}.quarto-figure-right>figure>p,.quarto-figure-right>figure>div{text-align:right}figure>p:empty{display:none}figure>p:first-child{margin-top:0;margin-bottom:0}figure>figcaption{margin-top:.5em}div[id^=tbl-]{position:relative}.quarto-figure>.anchorjs-link{position:absolute;top:.6em;right:.5em}div[id^=tbl-]>.anchorjs-link{position:absolute;top:.7em;right:.3em}.quarto-figure:hover>.anchorjs-link,div[id^=tbl-]:hover>.anchorjs-link,h2:hover>.anchorjs-link,.h2:hover>.anchorjs-link,h3:hover>.anchorjs-link,.h3:hover>.anchorjs-link,h4:hover>.anchorjs-link,.h4:hover>.anchorjs-link,h5:hover>.anchorjs-link,.h5:hover>.anchorjs-link,h6:hover>.anchorjs-link,.h6:hover>.anchorjs-link,.reveal-anchorjs-link>.anchorjs-link{opacity:1}#title-block-header{margin-block-end:1rem;position:relative;margin-top:-1px}#title-block-header .abstract{margin-block-start:1rem}#title-block-header .abstract .abstract-title{font-weight:600}#title-block-header a{text-decoration:none}#title-block-header .author,#title-block-header .date,#title-block-header .doi{margin-block-end:.2rem}#title-block-header .quarto-title-block>div{display:flex}#title-block-header .quarto-title-block>div>h1,#title-block-header .quarto-title-block>div>.h1{flex-grow:1}#title-block-header .quarto-title-block>div>button{flex-shrink:0;height:2.25rem;margin-top:0}@media(min-width: 992px){#title-block-header .quarto-title-block>div>button{margin-top:5px}}tr.header>th>p:last-of-type{margin-bottom:0px}table,.table{caption-side:top;margin-bottom:1.5rem}caption,.table-caption{padding-top:.5rem;padding-bottom:.5rem;text-align:center}.utterances{max-width:none;margin-left:-8px}iframe{margin-bottom:1em}details{margin-bottom:1em}details[show]{margin-bottom:0}details>summary{color:#6c757d}details>summary>p:only-child{display:inline}pre.sourceCode,code.sourceCode{position:relative}p code:not(.sourceCode){white-space:pre-wrap}code{white-space:pre}@media print{code{white-space:pre-wrap}}pre>code{display:block}pre>code.sourceCode{white-space:pre}pre>code.sourceCode>span>a:first-child::before{text-decoration:none}pre.code-overflow-wrap>code.sourceCode{white-space:pre-wrap}pre.code-overflow-scroll>code.sourceCode{white-space:pre}code a:any-link{color:inherit;text-decoration:none}code a:hover{color:inherit;text-decoration:underline}ul.task-list{padding-left:1em}[data-tippy-root]{display:inline-block}.tippy-content .footnote-back{display:none}.quarto-embedded-source-code{display:none}.quarto-unresolved-ref{font-weight:600}.quarto-cover-image{max-width:35%;float:right;margin-left:30px}.cell-output-display .widget-subarea{margin-bottom:1em}.cell-output-display:not(.no-overflow-x),.knitsql-table:not(.no-overflow-x){overflow-x:auto}.panel-input{margin-bottom:1em}.panel-input>div,.panel-input>div>div{display:inline-block;vertical-align:top;padding-right:12px}.panel-input>p:last-child{margin-bottom:0}.layout-sidebar{margin-bottom:1em}.layout-sidebar .tab-content{border:none}.tab-content>.page-columns.active{display:grid}div.sourceCode>iframe{width:100%;height:300px;margin-bottom:-0.5em}div.ansi-escaped-output{font-family:monospace;display:block}/*! +* +* ansi colors from IPython notebook's +* +*/.ansi-black-fg{color:#3e424d}.ansi-black-bg{background-color:#3e424d}.ansi-black-intense-fg{color:#282c36}.ansi-black-intense-bg{background-color:#282c36}.ansi-red-fg{color:#e75c58}.ansi-red-bg{background-color:#e75c58}.ansi-red-intense-fg{color:#b22b31}.ansi-red-intense-bg{background-color:#b22b31}.ansi-green-fg{color:#00a250}.ansi-green-bg{background-color:#00a250}.ansi-green-intense-fg{color:#007427}.ansi-green-intense-bg{background-color:#007427}.ansi-yellow-fg{color:#ddb62b}.ansi-yellow-bg{background-color:#ddb62b}.ansi-yellow-intense-fg{color:#b27d12}.ansi-yellow-intense-bg{background-color:#b27d12}.ansi-blue-fg{color:#208ffb}.ansi-blue-bg{background-color:#208ffb}.ansi-blue-intense-fg{color:#0065ca}.ansi-blue-intense-bg{background-color:#0065ca}.ansi-magenta-fg{color:#d160c4}.ansi-magenta-bg{background-color:#d160c4}.ansi-magenta-intense-fg{color:#a03196}.ansi-magenta-intense-bg{background-color:#a03196}.ansi-cyan-fg{color:#60c6c8}.ansi-cyan-bg{background-color:#60c6c8}.ansi-cyan-intense-fg{color:#258f8f}.ansi-cyan-intense-bg{background-color:#258f8f}.ansi-white-fg{color:#c5c1b4}.ansi-white-bg{background-color:#c5c1b4}.ansi-white-intense-fg{color:#a1a6b2}.ansi-white-intense-bg{background-color:#a1a6b2}.ansi-default-inverse-fg{color:#fff}.ansi-default-inverse-bg{background-color:#000}.ansi-bold{font-weight:bold}.ansi-underline{text-decoration:underline}:root{--quarto-body-bg: #fff;--quarto-body-color: #373a3c;--quarto-text-muted: #6c757d;--quarto-border-color: #dee2e6;--quarto-border-width: 1px;--quarto-border-radius: 0.25rem}table.gt_table{color:var(--quarto-body-color);font-size:1em;width:100%;background-color:rgba(0,0,0,0);border-top-width:inherit;border-bottom-width:inherit;border-color:var(--quarto-border-color)}table.gt_table th.gt_column_spanner_outer{color:var(--quarto-body-color);background-color:rgba(0,0,0,0);border-top-width:inherit;border-bottom-width:inherit;border-color:var(--quarto-border-color)}table.gt_table th.gt_col_heading{color:var(--quarto-body-color);font-weight:bold;background-color:rgba(0,0,0,0)}table.gt_table thead.gt_col_headings{border-bottom:1px solid currentColor;border-top-width:inherit;border-top-color:var(--quarto-border-color)}table.gt_table thead.gt_col_headings:not(:first-child){border-top-width:1px;border-top-color:var(--quarto-border-color)}table.gt_table td.gt_row{border-bottom-width:1px;border-bottom-color:var(--quarto-border-color);border-top-width:0px}table.gt_table tbody.gt_table_body{border-top-width:1px;border-bottom-width:1px;border-bottom-color:var(--quarto-border-color);border-top-color:currentColor}div.columns{display:initial;gap:initial}div.column{display:inline-block;overflow-x:initial;vertical-align:top;width:50%}.code-annotation-tip-content{word-wrap:break-word}.code-annotation-container-hidden{display:none !important}dl.code-annotation-container-grid{display:grid;grid-template-columns:min-content auto}dl.code-annotation-container-grid dt{grid-column:1}dl.code-annotation-container-grid dd{grid-column:2}pre.sourceCode.code-annotation-code{padding-right:0}code.sourceCode .code-annotation-anchor{z-index:100;position:absolute;right:.5em;left:inherit;background-color:rgba(0,0,0,0)}:root{--mermaid-bg-color: #fff;--mermaid-edge-color: #373a3c;--mermaid-node-fg-color: #373a3c;--mermaid-fg-color: #373a3c;--mermaid-fg-color--lighter: #4f5457;--mermaid-fg-color--lightest: #686d71;--mermaid-font-family: Source Sans Pro, -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Helvetica Neue, Arial, sans-serif, Apple Color Emoji, Segoe UI Emoji, Segoe UI Symbol;--mermaid-label-bg-color: #fff;--mermaid-label-fg-color: #2780e3;--mermaid-node-bg-color: rgba(39, 128, 227, 0.1);--mermaid-node-fg-color: #373a3c}@media print{:root{font-size:11pt}#quarto-sidebar,#TOC,.nav-page{display:none}.page-columns .content{grid-column-start:page-start}.fixed-top{position:relative}.panel-caption,.figure-caption,figcaption{color:#666}}.code-copy-button{position:absolute;top:0;right:0;border:0;margin-top:5px;margin-right:5px;background-color:rgba(0,0,0,0);z-index:3}.code-copy-button:focus{outline:none}.code-copy-button-tooltip{font-size:.75em}pre.sourceCode:hover>.code-copy-button>.bi::before{display:inline-block;height:1rem;width:1rem;content:"";vertical-align:-0.125em;background-image:url('data:image/svg+xml,');background-repeat:no-repeat;background-size:1rem 1rem}pre.sourceCode:hover>.code-copy-button-checked>.bi::before{background-image:url('data:image/svg+xml,')}pre.sourceCode:hover>.code-copy-button:hover>.bi::before{background-image:url('data:image/svg+xml,')}pre.sourceCode:hover>.code-copy-button-checked:hover>.bi::before{background-image:url('data:image/svg+xml,')}main ol ol,main ul ul,main ol ul,main ul ol{margin-bottom:1em}ul>li:not(:has(>p))>ul,ol>li:not(:has(>p))>ul,ul>li:not(:has(>p))>ol,ol>li:not(:has(>p))>ol{margin-bottom:0}ul>li:not(:has(>p))>ul>li:has(>p),ol>li:not(:has(>p))>ul>li:has(>p),ul>li:not(:has(>p))>ol>li:has(>p),ol>li:not(:has(>p))>ol>li:has(>p){margin-top:1rem}body{margin:0}main.page-columns>header>h1.title,main.page-columns>header>.title.h1{margin-bottom:0}@media(min-width: 992px){body .page-columns{display:grid;gap:0;grid-template-columns:[screen-start] 1.5em [screen-start-inset] 5fr [page-start page-start-inset] 35px [body-start-outset] 35px [body-start] 1.5em [body-content-start] minmax(500px, calc( 850px - 3em )) [body-content-end] 1.5em [body-end] 35px [body-end-outset] minmax(75px, 145px) [page-end-inset] 35px [page-end] 5fr [screen-end-inset] 1.5em [screen-end]}body.fullcontent:not(.floating):not(.docked) .page-columns{display:grid;gap:0;grid-template-columns:[screen-start] 1.5em [screen-start-inset] 5fr [page-start page-start-inset] 35px [body-start-outset] 35px [body-start] 1.5em [body-content-start] minmax(500px, calc( 850px - 3em )) [body-content-end] 1.5em [body-end] 35px [body-end-outset] 35px [page-end-inset page-end] 5fr [screen-end-inset] 1.5em}body.slimcontent:not(.floating):not(.docked) .page-columns{display:grid;gap:0;grid-template-columns:[screen-start] 1.5em [screen-start-inset] 5fr [page-start page-start-inset] 35px [body-start-outset] 35px [body-start] 1.5em [body-content-start] minmax(500px, calc( 850px - 3em )) [body-content-end] 1.5em [body-end] 50px [body-end-outset] minmax(0px, 200px) [page-end-inset] 35px [page-end] 5fr [screen-end-inset] 1.5em [screen-end]}body.listing:not(.floating):not(.docked) .page-columns{display:grid;gap:0;grid-template-columns:[screen-start] 1.5em [screen-start-inset page-start] minmax(50px, 100px) [page-start-inset] 50px [body-start-outset] 50px [body-start] 1.5em [body-content-start] minmax(500px, calc( 850px - 3em )) [body-content-end] 3em [body-end] 50px [body-end-outset] minmax(0px, 250px) [page-end-inset] minmax(50px, 100px) [page-end] 1fr [screen-end-inset] 1.5em [screen-end]}body:not(.floating):not(.docked) .page-columns.toc-left{display:grid;gap:0;grid-template-columns:[screen-start] 1.5em [screen-start-inset] 5fr [page-start] 35px [page-start-inset] minmax(0px, 175px) [body-start-outset] 35px [body-start] 1.5em [body-content-start] minmax(450px, calc( 800px - 3em )) [body-content-end] 1.5em [body-end] 50px [body-end-outset] minmax(0px, 200px) [page-end-inset] 50px [page-end] 5fr [screen-end-inset] 1.5em [screen-end]}body:not(.floating):not(.docked) .page-columns.toc-left .page-columns{display:grid;gap:0;grid-template-columns:[screen-start] 1.5em [screen-start-inset] 5fr [page-start] 35px [page-start-inset] minmax(0px, 175px) [body-start-outset] 35px [body-start] 1.5em [body-content-start] minmax(450px, calc( 800px - 3em )) [body-content-end] 1.5em [body-end] 50px [body-end-outset] minmax(0px, 200px) [page-end-inset] 50px [page-end] 5fr [screen-end-inset] 1.5em [screen-end]}body.floating .page-columns{display:grid;gap:0;grid-template-columns:[screen-start] 1.5em [screen-start-inset] 5fr [page-start] minmax(25px, 50px) [page-start-inset] minmax(50px, 150px) [body-start-outset] minmax(25px, 50px) [body-start] 1.5em [body-content-start] minmax(500px, calc( 800px - 3em )) [body-content-end] 1.5em [body-end] minmax(25px, 50px) [body-end-outset] minmax(50px, 150px) [page-end-inset] minmax(25px, 50px) [page-end] 5fr [screen-end-inset] 1.5em [screen-end]}body.docked .page-columns{display:grid;gap:0;grid-template-columns:[screen-start] 1.5em [screen-start-inset page-start] minmax(50px, 100px) [page-start-inset] 50px [body-start-outset] 50px [body-start] 1.5em [body-content-start] minmax(500px, calc( 1000px - 3em )) [body-content-end] 1.5em [body-end] 50px [body-end-outset] minmax(50px, 100px) [page-end-inset] 50px [page-end] 5fr [screen-end-inset] 1.5em [screen-end]}body.docked.fullcontent .page-columns{display:grid;gap:0;grid-template-columns:[screen-start] 1.5em [screen-start-inset page-start] minmax(50px, 100px) [page-start-inset] 50px [body-start-outset] 50px [body-start] 1.5em [body-content-start] minmax(500px, calc( 1000px - 3em )) [body-content-end] 1.5em [body-end body-end-outset page-end-inset page-end] 5fr [screen-end-inset] 1.5em [screen-end]}body.floating.fullcontent .page-columns{display:grid;gap:0;grid-template-columns:[screen-start] 1.5em [screen-start-inset] 5fr [page-start] 50px [page-start-inset] minmax(50px, 150px) [body-start-outset] 50px [body-start] 1.5em [body-content-start] minmax(500px, calc( 800px - 3em )) [body-content-end] 1.5em [body-end body-end-outset page-end-inset page-end] 5fr [screen-end-inset] 1.5em [screen-end]}body.docked.slimcontent .page-columns{display:grid;gap:0;grid-template-columns:[screen-start] 1.5em [screen-start-inset page-start] minmax(50px, 100px) [page-start-inset] 50px [body-start-outset] 50px [body-start] 1.5em [body-content-start] minmax(450px, calc( 750px - 3em )) [body-content-end] 1.5em [body-end] 50px [body-end-outset] minmax(0px, 200px) [page-end-inset] 50px [page-end] 5fr [screen-end-inset] 1.5em [screen-end]}body.docked.listing .page-columns{display:grid;gap:0;grid-template-columns:[screen-start] 1.5em [screen-start-inset page-start] minmax(50px, 100px) [page-start-inset] 50px [body-start-outset] 50px [body-start] 1.5em [body-content-start] minmax(500px, calc( 1000px - 3em )) [body-content-end] 1.5em [body-end] 50px [body-end-outset] minmax(0px, 200px) [page-end-inset] 50px [page-end] 5fr [screen-end-inset] 1.5em [screen-end]}body.floating.slimcontent .page-columns{display:grid;gap:0;grid-template-columns:[screen-start] 1.5em [screen-start-inset] 5fr [page-start] 50px [page-start-inset] minmax(50px, 150px) [body-start-outset] 50px [body-start] 1.5em [body-content-start] minmax(450px, calc( 750px - 3em )) [body-content-end] 1.5em [body-end] 50px [body-end-outset] minmax(50px, 150px) [page-end-inset] 50px [page-end] 5fr [screen-end-inset] 1.5em [screen-end]}body.floating.listing .page-columns{display:grid;gap:0;grid-template-columns:[screen-start] 1.5em [screen-start-inset] 5fr [page-start] minmax(25px, 50px) [page-start-inset] minmax(50px, 150px) [body-start-outset] minmax(25px, 50px) [body-start] 1.5em [body-content-start] minmax(500px, calc( 800px - 3em )) [body-content-end] 1.5em [body-end] minmax(25px, 50px) [body-end-outset] minmax(50px, 150px) [page-end-inset] minmax(25px, 50px) [page-end] 5fr [screen-end-inset] 1.5em [screen-end]}}@media(max-width: 991.98px){body .page-columns{display:grid;gap:0;grid-template-columns:[screen-start] 1.5em [screen-start-inset page-start page-start-inset body-start-outset] 5fr [body-start] 1.5em [body-content-start] minmax(500px, calc( 800px - 3em )) [body-content-end] 1.5em [body-end] 35px [body-end-outset] minmax(75px, 145px) [page-end-inset] 35px [page-end] 5fr [screen-end-inset] 1.5em [screen-end]}body.fullcontent:not(.floating):not(.docked) .page-columns{display:grid;gap:0;grid-template-columns:[screen-start] 1.5em [screen-start-inset page-start page-start-inset body-start-outset] 5fr [body-start] 1.5em [body-content-start] minmax(500px, calc( 800px - 3em )) [body-content-end] 1.5em [body-end body-end-outset page-end-inset page-end] 5fr [screen-end-inset] 1.5em [screen-end]}body.slimcontent:not(.floating):not(.docked) .page-columns{display:grid;gap:0;grid-template-columns:[screen-start] 1.5em [screen-start-inset page-start page-start-inset body-start-outset] 5fr [body-start] 1.5em [body-content-start] minmax(500px, calc( 800px - 3em )) [body-content-end] 1.5em [body-end] 35px [body-end-outset] minmax(75px, 145px) [page-end-inset] 35px [page-end] 5fr [screen-end-inset] 1.5em [screen-end]}body.listing:not(.floating):not(.docked) .page-columns{display:grid;gap:0;grid-template-columns:[screen-start] 1.5em [screen-start-inset page-start page-start-inset body-start-outset] 5fr [body-start] 1.5em [body-content-start] minmax(500px, calc( 1250px - 3em )) [body-content-end body-end body-end-outset page-end-inset page-end] 5fr [screen-end-inset] 1.5em [screen-end]}body:not(.floating):not(.docked) .page-columns.toc-left{display:grid;gap:0;grid-template-columns:[screen-start] 1.5em [screen-start-inset] 5fr [page-start] 35px [page-start-inset] minmax(0px, 145px) [body-start-outset] 35px [body-start] 1.5em [body-content-start] minmax(450px, calc( 800px - 3em )) [body-content-end] 1.5em [body-end body-end-outset page-end-inset page-end] 5fr [screen-end-inset] 1.5em [screen-end]}body:not(.floating):not(.docked) .page-columns.toc-left .page-columns{display:grid;gap:0;grid-template-columns:[screen-start] 1.5em [screen-start-inset] 5fr [page-start] 35px [page-start-inset] minmax(0px, 145px) [body-start-outset] 35px [body-start] 1.5em [body-content-start] minmax(450px, calc( 800px - 3em )) [body-content-end] 1.5em [body-end body-end-outset page-end-inset page-end] 5fr [screen-end-inset] 1.5em [screen-end]}body.floating .page-columns{display:grid;gap:0;grid-template-columns:[screen-start] 1.5em [screen-start-inset] 5fr [page-start page-start-inset body-start-outset body-start] 1.5em [body-content-start] minmax(500px, calc( 750px - 3em )) [body-content-end] 1.5em [body-end] 50px [body-end-outset] minmax(75px, 150px) [page-end-inset] 25px [page-end] 5fr [screen-end-inset] 1.5em [screen-end]}body.docked .page-columns{display:grid;gap:0;grid-template-columns:[screen-start] 1.5em [screen-start-inset page-start page-start-inset body-start-outset body-start body-content-start] minmax(500px, calc( 750px - 3em )) [body-content-end] 1.5em [body-end] 50px [body-end-outset] minmax(25px, 50px) [page-end-inset] 50px [page-end] 5fr [screen-end-inset] 1.5em [screen-end]}body.docked.fullcontent .page-columns{display:grid;gap:0;grid-template-columns:[screen-start] 1.5em [screen-start-inset page-start page-start-inset body-start-outset body-start body-content-start] minmax(500px, calc( 1000px - 3em )) [body-content-end] 1.5em [body-end body-end-outset page-end-inset page-end] 5fr [screen-end-inset] 1.5em [screen-end]}body.floating.fullcontent .page-columns{display:grid;gap:0;grid-template-columns:[screen-start] 1.5em [screen-start-inset] 5fr [page-start page-start-inset body-start-outset body-start] 1em [body-content-start] minmax(500px, calc( 800px - 3em )) [body-content-end] 1.5em [body-end body-end-outset page-end-inset page-end] 4fr [screen-end-inset] 1.5em [screen-end]}body.docked.slimcontent .page-columns{display:grid;gap:0;grid-template-columns:[screen-start] 1.5em [screen-start-inset page-start page-start-inset body-start-outset body-start body-content-start] minmax(500px, calc( 750px - 3em )) [body-content-end] 1.5em [body-end] 50px [body-end-outset] minmax(25px, 50px) [page-end-inset] 50px [page-end] 5fr [screen-end-inset] 1.5em [screen-end]}body.docked.listing .page-columns{display:grid;gap:0;grid-template-columns:[screen-start] 1.5em [screen-start-inset page-start page-start-inset body-start-outset body-start body-content-start] minmax(500px, calc( 750px - 3em )) [body-content-end] 1.5em [body-end] 50px [body-end-outset] minmax(25px, 50px) [page-end-inset] 50px [page-end] 5fr [screen-end-inset] 1.5em [screen-end]}body.floating.slimcontent .page-columns{display:grid;gap:0;grid-template-columns:[screen-start] 1.5em [screen-start-inset] 5fr [page-start page-start-inset body-start-outset body-start] 1em [body-content-start] minmax(500px, calc( 750px - 3em )) [body-content-end] 1.5em [body-end] 35px [body-end-outset] minmax(75px, 145px) [page-end-inset] 35px [page-end] 4fr [screen-end-inset] 1.5em [screen-end]}body.floating.listing .page-columns{display:grid;gap:0;grid-template-columns:[screen-start] 1.5em [screen-start-inset] 5fr [page-start page-start-inset body-start-outset body-start] 1em [body-content-start] minmax(500px, calc( 750px - 3em )) [body-content-end] 1.5em [body-end] 50px [body-end-outset] minmax(75px, 150px) [page-end-inset] 25px [page-end] 4fr [screen-end-inset] 1.5em [screen-end]}}@media(max-width: 767.98px){body .page-columns,body.fullcontent:not(.floating):not(.docked) .page-columns,body.slimcontent:not(.floating):not(.docked) .page-columns,body.docked .page-columns,body.docked.slimcontent .page-columns,body.docked.fullcontent .page-columns,body.floating .page-columns,body.floating.slimcontent .page-columns,body.floating.fullcontent .page-columns{display:grid;gap:0;grid-template-columns:[screen-start] 1.5em [screen-start-inset page-start page-start-inset body-start-outset body-start body-content-start] minmax(0px, 1fr) [body-content-end body-end body-end-outset page-end-inset page-end screen-end-inset] 1.5em [screen-end]}body:not(.floating):not(.docked) .page-columns.toc-left{display:grid;gap:0;grid-template-columns:[screen-start] 1.5em [screen-start-inset page-start page-start-inset body-start-outset body-start body-content-start] minmax(0px, 1fr) [body-content-end body-end body-end-outset page-end-inset page-end screen-end-inset] 1.5em [screen-end]}body:not(.floating):not(.docked) .page-columns.toc-left .page-columns{display:grid;gap:0;grid-template-columns:[screen-start] 1.5em [screen-start-inset page-start page-start-inset body-start-outset body-start body-content-start] minmax(0px, 1fr) [body-content-end body-end body-end-outset page-end-inset page-end screen-end-inset] 1.5em [screen-end]}nav[role=doc-toc]{display:none}}body,.page-row-navigation{grid-template-rows:[page-top] max-content [contents-top] max-content [contents-bottom] max-content [page-bottom]}.page-rows-contents{grid-template-rows:[content-top] minmax(max-content, 1fr) [content-bottom] minmax(60px, max-content) [page-bottom]}.page-full{grid-column:screen-start/screen-end !important}.page-columns>*{grid-column:body-content-start/body-content-end}.page-columns.column-page>*{grid-column:page-start/page-end}.page-columns.column-page-left>*{grid-column:page-start/body-content-end}.page-columns.column-page-right>*{grid-column:body-content-start/page-end}.page-rows{grid-auto-rows:auto}.header{grid-column:screen-start/screen-end;grid-row:page-top/contents-top}#quarto-content{padding:0;grid-column:screen-start/screen-end;grid-row:contents-top/contents-bottom}body.floating .sidebar.sidebar-navigation{grid-column:page-start/body-start;grid-row:content-top/page-bottom}body.docked .sidebar.sidebar-navigation{grid-column:screen-start/body-start;grid-row:content-top/page-bottom}.sidebar.toc-left{grid-column:page-start/body-start;grid-row:content-top/page-bottom}.sidebar.margin-sidebar{grid-column:body-end/page-end;grid-row:content-top/page-bottom}.page-columns .content{grid-column:body-content-start/body-content-end;grid-row:content-top/content-bottom;align-content:flex-start}.page-columns .page-navigation{grid-column:body-content-start/body-content-end;grid-row:content-bottom/page-bottom}.page-columns .footer{grid-column:screen-start/screen-end;grid-row:contents-bottom/page-bottom}.page-columns .column-body{grid-column:body-content-start/body-content-end}.page-columns .column-body-fullbleed{grid-column:body-start/body-end}.page-columns .column-body-outset{grid-column:body-start-outset/body-end-outset;z-index:998;transform:translate3d(0, 0, 0)}.page-columns .column-body-outset table{background:#fff}.page-columns .column-body-outset-left{grid-column:body-start-outset/body-content-end;z-index:998;transform:translate3d(0, 0, 0)}.page-columns .column-body-outset-left table{background:#fff}.page-columns .column-body-outset-right{grid-column:body-content-start/body-end-outset;z-index:998;transform:translate3d(0, 0, 0)}.page-columns .column-body-outset-right table{background:#fff}.page-columns .column-page{grid-column:page-start/page-end;z-index:998;transform:translate3d(0, 0, 0)}.page-columns .column-page table{background:#fff}.page-columns .column-page-inset{grid-column:page-start-inset/page-end-inset;z-index:998;transform:translate3d(0, 0, 0)}.page-columns .column-page-inset table{background:#fff}.page-columns .column-page-inset-left{grid-column:page-start-inset/body-content-end;z-index:998;transform:translate3d(0, 0, 0)}.page-columns .column-page-inset-left table{background:#fff}.page-columns .column-page-inset-right{grid-column:body-content-start/page-end-inset;z-index:998;transform:translate3d(0, 0, 0)}.page-columns .column-page-inset-right figcaption table{background:#fff}.page-columns .column-page-left{grid-column:page-start/body-content-end;z-index:998;transform:translate3d(0, 0, 0)}.page-columns .column-page-left table{background:#fff}.page-columns .column-page-right{grid-column:body-content-start/page-end;z-index:998;transform:translate3d(0, 0, 0)}.page-columns .column-page-right figcaption table{background:#fff}#quarto-content.page-columns #quarto-margin-sidebar,#quarto-content.page-columns #quarto-sidebar{z-index:1}@media(max-width: 991.98px){#quarto-content.page-columns #quarto-margin-sidebar.collapse,#quarto-content.page-columns #quarto-sidebar.collapse,#quarto-content.page-columns #quarto-margin-sidebar.collapsing,#quarto-content.page-columns #quarto-sidebar.collapsing{z-index:1055}}#quarto-content.page-columns main.column-page,#quarto-content.page-columns main.column-page-right,#quarto-content.page-columns main.column-page-left{z-index:0}.page-columns .column-screen-inset{grid-column:screen-start-inset/screen-end-inset;z-index:998;transform:translate3d(0, 0, 0)}.page-columns .column-screen-inset table{background:#fff}.page-columns .column-screen-inset-left{grid-column:screen-start-inset/body-content-end;z-index:998;transform:translate3d(0, 0, 0)}.page-columns .column-screen-inset-left table{background:#fff}.page-columns .column-screen-inset-right{grid-column:body-content-start/screen-end-inset;z-index:998;transform:translate3d(0, 0, 0)}.page-columns .column-screen-inset-right table{background:#fff}.page-columns .column-screen{grid-column:screen-start/screen-end;z-index:998;transform:translate3d(0, 0, 0)}.page-columns .column-screen table{background:#fff}.page-columns .column-screen-left{grid-column:screen-start/body-content-end;z-index:998;transform:translate3d(0, 0, 0)}.page-columns .column-screen-left table{background:#fff}.page-columns .column-screen-right{grid-column:body-content-start/screen-end;z-index:998;transform:translate3d(0, 0, 0)}.page-columns .column-screen-right table{background:#fff}.page-columns .column-screen-inset-shaded{grid-column:screen-start/screen-end;padding:1em;background:#f8f9fa;z-index:998;transform:translate3d(0, 0, 0);margin-bottom:1em}.zindex-content{z-index:998;transform:translate3d(0, 0, 0)}.zindex-modal{z-index:1055;transform:translate3d(0, 0, 0)}.zindex-over-content{z-index:999;transform:translate3d(0, 0, 0)}img.img-fluid.column-screen,img.img-fluid.column-screen-inset-shaded,img.img-fluid.column-screen-inset,img.img-fluid.column-screen-inset-left,img.img-fluid.column-screen-inset-right,img.img-fluid.column-screen-left,img.img-fluid.column-screen-right{width:100%}@media(min-width: 992px){.margin-caption,div.aside,aside,.column-margin{grid-column:body-end/page-end !important;z-index:998}.column-sidebar{grid-column:page-start/body-start !important;z-index:998}.column-leftmargin{grid-column:screen-start-inset/body-start !important;z-index:998}.no-row-height{height:1em;overflow:visible}}@media(max-width: 991.98px){.margin-caption,div.aside,aside,.column-margin{grid-column:body-end/page-end !important;z-index:998}.no-row-height{height:1em;overflow:visible}.page-columns.page-full{overflow:visible}.page-columns.toc-left .margin-caption,.page-columns.toc-left div.aside,.page-columns.toc-left aside,.page-columns.toc-left .column-margin{grid-column:body-content-start/body-content-end !important;z-index:998;transform:translate3d(0, 0, 0)}.page-columns.toc-left .no-row-height{height:initial;overflow:initial}}@media(max-width: 767.98px){.margin-caption,div.aside,aside,.column-margin{grid-column:body-content-start/body-content-end !important;z-index:998;transform:translate3d(0, 0, 0)}.no-row-height{height:initial;overflow:initial}#quarto-margin-sidebar{display:none}#quarto-sidebar-toc-left{display:none}.hidden-sm{display:none}}.panel-grid{display:grid;grid-template-rows:repeat(1, 1fr);grid-template-columns:repeat(24, 1fr);gap:1em}.panel-grid .g-col-1{grid-column:auto/span 1}.panel-grid .g-col-2{grid-column:auto/span 2}.panel-grid .g-col-3{grid-column:auto/span 3}.panel-grid .g-col-4{grid-column:auto/span 4}.panel-grid .g-col-5{grid-column:auto/span 5}.panel-grid .g-col-6{grid-column:auto/span 6}.panel-grid .g-col-7{grid-column:auto/span 7}.panel-grid .g-col-8{grid-column:auto/span 8}.panel-grid .g-col-9{grid-column:auto/span 9}.panel-grid .g-col-10{grid-column:auto/span 10}.panel-grid .g-col-11{grid-column:auto/span 11}.panel-grid .g-col-12{grid-column:auto/span 12}.panel-grid .g-col-13{grid-column:auto/span 13}.panel-grid .g-col-14{grid-column:auto/span 14}.panel-grid .g-col-15{grid-column:auto/span 15}.panel-grid .g-col-16{grid-column:auto/span 16}.panel-grid .g-col-17{grid-column:auto/span 17}.panel-grid .g-col-18{grid-column:auto/span 18}.panel-grid .g-col-19{grid-column:auto/span 19}.panel-grid .g-col-20{grid-column:auto/span 20}.panel-grid .g-col-21{grid-column:auto/span 21}.panel-grid .g-col-22{grid-column:auto/span 22}.panel-grid .g-col-23{grid-column:auto/span 23}.panel-grid .g-col-24{grid-column:auto/span 24}.panel-grid .g-start-1{grid-column-start:1}.panel-grid .g-start-2{grid-column-start:2}.panel-grid .g-start-3{grid-column-start:3}.panel-grid .g-start-4{grid-column-start:4}.panel-grid .g-start-5{grid-column-start:5}.panel-grid .g-start-6{grid-column-start:6}.panel-grid .g-start-7{grid-column-start:7}.panel-grid .g-start-8{grid-column-start:8}.panel-grid .g-start-9{grid-column-start:9}.panel-grid .g-start-10{grid-column-start:10}.panel-grid .g-start-11{grid-column-start:11}.panel-grid .g-start-12{grid-column-start:12}.panel-grid .g-start-13{grid-column-start:13}.panel-grid .g-start-14{grid-column-start:14}.panel-grid .g-start-15{grid-column-start:15}.panel-grid .g-start-16{grid-column-start:16}.panel-grid .g-start-17{grid-column-start:17}.panel-grid .g-start-18{grid-column-start:18}.panel-grid .g-start-19{grid-column-start:19}.panel-grid .g-start-20{grid-column-start:20}.panel-grid .g-start-21{grid-column-start:21}.panel-grid .g-start-22{grid-column-start:22}.panel-grid .g-start-23{grid-column-start:23}@media(min-width: 576px){.panel-grid .g-col-sm-1{grid-column:auto/span 1}.panel-grid .g-col-sm-2{grid-column:auto/span 2}.panel-grid .g-col-sm-3{grid-column:auto/span 3}.panel-grid .g-col-sm-4{grid-column:auto/span 4}.panel-grid .g-col-sm-5{grid-column:auto/span 5}.panel-grid .g-col-sm-6{grid-column:auto/span 6}.panel-grid .g-col-sm-7{grid-column:auto/span 7}.panel-grid .g-col-sm-8{grid-column:auto/span 8}.panel-grid .g-col-sm-9{grid-column:auto/span 9}.panel-grid .g-col-sm-10{grid-column:auto/span 10}.panel-grid .g-col-sm-11{grid-column:auto/span 11}.panel-grid .g-col-sm-12{grid-column:auto/span 12}.panel-grid .g-col-sm-13{grid-column:auto/span 13}.panel-grid .g-col-sm-14{grid-column:auto/span 14}.panel-grid .g-col-sm-15{grid-column:auto/span 15}.panel-grid .g-col-sm-16{grid-column:auto/span 16}.panel-grid .g-col-sm-17{grid-column:auto/span 17}.panel-grid .g-col-sm-18{grid-column:auto/span 18}.panel-grid .g-col-sm-19{grid-column:auto/span 19}.panel-grid .g-col-sm-20{grid-column:auto/span 20}.panel-grid .g-col-sm-21{grid-column:auto/span 21}.panel-grid .g-col-sm-22{grid-column:auto/span 22}.panel-grid .g-col-sm-23{grid-column:auto/span 23}.panel-grid .g-col-sm-24{grid-column:auto/span 24}.panel-grid .g-start-sm-1{grid-column-start:1}.panel-grid .g-start-sm-2{grid-column-start:2}.panel-grid .g-start-sm-3{grid-column-start:3}.panel-grid .g-start-sm-4{grid-column-start:4}.panel-grid .g-start-sm-5{grid-column-start:5}.panel-grid .g-start-sm-6{grid-column-start:6}.panel-grid .g-start-sm-7{grid-column-start:7}.panel-grid .g-start-sm-8{grid-column-start:8}.panel-grid .g-start-sm-9{grid-column-start:9}.panel-grid .g-start-sm-10{grid-column-start:10}.panel-grid .g-start-sm-11{grid-column-start:11}.panel-grid .g-start-sm-12{grid-column-start:12}.panel-grid .g-start-sm-13{grid-column-start:13}.panel-grid .g-start-sm-14{grid-column-start:14}.panel-grid .g-start-sm-15{grid-column-start:15}.panel-grid .g-start-sm-16{grid-column-start:16}.panel-grid .g-start-sm-17{grid-column-start:17}.panel-grid .g-start-sm-18{grid-column-start:18}.panel-grid .g-start-sm-19{grid-column-start:19}.panel-grid .g-start-sm-20{grid-column-start:20}.panel-grid .g-start-sm-21{grid-column-start:21}.panel-grid .g-start-sm-22{grid-column-start:22}.panel-grid .g-start-sm-23{grid-column-start:23}}@media(min-width: 768px){.panel-grid .g-col-md-1{grid-column:auto/span 1}.panel-grid .g-col-md-2{grid-column:auto/span 2}.panel-grid .g-col-md-3{grid-column:auto/span 3}.panel-grid .g-col-md-4{grid-column:auto/span 4}.panel-grid .g-col-md-5{grid-column:auto/span 5}.panel-grid .g-col-md-6{grid-column:auto/span 6}.panel-grid .g-col-md-7{grid-column:auto/span 7}.panel-grid .g-col-md-8{grid-column:auto/span 8}.panel-grid .g-col-md-9{grid-column:auto/span 9}.panel-grid .g-col-md-10{grid-column:auto/span 10}.panel-grid .g-col-md-11{grid-column:auto/span 11}.panel-grid .g-col-md-12{grid-column:auto/span 12}.panel-grid .g-col-md-13{grid-column:auto/span 13}.panel-grid .g-col-md-14{grid-column:auto/span 14}.panel-grid .g-col-md-15{grid-column:auto/span 15}.panel-grid .g-col-md-16{grid-column:auto/span 16}.panel-grid .g-col-md-17{grid-column:auto/span 17}.panel-grid .g-col-md-18{grid-column:auto/span 18}.panel-grid .g-col-md-19{grid-column:auto/span 19}.panel-grid .g-col-md-20{grid-column:auto/span 20}.panel-grid .g-col-md-21{grid-column:auto/span 21}.panel-grid .g-col-md-22{grid-column:auto/span 22}.panel-grid .g-col-md-23{grid-column:auto/span 23}.panel-grid .g-col-md-24{grid-column:auto/span 24}.panel-grid .g-start-md-1{grid-column-start:1}.panel-grid .g-start-md-2{grid-column-start:2}.panel-grid .g-start-md-3{grid-column-start:3}.panel-grid .g-start-md-4{grid-column-start:4}.panel-grid .g-start-md-5{grid-column-start:5}.panel-grid .g-start-md-6{grid-column-start:6}.panel-grid .g-start-md-7{grid-column-start:7}.panel-grid .g-start-md-8{grid-column-start:8}.panel-grid .g-start-md-9{grid-column-start:9}.panel-grid .g-start-md-10{grid-column-start:10}.panel-grid .g-start-md-11{grid-column-start:11}.panel-grid .g-start-md-12{grid-column-start:12}.panel-grid .g-start-md-13{grid-column-start:13}.panel-grid .g-start-md-14{grid-column-start:14}.panel-grid .g-start-md-15{grid-column-start:15}.panel-grid .g-start-md-16{grid-column-start:16}.panel-grid .g-start-md-17{grid-column-start:17}.panel-grid .g-start-md-18{grid-column-start:18}.panel-grid .g-start-md-19{grid-column-start:19}.panel-grid .g-start-md-20{grid-column-start:20}.panel-grid .g-start-md-21{grid-column-start:21}.panel-grid .g-start-md-22{grid-column-start:22}.panel-grid .g-start-md-23{grid-column-start:23}}@media(min-width: 992px){.panel-grid .g-col-lg-1{grid-column:auto/span 1}.panel-grid .g-col-lg-2{grid-column:auto/span 2}.panel-grid .g-col-lg-3{grid-column:auto/span 3}.panel-grid .g-col-lg-4{grid-column:auto/span 4}.panel-grid .g-col-lg-5{grid-column:auto/span 5}.panel-grid .g-col-lg-6{grid-column:auto/span 6}.panel-grid .g-col-lg-7{grid-column:auto/span 7}.panel-grid .g-col-lg-8{grid-column:auto/span 8}.panel-grid .g-col-lg-9{grid-column:auto/span 9}.panel-grid .g-col-lg-10{grid-column:auto/span 10}.panel-grid .g-col-lg-11{grid-column:auto/span 11}.panel-grid .g-col-lg-12{grid-column:auto/span 12}.panel-grid .g-col-lg-13{grid-column:auto/span 13}.panel-grid .g-col-lg-14{grid-column:auto/span 14}.panel-grid .g-col-lg-15{grid-column:auto/span 15}.panel-grid .g-col-lg-16{grid-column:auto/span 16}.panel-grid .g-col-lg-17{grid-column:auto/span 17}.panel-grid .g-col-lg-18{grid-column:auto/span 18}.panel-grid .g-col-lg-19{grid-column:auto/span 19}.panel-grid .g-col-lg-20{grid-column:auto/span 20}.panel-grid .g-col-lg-21{grid-column:auto/span 21}.panel-grid .g-col-lg-22{grid-column:auto/span 22}.panel-grid .g-col-lg-23{grid-column:auto/span 23}.panel-grid .g-col-lg-24{grid-column:auto/span 24}.panel-grid .g-start-lg-1{grid-column-start:1}.panel-grid .g-start-lg-2{grid-column-start:2}.panel-grid .g-start-lg-3{grid-column-start:3}.panel-grid .g-start-lg-4{grid-column-start:4}.panel-grid .g-start-lg-5{grid-column-start:5}.panel-grid .g-start-lg-6{grid-column-start:6}.panel-grid .g-start-lg-7{grid-column-start:7}.panel-grid .g-start-lg-8{grid-column-start:8}.panel-grid .g-start-lg-9{grid-column-start:9}.panel-grid .g-start-lg-10{grid-column-start:10}.panel-grid .g-start-lg-11{grid-column-start:11}.panel-grid .g-start-lg-12{grid-column-start:12}.panel-grid .g-start-lg-13{grid-column-start:13}.panel-grid .g-start-lg-14{grid-column-start:14}.panel-grid .g-start-lg-15{grid-column-start:15}.panel-grid .g-start-lg-16{grid-column-start:16}.panel-grid .g-start-lg-17{grid-column-start:17}.panel-grid .g-start-lg-18{grid-column-start:18}.panel-grid .g-start-lg-19{grid-column-start:19}.panel-grid .g-start-lg-20{grid-column-start:20}.panel-grid .g-start-lg-21{grid-column-start:21}.panel-grid .g-start-lg-22{grid-column-start:22}.panel-grid .g-start-lg-23{grid-column-start:23}}@media(min-width: 1200px){.panel-grid .g-col-xl-1{grid-column:auto/span 1}.panel-grid .g-col-xl-2{grid-column:auto/span 2}.panel-grid .g-col-xl-3{grid-column:auto/span 3}.panel-grid .g-col-xl-4{grid-column:auto/span 4}.panel-grid .g-col-xl-5{grid-column:auto/span 5}.panel-grid .g-col-xl-6{grid-column:auto/span 6}.panel-grid .g-col-xl-7{grid-column:auto/span 7}.panel-grid .g-col-xl-8{grid-column:auto/span 8}.panel-grid .g-col-xl-9{grid-column:auto/span 9}.panel-grid .g-col-xl-10{grid-column:auto/span 10}.panel-grid .g-col-xl-11{grid-column:auto/span 11}.panel-grid .g-col-xl-12{grid-column:auto/span 12}.panel-grid .g-col-xl-13{grid-column:auto/span 13}.panel-grid .g-col-xl-14{grid-column:auto/span 14}.panel-grid .g-col-xl-15{grid-column:auto/span 15}.panel-grid .g-col-xl-16{grid-column:auto/span 16}.panel-grid .g-col-xl-17{grid-column:auto/span 17}.panel-grid .g-col-xl-18{grid-column:auto/span 18}.panel-grid .g-col-xl-19{grid-column:auto/span 19}.panel-grid .g-col-xl-20{grid-column:auto/span 20}.panel-grid .g-col-xl-21{grid-column:auto/span 21}.panel-grid .g-col-xl-22{grid-column:auto/span 22}.panel-grid .g-col-xl-23{grid-column:auto/span 23}.panel-grid .g-col-xl-24{grid-column:auto/span 24}.panel-grid .g-start-xl-1{grid-column-start:1}.panel-grid .g-start-xl-2{grid-column-start:2}.panel-grid .g-start-xl-3{grid-column-start:3}.panel-grid .g-start-xl-4{grid-column-start:4}.panel-grid .g-start-xl-5{grid-column-start:5}.panel-grid .g-start-xl-6{grid-column-start:6}.panel-grid .g-start-xl-7{grid-column-start:7}.panel-grid .g-start-xl-8{grid-column-start:8}.panel-grid .g-start-xl-9{grid-column-start:9}.panel-grid .g-start-xl-10{grid-column-start:10}.panel-grid .g-start-xl-11{grid-column-start:11}.panel-grid .g-start-xl-12{grid-column-start:12}.panel-grid .g-start-xl-13{grid-column-start:13}.panel-grid .g-start-xl-14{grid-column-start:14}.panel-grid .g-start-xl-15{grid-column-start:15}.panel-grid .g-start-xl-16{grid-column-start:16}.panel-grid .g-start-xl-17{grid-column-start:17}.panel-grid .g-start-xl-18{grid-column-start:18}.panel-grid .g-start-xl-19{grid-column-start:19}.panel-grid .g-start-xl-20{grid-column-start:20}.panel-grid .g-start-xl-21{grid-column-start:21}.panel-grid .g-start-xl-22{grid-column-start:22}.panel-grid .g-start-xl-23{grid-column-start:23}}@media(min-width: 1400px){.panel-grid .g-col-xxl-1{grid-column:auto/span 1}.panel-grid .g-col-xxl-2{grid-column:auto/span 2}.panel-grid .g-col-xxl-3{grid-column:auto/span 3}.panel-grid .g-col-xxl-4{grid-column:auto/span 4}.panel-grid .g-col-xxl-5{grid-column:auto/span 5}.panel-grid .g-col-xxl-6{grid-column:auto/span 6}.panel-grid .g-col-xxl-7{grid-column:auto/span 7}.panel-grid .g-col-xxl-8{grid-column:auto/span 8}.panel-grid .g-col-xxl-9{grid-column:auto/span 9}.panel-grid .g-col-xxl-10{grid-column:auto/span 10}.panel-grid .g-col-xxl-11{grid-column:auto/span 11}.panel-grid .g-col-xxl-12{grid-column:auto/span 12}.panel-grid .g-col-xxl-13{grid-column:auto/span 13}.panel-grid .g-col-xxl-14{grid-column:auto/span 14}.panel-grid .g-col-xxl-15{grid-column:auto/span 15}.panel-grid .g-col-xxl-16{grid-column:auto/span 16}.panel-grid .g-col-xxl-17{grid-column:auto/span 17}.panel-grid .g-col-xxl-18{grid-column:auto/span 18}.panel-grid .g-col-xxl-19{grid-column:auto/span 19}.panel-grid .g-col-xxl-20{grid-column:auto/span 20}.panel-grid .g-col-xxl-21{grid-column:auto/span 21}.panel-grid .g-col-xxl-22{grid-column:auto/span 22}.panel-grid .g-col-xxl-23{grid-column:auto/span 23}.panel-grid .g-col-xxl-24{grid-column:auto/span 24}.panel-grid .g-start-xxl-1{grid-column-start:1}.panel-grid .g-start-xxl-2{grid-column-start:2}.panel-grid .g-start-xxl-3{grid-column-start:3}.panel-grid .g-start-xxl-4{grid-column-start:4}.panel-grid .g-start-xxl-5{grid-column-start:5}.panel-grid .g-start-xxl-6{grid-column-start:6}.panel-grid .g-start-xxl-7{grid-column-start:7}.panel-grid .g-start-xxl-8{grid-column-start:8}.panel-grid .g-start-xxl-9{grid-column-start:9}.panel-grid .g-start-xxl-10{grid-column-start:10}.panel-grid .g-start-xxl-11{grid-column-start:11}.panel-grid .g-start-xxl-12{grid-column-start:12}.panel-grid .g-start-xxl-13{grid-column-start:13}.panel-grid .g-start-xxl-14{grid-column-start:14}.panel-grid .g-start-xxl-15{grid-column-start:15}.panel-grid .g-start-xxl-16{grid-column-start:16}.panel-grid .g-start-xxl-17{grid-column-start:17}.panel-grid .g-start-xxl-18{grid-column-start:18}.panel-grid .g-start-xxl-19{grid-column-start:19}.panel-grid .g-start-xxl-20{grid-column-start:20}.panel-grid .g-start-xxl-21{grid-column-start:21}.panel-grid .g-start-xxl-22{grid-column-start:22}.panel-grid .g-start-xxl-23{grid-column-start:23}}main{margin-top:1em;margin-bottom:1em}h1,.h1,h2,.h2{opacity:.9;margin-top:2rem;margin-bottom:1rem;font-weight:600}h1.title,.title.h1{margin-top:0}h2,.h2{border-bottom:1px solid #dee2e6;padding-bottom:.5rem}h3,.h3{font-weight:600}h3,.h3,h4,.h4{opacity:.9;margin-top:1.5rem}h5,.h5,h6,.h6{opacity:.9}.header-section-number{color:#747a7f}.nav-link.active .header-section-number{color:inherit}mark,.mark{padding:0em}.panel-caption,caption,.figure-caption{font-size:.9rem}.panel-caption,.figure-caption,figcaption{color:#747a7f}.table-caption,caption{color:#373a3c}.quarto-layout-cell[data-ref-parent] caption{color:#747a7f}.column-margin figcaption,.margin-caption,div.aside,aside,.column-margin{color:#747a7f;font-size:.825rem}.panel-caption.margin-caption{text-align:inherit}.column-margin.column-container p{margin-bottom:0}.column-margin.column-container>*:not(.collapse){padding-top:.5em;padding-bottom:.5em;display:block}.column-margin.column-container>*.collapse:not(.show){display:none}@media(min-width: 768px){.column-margin.column-container .callout-margin-content:first-child{margin-top:4.5em}.column-margin.column-container .callout-margin-content-simple:first-child{margin-top:3.5em}}.margin-caption>*{padding-top:.5em;padding-bottom:.5em}@media(max-width: 767.98px){.quarto-layout-row{flex-direction:column}}.nav-tabs .nav-item{margin-top:1px;cursor:pointer}.tab-content{margin-top:0px;border-left:#dee2e6 1px solid;border-right:#dee2e6 1px solid;border-bottom:#dee2e6 1px solid;margin-left:0;padding:1em;margin-bottom:1em}@media(max-width: 767.98px){.layout-sidebar{margin-left:0;margin-right:0}}.panel-sidebar,.panel-sidebar .form-control,.panel-input,.panel-input .form-control,.selectize-dropdown{font-size:.9rem}.panel-sidebar .form-control,.panel-input .form-control{padding-top:.1rem}.tab-pane div.sourceCode{margin-top:0px}.tab-pane>p{padding-top:1em}.tab-content>.tab-pane:not(.active){display:none !important}div.sourceCode{background-color:rgba(233,236,239,.65);border:1px solid rgba(233,236,239,.65);border-radius:.25rem}pre.sourceCode{background-color:rgba(0,0,0,0)}pre.sourceCode{border:none;font-size:.875em;overflow:visible !important;padding:.4em}.callout pre.sourceCode{padding-left:0}div.sourceCode{overflow-y:hidden}.callout div.sourceCode{margin-left:initial}.blockquote{font-size:inherit;padding-left:1rem;padding-right:1.5rem;color:#747a7f}.blockquote h1:first-child,.blockquote .h1:first-child,.blockquote h2:first-child,.blockquote .h2:first-child,.blockquote h3:first-child,.blockquote .h3:first-child,.blockquote h4:first-child,.blockquote .h4:first-child,.blockquote h5:first-child,.blockquote .h5:first-child{margin-top:0}pre{background-color:initial;padding:initial;border:initial}p code:not(.sourceCode),li code:not(.sourceCode),td code:not(.sourceCode){background-color:#f7f7f7;padding:.2em}nav p code:not(.sourceCode),nav li code:not(.sourceCode),nav td code:not(.sourceCode){background-color:rgba(0,0,0,0);padding:0}td code:not(.sourceCode){white-space:pre-wrap}#quarto-embedded-source-code-modal>.modal-dialog{max-width:1000px;padding-left:1.75rem;padding-right:1.75rem}#quarto-embedded-source-code-modal>.modal-dialog>.modal-content>.modal-body{padding:0}#quarto-embedded-source-code-modal>.modal-dialog>.modal-content>.modal-body div.sourceCode{margin:0;padding:.2rem .2rem;border-radius:0px;border:none}#quarto-embedded-source-code-modal>.modal-dialog>.modal-content>.modal-header{padding:.7rem}.code-tools-button{font-size:1rem;padding:.15rem .15rem;margin-left:5px;color:#6c757d;background-color:rgba(0,0,0,0);transition:initial;cursor:pointer}.code-tools-button>.bi::before{display:inline-block;height:1rem;width:1rem;content:"";vertical-align:-0.125em;background-image:url('data:image/svg+xml,');background-repeat:no-repeat;background-size:1rem 1rem}.code-tools-button:hover>.bi::before{background-image:url('data:image/svg+xml,')}#quarto-embedded-source-code-modal .code-copy-button>.bi::before{background-image:url('data:image/svg+xml,')}#quarto-embedded-source-code-modal .code-copy-button-checked>.bi::before{background-image:url('data:image/svg+xml,')}.sidebar{will-change:top;transition:top 200ms linear;position:sticky;overflow-y:auto;padding-top:1.2em;max-height:100vh}.sidebar.toc-left,.sidebar.margin-sidebar{top:0px;padding-top:1em}.sidebar.toc-left>*,.sidebar.margin-sidebar>*{padding-top:.5em}.sidebar.quarto-banner-title-block-sidebar>*{padding-top:1.65em}figure .quarto-notebook-link{margin-top:.5em}.quarto-notebook-link{font-size:.75em;color:#6c757d;margin-bottom:1em;text-decoration:none;display:block}.quarto-notebook-link:hover{text-decoration:underline;color:#2780e3}.quarto-notebook-link::before{display:inline-block;height:.75rem;width:.75rem;margin-bottom:0em;margin-right:.25em;content:"";vertical-align:-0.125em;background-image:url('data:image/svg+xml,');background-repeat:no-repeat;background-size:.75rem .75rem}.quarto-alternate-notebooks i.bi,.quarto-alternate-formats i.bi{margin-right:.4em}.quarto-notebook .cell-container{display:flex}.quarto-notebook .cell-container .cell{flex-grow:4}.quarto-notebook .cell-container .cell-decorator{padding-top:1.5em;padding-right:1em;text-align:right}.quarto-notebook h2,.quarto-notebook .h2{border-bottom:none}.sidebar .quarto-alternate-formats a,.sidebar .quarto-alternate-notebooks a{text-decoration:none}.sidebar .quarto-alternate-formats a:hover,.sidebar .quarto-alternate-notebooks a:hover{color:#2780e3}.sidebar .quarto-alternate-notebooks h2,.sidebar .quarto-alternate-notebooks .h2,.sidebar .quarto-alternate-formats h2,.sidebar .quarto-alternate-formats .h2,.sidebar nav[role=doc-toc]>h2,.sidebar nav[role=doc-toc]>.h2{font-size:.875rem;font-weight:400;margin-bottom:.5rem;margin-top:.3rem;font-family:inherit;border-bottom:0;padding-bottom:0;padding-top:0px}.sidebar .quarto-alternate-notebooks h2,.sidebar .quarto-alternate-notebooks .h2,.sidebar .quarto-alternate-formats h2,.sidebar .quarto-alternate-formats .h2{margin-top:1rem}.sidebar nav[role=doc-toc]>ul a{border-left:1px solid #e9ecef;padding-left:.6rem}.sidebar .quarto-alternate-notebooks h2>ul a,.sidebar .quarto-alternate-notebooks .h2>ul a,.sidebar .quarto-alternate-formats h2>ul a,.sidebar .quarto-alternate-formats .h2>ul a{border-left:none;padding-left:.6rem}.sidebar .quarto-alternate-notebooks ul a:empty,.sidebar .quarto-alternate-formats ul a:empty,.sidebar nav[role=doc-toc]>ul a:empty{display:none}.sidebar .quarto-alternate-notebooks ul,.sidebar .quarto-alternate-formats ul,.sidebar nav[role=doc-toc] ul{padding-left:0;list-style:none;font-size:.875rem;font-weight:300}.sidebar .quarto-alternate-notebooks ul li a,.sidebar .quarto-alternate-formats ul li a,.sidebar nav[role=doc-toc]>ul li a{line-height:1.1rem;padding-bottom:.2rem;padding-top:.2rem;color:inherit}.sidebar nav[role=doc-toc] ul>li>ul>li>a{padding-left:1.2em}.sidebar nav[role=doc-toc] ul>li>ul>li>ul>li>a{padding-left:2.4em}.sidebar nav[role=doc-toc] ul>li>ul>li>ul>li>ul>li>a{padding-left:3.6em}.sidebar nav[role=doc-toc] ul>li>ul>li>ul>li>ul>li>ul>li>a{padding-left:4.8em}.sidebar nav[role=doc-toc] ul>li>ul>li>ul>li>ul>li>ul>li>ul>li>a{padding-left:6em}.sidebar nav[role=doc-toc] ul>li>a.active,.sidebar nav[role=doc-toc] ul>li>ul>li>a.active{border-left:1px solid #2780e3;color:#2780e3 !important}.sidebar nav[role=doc-toc] ul>li>a:hover,.sidebar nav[role=doc-toc] ul>li>ul>li>a:hover{color:#2780e3 !important}kbd,.kbd{color:#373a3c;background-color:#f8f9fa;border:1px solid;border-radius:5px;border-color:#dee2e6}div.hanging-indent{margin-left:1em;text-indent:-1em}.citation a,.footnote-ref{text-decoration:none}.footnotes ol{padding-left:1em}.tippy-content>*{margin-bottom:.7em}.tippy-content>*:last-child{margin-bottom:0}.table a{word-break:break-word}.table>thead{border-top-width:1px;border-top-color:#dee2e6;border-bottom:1px solid #b6babc}.callout{margin-top:1.25rem;margin-bottom:1.25rem;border-radius:.25rem;overflow-wrap:break-word}.callout .callout-title-container{overflow-wrap:anywhere}.callout.callout-style-simple{padding:.4em .7em;border-left:5px solid;border-right:1px solid #dee2e6;border-top:1px solid #dee2e6;border-bottom:1px solid #dee2e6}.callout.callout-style-default{border-left:5px solid;border-right:1px solid #dee2e6;border-top:1px solid #dee2e6;border-bottom:1px solid #dee2e6}.callout .callout-body-container{flex-grow:1}.callout.callout-style-simple .callout-body{font-size:.9rem;font-weight:400}.callout.callout-style-default .callout-body{font-size:.9rem;font-weight:400}.callout.callout-titled .callout-body{margin-top:.2em}.callout:not(.no-icon).callout-titled.callout-style-simple .callout-body{padding-left:1.6em}.callout.callout-titled>.callout-header{padding-top:.2em;margin-bottom:-0.2em}.callout.callout-style-simple>div.callout-header{border-bottom:none;font-size:.9rem;font-weight:600;opacity:75%}.callout.callout-style-default>div.callout-header{border-bottom:none;font-weight:600;opacity:85%;font-size:.9rem;padding-left:.5em;padding-right:.5em}.callout.callout-style-default div.callout-body{padding-left:.5em;padding-right:.5em}.callout.callout-style-default div.callout-body>:first-child{margin-top:.5em}.callout>div.callout-header[data-bs-toggle=collapse]{cursor:pointer}.callout.callout-style-default .callout-header[aria-expanded=false],.callout.callout-style-default .callout-header[aria-expanded=true]{padding-top:0px;margin-bottom:0px;align-items:center}.callout.callout-titled .callout-body>:last-child:not(.sourceCode),.callout.callout-titled .callout-body>div>:last-child:not(.sourceCode){margin-bottom:.5rem}.callout:not(.callout-titled) .callout-body>:first-child,.callout:not(.callout-titled) .callout-body>div>:first-child{margin-top:.25rem}.callout:not(.callout-titled) .callout-body>:last-child,.callout:not(.callout-titled) .callout-body>div>:last-child{margin-bottom:.2rem}.callout.callout-style-simple .callout-icon::before,.callout.callout-style-simple .callout-toggle::before{height:1rem;width:1rem;display:inline-block;content:"";background-repeat:no-repeat;background-size:1rem 1rem}.callout.callout-style-default .callout-icon::before,.callout.callout-style-default .callout-toggle::before{height:.9rem;width:.9rem;display:inline-block;content:"";background-repeat:no-repeat;background-size:.9rem .9rem}.callout.callout-style-default .callout-toggle::before{margin-top:5px}.callout .callout-btn-toggle .callout-toggle::before{transition:transform .2s linear}.callout .callout-header[aria-expanded=false] .callout-toggle::before{transform:rotate(-90deg)}.callout .callout-header[aria-expanded=true] .callout-toggle::before{transform:none}.callout.callout-style-simple:not(.no-icon) div.callout-icon-container{padding-top:.2em;padding-right:.55em}.callout.callout-style-default:not(.no-icon) div.callout-icon-container{padding-top:.1em;padding-right:.35em}.callout.callout-style-default:not(.no-icon) div.callout-title-container{margin-top:-1px}.callout.callout-style-default.callout-caution:not(.no-icon) div.callout-icon-container{padding-top:.3em;padding-right:.35em}.callout>.callout-body>.callout-icon-container>.no-icon,.callout>.callout-header>.callout-icon-container>.no-icon{display:none}div.callout.callout{border-left-color:#6c757d}div.callout.callout-style-default>.callout-header{background-color:#6c757d}div.callout-note.callout{border-left-color:#2780e3}div.callout-note.callout-style-default>.callout-header{background-color:#e9f2fc}div.callout-note:not(.callout-titled) .callout-icon::before{background-image:url('data:image/svg+xml,');}div.callout-note.callout-titled .callout-icon::before{background-image:url('data:image/svg+xml,');}div.callout-note .callout-toggle::before{background-image:url('data:image/svg+xml,')}div.callout-tip.callout{border-left-color:#3fb618}div.callout-tip.callout-style-default>.callout-header{background-color:#ecf8e8}div.callout-tip:not(.callout-titled) .callout-icon::before{background-image:url('data:image/svg+xml,');}div.callout-tip.callout-titled .callout-icon::before{background-image:url('data:image/svg+xml,');}div.callout-tip .callout-toggle::before{background-image:url('data:image/svg+xml,')}div.callout-warning.callout{border-left-color:#ff7518}div.callout-warning.callout-style-default>.callout-header{background-color:#fff1e8}div.callout-warning:not(.callout-titled) .callout-icon::before{background-image:url('data:image/svg+xml,');}div.callout-warning.callout-titled .callout-icon::before{background-image:url('data:image/svg+xml,');}div.callout-warning .callout-toggle::before{background-image:url('data:image/svg+xml,')}div.callout-caution.callout{border-left-color:#f0ad4e}div.callout-caution.callout-style-default>.callout-header{background-color:#fef7ed}div.callout-caution:not(.callout-titled) .callout-icon::before{background-image:url('data:image/svg+xml,');}div.callout-caution.callout-titled .callout-icon::before{background-image:url('data:image/svg+xml,');}div.callout-caution .callout-toggle::before{background-image:url('data:image/svg+xml,')}div.callout-important.callout{border-left-color:#ff0039}div.callout-important.callout-style-default>.callout-header{background-color:#ffe6eb}div.callout-important:not(.callout-titled) .callout-icon::before{background-image:url('data:image/svg+xml,');}div.callout-important.callout-titled .callout-icon::before{background-image:url('data:image/svg+xml,');}div.callout-important .callout-toggle::before{background-image:url('data:image/svg+xml,')}.quarto-toggle-container{display:flex;align-items:center}.quarto-reader-toggle .bi::before,.quarto-color-scheme-toggle .bi::before{display:inline-block;height:1rem;width:1rem;content:"";background-repeat:no-repeat;background-size:1rem 1rem}.sidebar-navigation{padding-left:20px}.navbar .quarto-color-scheme-toggle:not(.alternate) .bi::before{background-image:url('data:image/svg+xml,')}.navbar .quarto-color-scheme-toggle.alternate .bi::before{background-image:url('data:image/svg+xml,')}.sidebar-navigation .quarto-color-scheme-toggle:not(.alternate) .bi::before{background-image:url('data:image/svg+xml,')}.sidebar-navigation .quarto-color-scheme-toggle.alternate .bi::before{background-image:url('data:image/svg+xml,')}.quarto-sidebar-toggle{border-color:#dee2e6;border-bottom-left-radius:.25rem;border-bottom-right-radius:.25rem;border-style:solid;border-width:1px;overflow:hidden;border-top-width:0px;padding-top:0px !important}.quarto-sidebar-toggle-title{cursor:pointer;padding-bottom:2px;margin-left:.25em;text-align:center;font-weight:400;font-size:.775em}#quarto-content .quarto-sidebar-toggle{background:#fafafa}#quarto-content .quarto-sidebar-toggle-title{color:#373a3c}.quarto-sidebar-toggle-icon{color:#dee2e6;margin-right:.5em;float:right;transition:transform .2s ease}.quarto-sidebar-toggle-icon::before{padding-top:5px}.quarto-sidebar-toggle.expanded .quarto-sidebar-toggle-icon{transform:rotate(-180deg)}.quarto-sidebar-toggle.expanded .quarto-sidebar-toggle-title{border-bottom:solid #dee2e6 1px}.quarto-sidebar-toggle-contents{background-color:#fff;padding-right:10px;padding-left:10px;margin-top:0px !important;transition:max-height .5s ease}.quarto-sidebar-toggle.expanded .quarto-sidebar-toggle-contents{padding-top:1em;padding-bottom:10px}.quarto-sidebar-toggle:not(.expanded) .quarto-sidebar-toggle-contents{padding-top:0px !important;padding-bottom:0px}nav[role=doc-toc]{z-index:1020}#quarto-sidebar>*,nav[role=doc-toc]>*{transition:opacity .1s ease,border .1s ease}#quarto-sidebar.slow>*,nav[role=doc-toc].slow>*{transition:opacity .4s ease,border .4s ease}.quarto-color-scheme-toggle:not(.alternate).top-right .bi::before{background-image:url('data:image/svg+xml,')}.quarto-color-scheme-toggle.alternate.top-right .bi::before{background-image:url('data:image/svg+xml,')}#quarto-appendix.default{border-top:1px solid #dee2e6}#quarto-appendix.default{background-color:#fff;padding-top:1.5em;margin-top:2em;z-index:998}#quarto-appendix.default .quarto-appendix-heading{margin-top:0;line-height:1.4em;font-weight:600;opacity:.9;border-bottom:none;margin-bottom:0}#quarto-appendix.default .footnotes ol,#quarto-appendix.default .footnotes ol li>p:last-of-type,#quarto-appendix.default .quarto-appendix-contents>p:last-of-type{margin-bottom:0}#quarto-appendix.default .quarto-appendix-secondary-label{margin-bottom:.4em}#quarto-appendix.default .quarto-appendix-bibtex{font-size:.7em;padding:1em;border:solid 1px #dee2e6;margin-bottom:1em}#quarto-appendix.default .quarto-appendix-bibtex code.sourceCode{white-space:pre-wrap}#quarto-appendix.default .quarto-appendix-citeas{font-size:.9em;padding:1em;border:solid 1px #dee2e6;margin-bottom:1em}#quarto-appendix.default .quarto-appendix-heading{font-size:1em !important}#quarto-appendix.default *[role=doc-endnotes]>ol,#quarto-appendix.default .quarto-appendix-contents>*:not(h2):not(.h2){font-size:.9em}#quarto-appendix.default section{padding-bottom:1.5em}#quarto-appendix.default section *[role=doc-endnotes],#quarto-appendix.default section>*:not(a){opacity:.9;word-wrap:break-word}.btn.btn-quarto,div.cell-output-display .btn-quarto{color:#cbcccc;background-color:#373a3c;border-color:#373a3c}.btn.btn-quarto:hover,div.cell-output-display .btn-quarto:hover{color:#cbcccc;background-color:#555859;border-color:#4b4e50}.btn-check:focus+.btn.btn-quarto,.btn.btn-quarto:focus,.btn-check:focus+div.cell-output-display .btn-quarto,div.cell-output-display .btn-quarto:focus{color:#cbcccc;background-color:#555859;border-color:#4b4e50;box-shadow:0 0 0 .25rem rgba(77,80,82,.5)}.btn-check:checked+.btn.btn-quarto,.btn-check:active+.btn.btn-quarto,.btn.btn-quarto:active,.btn.btn-quarto.active,.show>.btn.btn-quarto.dropdown-toggle,.btn-check:checked+div.cell-output-display .btn-quarto,.btn-check:active+div.cell-output-display .btn-quarto,div.cell-output-display .btn-quarto:active,div.cell-output-display .btn-quarto.active,.show>div.cell-output-display .btn-quarto.dropdown-toggle{color:#fff;background-color:#5f6163;border-color:#4b4e50}.btn-check:checked+.btn.btn-quarto:focus,.btn-check:active+.btn.btn-quarto:focus,.btn.btn-quarto:active:focus,.btn.btn-quarto.active:focus,.show>.btn.btn-quarto.dropdown-toggle:focus,.btn-check:checked+div.cell-output-display .btn-quarto:focus,.btn-check:active+div.cell-output-display .btn-quarto:focus,div.cell-output-display .btn-quarto:active:focus,div.cell-output-display .btn-quarto.active:focus,.show>div.cell-output-display .btn-quarto.dropdown-toggle:focus{box-shadow:0 0 0 .25rem rgba(77,80,82,.5)}.btn.btn-quarto:disabled,.btn.btn-quarto.disabled,div.cell-output-display .btn-quarto:disabled,div.cell-output-display .btn-quarto.disabled{color:#fff;background-color:#373a3c;border-color:#373a3c}nav.quarto-secondary-nav.color-navbar{background-color:#2780e3;color:#fdfeff}nav.quarto-secondary-nav.color-navbar h1,nav.quarto-secondary-nav.color-navbar .h1,nav.quarto-secondary-nav.color-navbar .quarto-btn-toggle{color:#fdfeff}@media(max-width: 991.98px){body.nav-sidebar .quarto-title-banner{margin-bottom:0;padding-bottom:0}body.nav-sidebar #title-block-header{margin-block-end:0}}p.subtitle{margin-top:.25em;margin-bottom:.5em}code a:any-link{color:inherit;text-decoration-color:#6c757d}/*! light */div.observablehq table thead tr th{background-color:var(--bs-body-bg)}input,button,select,optgroup,textarea{background-color:var(--bs-body-bg)}.code-annotated .code-copy-button{margin-right:1.25em;margin-top:0;padding-bottom:0;padding-top:3px}.code-annotation-gutter-bg{background-color:#fff}.code-annotation-gutter{background-color:rgba(233,236,239,.65)}.code-annotation-gutter,.code-annotation-gutter-bg{height:100%;width:calc(20px + .5em);position:absolute;top:0;right:0}dl.code-annotation-container-grid dt{margin-right:1em;margin-top:.25rem}dl.code-annotation-container-grid dt{font-family:var(--bs-font-monospace);color:#4f5457;border:solid #4f5457 1px;border-radius:50%;height:22px;width:22px;line-height:22px;font-size:11px;text-align:center;vertical-align:middle;text-decoration:none}dl.code-annotation-container-grid dt[data-target-cell]{cursor:pointer}dl.code-annotation-container-grid dt[data-target-cell].code-annotation-active{color:#fff;border:solid #aaa 1px;background-color:#aaa}pre.code-annotation-code{padding-top:0;padding-bottom:0}pre.code-annotation-code code{z-index:3}#code-annotation-line-highlight-gutter{width:100%;border-top:solid rgba(170,170,170,.2666666667) 1px;border-bottom:solid rgba(170,170,170,.2666666667) 1px;z-index:2;background-color:rgba(170,170,170,.1333333333)}#code-annotation-line-highlight{margin-left:-4em;width:calc(100% + 4em);border-top:solid rgba(170,170,170,.2666666667) 1px;border-bottom:solid rgba(170,170,170,.2666666667) 1px;z-index:2;background-color:rgba(170,170,170,.1333333333)}code.sourceCode .code-annotation-anchor.code-annotation-active{background-color:var(--quarto-hl-normal-color, #aaaaaa);border:solid var(--quarto-hl-normal-color, #aaaaaa) 1px;color:#e9ecef;font-weight:bolder}code.sourceCode .code-annotation-anchor{font-family:var(--bs-font-monospace);color:var(--quarto-hl-co-color);border:solid var(--quarto-hl-co-color) 1px;border-radius:50%;height:18px;width:18px;font-size:9px;margin-top:2px}code.sourceCode button.code-annotation-anchor{padding:2px}code.sourceCode a.code-annotation-anchor{line-height:18px;text-align:center;vertical-align:middle;cursor:default;text-decoration:none}@media print{.page-columns .column-screen-inset{grid-column:page-start-inset/page-end-inset;z-index:998;transform:translate3d(0, 0, 0)}.page-columns .column-screen-inset table{background:#fff}.page-columns .column-screen-inset-left{grid-column:page-start-inset/body-content-end;z-index:998;transform:translate3d(0, 0, 0)}.page-columns .column-screen-inset-left table{background:#fff}.page-columns .column-screen-inset-right{grid-column:body-content-start/page-end-inset;z-index:998;transform:translate3d(0, 0, 0)}.page-columns .column-screen-inset-right table{background:#fff}.page-columns .column-screen{grid-column:page-start/page-end;z-index:998;transform:translate3d(0, 0, 0)}.page-columns .column-screen table{background:#fff}.page-columns .column-screen-left{grid-column:page-start/body-content-end;z-index:998;transform:translate3d(0, 0, 0)}.page-columns .column-screen-left table{background:#fff}.page-columns .column-screen-right{grid-column:body-content-start/page-end;z-index:998;transform:translate3d(0, 0, 0)}.page-columns .column-screen-right table{background:#fff}.page-columns .column-screen-inset-shaded{grid-column:page-start-inset/page-end-inset;padding:1em;background:#f8f9fa;z-index:998;transform:translate3d(0, 0, 0);margin-bottom:1em}}.quarto-video{margin-bottom:1em}.table>thead{border-top-width:0}.table>:not(caption)>*:not(:last-child)>*{border-bottom-color:#ebeced;border-bottom-style:solid;border-bottom-width:1px}.table>:not(:first-child){border-top:1px solid #b6babc;border-bottom:1px solid inherit}.table tbody{border-bottom-color:#b6babc}a.external:after{display:inline-block;height:.75rem;width:.75rem;margin-bottom:.15em;margin-left:.25em;content:"";vertical-align:-0.125em;background-image:url('data:image/svg+xml,');background-repeat:no-repeat;background-size:.75rem .75rem}div.sourceCode code a.external:after{content:none}a.external:after:hover{cursor:pointer}.quarto-ext-icon{display:inline-block;font-size:.75em;padding-left:.3em}.code-with-filename .code-with-filename-file{margin-bottom:0;padding-bottom:2px;padding-top:2px;padding-left:.7em;border:var(--quarto-border-width) solid var(--quarto-border-color);border-radius:var(--quarto-border-radius);border-bottom:0;border-bottom-left-radius:0%;border-bottom-right-radius:0%}.code-with-filename div.sourceCode,.reveal .code-with-filename div.sourceCode{margin-top:0;border-top-left-radius:0%;border-top-right-radius:0%}.code-with-filename .code-with-filename-file pre{margin-bottom:0}.code-with-filename .code-with-filename-file,.code-with-filename .code-with-filename-file pre{background-color:rgba(219,219,219,.8)}.quarto-dark .code-with-filename .code-with-filename-file,.quarto-dark .code-with-filename .code-with-filename-file pre{background-color:#555}.code-with-filename .code-with-filename-file strong{font-weight:400}.quarto-title-banner{margin-bottom:1em;color:#fdfeff;background:#2780e3}.quarto-title-banner .code-tools-button{color:#97cbff}.quarto-title-banner .code-tools-button:hover{color:#fdfeff}.quarto-title-banner .code-tools-button>.bi::before{background-image:url('data:image/svg+xml,')}.quarto-title-banner .code-tools-button:hover>.bi::before{background-image:url('data:image/svg+xml,')}.quarto-title-banner .quarto-title .title{font-weight:600}.quarto-title-banner .quarto-categories{margin-top:.75em}@media(min-width: 992px){.quarto-title-banner{padding-top:2.5em;padding-bottom:2.5em}}@media(max-width: 991.98px){.quarto-title-banner{padding-top:1em;padding-bottom:1em}}main.quarto-banner-title-block>section:first-child>h2,main.quarto-banner-title-block>section:first-child>.h2,main.quarto-banner-title-block>section:first-child>h3,main.quarto-banner-title-block>section:first-child>.h3,main.quarto-banner-title-block>section:first-child>h4,main.quarto-banner-title-block>section:first-child>.h4{margin-top:0}.quarto-title .quarto-categories{display:flex;flex-wrap:wrap;row-gap:.5em;column-gap:.4em;padding-bottom:.5em;margin-top:.75em}.quarto-title .quarto-categories .quarto-category{padding:.25em .75em;font-size:.65em;text-transform:uppercase;border:solid 1px;border-radius:.25rem;opacity:.6}.quarto-title .quarto-categories .quarto-category a{color:inherit}#title-block-header.quarto-title-block.default .quarto-title-meta{display:grid;grid-template-columns:repeat(2, 1fr)}#title-block-header.quarto-title-block.default .quarto-title .title{margin-bottom:0}#title-block-header.quarto-title-block.default .quarto-title-author-orcid img{margin-top:-5px}#title-block-header.quarto-title-block.default .quarto-description p:last-of-type{margin-bottom:0}#title-block-header.quarto-title-block.default .quarto-title-meta-contents p,#title-block-header.quarto-title-block.default .quarto-title-authors p,#title-block-header.quarto-title-block.default .quarto-title-affiliations p{margin-bottom:.1em}#title-block-header.quarto-title-block.default .quarto-title-meta-heading{text-transform:uppercase;margin-top:1em;font-size:.8em;opacity:.8;font-weight:400}#title-block-header.quarto-title-block.default .quarto-title-meta-contents{font-size:.9em}#title-block-header.quarto-title-block.default .quarto-title-meta-contents a{color:#373a3c}#title-block-header.quarto-title-block.default .quarto-title-meta-contents p.affiliation:last-of-type{margin-bottom:.7em}#title-block-header.quarto-title-block.default p.affiliation{margin-bottom:.1em}#title-block-header.quarto-title-block.default .description,#title-block-header.quarto-title-block.default .abstract{margin-top:0}#title-block-header.quarto-title-block.default .description>p,#title-block-header.quarto-title-block.default .abstract>p{font-size:.9em}#title-block-header.quarto-title-block.default .description>p:last-of-type,#title-block-header.quarto-title-block.default .abstract>p:last-of-type{margin-bottom:0}#title-block-header.quarto-title-block.default .description .abstract-title,#title-block-header.quarto-title-block.default .abstract .abstract-title{margin-top:1em;text-transform:uppercase;font-size:.8em;opacity:.8;font-weight:400}#title-block-header.quarto-title-block.default .quarto-title-meta-author{display:grid;grid-template-columns:1fr 1fr}.quarto-title-tools-only{display:flex;justify-content:right}body{-webkit-font-smoothing:antialiased}.badge.bg-light{color:#373a3c}.progress .progress-bar{font-size:8px;line-height:8px}/*# sourceMappingURL=9161419e6f82ea4435380a70856fa72b.css.map */ diff --git a/site_libs/bootstrap/bootstrap.min.js b/site_libs/bootstrap/bootstrap.min.js new file mode 100644 index 00000000..cc0a2556 --- /dev/null +++ b/site_libs/bootstrap/bootstrap.min.js @@ -0,0 +1,7 @@ +/*! + * Bootstrap v5.1.3 (https://getbootstrap.com/) + * Copyright 2011-2021 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors) + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) + */ +!function(t,e){"object"==typeof exports&&"undefined"!=typeof module?module.exports=e():"function"==typeof define&&define.amd?define(e):(t="undefined"!=typeof globalThis?globalThis:t||self).bootstrap=e()}(this,(function(){"use strict";const t="transitionend",e=t=>{let e=t.getAttribute("data-bs-target");if(!e||"#"===e){let i=t.getAttribute("href");if(!i||!i.includes("#")&&!i.startsWith("."))return null;i.includes("#")&&!i.startsWith("#")&&(i=`#${i.split("#")[1]}`),e=i&&"#"!==i?i.trim():null}return e},i=t=>{const i=e(t);return i&&document.querySelector(i)?i:null},n=t=>{const i=e(t);return i?document.querySelector(i):null},s=e=>{e.dispatchEvent(new Event(t))},o=t=>!(!t||"object"!=typeof t)&&(void 0!==t.jquery&&(t=t[0]),void 0!==t.nodeType),r=t=>o(t)?t.jquery?t[0]:t:"string"==typeof t&&t.length>0?document.querySelector(t):null,a=(t,e,i)=>{Object.keys(i).forEach((n=>{const s=i[n],r=e[n],a=r&&o(r)?"element":null==(l=r)?`${l}`:{}.toString.call(l).match(/\s([a-z]+)/i)[1].toLowerCase();var l;if(!new RegExp(s).test(a))throw new TypeError(`${t.toUpperCase()}: Option "${n}" provided type "${a}" but expected type "${s}".`)}))},l=t=>!(!o(t)||0===t.getClientRects().length)&&"visible"===getComputedStyle(t).getPropertyValue("visibility"),c=t=>!t||t.nodeType!==Node.ELEMENT_NODE||!!t.classList.contains("disabled")||(void 0!==t.disabled?t.disabled:t.hasAttribute("disabled")&&"false"!==t.getAttribute("disabled")),h=t=>{if(!document.documentElement.attachShadow)return null;if("function"==typeof t.getRootNode){const e=t.getRootNode();return e instanceof ShadowRoot?e:null}return t instanceof ShadowRoot?t:t.parentNode?h(t.parentNode):null},d=()=>{},u=t=>{t.offsetHeight},f=()=>{const{jQuery:t}=window;return t&&!document.body.hasAttribute("data-bs-no-jquery")?t:null},p=[],m=()=>"rtl"===document.documentElement.dir,g=t=>{var e;e=()=>{const e=f();if(e){const i=t.NAME,n=e.fn[i];e.fn[i]=t.jQueryInterface,e.fn[i].Constructor=t,e.fn[i].noConflict=()=>(e.fn[i]=n,t.jQueryInterface)}},"loading"===document.readyState?(p.length||document.addEventListener("DOMContentLoaded",(()=>{p.forEach((t=>t()))})),p.push(e)):e()},_=t=>{"function"==typeof t&&t()},b=(e,i,n=!0)=>{if(!n)return void _(e);const o=(t=>{if(!t)return 0;let{transitionDuration:e,transitionDelay:i}=window.getComputedStyle(t);const n=Number.parseFloat(e),s=Number.parseFloat(i);return n||s?(e=e.split(",")[0],i=i.split(",")[0],1e3*(Number.parseFloat(e)+Number.parseFloat(i))):0})(i)+5;let r=!1;const a=({target:n})=>{n===i&&(r=!0,i.removeEventListener(t,a),_(e))};i.addEventListener(t,a),setTimeout((()=>{r||s(i)}),o)},v=(t,e,i,n)=>{let s=t.indexOf(e);if(-1===s)return t[!i&&n?t.length-1:0];const o=t.length;return s+=i?1:-1,n&&(s=(s+o)%o),t[Math.max(0,Math.min(s,o-1))]},y=/[^.]*(?=\..*)\.|.*/,w=/\..*/,E=/::\d+$/,A={};let T=1;const O={mouseenter:"mouseover",mouseleave:"mouseout"},C=/^(mouseenter|mouseleave)/i,k=new Set(["click","dblclick","mouseup","mousedown","contextmenu","mousewheel","DOMMouseScroll","mouseover","mouseout","mousemove","selectstart","selectend","keydown","keypress","keyup","orientationchange","touchstart","touchmove","touchend","touchcancel","pointerdown","pointermove","pointerup","pointerleave","pointercancel","gesturestart","gesturechange","gestureend","focus","blur","change","reset","select","submit","focusin","focusout","load","unload","beforeunload","resize","move","DOMContentLoaded","readystatechange","error","abort","scroll"]);function L(t,e){return e&&`${e}::${T++}`||t.uidEvent||T++}function x(t){const e=L(t);return t.uidEvent=e,A[e]=A[e]||{},A[e]}function D(t,e,i=null){const n=Object.keys(t);for(let s=0,o=n.length;sfunction(e){if(!e.relatedTarget||e.relatedTarget!==e.delegateTarget&&!e.delegateTarget.contains(e.relatedTarget))return t.call(this,e)};n?n=t(n):i=t(i)}const[o,r,a]=S(e,i,n),l=x(t),c=l[a]||(l[a]={}),h=D(c,r,o?i:null);if(h)return void(h.oneOff=h.oneOff&&s);const d=L(r,e.replace(y,"")),u=o?function(t,e,i){return function n(s){const o=t.querySelectorAll(e);for(let{target:r}=s;r&&r!==this;r=r.parentNode)for(let a=o.length;a--;)if(o[a]===r)return s.delegateTarget=r,n.oneOff&&j.off(t,s.type,e,i),i.apply(r,[s]);return null}}(t,i,n):function(t,e){return function i(n){return n.delegateTarget=t,i.oneOff&&j.off(t,n.type,e),e.apply(t,[n])}}(t,i);u.delegationSelector=o?i:null,u.originalHandler=r,u.oneOff=s,u.uidEvent=d,c[d]=u,t.addEventListener(a,u,o)}function I(t,e,i,n,s){const o=D(e[i],n,s);o&&(t.removeEventListener(i,o,Boolean(s)),delete e[i][o.uidEvent])}function P(t){return t=t.replace(w,""),O[t]||t}const j={on(t,e,i,n){N(t,e,i,n,!1)},one(t,e,i,n){N(t,e,i,n,!0)},off(t,e,i,n){if("string"!=typeof e||!t)return;const[s,o,r]=S(e,i,n),a=r!==e,l=x(t),c=e.startsWith(".");if(void 0!==o){if(!l||!l[r])return;return void I(t,l,r,o,s?i:null)}c&&Object.keys(l).forEach((i=>{!function(t,e,i,n){const s=e[i]||{};Object.keys(s).forEach((o=>{if(o.includes(n)){const n=s[o];I(t,e,i,n.originalHandler,n.delegationSelector)}}))}(t,l,i,e.slice(1))}));const h=l[r]||{};Object.keys(h).forEach((i=>{const n=i.replace(E,"");if(!a||e.includes(n)){const e=h[i];I(t,l,r,e.originalHandler,e.delegationSelector)}}))},trigger(t,e,i){if("string"!=typeof e||!t)return null;const n=f(),s=P(e),o=e!==s,r=k.has(s);let a,l=!0,c=!0,h=!1,d=null;return o&&n&&(a=n.Event(e,i),n(t).trigger(a),l=!a.isPropagationStopped(),c=!a.isImmediatePropagationStopped(),h=a.isDefaultPrevented()),r?(d=document.createEvent("HTMLEvents"),d.initEvent(s,l,!0)):d=new CustomEvent(e,{bubbles:l,cancelable:!0}),void 0!==i&&Object.keys(i).forEach((t=>{Object.defineProperty(d,t,{get:()=>i[t]})})),h&&d.preventDefault(),c&&t.dispatchEvent(d),d.defaultPrevented&&void 0!==a&&a.preventDefault(),d}},M=new Map,H={set(t,e,i){M.has(t)||M.set(t,new Map);const n=M.get(t);n.has(e)||0===n.size?n.set(e,i):console.error(`Bootstrap doesn't allow more than one instance per element. Bound instance: ${Array.from(n.keys())[0]}.`)},get:(t,e)=>M.has(t)&&M.get(t).get(e)||null,remove(t,e){if(!M.has(t))return;const i=M.get(t);i.delete(e),0===i.size&&M.delete(t)}};class B{constructor(t){(t=r(t))&&(this._element=t,H.set(this._element,this.constructor.DATA_KEY,this))}dispose(){H.remove(this._element,this.constructor.DATA_KEY),j.off(this._element,this.constructor.EVENT_KEY),Object.getOwnPropertyNames(this).forEach((t=>{this[t]=null}))}_queueCallback(t,e,i=!0){b(t,e,i)}static getInstance(t){return H.get(r(t),this.DATA_KEY)}static getOrCreateInstance(t,e={}){return this.getInstance(t)||new this(t,"object"==typeof e?e:null)}static get VERSION(){return"5.1.3"}static get NAME(){throw new Error('You have to implement the static method "NAME", for each component!')}static get DATA_KEY(){return`bs.${this.NAME}`}static get EVENT_KEY(){return`.${this.DATA_KEY}`}}const R=(t,e="hide")=>{const i=`click.dismiss${t.EVENT_KEY}`,s=t.NAME;j.on(document,i,`[data-bs-dismiss="${s}"]`,(function(i){if(["A","AREA"].includes(this.tagName)&&i.preventDefault(),c(this))return;const o=n(this)||this.closest(`.${s}`);t.getOrCreateInstance(o)[e]()}))};class W extends B{static get NAME(){return"alert"}close(){if(j.trigger(this._element,"close.bs.alert").defaultPrevented)return;this._element.classList.remove("show");const t=this._element.classList.contains("fade");this._queueCallback((()=>this._destroyElement()),this._element,t)}_destroyElement(){this._element.remove(),j.trigger(this._element,"closed.bs.alert"),this.dispose()}static jQueryInterface(t){return this.each((function(){const e=W.getOrCreateInstance(this);if("string"==typeof t){if(void 0===e[t]||t.startsWith("_")||"constructor"===t)throw new TypeError(`No method named "${t}"`);e[t](this)}}))}}R(W,"close"),g(W);const $='[data-bs-toggle="button"]';class z extends B{static get NAME(){return"button"}toggle(){this._element.setAttribute("aria-pressed",this._element.classList.toggle("active"))}static jQueryInterface(t){return this.each((function(){const e=z.getOrCreateInstance(this);"toggle"===t&&e[t]()}))}}function q(t){return"true"===t||"false"!==t&&(t===Number(t).toString()?Number(t):""===t||"null"===t?null:t)}function F(t){return t.replace(/[A-Z]/g,(t=>`-${t.toLowerCase()}`))}j.on(document,"click.bs.button.data-api",$,(t=>{t.preventDefault();const e=t.target.closest($);z.getOrCreateInstance(e).toggle()})),g(z);const U={setDataAttribute(t,e,i){t.setAttribute(`data-bs-${F(e)}`,i)},removeDataAttribute(t,e){t.removeAttribute(`data-bs-${F(e)}`)},getDataAttributes(t){if(!t)return{};const e={};return Object.keys(t.dataset).filter((t=>t.startsWith("bs"))).forEach((i=>{let n=i.replace(/^bs/,"");n=n.charAt(0).toLowerCase()+n.slice(1,n.length),e[n]=q(t.dataset[i])})),e},getDataAttribute:(t,e)=>q(t.getAttribute(`data-bs-${F(e)}`)),offset(t){const e=t.getBoundingClientRect();return{top:e.top+window.pageYOffset,left:e.left+window.pageXOffset}},position:t=>({top:t.offsetTop,left:t.offsetLeft})},V={find:(t,e=document.documentElement)=>[].concat(...Element.prototype.querySelectorAll.call(e,t)),findOne:(t,e=document.documentElement)=>Element.prototype.querySelector.call(e,t),children:(t,e)=>[].concat(...t.children).filter((t=>t.matches(e))),parents(t,e){const i=[];let n=t.parentNode;for(;n&&n.nodeType===Node.ELEMENT_NODE&&3!==n.nodeType;)n.matches(e)&&i.push(n),n=n.parentNode;return i},prev(t,e){let i=t.previousElementSibling;for(;i;){if(i.matches(e))return[i];i=i.previousElementSibling}return[]},next(t,e){let i=t.nextElementSibling;for(;i;){if(i.matches(e))return[i];i=i.nextElementSibling}return[]},focusableChildren(t){const e=["a","button","input","textarea","select","details","[tabindex]",'[contenteditable="true"]'].map((t=>`${t}:not([tabindex^="-"])`)).join(", ");return this.find(e,t).filter((t=>!c(t)&&l(t)))}},K="carousel",X={interval:5e3,keyboard:!0,slide:!1,pause:"hover",wrap:!0,touch:!0},Y={interval:"(number|boolean)",keyboard:"boolean",slide:"(boolean|string)",pause:"(string|boolean)",wrap:"boolean",touch:"boolean"},Q="next",G="prev",Z="left",J="right",tt={ArrowLeft:J,ArrowRight:Z},et="slid.bs.carousel",it="active",nt=".active.carousel-item";class st extends B{constructor(t,e){super(t),this._items=null,this._interval=null,this._activeElement=null,this._isPaused=!1,this._isSliding=!1,this.touchTimeout=null,this.touchStartX=0,this.touchDeltaX=0,this._config=this._getConfig(e),this._indicatorsElement=V.findOne(".carousel-indicators",this._element),this._touchSupported="ontouchstart"in document.documentElement||navigator.maxTouchPoints>0,this._pointerEvent=Boolean(window.PointerEvent),this._addEventListeners()}static get Default(){return X}static get NAME(){return K}next(){this._slide(Q)}nextWhenVisible(){!document.hidden&&l(this._element)&&this.next()}prev(){this._slide(G)}pause(t){t||(this._isPaused=!0),V.findOne(".carousel-item-next, .carousel-item-prev",this._element)&&(s(this._element),this.cycle(!0)),clearInterval(this._interval),this._interval=null}cycle(t){t||(this._isPaused=!1),this._interval&&(clearInterval(this._interval),this._interval=null),this._config&&this._config.interval&&!this._isPaused&&(this._updateInterval(),this._interval=setInterval((document.visibilityState?this.nextWhenVisible:this.next).bind(this),this._config.interval))}to(t){this._activeElement=V.findOne(nt,this._element);const e=this._getItemIndex(this._activeElement);if(t>this._items.length-1||t<0)return;if(this._isSliding)return void j.one(this._element,et,(()=>this.to(t)));if(e===t)return this.pause(),void this.cycle();const i=t>e?Q:G;this._slide(i,this._items[t])}_getConfig(t){return t={...X,...U.getDataAttributes(this._element),..."object"==typeof t?t:{}},a(K,t,Y),t}_handleSwipe(){const t=Math.abs(this.touchDeltaX);if(t<=40)return;const e=t/this.touchDeltaX;this.touchDeltaX=0,e&&this._slide(e>0?J:Z)}_addEventListeners(){this._config.keyboard&&j.on(this._element,"keydown.bs.carousel",(t=>this._keydown(t))),"hover"===this._config.pause&&(j.on(this._element,"mouseenter.bs.carousel",(t=>this.pause(t))),j.on(this._element,"mouseleave.bs.carousel",(t=>this.cycle(t)))),this._config.touch&&this._touchSupported&&this._addTouchEventListeners()}_addTouchEventListeners(){const t=t=>this._pointerEvent&&("pen"===t.pointerType||"touch"===t.pointerType),e=e=>{t(e)?this.touchStartX=e.clientX:this._pointerEvent||(this.touchStartX=e.touches[0].clientX)},i=t=>{this.touchDeltaX=t.touches&&t.touches.length>1?0:t.touches[0].clientX-this.touchStartX},n=e=>{t(e)&&(this.touchDeltaX=e.clientX-this.touchStartX),this._handleSwipe(),"hover"===this._config.pause&&(this.pause(),this.touchTimeout&&clearTimeout(this.touchTimeout),this.touchTimeout=setTimeout((t=>this.cycle(t)),500+this._config.interval))};V.find(".carousel-item img",this._element).forEach((t=>{j.on(t,"dragstart.bs.carousel",(t=>t.preventDefault()))})),this._pointerEvent?(j.on(this._element,"pointerdown.bs.carousel",(t=>e(t))),j.on(this._element,"pointerup.bs.carousel",(t=>n(t))),this._element.classList.add("pointer-event")):(j.on(this._element,"touchstart.bs.carousel",(t=>e(t))),j.on(this._element,"touchmove.bs.carousel",(t=>i(t))),j.on(this._element,"touchend.bs.carousel",(t=>n(t))))}_keydown(t){if(/input|textarea/i.test(t.target.tagName))return;const e=tt[t.key];e&&(t.preventDefault(),this._slide(e))}_getItemIndex(t){return this._items=t&&t.parentNode?V.find(".carousel-item",t.parentNode):[],this._items.indexOf(t)}_getItemByOrder(t,e){const i=t===Q;return v(this._items,e,i,this._config.wrap)}_triggerSlideEvent(t,e){const i=this._getItemIndex(t),n=this._getItemIndex(V.findOne(nt,this._element));return j.trigger(this._element,"slide.bs.carousel",{relatedTarget:t,direction:e,from:n,to:i})}_setActiveIndicatorElement(t){if(this._indicatorsElement){const e=V.findOne(".active",this._indicatorsElement);e.classList.remove(it),e.removeAttribute("aria-current");const i=V.find("[data-bs-target]",this._indicatorsElement);for(let e=0;e{j.trigger(this._element,et,{relatedTarget:o,direction:d,from:s,to:r})};if(this._element.classList.contains("slide")){o.classList.add(h),u(o),n.classList.add(c),o.classList.add(c);const t=()=>{o.classList.remove(c,h),o.classList.add(it),n.classList.remove(it,h,c),this._isSliding=!1,setTimeout(f,0)};this._queueCallback(t,n,!0)}else n.classList.remove(it),o.classList.add(it),this._isSliding=!1,f();a&&this.cycle()}_directionToOrder(t){return[J,Z].includes(t)?m()?t===Z?G:Q:t===Z?Q:G:t}_orderToDirection(t){return[Q,G].includes(t)?m()?t===G?Z:J:t===G?J:Z:t}static carouselInterface(t,e){const i=st.getOrCreateInstance(t,e);let{_config:n}=i;"object"==typeof e&&(n={...n,...e});const s="string"==typeof e?e:n.slide;if("number"==typeof e)i.to(e);else if("string"==typeof s){if(void 0===i[s])throw new TypeError(`No method named "${s}"`);i[s]()}else n.interval&&n.ride&&(i.pause(),i.cycle())}static jQueryInterface(t){return this.each((function(){st.carouselInterface(this,t)}))}static dataApiClickHandler(t){const e=n(this);if(!e||!e.classList.contains("carousel"))return;const i={...U.getDataAttributes(e),...U.getDataAttributes(this)},s=this.getAttribute("data-bs-slide-to");s&&(i.interval=!1),st.carouselInterface(e,i),s&&st.getInstance(e).to(s),t.preventDefault()}}j.on(document,"click.bs.carousel.data-api","[data-bs-slide], [data-bs-slide-to]",st.dataApiClickHandler),j.on(window,"load.bs.carousel.data-api",(()=>{const t=V.find('[data-bs-ride="carousel"]');for(let e=0,i=t.length;et===this._element));null!==s&&o.length&&(this._selector=s,this._triggerArray.push(e))}this._initializeChildren(),this._config.parent||this._addAriaAndCollapsedClass(this._triggerArray,this._isShown()),this._config.toggle&&this.toggle()}static get Default(){return rt}static get NAME(){return ot}toggle(){this._isShown()?this.hide():this.show()}show(){if(this._isTransitioning||this._isShown())return;let t,e=[];if(this._config.parent){const t=V.find(ut,this._config.parent);e=V.find(".collapse.show, .collapse.collapsing",this._config.parent).filter((e=>!t.includes(e)))}const i=V.findOne(this._selector);if(e.length){const n=e.find((t=>i!==t));if(t=n?pt.getInstance(n):null,t&&t._isTransitioning)return}if(j.trigger(this._element,"show.bs.collapse").defaultPrevented)return;e.forEach((e=>{i!==e&&pt.getOrCreateInstance(e,{toggle:!1}).hide(),t||H.set(e,"bs.collapse",null)}));const n=this._getDimension();this._element.classList.remove(ct),this._element.classList.add(ht),this._element.style[n]=0,this._addAriaAndCollapsedClass(this._triggerArray,!0),this._isTransitioning=!0;const s=`scroll${n[0].toUpperCase()+n.slice(1)}`;this._queueCallback((()=>{this._isTransitioning=!1,this._element.classList.remove(ht),this._element.classList.add(ct,lt),this._element.style[n]="",j.trigger(this._element,"shown.bs.collapse")}),this._element,!0),this._element.style[n]=`${this._element[s]}px`}hide(){if(this._isTransitioning||!this._isShown())return;if(j.trigger(this._element,"hide.bs.collapse").defaultPrevented)return;const t=this._getDimension();this._element.style[t]=`${this._element.getBoundingClientRect()[t]}px`,u(this._element),this._element.classList.add(ht),this._element.classList.remove(ct,lt);const e=this._triggerArray.length;for(let t=0;t{this._isTransitioning=!1,this._element.classList.remove(ht),this._element.classList.add(ct),j.trigger(this._element,"hidden.bs.collapse")}),this._element,!0)}_isShown(t=this._element){return t.classList.contains(lt)}_getConfig(t){return(t={...rt,...U.getDataAttributes(this._element),...t}).toggle=Boolean(t.toggle),t.parent=r(t.parent),a(ot,t,at),t}_getDimension(){return this._element.classList.contains("collapse-horizontal")?"width":"height"}_initializeChildren(){if(!this._config.parent)return;const t=V.find(ut,this._config.parent);V.find(ft,this._config.parent).filter((e=>!t.includes(e))).forEach((t=>{const e=n(t);e&&this._addAriaAndCollapsedClass([t],this._isShown(e))}))}_addAriaAndCollapsedClass(t,e){t.length&&t.forEach((t=>{e?t.classList.remove(dt):t.classList.add(dt),t.setAttribute("aria-expanded",e)}))}static jQueryInterface(t){return this.each((function(){const e={};"string"==typeof t&&/show|hide/.test(t)&&(e.toggle=!1);const i=pt.getOrCreateInstance(this,e);if("string"==typeof t){if(void 0===i[t])throw new TypeError(`No method named "${t}"`);i[t]()}}))}}j.on(document,"click.bs.collapse.data-api",ft,(function(t){("A"===t.target.tagName||t.delegateTarget&&"A"===t.delegateTarget.tagName)&&t.preventDefault();const e=i(this);V.find(e).forEach((t=>{pt.getOrCreateInstance(t,{toggle:!1}).toggle()}))})),g(pt);var mt="top",gt="bottom",_t="right",bt="left",vt="auto",yt=[mt,gt,_t,bt],wt="start",Et="end",At="clippingParents",Tt="viewport",Ot="popper",Ct="reference",kt=yt.reduce((function(t,e){return t.concat([e+"-"+wt,e+"-"+Et])}),[]),Lt=[].concat(yt,[vt]).reduce((function(t,e){return t.concat([e,e+"-"+wt,e+"-"+Et])}),[]),xt="beforeRead",Dt="read",St="afterRead",Nt="beforeMain",It="main",Pt="afterMain",jt="beforeWrite",Mt="write",Ht="afterWrite",Bt=[xt,Dt,St,Nt,It,Pt,jt,Mt,Ht];function Rt(t){return t?(t.nodeName||"").toLowerCase():null}function Wt(t){if(null==t)return window;if("[object Window]"!==t.toString()){var e=t.ownerDocument;return e&&e.defaultView||window}return t}function $t(t){return t instanceof Wt(t).Element||t instanceof Element}function zt(t){return t instanceof Wt(t).HTMLElement||t instanceof HTMLElement}function qt(t){return"undefined"!=typeof ShadowRoot&&(t instanceof Wt(t).ShadowRoot||t instanceof ShadowRoot)}const Ft={name:"applyStyles",enabled:!0,phase:"write",fn:function(t){var e=t.state;Object.keys(e.elements).forEach((function(t){var i=e.styles[t]||{},n=e.attributes[t]||{},s=e.elements[t];zt(s)&&Rt(s)&&(Object.assign(s.style,i),Object.keys(n).forEach((function(t){var e=n[t];!1===e?s.removeAttribute(t):s.setAttribute(t,!0===e?"":e)})))}))},effect:function(t){var e=t.state,i={popper:{position:e.options.strategy,left:"0",top:"0",margin:"0"},arrow:{position:"absolute"},reference:{}};return Object.assign(e.elements.popper.style,i.popper),e.styles=i,e.elements.arrow&&Object.assign(e.elements.arrow.style,i.arrow),function(){Object.keys(e.elements).forEach((function(t){var n=e.elements[t],s=e.attributes[t]||{},o=Object.keys(e.styles.hasOwnProperty(t)?e.styles[t]:i[t]).reduce((function(t,e){return t[e]="",t}),{});zt(n)&&Rt(n)&&(Object.assign(n.style,o),Object.keys(s).forEach((function(t){n.removeAttribute(t)})))}))}},requires:["computeStyles"]};function Ut(t){return t.split("-")[0]}function Vt(t,e){var i=t.getBoundingClientRect();return{width:i.width/1,height:i.height/1,top:i.top/1,right:i.right/1,bottom:i.bottom/1,left:i.left/1,x:i.left/1,y:i.top/1}}function Kt(t){var e=Vt(t),i=t.offsetWidth,n=t.offsetHeight;return Math.abs(e.width-i)<=1&&(i=e.width),Math.abs(e.height-n)<=1&&(n=e.height),{x:t.offsetLeft,y:t.offsetTop,width:i,height:n}}function Xt(t,e){var i=e.getRootNode&&e.getRootNode();if(t.contains(e))return!0;if(i&&qt(i)){var n=e;do{if(n&&t.isSameNode(n))return!0;n=n.parentNode||n.host}while(n)}return!1}function Yt(t){return Wt(t).getComputedStyle(t)}function Qt(t){return["table","td","th"].indexOf(Rt(t))>=0}function Gt(t){return(($t(t)?t.ownerDocument:t.document)||window.document).documentElement}function Zt(t){return"html"===Rt(t)?t:t.assignedSlot||t.parentNode||(qt(t)?t.host:null)||Gt(t)}function Jt(t){return zt(t)&&"fixed"!==Yt(t).position?t.offsetParent:null}function te(t){for(var e=Wt(t),i=Jt(t);i&&Qt(i)&&"static"===Yt(i).position;)i=Jt(i);return i&&("html"===Rt(i)||"body"===Rt(i)&&"static"===Yt(i).position)?e:i||function(t){var e=-1!==navigator.userAgent.toLowerCase().indexOf("firefox");if(-1!==navigator.userAgent.indexOf("Trident")&&zt(t)&&"fixed"===Yt(t).position)return null;for(var i=Zt(t);zt(i)&&["html","body"].indexOf(Rt(i))<0;){var n=Yt(i);if("none"!==n.transform||"none"!==n.perspective||"paint"===n.contain||-1!==["transform","perspective"].indexOf(n.willChange)||e&&"filter"===n.willChange||e&&n.filter&&"none"!==n.filter)return i;i=i.parentNode}return null}(t)||e}function ee(t){return["top","bottom"].indexOf(t)>=0?"x":"y"}var ie=Math.max,ne=Math.min,se=Math.round;function oe(t,e,i){return ie(t,ne(e,i))}function re(t){return Object.assign({},{top:0,right:0,bottom:0,left:0},t)}function ae(t,e){return e.reduce((function(e,i){return e[i]=t,e}),{})}const le={name:"arrow",enabled:!0,phase:"main",fn:function(t){var e,i=t.state,n=t.name,s=t.options,o=i.elements.arrow,r=i.modifiersData.popperOffsets,a=Ut(i.placement),l=ee(a),c=[bt,_t].indexOf(a)>=0?"height":"width";if(o&&r){var h=function(t,e){return re("number"!=typeof(t="function"==typeof t?t(Object.assign({},e.rects,{placement:e.placement})):t)?t:ae(t,yt))}(s.padding,i),d=Kt(o),u="y"===l?mt:bt,f="y"===l?gt:_t,p=i.rects.reference[c]+i.rects.reference[l]-r[l]-i.rects.popper[c],m=r[l]-i.rects.reference[l],g=te(o),_=g?"y"===l?g.clientHeight||0:g.clientWidth||0:0,b=p/2-m/2,v=h[u],y=_-d[c]-h[f],w=_/2-d[c]/2+b,E=oe(v,w,y),A=l;i.modifiersData[n]=((e={})[A]=E,e.centerOffset=E-w,e)}},effect:function(t){var e=t.state,i=t.options.element,n=void 0===i?"[data-popper-arrow]":i;null!=n&&("string"!=typeof n||(n=e.elements.popper.querySelector(n)))&&Xt(e.elements.popper,n)&&(e.elements.arrow=n)},requires:["popperOffsets"],requiresIfExists:["preventOverflow"]};function ce(t){return t.split("-")[1]}var he={top:"auto",right:"auto",bottom:"auto",left:"auto"};function de(t){var e,i=t.popper,n=t.popperRect,s=t.placement,o=t.variation,r=t.offsets,a=t.position,l=t.gpuAcceleration,c=t.adaptive,h=t.roundOffsets,d=!0===h?function(t){var e=t.x,i=t.y,n=window.devicePixelRatio||1;return{x:se(se(e*n)/n)||0,y:se(se(i*n)/n)||0}}(r):"function"==typeof h?h(r):r,u=d.x,f=void 0===u?0:u,p=d.y,m=void 0===p?0:p,g=r.hasOwnProperty("x"),_=r.hasOwnProperty("y"),b=bt,v=mt,y=window;if(c){var w=te(i),E="clientHeight",A="clientWidth";w===Wt(i)&&"static"!==Yt(w=Gt(i)).position&&"absolute"===a&&(E="scrollHeight",A="scrollWidth"),w=w,s!==mt&&(s!==bt&&s!==_t||o!==Et)||(v=gt,m-=w[E]-n.height,m*=l?1:-1),s!==bt&&(s!==mt&&s!==gt||o!==Et)||(b=_t,f-=w[A]-n.width,f*=l?1:-1)}var T,O=Object.assign({position:a},c&&he);return l?Object.assign({},O,((T={})[v]=_?"0":"",T[b]=g?"0":"",T.transform=(y.devicePixelRatio||1)<=1?"translate("+f+"px, "+m+"px)":"translate3d("+f+"px, "+m+"px, 0)",T)):Object.assign({},O,((e={})[v]=_?m+"px":"",e[b]=g?f+"px":"",e.transform="",e))}const ue={name:"computeStyles",enabled:!0,phase:"beforeWrite",fn:function(t){var e=t.state,i=t.options,n=i.gpuAcceleration,s=void 0===n||n,o=i.adaptive,r=void 0===o||o,a=i.roundOffsets,l=void 0===a||a,c={placement:Ut(e.placement),variation:ce(e.placement),popper:e.elements.popper,popperRect:e.rects.popper,gpuAcceleration:s};null!=e.modifiersData.popperOffsets&&(e.styles.popper=Object.assign({},e.styles.popper,de(Object.assign({},c,{offsets:e.modifiersData.popperOffsets,position:e.options.strategy,adaptive:r,roundOffsets:l})))),null!=e.modifiersData.arrow&&(e.styles.arrow=Object.assign({},e.styles.arrow,de(Object.assign({},c,{offsets:e.modifiersData.arrow,position:"absolute",adaptive:!1,roundOffsets:l})))),e.attributes.popper=Object.assign({},e.attributes.popper,{"data-popper-placement":e.placement})},data:{}};var fe={passive:!0};const pe={name:"eventListeners",enabled:!0,phase:"write",fn:function(){},effect:function(t){var e=t.state,i=t.instance,n=t.options,s=n.scroll,o=void 0===s||s,r=n.resize,a=void 0===r||r,l=Wt(e.elements.popper),c=[].concat(e.scrollParents.reference,e.scrollParents.popper);return o&&c.forEach((function(t){t.addEventListener("scroll",i.update,fe)})),a&&l.addEventListener("resize",i.update,fe),function(){o&&c.forEach((function(t){t.removeEventListener("scroll",i.update,fe)})),a&&l.removeEventListener("resize",i.update,fe)}},data:{}};var me={left:"right",right:"left",bottom:"top",top:"bottom"};function ge(t){return t.replace(/left|right|bottom|top/g,(function(t){return me[t]}))}var _e={start:"end",end:"start"};function be(t){return t.replace(/start|end/g,(function(t){return _e[t]}))}function ve(t){var e=Wt(t);return{scrollLeft:e.pageXOffset,scrollTop:e.pageYOffset}}function ye(t){return Vt(Gt(t)).left+ve(t).scrollLeft}function we(t){var e=Yt(t),i=e.overflow,n=e.overflowX,s=e.overflowY;return/auto|scroll|overlay|hidden/.test(i+s+n)}function Ee(t){return["html","body","#document"].indexOf(Rt(t))>=0?t.ownerDocument.body:zt(t)&&we(t)?t:Ee(Zt(t))}function Ae(t,e){var i;void 0===e&&(e=[]);var n=Ee(t),s=n===(null==(i=t.ownerDocument)?void 0:i.body),o=Wt(n),r=s?[o].concat(o.visualViewport||[],we(n)?n:[]):n,a=e.concat(r);return s?a:a.concat(Ae(Zt(r)))}function Te(t){return Object.assign({},t,{left:t.x,top:t.y,right:t.x+t.width,bottom:t.y+t.height})}function Oe(t,e){return e===Tt?Te(function(t){var e=Wt(t),i=Gt(t),n=e.visualViewport,s=i.clientWidth,o=i.clientHeight,r=0,a=0;return n&&(s=n.width,o=n.height,/^((?!chrome|android).)*safari/i.test(navigator.userAgent)||(r=n.offsetLeft,a=n.offsetTop)),{width:s,height:o,x:r+ye(t),y:a}}(t)):zt(e)?function(t){var e=Vt(t);return e.top=e.top+t.clientTop,e.left=e.left+t.clientLeft,e.bottom=e.top+t.clientHeight,e.right=e.left+t.clientWidth,e.width=t.clientWidth,e.height=t.clientHeight,e.x=e.left,e.y=e.top,e}(e):Te(function(t){var e,i=Gt(t),n=ve(t),s=null==(e=t.ownerDocument)?void 0:e.body,o=ie(i.scrollWidth,i.clientWidth,s?s.scrollWidth:0,s?s.clientWidth:0),r=ie(i.scrollHeight,i.clientHeight,s?s.scrollHeight:0,s?s.clientHeight:0),a=-n.scrollLeft+ye(t),l=-n.scrollTop;return"rtl"===Yt(s||i).direction&&(a+=ie(i.clientWidth,s?s.clientWidth:0)-o),{width:o,height:r,x:a,y:l}}(Gt(t)))}function Ce(t){var e,i=t.reference,n=t.element,s=t.placement,o=s?Ut(s):null,r=s?ce(s):null,a=i.x+i.width/2-n.width/2,l=i.y+i.height/2-n.height/2;switch(o){case mt:e={x:a,y:i.y-n.height};break;case gt:e={x:a,y:i.y+i.height};break;case _t:e={x:i.x+i.width,y:l};break;case bt:e={x:i.x-n.width,y:l};break;default:e={x:i.x,y:i.y}}var c=o?ee(o):null;if(null!=c){var h="y"===c?"height":"width";switch(r){case wt:e[c]=e[c]-(i[h]/2-n[h]/2);break;case Et:e[c]=e[c]+(i[h]/2-n[h]/2)}}return e}function ke(t,e){void 0===e&&(e={});var i=e,n=i.placement,s=void 0===n?t.placement:n,o=i.boundary,r=void 0===o?At:o,a=i.rootBoundary,l=void 0===a?Tt:a,c=i.elementContext,h=void 0===c?Ot:c,d=i.altBoundary,u=void 0!==d&&d,f=i.padding,p=void 0===f?0:f,m=re("number"!=typeof p?p:ae(p,yt)),g=h===Ot?Ct:Ot,_=t.rects.popper,b=t.elements[u?g:h],v=function(t,e,i){var n="clippingParents"===e?function(t){var e=Ae(Zt(t)),i=["absolute","fixed"].indexOf(Yt(t).position)>=0&&zt(t)?te(t):t;return $t(i)?e.filter((function(t){return $t(t)&&Xt(t,i)&&"body"!==Rt(t)})):[]}(t):[].concat(e),s=[].concat(n,[i]),o=s[0],r=s.reduce((function(e,i){var n=Oe(t,i);return e.top=ie(n.top,e.top),e.right=ne(n.right,e.right),e.bottom=ne(n.bottom,e.bottom),e.left=ie(n.left,e.left),e}),Oe(t,o));return r.width=r.right-r.left,r.height=r.bottom-r.top,r.x=r.left,r.y=r.top,r}($t(b)?b:b.contextElement||Gt(t.elements.popper),r,l),y=Vt(t.elements.reference),w=Ce({reference:y,element:_,strategy:"absolute",placement:s}),E=Te(Object.assign({},_,w)),A=h===Ot?E:y,T={top:v.top-A.top+m.top,bottom:A.bottom-v.bottom+m.bottom,left:v.left-A.left+m.left,right:A.right-v.right+m.right},O=t.modifiersData.offset;if(h===Ot&&O){var C=O[s];Object.keys(T).forEach((function(t){var e=[_t,gt].indexOf(t)>=0?1:-1,i=[mt,gt].indexOf(t)>=0?"y":"x";T[t]+=C[i]*e}))}return T}function Le(t,e){void 0===e&&(e={});var i=e,n=i.placement,s=i.boundary,o=i.rootBoundary,r=i.padding,a=i.flipVariations,l=i.allowedAutoPlacements,c=void 0===l?Lt:l,h=ce(n),d=h?a?kt:kt.filter((function(t){return ce(t)===h})):yt,u=d.filter((function(t){return c.indexOf(t)>=0}));0===u.length&&(u=d);var f=u.reduce((function(e,i){return e[i]=ke(t,{placement:i,boundary:s,rootBoundary:o,padding:r})[Ut(i)],e}),{});return Object.keys(f).sort((function(t,e){return f[t]-f[e]}))}const xe={name:"flip",enabled:!0,phase:"main",fn:function(t){var e=t.state,i=t.options,n=t.name;if(!e.modifiersData[n]._skip){for(var s=i.mainAxis,o=void 0===s||s,r=i.altAxis,a=void 0===r||r,l=i.fallbackPlacements,c=i.padding,h=i.boundary,d=i.rootBoundary,u=i.altBoundary,f=i.flipVariations,p=void 0===f||f,m=i.allowedAutoPlacements,g=e.options.placement,_=Ut(g),b=l||(_!==g&&p?function(t){if(Ut(t)===vt)return[];var e=ge(t);return[be(t),e,be(e)]}(g):[ge(g)]),v=[g].concat(b).reduce((function(t,i){return t.concat(Ut(i)===vt?Le(e,{placement:i,boundary:h,rootBoundary:d,padding:c,flipVariations:p,allowedAutoPlacements:m}):i)}),[]),y=e.rects.reference,w=e.rects.popper,E=new Map,A=!0,T=v[0],O=0;O=0,D=x?"width":"height",S=ke(e,{placement:C,boundary:h,rootBoundary:d,altBoundary:u,padding:c}),N=x?L?_t:bt:L?gt:mt;y[D]>w[D]&&(N=ge(N));var I=ge(N),P=[];if(o&&P.push(S[k]<=0),a&&P.push(S[N]<=0,S[I]<=0),P.every((function(t){return t}))){T=C,A=!1;break}E.set(C,P)}if(A)for(var j=function(t){var e=v.find((function(e){var i=E.get(e);if(i)return i.slice(0,t).every((function(t){return t}))}));if(e)return T=e,"break"},M=p?3:1;M>0&&"break"!==j(M);M--);e.placement!==T&&(e.modifiersData[n]._skip=!0,e.placement=T,e.reset=!0)}},requiresIfExists:["offset"],data:{_skip:!1}};function De(t,e,i){return void 0===i&&(i={x:0,y:0}),{top:t.top-e.height-i.y,right:t.right-e.width+i.x,bottom:t.bottom-e.height+i.y,left:t.left-e.width-i.x}}function Se(t){return[mt,_t,gt,bt].some((function(e){return t[e]>=0}))}const Ne={name:"hide",enabled:!0,phase:"main",requiresIfExists:["preventOverflow"],fn:function(t){var e=t.state,i=t.name,n=e.rects.reference,s=e.rects.popper,o=e.modifiersData.preventOverflow,r=ke(e,{elementContext:"reference"}),a=ke(e,{altBoundary:!0}),l=De(r,n),c=De(a,s,o),h=Se(l),d=Se(c);e.modifiersData[i]={referenceClippingOffsets:l,popperEscapeOffsets:c,isReferenceHidden:h,hasPopperEscaped:d},e.attributes.popper=Object.assign({},e.attributes.popper,{"data-popper-reference-hidden":h,"data-popper-escaped":d})}},Ie={name:"offset",enabled:!0,phase:"main",requires:["popperOffsets"],fn:function(t){var e=t.state,i=t.options,n=t.name,s=i.offset,o=void 0===s?[0,0]:s,r=Lt.reduce((function(t,i){return t[i]=function(t,e,i){var n=Ut(t),s=[bt,mt].indexOf(n)>=0?-1:1,o="function"==typeof i?i(Object.assign({},e,{placement:t})):i,r=o[0],a=o[1];return r=r||0,a=(a||0)*s,[bt,_t].indexOf(n)>=0?{x:a,y:r}:{x:r,y:a}}(i,e.rects,o),t}),{}),a=r[e.placement],l=a.x,c=a.y;null!=e.modifiersData.popperOffsets&&(e.modifiersData.popperOffsets.x+=l,e.modifiersData.popperOffsets.y+=c),e.modifiersData[n]=r}},Pe={name:"popperOffsets",enabled:!0,phase:"read",fn:function(t){var e=t.state,i=t.name;e.modifiersData[i]=Ce({reference:e.rects.reference,element:e.rects.popper,strategy:"absolute",placement:e.placement})},data:{}},je={name:"preventOverflow",enabled:!0,phase:"main",fn:function(t){var e=t.state,i=t.options,n=t.name,s=i.mainAxis,o=void 0===s||s,r=i.altAxis,a=void 0!==r&&r,l=i.boundary,c=i.rootBoundary,h=i.altBoundary,d=i.padding,u=i.tether,f=void 0===u||u,p=i.tetherOffset,m=void 0===p?0:p,g=ke(e,{boundary:l,rootBoundary:c,padding:d,altBoundary:h}),_=Ut(e.placement),b=ce(e.placement),v=!b,y=ee(_),w="x"===y?"y":"x",E=e.modifiersData.popperOffsets,A=e.rects.reference,T=e.rects.popper,O="function"==typeof m?m(Object.assign({},e.rects,{placement:e.placement})):m,C={x:0,y:0};if(E){if(o||a){var k="y"===y?mt:bt,L="y"===y?gt:_t,x="y"===y?"height":"width",D=E[y],S=E[y]+g[k],N=E[y]-g[L],I=f?-T[x]/2:0,P=b===wt?A[x]:T[x],j=b===wt?-T[x]:-A[x],M=e.elements.arrow,H=f&&M?Kt(M):{width:0,height:0},B=e.modifiersData["arrow#persistent"]?e.modifiersData["arrow#persistent"].padding:{top:0,right:0,bottom:0,left:0},R=B[k],W=B[L],$=oe(0,A[x],H[x]),z=v?A[x]/2-I-$-R-O:P-$-R-O,q=v?-A[x]/2+I+$+W+O:j+$+W+O,F=e.elements.arrow&&te(e.elements.arrow),U=F?"y"===y?F.clientTop||0:F.clientLeft||0:0,V=e.modifiersData.offset?e.modifiersData.offset[e.placement][y]:0,K=E[y]+z-V-U,X=E[y]+q-V;if(o){var Y=oe(f?ne(S,K):S,D,f?ie(N,X):N);E[y]=Y,C[y]=Y-D}if(a){var Q="x"===y?mt:bt,G="x"===y?gt:_t,Z=E[w],J=Z+g[Q],tt=Z-g[G],et=oe(f?ne(J,K):J,Z,f?ie(tt,X):tt);E[w]=et,C[w]=et-Z}}e.modifiersData[n]=C}},requiresIfExists:["offset"]};function Me(t,e,i){void 0===i&&(i=!1);var n=zt(e);zt(e)&&function(t){var e=t.getBoundingClientRect();e.width,t.offsetWidth,e.height,t.offsetHeight}(e);var s,o,r=Gt(e),a=Vt(t),l={scrollLeft:0,scrollTop:0},c={x:0,y:0};return(n||!n&&!i)&&(("body"!==Rt(e)||we(r))&&(l=(s=e)!==Wt(s)&&zt(s)?{scrollLeft:(o=s).scrollLeft,scrollTop:o.scrollTop}:ve(s)),zt(e)?((c=Vt(e)).x+=e.clientLeft,c.y+=e.clientTop):r&&(c.x=ye(r))),{x:a.left+l.scrollLeft-c.x,y:a.top+l.scrollTop-c.y,width:a.width,height:a.height}}function He(t){var e=new Map,i=new Set,n=[];function s(t){i.add(t.name),[].concat(t.requires||[],t.requiresIfExists||[]).forEach((function(t){if(!i.has(t)){var n=e.get(t);n&&s(n)}})),n.push(t)}return t.forEach((function(t){e.set(t.name,t)})),t.forEach((function(t){i.has(t.name)||s(t)})),n}var Be={placement:"bottom",modifiers:[],strategy:"absolute"};function Re(){for(var t=arguments.length,e=new Array(t),i=0;ij.on(t,"mouseover",d))),this._element.focus(),this._element.setAttribute("aria-expanded",!0),this._menu.classList.add(Je),this._element.classList.add(Je),j.trigger(this._element,"shown.bs.dropdown",t)}hide(){if(c(this._element)||!this._isShown(this._menu))return;const t={relatedTarget:this._element};this._completeHide(t)}dispose(){this._popper&&this._popper.destroy(),super.dispose()}update(){this._inNavbar=this._detectNavbar(),this._popper&&this._popper.update()}_completeHide(t){j.trigger(this._element,"hide.bs.dropdown",t).defaultPrevented||("ontouchstart"in document.documentElement&&[].concat(...document.body.children).forEach((t=>j.off(t,"mouseover",d))),this._popper&&this._popper.destroy(),this._menu.classList.remove(Je),this._element.classList.remove(Je),this._element.setAttribute("aria-expanded","false"),U.removeDataAttribute(this._menu,"popper"),j.trigger(this._element,"hidden.bs.dropdown",t))}_getConfig(t){if(t={...this.constructor.Default,...U.getDataAttributes(this._element),...t},a(Ue,t,this.constructor.DefaultType),"object"==typeof t.reference&&!o(t.reference)&&"function"!=typeof t.reference.getBoundingClientRect)throw new TypeError(`${Ue.toUpperCase()}: Option "reference" provided type "object" without a required "getBoundingClientRect" method.`);return t}_createPopper(t){if(void 0===Fe)throw new TypeError("Bootstrap's dropdowns require Popper (https://popper.js.org)");let e=this._element;"parent"===this._config.reference?e=t:o(this._config.reference)?e=r(this._config.reference):"object"==typeof this._config.reference&&(e=this._config.reference);const i=this._getPopperConfig(),n=i.modifiers.find((t=>"applyStyles"===t.name&&!1===t.enabled));this._popper=qe(e,this._menu,i),n&&U.setDataAttribute(this._menu,"popper","static")}_isShown(t=this._element){return t.classList.contains(Je)}_getMenuElement(){return V.next(this._element,ei)[0]}_getPlacement(){const t=this._element.parentNode;if(t.classList.contains("dropend"))return ri;if(t.classList.contains("dropstart"))return ai;const e="end"===getComputedStyle(this._menu).getPropertyValue("--bs-position").trim();return t.classList.contains("dropup")?e?ni:ii:e?oi:si}_detectNavbar(){return null!==this._element.closest(".navbar")}_getOffset(){const{offset:t}=this._config;return"string"==typeof t?t.split(",").map((t=>Number.parseInt(t,10))):"function"==typeof t?e=>t(e,this._element):t}_getPopperConfig(){const t={placement:this._getPlacement(),modifiers:[{name:"preventOverflow",options:{boundary:this._config.boundary}},{name:"offset",options:{offset:this._getOffset()}}]};return"static"===this._config.display&&(t.modifiers=[{name:"applyStyles",enabled:!1}]),{...t,..."function"==typeof this._config.popperConfig?this._config.popperConfig(t):this._config.popperConfig}}_selectMenuItem({key:t,target:e}){const i=V.find(".dropdown-menu .dropdown-item:not(.disabled):not(:disabled)",this._menu).filter(l);i.length&&v(i,e,t===Ye,!i.includes(e)).focus()}static jQueryInterface(t){return this.each((function(){const e=hi.getOrCreateInstance(this,t);if("string"==typeof t){if(void 0===e[t])throw new TypeError(`No method named "${t}"`);e[t]()}}))}static clearMenus(t){if(t&&(2===t.button||"keyup"===t.type&&"Tab"!==t.key))return;const e=V.find(ti);for(let i=0,n=e.length;ie+t)),this._setElementAttributes(di,"paddingRight",(e=>e+t)),this._setElementAttributes(ui,"marginRight",(e=>e-t))}_disableOverFlow(){this._saveInitialAttribute(this._element,"overflow"),this._element.style.overflow="hidden"}_setElementAttributes(t,e,i){const n=this.getWidth();this._applyManipulationCallback(t,(t=>{if(t!==this._element&&window.innerWidth>t.clientWidth+n)return;this._saveInitialAttribute(t,e);const s=window.getComputedStyle(t)[e];t.style[e]=`${i(Number.parseFloat(s))}px`}))}reset(){this._resetElementAttributes(this._element,"overflow"),this._resetElementAttributes(this._element,"paddingRight"),this._resetElementAttributes(di,"paddingRight"),this._resetElementAttributes(ui,"marginRight")}_saveInitialAttribute(t,e){const i=t.style[e];i&&U.setDataAttribute(t,e,i)}_resetElementAttributes(t,e){this._applyManipulationCallback(t,(t=>{const i=U.getDataAttribute(t,e);void 0===i?t.style.removeProperty(e):(U.removeDataAttribute(t,e),t.style[e]=i)}))}_applyManipulationCallback(t,e){o(t)?e(t):V.find(t,this._element).forEach(e)}isOverflowing(){return this.getWidth()>0}}const pi={className:"modal-backdrop",isVisible:!0,isAnimated:!1,rootElement:"body",clickCallback:null},mi={className:"string",isVisible:"boolean",isAnimated:"boolean",rootElement:"(element|string)",clickCallback:"(function|null)"},gi="show",_i="mousedown.bs.backdrop";class bi{constructor(t){this._config=this._getConfig(t),this._isAppended=!1,this._element=null}show(t){this._config.isVisible?(this._append(),this._config.isAnimated&&u(this._getElement()),this._getElement().classList.add(gi),this._emulateAnimation((()=>{_(t)}))):_(t)}hide(t){this._config.isVisible?(this._getElement().classList.remove(gi),this._emulateAnimation((()=>{this.dispose(),_(t)}))):_(t)}_getElement(){if(!this._element){const t=document.createElement("div");t.className=this._config.className,this._config.isAnimated&&t.classList.add("fade"),this._element=t}return this._element}_getConfig(t){return(t={...pi,..."object"==typeof t?t:{}}).rootElement=r(t.rootElement),a("backdrop",t,mi),t}_append(){this._isAppended||(this._config.rootElement.append(this._getElement()),j.on(this._getElement(),_i,(()=>{_(this._config.clickCallback)})),this._isAppended=!0)}dispose(){this._isAppended&&(j.off(this._element,_i),this._element.remove(),this._isAppended=!1)}_emulateAnimation(t){b(t,this._getElement(),this._config.isAnimated)}}const vi={trapElement:null,autofocus:!0},yi={trapElement:"element",autofocus:"boolean"},wi=".bs.focustrap",Ei="backward";class Ai{constructor(t){this._config=this._getConfig(t),this._isActive=!1,this._lastTabNavDirection=null}activate(){const{trapElement:t,autofocus:e}=this._config;this._isActive||(e&&t.focus(),j.off(document,wi),j.on(document,"focusin.bs.focustrap",(t=>this._handleFocusin(t))),j.on(document,"keydown.tab.bs.focustrap",(t=>this._handleKeydown(t))),this._isActive=!0)}deactivate(){this._isActive&&(this._isActive=!1,j.off(document,wi))}_handleFocusin(t){const{target:e}=t,{trapElement:i}=this._config;if(e===document||e===i||i.contains(e))return;const n=V.focusableChildren(i);0===n.length?i.focus():this._lastTabNavDirection===Ei?n[n.length-1].focus():n[0].focus()}_handleKeydown(t){"Tab"===t.key&&(this._lastTabNavDirection=t.shiftKey?Ei:"forward")}_getConfig(t){return t={...vi,..."object"==typeof t?t:{}},a("focustrap",t,yi),t}}const Ti="modal",Oi="Escape",Ci={backdrop:!0,keyboard:!0,focus:!0},ki={backdrop:"(boolean|string)",keyboard:"boolean",focus:"boolean"},Li="hidden.bs.modal",xi="show.bs.modal",Di="resize.bs.modal",Si="click.dismiss.bs.modal",Ni="keydown.dismiss.bs.modal",Ii="mousedown.dismiss.bs.modal",Pi="modal-open",ji="show",Mi="modal-static";class Hi extends B{constructor(t,e){super(t),this._config=this._getConfig(e),this._dialog=V.findOne(".modal-dialog",this._element),this._backdrop=this._initializeBackDrop(),this._focustrap=this._initializeFocusTrap(),this._isShown=!1,this._ignoreBackdropClick=!1,this._isTransitioning=!1,this._scrollBar=new fi}static get Default(){return Ci}static get NAME(){return Ti}toggle(t){return this._isShown?this.hide():this.show(t)}show(t){this._isShown||this._isTransitioning||j.trigger(this._element,xi,{relatedTarget:t}).defaultPrevented||(this._isShown=!0,this._isAnimated()&&(this._isTransitioning=!0),this._scrollBar.hide(),document.body.classList.add(Pi),this._adjustDialog(),this._setEscapeEvent(),this._setResizeEvent(),j.on(this._dialog,Ii,(()=>{j.one(this._element,"mouseup.dismiss.bs.modal",(t=>{t.target===this._element&&(this._ignoreBackdropClick=!0)}))})),this._showBackdrop((()=>this._showElement(t))))}hide(){if(!this._isShown||this._isTransitioning)return;if(j.trigger(this._element,"hide.bs.modal").defaultPrevented)return;this._isShown=!1;const t=this._isAnimated();t&&(this._isTransitioning=!0),this._setEscapeEvent(),this._setResizeEvent(),this._focustrap.deactivate(),this._element.classList.remove(ji),j.off(this._element,Si),j.off(this._dialog,Ii),this._queueCallback((()=>this._hideModal()),this._element,t)}dispose(){[window,this._dialog].forEach((t=>j.off(t,".bs.modal"))),this._backdrop.dispose(),this._focustrap.deactivate(),super.dispose()}handleUpdate(){this._adjustDialog()}_initializeBackDrop(){return new bi({isVisible:Boolean(this._config.backdrop),isAnimated:this._isAnimated()})}_initializeFocusTrap(){return new Ai({trapElement:this._element})}_getConfig(t){return t={...Ci,...U.getDataAttributes(this._element),..."object"==typeof t?t:{}},a(Ti,t,ki),t}_showElement(t){const e=this._isAnimated(),i=V.findOne(".modal-body",this._dialog);this._element.parentNode&&this._element.parentNode.nodeType===Node.ELEMENT_NODE||document.body.append(this._element),this._element.style.display="block",this._element.removeAttribute("aria-hidden"),this._element.setAttribute("aria-modal",!0),this._element.setAttribute("role","dialog"),this._element.scrollTop=0,i&&(i.scrollTop=0),e&&u(this._element),this._element.classList.add(ji),this._queueCallback((()=>{this._config.focus&&this._focustrap.activate(),this._isTransitioning=!1,j.trigger(this._element,"shown.bs.modal",{relatedTarget:t})}),this._dialog,e)}_setEscapeEvent(){this._isShown?j.on(this._element,Ni,(t=>{this._config.keyboard&&t.key===Oi?(t.preventDefault(),this.hide()):this._config.keyboard||t.key!==Oi||this._triggerBackdropTransition()})):j.off(this._element,Ni)}_setResizeEvent(){this._isShown?j.on(window,Di,(()=>this._adjustDialog())):j.off(window,Di)}_hideModal(){this._element.style.display="none",this._element.setAttribute("aria-hidden",!0),this._element.removeAttribute("aria-modal"),this._element.removeAttribute("role"),this._isTransitioning=!1,this._backdrop.hide((()=>{document.body.classList.remove(Pi),this._resetAdjustments(),this._scrollBar.reset(),j.trigger(this._element,Li)}))}_showBackdrop(t){j.on(this._element,Si,(t=>{this._ignoreBackdropClick?this._ignoreBackdropClick=!1:t.target===t.currentTarget&&(!0===this._config.backdrop?this.hide():"static"===this._config.backdrop&&this._triggerBackdropTransition())})),this._backdrop.show(t)}_isAnimated(){return this._element.classList.contains("fade")}_triggerBackdropTransition(){if(j.trigger(this._element,"hidePrevented.bs.modal").defaultPrevented)return;const{classList:t,scrollHeight:e,style:i}=this._element,n=e>document.documentElement.clientHeight;!n&&"hidden"===i.overflowY||t.contains(Mi)||(n||(i.overflowY="hidden"),t.add(Mi),this._queueCallback((()=>{t.remove(Mi),n||this._queueCallback((()=>{i.overflowY=""}),this._dialog)}),this._dialog),this._element.focus())}_adjustDialog(){const t=this._element.scrollHeight>document.documentElement.clientHeight,e=this._scrollBar.getWidth(),i=e>0;(!i&&t&&!m()||i&&!t&&m())&&(this._element.style.paddingLeft=`${e}px`),(i&&!t&&!m()||!i&&t&&m())&&(this._element.style.paddingRight=`${e}px`)}_resetAdjustments(){this._element.style.paddingLeft="",this._element.style.paddingRight=""}static jQueryInterface(t,e){return this.each((function(){const i=Hi.getOrCreateInstance(this,t);if("string"==typeof t){if(void 0===i[t])throw new TypeError(`No method named "${t}"`);i[t](e)}}))}}j.on(document,"click.bs.modal.data-api",'[data-bs-toggle="modal"]',(function(t){const e=n(this);["A","AREA"].includes(this.tagName)&&t.preventDefault(),j.one(e,xi,(t=>{t.defaultPrevented||j.one(e,Li,(()=>{l(this)&&this.focus()}))}));const i=V.findOne(".modal.show");i&&Hi.getInstance(i).hide(),Hi.getOrCreateInstance(e).toggle(this)})),R(Hi),g(Hi);const Bi="offcanvas",Ri={backdrop:!0,keyboard:!0,scroll:!1},Wi={backdrop:"boolean",keyboard:"boolean",scroll:"boolean"},$i="show",zi=".offcanvas.show",qi="hidden.bs.offcanvas";class Fi extends B{constructor(t,e){super(t),this._config=this._getConfig(e),this._isShown=!1,this._backdrop=this._initializeBackDrop(),this._focustrap=this._initializeFocusTrap(),this._addEventListeners()}static get NAME(){return Bi}static get Default(){return Ri}toggle(t){return this._isShown?this.hide():this.show(t)}show(t){this._isShown||j.trigger(this._element,"show.bs.offcanvas",{relatedTarget:t}).defaultPrevented||(this._isShown=!0,this._element.style.visibility="visible",this._backdrop.show(),this._config.scroll||(new fi).hide(),this._element.removeAttribute("aria-hidden"),this._element.setAttribute("aria-modal",!0),this._element.setAttribute("role","dialog"),this._element.classList.add($i),this._queueCallback((()=>{this._config.scroll||this._focustrap.activate(),j.trigger(this._element,"shown.bs.offcanvas",{relatedTarget:t})}),this._element,!0))}hide(){this._isShown&&(j.trigger(this._element,"hide.bs.offcanvas").defaultPrevented||(this._focustrap.deactivate(),this._element.blur(),this._isShown=!1,this._element.classList.remove($i),this._backdrop.hide(),this._queueCallback((()=>{this._element.setAttribute("aria-hidden",!0),this._element.removeAttribute("aria-modal"),this._element.removeAttribute("role"),this._element.style.visibility="hidden",this._config.scroll||(new fi).reset(),j.trigger(this._element,qi)}),this._element,!0)))}dispose(){this._backdrop.dispose(),this._focustrap.deactivate(),super.dispose()}_getConfig(t){return t={...Ri,...U.getDataAttributes(this._element),..."object"==typeof t?t:{}},a(Bi,t,Wi),t}_initializeBackDrop(){return new bi({className:"offcanvas-backdrop",isVisible:this._config.backdrop,isAnimated:!0,rootElement:this._element.parentNode,clickCallback:()=>this.hide()})}_initializeFocusTrap(){return new Ai({trapElement:this._element})}_addEventListeners(){j.on(this._element,"keydown.dismiss.bs.offcanvas",(t=>{this._config.keyboard&&"Escape"===t.key&&this.hide()}))}static jQueryInterface(t){return this.each((function(){const e=Fi.getOrCreateInstance(this,t);if("string"==typeof t){if(void 0===e[t]||t.startsWith("_")||"constructor"===t)throw new TypeError(`No method named "${t}"`);e[t](this)}}))}}j.on(document,"click.bs.offcanvas.data-api",'[data-bs-toggle="offcanvas"]',(function(t){const e=n(this);if(["A","AREA"].includes(this.tagName)&&t.preventDefault(),c(this))return;j.one(e,qi,(()=>{l(this)&&this.focus()}));const i=V.findOne(zi);i&&i!==e&&Fi.getInstance(i).hide(),Fi.getOrCreateInstance(e).toggle(this)})),j.on(window,"load.bs.offcanvas.data-api",(()=>V.find(zi).forEach((t=>Fi.getOrCreateInstance(t).show())))),R(Fi),g(Fi);const Ui=new Set(["background","cite","href","itemtype","longdesc","poster","src","xlink:href"]),Vi=/^(?:(?:https?|mailto|ftp|tel|file|sms):|[^#&/:?]*(?:[#/?]|$))/i,Ki=/^data:(?:image\/(?:bmp|gif|jpeg|jpg|png|tiff|webp)|video\/(?:mpeg|mp4|ogg|webm)|audio\/(?:mp3|oga|ogg|opus));base64,[\d+/a-z]+=*$/i,Xi=(t,e)=>{const i=t.nodeName.toLowerCase();if(e.includes(i))return!Ui.has(i)||Boolean(Vi.test(t.nodeValue)||Ki.test(t.nodeValue));const n=e.filter((t=>t instanceof RegExp));for(let t=0,e=n.length;t{Xi(t,r)||i.removeAttribute(t.nodeName)}))}return n.body.innerHTML}const Qi="tooltip",Gi=new Set(["sanitize","allowList","sanitizeFn"]),Zi={animation:"boolean",template:"string",title:"(string|element|function)",trigger:"string",delay:"(number|object)",html:"boolean",selector:"(string|boolean)",placement:"(string|function)",offset:"(array|string|function)",container:"(string|element|boolean)",fallbackPlacements:"array",boundary:"(string|element)",customClass:"(string|function)",sanitize:"boolean",sanitizeFn:"(null|function)",allowList:"object",popperConfig:"(null|object|function)"},Ji={AUTO:"auto",TOP:"top",RIGHT:m()?"left":"right",BOTTOM:"bottom",LEFT:m()?"right":"left"},tn={animation:!0,template:'',trigger:"hover focus",title:"",delay:0,html:!1,selector:!1,placement:"top",offset:[0,0],container:!1,fallbackPlacements:["top","right","bottom","left"],boundary:"clippingParents",customClass:"",sanitize:!0,sanitizeFn:null,allowList:{"*":["class","dir","id","lang","role",/^aria-[\w-]*$/i],a:["target","href","title","rel"],area:[],b:[],br:[],col:[],code:[],div:[],em:[],hr:[],h1:[],h2:[],h3:[],h4:[],h5:[],h6:[],i:[],img:["src","srcset","alt","title","width","height"],li:[],ol:[],p:[],pre:[],s:[],small:[],span:[],sub:[],sup:[],strong:[],u:[],ul:[]},popperConfig:null},en={HIDE:"hide.bs.tooltip",HIDDEN:"hidden.bs.tooltip",SHOW:"show.bs.tooltip",SHOWN:"shown.bs.tooltip",INSERTED:"inserted.bs.tooltip",CLICK:"click.bs.tooltip",FOCUSIN:"focusin.bs.tooltip",FOCUSOUT:"focusout.bs.tooltip",MOUSEENTER:"mouseenter.bs.tooltip",MOUSELEAVE:"mouseleave.bs.tooltip"},nn="fade",sn="show",on="show",rn="out",an=".tooltip-inner",ln=".modal",cn="hide.bs.modal",hn="hover",dn="focus";class un extends B{constructor(t,e){if(void 0===Fe)throw new TypeError("Bootstrap's tooltips require Popper (https://popper.js.org)");super(t),this._isEnabled=!0,this._timeout=0,this._hoverState="",this._activeTrigger={},this._popper=null,this._config=this._getConfig(e),this.tip=null,this._setListeners()}static get Default(){return tn}static get NAME(){return Qi}static get Event(){return en}static get DefaultType(){return Zi}enable(){this._isEnabled=!0}disable(){this._isEnabled=!1}toggleEnabled(){this._isEnabled=!this._isEnabled}toggle(t){if(this._isEnabled)if(t){const e=this._initializeOnDelegatedTarget(t);e._activeTrigger.click=!e._activeTrigger.click,e._isWithActiveTrigger()?e._enter(null,e):e._leave(null,e)}else{if(this.getTipElement().classList.contains(sn))return void this._leave(null,this);this._enter(null,this)}}dispose(){clearTimeout(this._timeout),j.off(this._element.closest(ln),cn,this._hideModalHandler),this.tip&&this.tip.remove(),this._disposePopper(),super.dispose()}show(){if("none"===this._element.style.display)throw new Error("Please use show on visible elements");if(!this.isWithContent()||!this._isEnabled)return;const t=j.trigger(this._element,this.constructor.Event.SHOW),e=h(this._element),i=null===e?this._element.ownerDocument.documentElement.contains(this._element):e.contains(this._element);if(t.defaultPrevented||!i)return;"tooltip"===this.constructor.NAME&&this.tip&&this.getTitle()!==this.tip.querySelector(an).innerHTML&&(this._disposePopper(),this.tip.remove(),this.tip=null);const n=this.getTipElement(),s=(t=>{do{t+=Math.floor(1e6*Math.random())}while(document.getElementById(t));return t})(this.constructor.NAME);n.setAttribute("id",s),this._element.setAttribute("aria-describedby",s),this._config.animation&&n.classList.add(nn);const o="function"==typeof this._config.placement?this._config.placement.call(this,n,this._element):this._config.placement,r=this._getAttachment(o);this._addAttachmentClass(r);const{container:a}=this._config;H.set(n,this.constructor.DATA_KEY,this),this._element.ownerDocument.documentElement.contains(this.tip)||(a.append(n),j.trigger(this._element,this.constructor.Event.INSERTED)),this._popper?this._popper.update():this._popper=qe(this._element,n,this._getPopperConfig(r)),n.classList.add(sn);const l=this._resolvePossibleFunction(this._config.customClass);l&&n.classList.add(...l.split(" ")),"ontouchstart"in document.documentElement&&[].concat(...document.body.children).forEach((t=>{j.on(t,"mouseover",d)}));const c=this.tip.classList.contains(nn);this._queueCallback((()=>{const t=this._hoverState;this._hoverState=null,j.trigger(this._element,this.constructor.Event.SHOWN),t===rn&&this._leave(null,this)}),this.tip,c)}hide(){if(!this._popper)return;const t=this.getTipElement();if(j.trigger(this._element,this.constructor.Event.HIDE).defaultPrevented)return;t.classList.remove(sn),"ontouchstart"in document.documentElement&&[].concat(...document.body.children).forEach((t=>j.off(t,"mouseover",d))),this._activeTrigger.click=!1,this._activeTrigger.focus=!1,this._activeTrigger.hover=!1;const e=this.tip.classList.contains(nn);this._queueCallback((()=>{this._isWithActiveTrigger()||(this._hoverState!==on&&t.remove(),this._cleanTipClass(),this._element.removeAttribute("aria-describedby"),j.trigger(this._element,this.constructor.Event.HIDDEN),this._disposePopper())}),this.tip,e),this._hoverState=""}update(){null!==this._popper&&this._popper.update()}isWithContent(){return Boolean(this.getTitle())}getTipElement(){if(this.tip)return this.tip;const t=document.createElement("div");t.innerHTML=this._config.template;const e=t.children[0];return this.setContent(e),e.classList.remove(nn,sn),this.tip=e,this.tip}setContent(t){this._sanitizeAndSetContent(t,this.getTitle(),an)}_sanitizeAndSetContent(t,e,i){const n=V.findOne(i,t);e||!n?this.setElementContent(n,e):n.remove()}setElementContent(t,e){if(null!==t)return o(e)?(e=r(e),void(this._config.html?e.parentNode!==t&&(t.innerHTML="",t.append(e)):t.textContent=e.textContent)):void(this._config.html?(this._config.sanitize&&(e=Yi(e,this._config.allowList,this._config.sanitizeFn)),t.innerHTML=e):t.textContent=e)}getTitle(){const t=this._element.getAttribute("data-bs-original-title")||this._config.title;return this._resolvePossibleFunction(t)}updateAttachment(t){return"right"===t?"end":"left"===t?"start":t}_initializeOnDelegatedTarget(t,e){return e||this.constructor.getOrCreateInstance(t.delegateTarget,this._getDelegateConfig())}_getOffset(){const{offset:t}=this._config;return"string"==typeof t?t.split(",").map((t=>Number.parseInt(t,10))):"function"==typeof t?e=>t(e,this._element):t}_resolvePossibleFunction(t){return"function"==typeof t?t.call(this._element):t}_getPopperConfig(t){const e={placement:t,modifiers:[{name:"flip",options:{fallbackPlacements:this._config.fallbackPlacements}},{name:"offset",options:{offset:this._getOffset()}},{name:"preventOverflow",options:{boundary:this._config.boundary}},{name:"arrow",options:{element:`.${this.constructor.NAME}-arrow`}},{name:"onChange",enabled:!0,phase:"afterWrite",fn:t=>this._handlePopperPlacementChange(t)}],onFirstUpdate:t=>{t.options.placement!==t.placement&&this._handlePopperPlacementChange(t)}};return{...e,..."function"==typeof this._config.popperConfig?this._config.popperConfig(e):this._config.popperConfig}}_addAttachmentClass(t){this.getTipElement().classList.add(`${this._getBasicClassPrefix()}-${this.updateAttachment(t)}`)}_getAttachment(t){return Ji[t.toUpperCase()]}_setListeners(){this._config.trigger.split(" ").forEach((t=>{if("click"===t)j.on(this._element,this.constructor.Event.CLICK,this._config.selector,(t=>this.toggle(t)));else if("manual"!==t){const e=t===hn?this.constructor.Event.MOUSEENTER:this.constructor.Event.FOCUSIN,i=t===hn?this.constructor.Event.MOUSELEAVE:this.constructor.Event.FOCUSOUT;j.on(this._element,e,this._config.selector,(t=>this._enter(t))),j.on(this._element,i,this._config.selector,(t=>this._leave(t)))}})),this._hideModalHandler=()=>{this._element&&this.hide()},j.on(this._element.closest(ln),cn,this._hideModalHandler),this._config.selector?this._config={...this._config,trigger:"manual",selector:""}:this._fixTitle()}_fixTitle(){const t=this._element.getAttribute("title"),e=typeof this._element.getAttribute("data-bs-original-title");(t||"string"!==e)&&(this._element.setAttribute("data-bs-original-title",t||""),!t||this._element.getAttribute("aria-label")||this._element.textContent||this._element.setAttribute("aria-label",t),this._element.setAttribute("title",""))}_enter(t,e){e=this._initializeOnDelegatedTarget(t,e),t&&(e._activeTrigger["focusin"===t.type?dn:hn]=!0),e.getTipElement().classList.contains(sn)||e._hoverState===on?e._hoverState=on:(clearTimeout(e._timeout),e._hoverState=on,e._config.delay&&e._config.delay.show?e._timeout=setTimeout((()=>{e._hoverState===on&&e.show()}),e._config.delay.show):e.show())}_leave(t,e){e=this._initializeOnDelegatedTarget(t,e),t&&(e._activeTrigger["focusout"===t.type?dn:hn]=e._element.contains(t.relatedTarget)),e._isWithActiveTrigger()||(clearTimeout(e._timeout),e._hoverState=rn,e._config.delay&&e._config.delay.hide?e._timeout=setTimeout((()=>{e._hoverState===rn&&e.hide()}),e._config.delay.hide):e.hide())}_isWithActiveTrigger(){for(const t in this._activeTrigger)if(this._activeTrigger[t])return!0;return!1}_getConfig(t){const e=U.getDataAttributes(this._element);return Object.keys(e).forEach((t=>{Gi.has(t)&&delete e[t]})),(t={...this.constructor.Default,...e,..."object"==typeof t&&t?t:{}}).container=!1===t.container?document.body:r(t.container),"number"==typeof t.delay&&(t.delay={show:t.delay,hide:t.delay}),"number"==typeof t.title&&(t.title=t.title.toString()),"number"==typeof t.content&&(t.content=t.content.toString()),a(Qi,t,this.constructor.DefaultType),t.sanitize&&(t.template=Yi(t.template,t.allowList,t.sanitizeFn)),t}_getDelegateConfig(){const t={};for(const e in this._config)this.constructor.Default[e]!==this._config[e]&&(t[e]=this._config[e]);return t}_cleanTipClass(){const t=this.getTipElement(),e=new RegExp(`(^|\\s)${this._getBasicClassPrefix()}\\S+`,"g"),i=t.getAttribute("class").match(e);null!==i&&i.length>0&&i.map((t=>t.trim())).forEach((e=>t.classList.remove(e)))}_getBasicClassPrefix(){return"bs-tooltip"}_handlePopperPlacementChange(t){const{state:e}=t;e&&(this.tip=e.elements.popper,this._cleanTipClass(),this._addAttachmentClass(this._getAttachment(e.placement)))}_disposePopper(){this._popper&&(this._popper.destroy(),this._popper=null)}static jQueryInterface(t){return this.each((function(){const e=un.getOrCreateInstance(this,t);if("string"==typeof t){if(void 0===e[t])throw new TypeError(`No method named "${t}"`);e[t]()}}))}}g(un);const fn={...un.Default,placement:"right",offset:[0,8],trigger:"click",content:"",template:''},pn={...un.DefaultType,content:"(string|element|function)"},mn={HIDE:"hide.bs.popover",HIDDEN:"hidden.bs.popover",SHOW:"show.bs.popover",SHOWN:"shown.bs.popover",INSERTED:"inserted.bs.popover",CLICK:"click.bs.popover",FOCUSIN:"focusin.bs.popover",FOCUSOUT:"focusout.bs.popover",MOUSEENTER:"mouseenter.bs.popover",MOUSELEAVE:"mouseleave.bs.popover"};class gn extends un{static get Default(){return fn}static get NAME(){return"popover"}static get Event(){return mn}static get DefaultType(){return pn}isWithContent(){return this.getTitle()||this._getContent()}setContent(t){this._sanitizeAndSetContent(t,this.getTitle(),".popover-header"),this._sanitizeAndSetContent(t,this._getContent(),".popover-body")}_getContent(){return this._resolvePossibleFunction(this._config.content)}_getBasicClassPrefix(){return"bs-popover"}static jQueryInterface(t){return this.each((function(){const e=gn.getOrCreateInstance(this,t);if("string"==typeof t){if(void 0===e[t])throw new TypeError(`No method named "${t}"`);e[t]()}}))}}g(gn);const _n="scrollspy",bn={offset:10,method:"auto",target:""},vn={offset:"number",method:"string",target:"(string|element)"},yn="active",wn=".nav-link, .list-group-item, .dropdown-item",En="position";class An extends B{constructor(t,e){super(t),this._scrollElement="BODY"===this._element.tagName?window:this._element,this._config=this._getConfig(e),this._offsets=[],this._targets=[],this._activeTarget=null,this._scrollHeight=0,j.on(this._scrollElement,"scroll.bs.scrollspy",(()=>this._process())),this.refresh(),this._process()}static get Default(){return bn}static get NAME(){return _n}refresh(){const t=this._scrollElement===this._scrollElement.window?"offset":En,e="auto"===this._config.method?t:this._config.method,n=e===En?this._getScrollTop():0;this._offsets=[],this._targets=[],this._scrollHeight=this._getScrollHeight(),V.find(wn,this._config.target).map((t=>{const s=i(t),o=s?V.findOne(s):null;if(o){const t=o.getBoundingClientRect();if(t.width||t.height)return[U[e](o).top+n,s]}return null})).filter((t=>t)).sort(((t,e)=>t[0]-e[0])).forEach((t=>{this._offsets.push(t[0]),this._targets.push(t[1])}))}dispose(){j.off(this._scrollElement,".bs.scrollspy"),super.dispose()}_getConfig(t){return(t={...bn,...U.getDataAttributes(this._element),..."object"==typeof t&&t?t:{}}).target=r(t.target)||document.documentElement,a(_n,t,vn),t}_getScrollTop(){return this._scrollElement===window?this._scrollElement.pageYOffset:this._scrollElement.scrollTop}_getScrollHeight(){return this._scrollElement.scrollHeight||Math.max(document.body.scrollHeight,document.documentElement.scrollHeight)}_getOffsetHeight(){return this._scrollElement===window?window.innerHeight:this._scrollElement.getBoundingClientRect().height}_process(){const t=this._getScrollTop()+this._config.offset,e=this._getScrollHeight(),i=this._config.offset+e-this._getOffsetHeight();if(this._scrollHeight!==e&&this.refresh(),t>=i){const t=this._targets[this._targets.length-1];this._activeTarget!==t&&this._activate(t)}else{if(this._activeTarget&&t0)return this._activeTarget=null,void this._clear();for(let e=this._offsets.length;e--;)this._activeTarget!==this._targets[e]&&t>=this._offsets[e]&&(void 0===this._offsets[e+1]||t`${e}[data-bs-target="${t}"],${e}[href="${t}"]`)),i=V.findOne(e.join(","),this._config.target);i.classList.add(yn),i.classList.contains("dropdown-item")?V.findOne(".dropdown-toggle",i.closest(".dropdown")).classList.add(yn):V.parents(i,".nav, .list-group").forEach((t=>{V.prev(t,".nav-link, .list-group-item").forEach((t=>t.classList.add(yn))),V.prev(t,".nav-item").forEach((t=>{V.children(t,".nav-link").forEach((t=>t.classList.add(yn)))}))})),j.trigger(this._scrollElement,"activate.bs.scrollspy",{relatedTarget:t})}_clear(){V.find(wn,this._config.target).filter((t=>t.classList.contains(yn))).forEach((t=>t.classList.remove(yn)))}static jQueryInterface(t){return this.each((function(){const e=An.getOrCreateInstance(this,t);if("string"==typeof t){if(void 0===e[t])throw new TypeError(`No method named "${t}"`);e[t]()}}))}}j.on(window,"load.bs.scrollspy.data-api",(()=>{V.find('[data-bs-spy="scroll"]').forEach((t=>new An(t)))})),g(An);const Tn="active",On="fade",Cn="show",kn=".active",Ln=":scope > li > .active";class xn extends B{static get NAME(){return"tab"}show(){if(this._element.parentNode&&this._element.parentNode.nodeType===Node.ELEMENT_NODE&&this._element.classList.contains(Tn))return;let t;const e=n(this._element),i=this._element.closest(".nav, .list-group");if(i){const e="UL"===i.nodeName||"OL"===i.nodeName?Ln:kn;t=V.find(e,i),t=t[t.length-1]}const s=t?j.trigger(t,"hide.bs.tab",{relatedTarget:this._element}):null;if(j.trigger(this._element,"show.bs.tab",{relatedTarget:t}).defaultPrevented||null!==s&&s.defaultPrevented)return;this._activate(this._element,i);const o=()=>{j.trigger(t,"hidden.bs.tab",{relatedTarget:this._element}),j.trigger(this._element,"shown.bs.tab",{relatedTarget:t})};e?this._activate(e,e.parentNode,o):o()}_activate(t,e,i){const n=(!e||"UL"!==e.nodeName&&"OL"!==e.nodeName?V.children(e,kn):V.find(Ln,e))[0],s=i&&n&&n.classList.contains(On),o=()=>this._transitionComplete(t,n,i);n&&s?(n.classList.remove(Cn),this._queueCallback(o,t,!0)):o()}_transitionComplete(t,e,i){if(e){e.classList.remove(Tn);const t=V.findOne(":scope > .dropdown-menu .active",e.parentNode);t&&t.classList.remove(Tn),"tab"===e.getAttribute("role")&&e.setAttribute("aria-selected",!1)}t.classList.add(Tn),"tab"===t.getAttribute("role")&&t.setAttribute("aria-selected",!0),u(t),t.classList.contains(On)&&t.classList.add(Cn);let n=t.parentNode;if(n&&"LI"===n.nodeName&&(n=n.parentNode),n&&n.classList.contains("dropdown-menu")){const e=t.closest(".dropdown");e&&V.find(".dropdown-toggle",e).forEach((t=>t.classList.add(Tn))),t.setAttribute("aria-expanded",!0)}i&&i()}static jQueryInterface(t){return this.each((function(){const e=xn.getOrCreateInstance(this);if("string"==typeof t){if(void 0===e[t])throw new TypeError(`No method named "${t}"`);e[t]()}}))}}j.on(document,"click.bs.tab.data-api",'[data-bs-toggle="tab"], [data-bs-toggle="pill"], [data-bs-toggle="list"]',(function(t){["A","AREA"].includes(this.tagName)&&t.preventDefault(),c(this)||xn.getOrCreateInstance(this).show()})),g(xn);const Dn="toast",Sn="hide",Nn="show",In="showing",Pn={animation:"boolean",autohide:"boolean",delay:"number"},jn={animation:!0,autohide:!0,delay:5e3};class Mn extends B{constructor(t,e){super(t),this._config=this._getConfig(e),this._timeout=null,this._hasMouseInteraction=!1,this._hasKeyboardInteraction=!1,this._setListeners()}static get DefaultType(){return Pn}static get Default(){return jn}static get NAME(){return Dn}show(){j.trigger(this._element,"show.bs.toast").defaultPrevented||(this._clearTimeout(),this._config.animation&&this._element.classList.add("fade"),this._element.classList.remove(Sn),u(this._element),this._element.classList.add(Nn),this._element.classList.add(In),this._queueCallback((()=>{this._element.classList.remove(In),j.trigger(this._element,"shown.bs.toast"),this._maybeScheduleHide()}),this._element,this._config.animation))}hide(){this._element.classList.contains(Nn)&&(j.trigger(this._element,"hide.bs.toast").defaultPrevented||(this._element.classList.add(In),this._queueCallback((()=>{this._element.classList.add(Sn),this._element.classList.remove(In),this._element.classList.remove(Nn),j.trigger(this._element,"hidden.bs.toast")}),this._element,this._config.animation)))}dispose(){this._clearTimeout(),this._element.classList.contains(Nn)&&this._element.classList.remove(Nn),super.dispose()}_getConfig(t){return t={...jn,...U.getDataAttributes(this._element),..."object"==typeof t&&t?t:{}},a(Dn,t,this.constructor.DefaultType),t}_maybeScheduleHide(){this._config.autohide&&(this._hasMouseInteraction||this._hasKeyboardInteraction||(this._timeout=setTimeout((()=>{this.hide()}),this._config.delay)))}_onInteraction(t,e){switch(t.type){case"mouseover":case"mouseout":this._hasMouseInteraction=e;break;case"focusin":case"focusout":this._hasKeyboardInteraction=e}if(e)return void this._clearTimeout();const i=t.relatedTarget;this._element===i||this._element.contains(i)||this._maybeScheduleHide()}_setListeners(){j.on(this._element,"mouseover.bs.toast",(t=>this._onInteraction(t,!0))),j.on(this._element,"mouseout.bs.toast",(t=>this._onInteraction(t,!1))),j.on(this._element,"focusin.bs.toast",(t=>this._onInteraction(t,!0))),j.on(this._element,"focusout.bs.toast",(t=>this._onInteraction(t,!1)))}_clearTimeout(){clearTimeout(this._timeout),this._timeout=null}static jQueryInterface(t){return this.each((function(){const e=Mn.getOrCreateInstance(this,t);if("string"==typeof t){if(void 0===e[t])throw new TypeError(`No method named "${t}"`);e[t](this)}}))}}return R(Mn),g(Mn),{Alert:W,Button:z,Carousel:st,Collapse:pt,Dropdown:hi,Modal:Hi,Offcanvas:Fi,Popover:gn,ScrollSpy:An,Tab:xn,Toast:Mn,Tooltip:un}})); +//# sourceMappingURL=bootstrap.bundle.min.js.map \ No newline at end of file diff --git a/site_libs/clipboard/clipboard.min.js b/site_libs/clipboard/clipboard.min.js new file mode 100644 index 00000000..1103f811 --- /dev/null +++ b/site_libs/clipboard/clipboard.min.js @@ -0,0 +1,7 @@ +/*! + * clipboard.js v2.0.11 + * https://clipboardjs.com/ + * + * Licensed MIT Ā© Zeno Rocha + */ +!function(t,e){"object"==typeof exports&&"object"==typeof module?module.exports=e():"function"==typeof define&&define.amd?define([],e):"object"==typeof exports?exports.ClipboardJS=e():t.ClipboardJS=e()}(this,function(){return n={686:function(t,e,n){"use strict";n.d(e,{default:function(){return b}});var e=n(279),i=n.n(e),e=n(370),u=n.n(e),e=n(817),r=n.n(e);function c(t){try{return document.execCommand(t)}catch(t){return}}var a=function(t){t=r()(t);return c("cut"),t};function o(t,e){var n,o,t=(n=t,o="rtl"===document.documentElement.getAttribute("dir"),(t=document.createElement("textarea")).style.fontSize="12pt",t.style.border="0",t.style.padding="0",t.style.margin="0",t.style.position="absolute",t.style[o?"right":"left"]="-9999px",o=window.pageYOffset||document.documentElement.scrollTop,t.style.top="".concat(o,"px"),t.setAttribute("readonly",""),t.value=n,t);return e.container.appendChild(t),e=r()(t),c("copy"),t.remove(),e}var f=function(t){var e=1.anchorjs-link,.anchorjs-link:focus{opacity:1}",u.sheet.cssRules.length),u.sheet.insertRule("[data-anchorjs-icon]::after{content:attr(data-anchorjs-icon)}",u.sheet.cssRules.length),u.sheet.insertRule('@font-face{font-family:anchorjs-icons;src:url(data:n/a;base64,AAEAAAALAIAAAwAwT1MvMg8yG2cAAAE4AAAAYGNtYXDp3gC3AAABpAAAAExnYXNwAAAAEAAAA9wAAAAIZ2x5ZlQCcfwAAAH4AAABCGhlYWQHFvHyAAAAvAAAADZoaGVhBnACFwAAAPQAAAAkaG10eASAADEAAAGYAAAADGxvY2EACACEAAAB8AAAAAhtYXhwAAYAVwAAARgAAAAgbmFtZQGOH9cAAAMAAAAAunBvc3QAAwAAAAADvAAAACAAAQAAAAEAAHzE2p9fDzz1AAkEAAAAAADRecUWAAAAANQA6R8AAAAAAoACwAAAAAgAAgAAAAAAAAABAAADwP/AAAACgAAA/9MCrQABAAAAAAAAAAAAAAAAAAAAAwABAAAAAwBVAAIAAAAAAAIAAAAAAAAAAAAAAAAAAAAAAAMCQAGQAAUAAAKZAswAAACPApkCzAAAAesAMwEJAAAAAAAAAAAAAAAAAAAAARAAAAAAAAAAAAAAAAAAAAAAQAAg//0DwP/AAEADwABAAAAAAQAAAAAAAAAAAAAAIAAAAAAAAAIAAAACgAAxAAAAAwAAAAMAAAAcAAEAAwAAABwAAwABAAAAHAAEADAAAAAIAAgAAgAAACDpy//9//8AAAAg6cv//f///+EWNwADAAEAAAAAAAAAAAAAAAAACACEAAEAAAAAAAAAAAAAAAAxAAACAAQARAKAAsAAKwBUAAABIiYnJjQ3NzY2MzIWFxYUBwcGIicmNDc3NjQnJiYjIgYHBwYUFxYUBwYGIwciJicmNDc3NjIXFhQHBwYUFxYWMzI2Nzc2NCcmNDc2MhcWFAcHBgYjARQGDAUtLXoWOR8fORYtLTgKGwoKCjgaGg0gEhIgDXoaGgkJBQwHdR85Fi0tOAobCgoKOBoaDSASEiANehoaCQkKGwotLXoWOR8BMwUFLYEuehYXFxYugC44CQkKGwo4GkoaDQ0NDXoaShoKGwoFBe8XFi6ALjgJCQobCjgaShoNDQ0NehpKGgobCgoKLYEuehYXAAAADACWAAEAAAAAAAEACAAAAAEAAAAAAAIAAwAIAAEAAAAAAAMACAAAAAEAAAAAAAQACAAAAAEAAAAAAAUAAQALAAEAAAAAAAYACAAAAAMAAQQJAAEAEAAMAAMAAQQJAAIABgAcAAMAAQQJAAMAEAAMAAMAAQQJAAQAEAAMAAMAAQQJAAUAAgAiAAMAAQQJAAYAEAAMYW5jaG9yanM0MDBAAGEAbgBjAGgAbwByAGoAcwA0ADAAMABAAAAAAwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAH//wAP) format("truetype")}',u.sheet.cssRules.length)),u=document.querySelectorAll("[id]"),t=[].map.call(u,function(A){return A.id}),i=0;i\]./()*\\\n\t\b\v\u00A0]/g,"-").replace(/-{2,}/g,"-").substring(0,this.options.truncate).replace(/^-+|-+$/gm,"").toLowerCase()},this.hasAnchorJSLink=function(A){var e=A.firstChild&&-1<(" "+A.firstChild.className+" ").indexOf(" anchorjs-link "),A=A.lastChild&&-1<(" "+A.lastChild.className+" ").indexOf(" anchorjs-link ");return e||A||!1}}}); +// @license-end \ No newline at end of file diff --git a/site_libs/quarto-html/popper.min.js b/site_libs/quarto-html/popper.min.js new file mode 100644 index 00000000..2269d669 --- /dev/null +++ b/site_libs/quarto-html/popper.min.js @@ -0,0 +1,6 @@ +/** + * @popperjs/core v2.11.4 - MIT License + */ + +!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?t(exports):"function"==typeof define&&define.amd?define(["exports"],t):t((e="undefined"!=typeof globalThis?globalThis:e||self).Popper={})}(this,(function(e){"use strict";function t(e){if(null==e)return window;if("[object Window]"!==e.toString()){var t=e.ownerDocument;return t&&t.defaultView||window}return e}function n(e){return e instanceof t(e).Element||e instanceof Element}function r(e){return e instanceof t(e).HTMLElement||e instanceof HTMLElement}function o(e){return"undefined"!=typeof ShadowRoot&&(e instanceof t(e).ShadowRoot||e instanceof ShadowRoot)}var i=Math.max,a=Math.min,s=Math.round;function f(e,t){void 0===t&&(t=!1);var n=e.getBoundingClientRect(),o=1,i=1;if(r(e)&&t){var a=e.offsetHeight,f=e.offsetWidth;f>0&&(o=s(n.width)/f||1),a>0&&(i=s(n.height)/a||1)}return{width:n.width/o,height:n.height/i,top:n.top/i,right:n.right/o,bottom:n.bottom/i,left:n.left/o,x:n.left/o,y:n.top/i}}function c(e){var n=t(e);return{scrollLeft:n.pageXOffset,scrollTop:n.pageYOffset}}function p(e){return e?(e.nodeName||"").toLowerCase():null}function u(e){return((n(e)?e.ownerDocument:e.document)||window.document).documentElement}function l(e){return f(u(e)).left+c(e).scrollLeft}function d(e){return t(e).getComputedStyle(e)}function h(e){var t=d(e),n=t.overflow,r=t.overflowX,o=t.overflowY;return/auto|scroll|overlay|hidden/.test(n+o+r)}function m(e,n,o){void 0===o&&(o=!1);var i,a,d=r(n),m=r(n)&&function(e){var t=e.getBoundingClientRect(),n=s(t.width)/e.offsetWidth||1,r=s(t.height)/e.offsetHeight||1;return 1!==n||1!==r}(n),v=u(n),g=f(e,m),y={scrollLeft:0,scrollTop:0},b={x:0,y:0};return(d||!d&&!o)&&(("body"!==p(n)||h(v))&&(y=(i=n)!==t(i)&&r(i)?{scrollLeft:(a=i).scrollLeft,scrollTop:a.scrollTop}:c(i)),r(n)?((b=f(n,!0)).x+=n.clientLeft,b.y+=n.clientTop):v&&(b.x=l(v))),{x:g.left+y.scrollLeft-b.x,y:g.top+y.scrollTop-b.y,width:g.width,height:g.height}}function v(e){var t=f(e),n=e.offsetWidth,r=e.offsetHeight;return Math.abs(t.width-n)<=1&&(n=t.width),Math.abs(t.height-r)<=1&&(r=t.height),{x:e.offsetLeft,y:e.offsetTop,width:n,height:r}}function g(e){return"html"===p(e)?e:e.assignedSlot||e.parentNode||(o(e)?e.host:null)||u(e)}function y(e){return["html","body","#document"].indexOf(p(e))>=0?e.ownerDocument.body:r(e)&&h(e)?e:y(g(e))}function b(e,n){var r;void 0===n&&(n=[]);var o=y(e),i=o===(null==(r=e.ownerDocument)?void 0:r.body),a=t(o),s=i?[a].concat(a.visualViewport||[],h(o)?o:[]):o,f=n.concat(s);return i?f:f.concat(b(g(s)))}function x(e){return["table","td","th"].indexOf(p(e))>=0}function w(e){return r(e)&&"fixed"!==d(e).position?e.offsetParent:null}function O(e){for(var n=t(e),i=w(e);i&&x(i)&&"static"===d(i).position;)i=w(i);return i&&("html"===p(i)||"body"===p(i)&&"static"===d(i).position)?n:i||function(e){var t=-1!==navigator.userAgent.toLowerCase().indexOf("firefox");if(-1!==navigator.userAgent.indexOf("Trident")&&r(e)&&"fixed"===d(e).position)return null;var n=g(e);for(o(n)&&(n=n.host);r(n)&&["html","body"].indexOf(p(n))<0;){var i=d(n);if("none"!==i.transform||"none"!==i.perspective||"paint"===i.contain||-1!==["transform","perspective"].indexOf(i.willChange)||t&&"filter"===i.willChange||t&&i.filter&&"none"!==i.filter)return n;n=n.parentNode}return null}(e)||n}var j="top",E="bottom",D="right",A="left",L="auto",P=[j,E,D,A],M="start",k="end",W="viewport",B="popper",H=P.reduce((function(e,t){return e.concat([t+"-"+M,t+"-"+k])}),[]),T=[].concat(P,[L]).reduce((function(e,t){return e.concat([t,t+"-"+M,t+"-"+k])}),[]),R=["beforeRead","read","afterRead","beforeMain","main","afterMain","beforeWrite","write","afterWrite"];function S(e){var t=new Map,n=new Set,r=[];function o(e){n.add(e.name),[].concat(e.requires||[],e.requiresIfExists||[]).forEach((function(e){if(!n.has(e)){var r=t.get(e);r&&o(r)}})),r.push(e)}return e.forEach((function(e){t.set(e.name,e)})),e.forEach((function(e){n.has(e.name)||o(e)})),r}function C(e){return e.split("-")[0]}function q(e,t){var n=t.getRootNode&&t.getRootNode();if(e.contains(t))return!0;if(n&&o(n)){var r=t;do{if(r&&e.isSameNode(r))return!0;r=r.parentNode||r.host}while(r)}return!1}function V(e){return Object.assign({},e,{left:e.x,top:e.y,right:e.x+e.width,bottom:e.y+e.height})}function N(e,r){return r===W?V(function(e){var n=t(e),r=u(e),o=n.visualViewport,i=r.clientWidth,a=r.clientHeight,s=0,f=0;return o&&(i=o.width,a=o.height,/^((?!chrome|android).)*safari/i.test(navigator.userAgent)||(s=o.offsetLeft,f=o.offsetTop)),{width:i,height:a,x:s+l(e),y:f}}(e)):n(r)?function(e){var t=f(e);return t.top=t.top+e.clientTop,t.left=t.left+e.clientLeft,t.bottom=t.top+e.clientHeight,t.right=t.left+e.clientWidth,t.width=e.clientWidth,t.height=e.clientHeight,t.x=t.left,t.y=t.top,t}(r):V(function(e){var t,n=u(e),r=c(e),o=null==(t=e.ownerDocument)?void 0:t.body,a=i(n.scrollWidth,n.clientWidth,o?o.scrollWidth:0,o?o.clientWidth:0),s=i(n.scrollHeight,n.clientHeight,o?o.scrollHeight:0,o?o.clientHeight:0),f=-r.scrollLeft+l(e),p=-r.scrollTop;return"rtl"===d(o||n).direction&&(f+=i(n.clientWidth,o?o.clientWidth:0)-a),{width:a,height:s,x:f,y:p}}(u(e)))}function I(e,t,o){var s="clippingParents"===t?function(e){var t=b(g(e)),o=["absolute","fixed"].indexOf(d(e).position)>=0&&r(e)?O(e):e;return n(o)?t.filter((function(e){return n(e)&&q(e,o)&&"body"!==p(e)})):[]}(e):[].concat(t),f=[].concat(s,[o]),c=f[0],u=f.reduce((function(t,n){var r=N(e,n);return t.top=i(r.top,t.top),t.right=a(r.right,t.right),t.bottom=a(r.bottom,t.bottom),t.left=i(r.left,t.left),t}),N(e,c));return u.width=u.right-u.left,u.height=u.bottom-u.top,u.x=u.left,u.y=u.top,u}function _(e){return e.split("-")[1]}function F(e){return["top","bottom"].indexOf(e)>=0?"x":"y"}function U(e){var t,n=e.reference,r=e.element,o=e.placement,i=o?C(o):null,a=o?_(o):null,s=n.x+n.width/2-r.width/2,f=n.y+n.height/2-r.height/2;switch(i){case j:t={x:s,y:n.y-r.height};break;case E:t={x:s,y:n.y+n.height};break;case D:t={x:n.x+n.width,y:f};break;case A:t={x:n.x-r.width,y:f};break;default:t={x:n.x,y:n.y}}var c=i?F(i):null;if(null!=c){var p="y"===c?"height":"width";switch(a){case M:t[c]=t[c]-(n[p]/2-r[p]/2);break;case k:t[c]=t[c]+(n[p]/2-r[p]/2)}}return t}function z(e){return Object.assign({},{top:0,right:0,bottom:0,left:0},e)}function X(e,t){return t.reduce((function(t,n){return t[n]=e,t}),{})}function Y(e,t){void 0===t&&(t={});var r=t,o=r.placement,i=void 0===o?e.placement:o,a=r.boundary,s=void 0===a?"clippingParents":a,c=r.rootBoundary,p=void 0===c?W:c,l=r.elementContext,d=void 0===l?B:l,h=r.altBoundary,m=void 0!==h&&h,v=r.padding,g=void 0===v?0:v,y=z("number"!=typeof g?g:X(g,P)),b=d===B?"reference":B,x=e.rects.popper,w=e.elements[m?b:d],O=I(n(w)?w:w.contextElement||u(e.elements.popper),s,p),A=f(e.elements.reference),L=U({reference:A,element:x,strategy:"absolute",placement:i}),M=V(Object.assign({},x,L)),k=d===B?M:A,H={top:O.top-k.top+y.top,bottom:k.bottom-O.bottom+y.bottom,left:O.left-k.left+y.left,right:k.right-O.right+y.right},T=e.modifiersData.offset;if(d===B&&T){var R=T[i];Object.keys(H).forEach((function(e){var t=[D,E].indexOf(e)>=0?1:-1,n=[j,E].indexOf(e)>=0?"y":"x";H[e]+=R[n]*t}))}return H}var G={placement:"bottom",modifiers:[],strategy:"absolute"};function J(){for(var e=arguments.length,t=new Array(e),n=0;n=0?-1:1,i="function"==typeof n?n(Object.assign({},t,{placement:e})):n,a=i[0],s=i[1];return a=a||0,s=(s||0)*o,[A,D].indexOf(r)>=0?{x:s,y:a}:{x:a,y:s}}(n,t.rects,i),e}),{}),s=a[t.placement],f=s.x,c=s.y;null!=t.modifiersData.popperOffsets&&(t.modifiersData.popperOffsets.x+=f,t.modifiersData.popperOffsets.y+=c),t.modifiersData[r]=a}},ie={left:"right",right:"left",bottom:"top",top:"bottom"};function ae(e){return e.replace(/left|right|bottom|top/g,(function(e){return ie[e]}))}var se={start:"end",end:"start"};function fe(e){return e.replace(/start|end/g,(function(e){return se[e]}))}function ce(e,t){void 0===t&&(t={});var n=t,r=n.placement,o=n.boundary,i=n.rootBoundary,a=n.padding,s=n.flipVariations,f=n.allowedAutoPlacements,c=void 0===f?T:f,p=_(r),u=p?s?H:H.filter((function(e){return _(e)===p})):P,l=u.filter((function(e){return c.indexOf(e)>=0}));0===l.length&&(l=u);var d=l.reduce((function(t,n){return t[n]=Y(e,{placement:n,boundary:o,rootBoundary:i,padding:a})[C(n)],t}),{});return Object.keys(d).sort((function(e,t){return d[e]-d[t]}))}var pe={name:"flip",enabled:!0,phase:"main",fn:function(e){var t=e.state,n=e.options,r=e.name;if(!t.modifiersData[r]._skip){for(var o=n.mainAxis,i=void 0===o||o,a=n.altAxis,s=void 0===a||a,f=n.fallbackPlacements,c=n.padding,p=n.boundary,u=n.rootBoundary,l=n.altBoundary,d=n.flipVariations,h=void 0===d||d,m=n.allowedAutoPlacements,v=t.options.placement,g=C(v),y=f||(g===v||!h?[ae(v)]:function(e){if(C(e)===L)return[];var t=ae(e);return[fe(e),t,fe(t)]}(v)),b=[v].concat(y).reduce((function(e,n){return e.concat(C(n)===L?ce(t,{placement:n,boundary:p,rootBoundary:u,padding:c,flipVariations:h,allowedAutoPlacements:m}):n)}),[]),x=t.rects.reference,w=t.rects.popper,O=new Map,P=!0,k=b[0],W=0;W=0,S=R?"width":"height",q=Y(t,{placement:B,boundary:p,rootBoundary:u,altBoundary:l,padding:c}),V=R?T?D:A:T?E:j;x[S]>w[S]&&(V=ae(V));var N=ae(V),I=[];if(i&&I.push(q[H]<=0),s&&I.push(q[V]<=0,q[N]<=0),I.every((function(e){return e}))){k=B,P=!1;break}O.set(B,I)}if(P)for(var F=function(e){var t=b.find((function(t){var n=O.get(t);if(n)return n.slice(0,e).every((function(e){return e}))}));if(t)return k=t,"break"},U=h?3:1;U>0;U--){if("break"===F(U))break}t.placement!==k&&(t.modifiersData[r]._skip=!0,t.placement=k,t.reset=!0)}},requiresIfExists:["offset"],data:{_skip:!1}};function ue(e,t,n){return i(e,a(t,n))}var le={name:"preventOverflow",enabled:!0,phase:"main",fn:function(e){var t=e.state,n=e.options,r=e.name,o=n.mainAxis,s=void 0===o||o,f=n.altAxis,c=void 0!==f&&f,p=n.boundary,u=n.rootBoundary,l=n.altBoundary,d=n.padding,h=n.tether,m=void 0===h||h,g=n.tetherOffset,y=void 0===g?0:g,b=Y(t,{boundary:p,rootBoundary:u,padding:d,altBoundary:l}),x=C(t.placement),w=_(t.placement),L=!w,P=F(x),k="x"===P?"y":"x",W=t.modifiersData.popperOffsets,B=t.rects.reference,H=t.rects.popper,T="function"==typeof y?y(Object.assign({},t.rects,{placement:t.placement})):y,R="number"==typeof T?{mainAxis:T,altAxis:T}:Object.assign({mainAxis:0,altAxis:0},T),S=t.modifiersData.offset?t.modifiersData.offset[t.placement]:null,q={x:0,y:0};if(W){if(s){var V,N="y"===P?j:A,I="y"===P?E:D,U="y"===P?"height":"width",z=W[P],X=z+b[N],G=z-b[I],J=m?-H[U]/2:0,K=w===M?B[U]:H[U],Q=w===M?-H[U]:-B[U],Z=t.elements.arrow,$=m&&Z?v(Z):{width:0,height:0},ee=t.modifiersData["arrow#persistent"]?t.modifiersData["arrow#persistent"].padding:{top:0,right:0,bottom:0,left:0},te=ee[N],ne=ee[I],re=ue(0,B[U],$[U]),oe=L?B[U]/2-J-re-te-R.mainAxis:K-re-te-R.mainAxis,ie=L?-B[U]/2+J+re+ne+R.mainAxis:Q+re+ne+R.mainAxis,ae=t.elements.arrow&&O(t.elements.arrow),se=ae?"y"===P?ae.clientTop||0:ae.clientLeft||0:0,fe=null!=(V=null==S?void 0:S[P])?V:0,ce=z+ie-fe,pe=ue(m?a(X,z+oe-fe-se):X,z,m?i(G,ce):G);W[P]=pe,q[P]=pe-z}if(c){var le,de="x"===P?j:A,he="x"===P?E:D,me=W[k],ve="y"===k?"height":"width",ge=me+b[de],ye=me-b[he],be=-1!==[j,A].indexOf(x),xe=null!=(le=null==S?void 0:S[k])?le:0,we=be?ge:me-B[ve]-H[ve]-xe+R.altAxis,Oe=be?me+B[ve]+H[ve]-xe-R.altAxis:ye,je=m&&be?function(e,t,n){var r=ue(e,t,n);return r>n?n:r}(we,me,Oe):ue(m?we:ge,me,m?Oe:ye);W[k]=je,q[k]=je-me}t.modifiersData[r]=q}},requiresIfExists:["offset"]};var de={name:"arrow",enabled:!0,phase:"main",fn:function(e){var t,n=e.state,r=e.name,o=e.options,i=n.elements.arrow,a=n.modifiersData.popperOffsets,s=C(n.placement),f=F(s),c=[A,D].indexOf(s)>=0?"height":"width";if(i&&a){var p=function(e,t){return z("number"!=typeof(e="function"==typeof e?e(Object.assign({},t.rects,{placement:t.placement})):e)?e:X(e,P))}(o.padding,n),u=v(i),l="y"===f?j:A,d="y"===f?E:D,h=n.rects.reference[c]+n.rects.reference[f]-a[f]-n.rects.popper[c],m=a[f]-n.rects.reference[f],g=O(i),y=g?"y"===f?g.clientHeight||0:g.clientWidth||0:0,b=h/2-m/2,x=p[l],w=y-u[c]-p[d],L=y/2-u[c]/2+b,M=ue(x,L,w),k=f;n.modifiersData[r]=((t={})[k]=M,t.centerOffset=M-L,t)}},effect:function(e){var t=e.state,n=e.options.element,r=void 0===n?"[data-popper-arrow]":n;null!=r&&("string"!=typeof r||(r=t.elements.popper.querySelector(r)))&&q(t.elements.popper,r)&&(t.elements.arrow=r)},requires:["popperOffsets"],requiresIfExists:["preventOverflow"]};function he(e,t,n){return void 0===n&&(n={x:0,y:0}),{top:e.top-t.height-n.y,right:e.right-t.width+n.x,bottom:e.bottom-t.height+n.y,left:e.left-t.width-n.x}}function me(e){return[j,D,E,A].some((function(t){return e[t]>=0}))}var ve={name:"hide",enabled:!0,phase:"main",requiresIfExists:["preventOverflow"],fn:function(e){var t=e.state,n=e.name,r=t.rects.reference,o=t.rects.popper,i=t.modifiersData.preventOverflow,a=Y(t,{elementContext:"reference"}),s=Y(t,{altBoundary:!0}),f=he(a,r),c=he(s,o,i),p=me(f),u=me(c);t.modifiersData[n]={referenceClippingOffsets:f,popperEscapeOffsets:c,isReferenceHidden:p,hasPopperEscaped:u},t.attributes.popper=Object.assign({},t.attributes.popper,{"data-popper-reference-hidden":p,"data-popper-escaped":u})}},ge=K({defaultModifiers:[Z,$,ne,re]}),ye=[Z,$,ne,re,oe,pe,le,de,ve],be=K({defaultModifiers:ye});e.applyStyles=re,e.arrow=de,e.computeStyles=ne,e.createPopper=be,e.createPopperLite=ge,e.defaultModifiers=ye,e.detectOverflow=Y,e.eventListeners=Z,e.flip=pe,e.hide=ve,e.offset=oe,e.popperGenerator=K,e.popperOffsets=$,e.preventOverflow=le,Object.defineProperty(e,"__esModule",{value:!0})})); + diff --git a/site_libs/quarto-html/quarto-syntax-highlighting.css b/site_libs/quarto-html/quarto-syntax-highlighting.css new file mode 100644 index 00000000..d9fd98f0 --- /dev/null +++ b/site_libs/quarto-html/quarto-syntax-highlighting.css @@ -0,0 +1,203 @@ +/* quarto syntax highlight colors */ +:root { + --quarto-hl-ot-color: #003B4F; + --quarto-hl-at-color: #657422; + --quarto-hl-ss-color: #20794D; + --quarto-hl-an-color: #5E5E5E; + --quarto-hl-fu-color: #4758AB; + --quarto-hl-st-color: #20794D; + --quarto-hl-cf-color: #003B4F; + --quarto-hl-op-color: #5E5E5E; + --quarto-hl-er-color: #AD0000; + --quarto-hl-bn-color: #AD0000; + --quarto-hl-al-color: #AD0000; + --quarto-hl-va-color: #111111; + --quarto-hl-bu-color: inherit; + --quarto-hl-ex-color: inherit; + --quarto-hl-pp-color: #AD0000; + --quarto-hl-in-color: #5E5E5E; + --quarto-hl-vs-color: #20794D; + --quarto-hl-wa-color: #5E5E5E; + --quarto-hl-do-color: #5E5E5E; + --quarto-hl-im-color: #00769E; + --quarto-hl-ch-color: #20794D; + --quarto-hl-dt-color: #AD0000; + --quarto-hl-fl-color: #AD0000; + --quarto-hl-co-color: #5E5E5E; + --quarto-hl-cv-color: #5E5E5E; + --quarto-hl-cn-color: #8f5902; + --quarto-hl-sc-color: #5E5E5E; + --quarto-hl-dv-color: #AD0000; + --quarto-hl-kw-color: #003B4F; +} + +/* other quarto variables */ +:root { + --quarto-font-monospace: SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace; +} + +pre > code.sourceCode > span { + color: #003B4F; +} + +code span { + color: #003B4F; +} + +code.sourceCode > span { + color: #003B4F; +} + +div.sourceCode, +div.sourceCode pre.sourceCode { + color: #003B4F; +} + +code span.ot { + color: #003B4F; + font-style: inherit; +} + +code span.at { + color: #657422; + font-style: inherit; +} + +code span.ss { + color: #20794D; + font-style: inherit; +} + +code span.an { + color: #5E5E5E; + font-style: inherit; +} + +code span.fu { + color: #4758AB; + font-style: inherit; +} + +code span.st { + color: #20794D; + font-style: inherit; +} + +code span.cf { + color: #003B4F; + font-style: inherit; +} + +code span.op { + color: #5E5E5E; + font-style: inherit; +} + +code span.er { + color: #AD0000; + font-style: inherit; +} + +code span.bn { + color: #AD0000; + font-style: inherit; +} + +code span.al { + color: #AD0000; + font-style: inherit; +} + +code span.va { + color: #111111; + font-style: inherit; +} + +code span.bu { + font-style: inherit; +} + +code span.ex { + font-style: inherit; +} + +code span.pp { + color: #AD0000; + font-style: inherit; +} + +code span.in { + color: #5E5E5E; + font-style: inherit; +} + +code span.vs { + color: #20794D; + font-style: inherit; +} + +code span.wa { + color: #5E5E5E; + font-style: italic; +} + +code span.do { + color: #5E5E5E; + font-style: italic; +} + +code span.im { + color: #00769E; + font-style: inherit; +} + +code span.ch { + color: #20794D; + font-style: inherit; +} + +code span.dt { + color: #AD0000; + font-style: inherit; +} + +code span.fl { + color: #AD0000; + font-style: inherit; +} + +code span.co { + color: #5E5E5E; + font-style: inherit; +} + +code span.cv { + color: #5E5E5E; + font-style: italic; +} + +code span.cn { + color: #8f5902; + font-style: inherit; +} + +code span.sc { + color: #5E5E5E; + font-style: inherit; +} + +code span.dv { + color: #AD0000; + font-style: inherit; +} + +code span.kw { + color: #003B4F; + font-style: inherit; +} + +.prevent-inlining { + content: " { + // Find any conflicting margin elements and add margins to the + // top to prevent overlap + const marginChildren = window.document.querySelectorAll( + ".column-margin.column-container > * " + ); + + let lastBottom = 0; + for (const marginChild of marginChildren) { + if (marginChild.offsetParent !== null) { + // clear the top margin so we recompute it + marginChild.style.marginTop = null; + const top = marginChild.getBoundingClientRect().top + window.scrollY; + console.log({ + childtop: marginChild.getBoundingClientRect().top, + scroll: window.scrollY, + top, + lastBottom, + }); + if (top < lastBottom) { + const margin = lastBottom - top; + marginChild.style.marginTop = `${margin}px`; + } + const styles = window.getComputedStyle(marginChild); + const marginTop = parseFloat(styles["marginTop"]); + + console.log({ + top, + height: marginChild.getBoundingClientRect().height, + marginTop, + total: top + marginChild.getBoundingClientRect().height + marginTop, + }); + lastBottom = top + marginChild.getBoundingClientRect().height + marginTop; + } + } +}; + +window.document.addEventListener("DOMContentLoaded", function (_event) { + // Recompute the position of margin elements anytime the body size changes + if (window.ResizeObserver) { + const resizeObserver = new window.ResizeObserver( + throttle(layoutMarginEls, 50) + ); + resizeObserver.observe(window.document.body); + } + + const tocEl = window.document.querySelector('nav.toc-active[role="doc-toc"]'); + const sidebarEl = window.document.getElementById("quarto-sidebar"); + const leftTocEl = window.document.getElementById("quarto-sidebar-toc-left"); + const marginSidebarEl = window.document.getElementById( + "quarto-margin-sidebar" + ); + // function to determine whether the element has a previous sibling that is active + const prevSiblingIsActiveLink = (el) => { + const sibling = el.previousElementSibling; + if (sibling && sibling.tagName === "A") { + return sibling.classList.contains("active"); + } else { + return false; + } + }; + + // fire slideEnter for bootstrap tab activations (for htmlwidget resize behavior) + function fireSlideEnter(e) { + const event = window.document.createEvent("Event"); + event.initEvent("slideenter", true, true); + window.document.dispatchEvent(event); + } + const tabs = window.document.querySelectorAll('a[data-bs-toggle="tab"]'); + tabs.forEach((tab) => { + tab.addEventListener("shown.bs.tab", fireSlideEnter); + }); + + // fire slideEnter for tabby tab activations (for htmlwidget resize behavior) + document.addEventListener("tabby", fireSlideEnter, false); + + // Track scrolling and mark TOC links as active + // get table of contents and sidebar (bail if we don't have at least one) + const tocLinks = tocEl + ? [...tocEl.querySelectorAll("a[data-scroll-target]")] + : []; + const makeActive = (link) => tocLinks[link].classList.add("active"); + const removeActive = (link) => tocLinks[link].classList.remove("active"); + const removeAllActive = () => + [...Array(tocLinks.length).keys()].forEach((link) => removeActive(link)); + + // activate the anchor for a section associated with this TOC entry + tocLinks.forEach((link) => { + link.addEventListener("click", () => { + if (link.href.indexOf("#") !== -1) { + const anchor = link.href.split("#")[1]; + const heading = window.document.querySelector( + `[data-anchor-id=${anchor}]` + ); + if (heading) { + // Add the class + heading.classList.add("reveal-anchorjs-link"); + + // function to show the anchor + const handleMouseout = () => { + heading.classList.remove("reveal-anchorjs-link"); + heading.removeEventListener("mouseout", handleMouseout); + }; + + // add a function to clear the anchor when the user mouses out of it + heading.addEventListener("mouseout", handleMouseout); + } + } + }); + }); + + const sections = tocLinks.map((link) => { + const target = link.getAttribute("data-scroll-target"); + if (target.startsWith("#")) { + return window.document.getElementById(decodeURI(`${target.slice(1)}`)); + } else { + return window.document.querySelector(decodeURI(`${target}`)); + } + }); + + const sectionMargin = 200; + let currentActive = 0; + // track whether we've initialized state the first time + let init = false; + + const updateActiveLink = () => { + // The index from bottom to top (e.g. reversed list) + let sectionIndex = -1; + if ( + window.innerHeight + window.pageYOffset >= + window.document.body.offsetHeight + ) { + sectionIndex = 0; + } else { + sectionIndex = [...sections].reverse().findIndex((section) => { + if (section) { + return window.pageYOffset >= section.offsetTop - sectionMargin; + } else { + return false; + } + }); + } + if (sectionIndex > -1) { + const current = sections.length - sectionIndex - 1; + if (current !== currentActive) { + removeAllActive(); + currentActive = current; + makeActive(current); + if (init) { + window.dispatchEvent(sectionChanged); + } + init = true; + } + } + }; + + const inHiddenRegion = (top, bottom, hiddenRegions) => { + for (const region of hiddenRegions) { + if (top <= region.bottom && bottom >= region.top) { + return true; + } + } + return false; + }; + + const categorySelector = "header.quarto-title-block .quarto-category"; + const activateCategories = (href) => { + // Find any categories + // Surround them with a link pointing back to: + // #category=Authoring + try { + const categoryEls = window.document.querySelectorAll(categorySelector); + for (const categoryEl of categoryEls) { + const categoryText = categoryEl.textContent; + if (categoryText) { + const link = `${href}#category=${encodeURIComponent(categoryText)}`; + const linkEl = window.document.createElement("a"); + linkEl.setAttribute("href", link); + for (const child of categoryEl.childNodes) { + linkEl.append(child); + } + categoryEl.appendChild(linkEl); + } + } + } catch { + // Ignore errors + } + }; + function hasTitleCategories() { + return window.document.querySelector(categorySelector) !== null; + } + + function offsetRelativeUrl(url) { + const offset = getMeta("quarto:offset"); + return offset ? offset + url : url; + } + + function offsetAbsoluteUrl(url) { + const offset = getMeta("quarto:offset"); + const baseUrl = new URL(offset, window.location); + + const projRelativeUrl = url.replace(baseUrl, ""); + if (projRelativeUrl.startsWith("/")) { + return projRelativeUrl; + } else { + return "/" + projRelativeUrl; + } + } + + // read a meta tag value + function getMeta(metaName) { + const metas = window.document.getElementsByTagName("meta"); + for (let i = 0; i < metas.length; i++) { + if (metas[i].getAttribute("name") === metaName) { + return metas[i].getAttribute("content"); + } + } + return ""; + } + + async function findAndActivateCategories() { + const currentPagePath = offsetAbsoluteUrl(window.location.href); + const response = await fetch(offsetRelativeUrl("listings.json")); + if (response.status == 200) { + return response.json().then(function (listingPaths) { + const listingHrefs = []; + for (const listingPath of listingPaths) { + const pathWithoutLeadingSlash = listingPath.listing.substring(1); + for (const item of listingPath.items) { + if ( + item === currentPagePath || + item === currentPagePath + "index.html" + ) { + // Resolve this path against the offset to be sure + // we already are using the correct path to the listing + // (this adjusts the listing urls to be rooted against + // whatever root the page is actually running against) + const relative = offsetRelativeUrl(pathWithoutLeadingSlash); + const baseUrl = window.location; + const resolvedPath = new URL(relative, baseUrl); + listingHrefs.push(resolvedPath.pathname); + break; + } + } + } + + // Look up the tree for a nearby linting and use that if we find one + const nearestListing = findNearestParentListing( + offsetAbsoluteUrl(window.location.pathname), + listingHrefs + ); + if (nearestListing) { + activateCategories(nearestListing); + } else { + // See if the referrer is a listing page for this item + const referredRelativePath = offsetAbsoluteUrl(document.referrer); + const referrerListing = listingHrefs.find((listingHref) => { + const isListingReferrer = + listingHref === referredRelativePath || + listingHref === referredRelativePath + "index.html"; + return isListingReferrer; + }); + + if (referrerListing) { + // Try to use the referrer if possible + activateCategories(referrerListing); + } else if (listingHrefs.length > 0) { + // Otherwise, just fall back to the first listing + activateCategories(listingHrefs[0]); + } + } + }); + } + } + if (hasTitleCategories()) { + findAndActivateCategories(); + } + + const findNearestParentListing = (href, listingHrefs) => { + if (!href || !listingHrefs) { + return undefined; + } + // Look up the tree for a nearby linting and use that if we find one + const relativeParts = href.substring(1).split("/"); + while (relativeParts.length > 0) { + const path = relativeParts.join("/"); + for (const listingHref of listingHrefs) { + if (listingHref.startsWith(path)) { + return listingHref; + } + } + relativeParts.pop(); + } + + return undefined; + }; + + const manageSidebarVisiblity = (el, placeholderDescriptor) => { + let isVisible = true; + let elRect; + + return (hiddenRegions) => { + if (el === null) { + return; + } + + // Find the last element of the TOC + const lastChildEl = el.lastElementChild; + + if (lastChildEl) { + // Converts the sidebar to a menu + const convertToMenu = () => { + for (const child of el.children) { + child.style.opacity = 0; + child.style.overflow = "hidden"; + } + + nexttick(() => { + const toggleContainer = window.document.createElement("div"); + toggleContainer.style.width = "100%"; + toggleContainer.classList.add("zindex-over-content"); + toggleContainer.classList.add("quarto-sidebar-toggle"); + toggleContainer.classList.add("headroom-target"); // Marks this to be managed by headeroom + toggleContainer.id = placeholderDescriptor.id; + toggleContainer.style.position = "fixed"; + + const toggleIcon = window.document.createElement("i"); + toggleIcon.classList.add("quarto-sidebar-toggle-icon"); + toggleIcon.classList.add("bi"); + toggleIcon.classList.add("bi-caret-down-fill"); + + const toggleTitle = window.document.createElement("div"); + const titleEl = window.document.body.querySelector( + placeholderDescriptor.titleSelector + ); + if (titleEl) { + toggleTitle.append( + titleEl.textContent || titleEl.innerText, + toggleIcon + ); + } + toggleTitle.classList.add("zindex-over-content"); + toggleTitle.classList.add("quarto-sidebar-toggle-title"); + toggleContainer.append(toggleTitle); + + const toggleContents = window.document.createElement("div"); + toggleContents.classList = el.classList; + toggleContents.classList.add("zindex-over-content"); + toggleContents.classList.add("quarto-sidebar-toggle-contents"); + for (const child of el.children) { + if (child.id === "toc-title") { + continue; + } + + const clone = child.cloneNode(true); + clone.style.opacity = 1; + clone.style.display = null; + toggleContents.append(clone); + } + toggleContents.style.height = "0px"; + const positionToggle = () => { + // position the element (top left of parent, same width as parent) + if (!elRect) { + elRect = el.getBoundingClientRect(); + } + toggleContainer.style.left = `${elRect.left}px`; + toggleContainer.style.top = `${elRect.top}px`; + toggleContainer.style.width = `${elRect.width}px`; + }; + positionToggle(); + + toggleContainer.append(toggleContents); + el.parentElement.prepend(toggleContainer); + + // Process clicks + let tocShowing = false; + // Allow the caller to control whether this is dismissed + // when it is clicked (e.g. sidebar navigation supports + // opening and closing the nav tree, so don't dismiss on click) + const clickEl = placeholderDescriptor.dismissOnClick + ? toggleContainer + : toggleTitle; + + const closeToggle = () => { + if (tocShowing) { + toggleContainer.classList.remove("expanded"); + toggleContents.style.height = "0px"; + tocShowing = false; + } + }; + + // Get rid of any expanded toggle if the user scrolls + window.document.addEventListener( + "scroll", + throttle(() => { + closeToggle(); + }, 50) + ); + + // Handle positioning of the toggle + window.addEventListener( + "resize", + throttle(() => { + elRect = undefined; + positionToggle(); + }, 50) + ); + + window.addEventListener("quarto-hrChanged", () => { + elRect = undefined; + }); + + // Process the click + clickEl.onclick = () => { + if (!tocShowing) { + toggleContainer.classList.add("expanded"); + toggleContents.style.height = null; + tocShowing = true; + } else { + closeToggle(); + } + }; + }); + }; + + // Converts a sidebar from a menu back to a sidebar + const convertToSidebar = () => { + for (const child of el.children) { + child.style.opacity = 1; + child.style.overflow = null; + } + + const placeholderEl = window.document.getElementById( + placeholderDescriptor.id + ); + if (placeholderEl) { + placeholderEl.remove(); + } + + el.classList.remove("rollup"); + }; + + if (isReaderMode()) { + convertToMenu(); + isVisible = false; + } else { + // Find the top and bottom o the element that is being managed + const elTop = el.offsetTop; + const elBottom = + elTop + lastChildEl.offsetTop + lastChildEl.offsetHeight; + + if (!isVisible) { + // If the element is current not visible reveal if there are + // no conflicts with overlay regions + if (!inHiddenRegion(elTop, elBottom, hiddenRegions)) { + convertToSidebar(); + isVisible = true; + } + } else { + // If the element is visible, hide it if it conflicts with overlay regions + // and insert a placeholder toggle (or if we're in reader mode) + if (inHiddenRegion(elTop, elBottom, hiddenRegions)) { + convertToMenu(); + isVisible = false; + } + } + } + } + }; + }; + + const tabEls = document.querySelectorAll('a[data-bs-toggle="tab"]'); + for (const tabEl of tabEls) { + const id = tabEl.getAttribute("data-bs-target"); + if (id) { + const columnEl = document.querySelector( + `${id} .column-margin, .tabset-margin-content` + ); + if (columnEl) + tabEl.addEventListener("shown.bs.tab", function (event) { + const el = event.srcElement; + if (el) { + const visibleCls = `${el.id}-margin-content`; + // walk up until we find a parent tabset + let panelTabsetEl = el.parentElement; + while (panelTabsetEl) { + if (panelTabsetEl.classList.contains("panel-tabset")) { + break; + } + panelTabsetEl = panelTabsetEl.parentElement; + } + + if (panelTabsetEl) { + const prevSib = panelTabsetEl.previousElementSibling; + if ( + prevSib && + prevSib.classList.contains("tabset-margin-container") + ) { + const childNodes = prevSib.querySelectorAll( + ".tabset-margin-content" + ); + for (const childEl of childNodes) { + if (childEl.classList.contains(visibleCls)) { + childEl.classList.remove("collapse"); + } else { + childEl.classList.add("collapse"); + } + } + } + } + } + + layoutMarginEls(); + }); + } + } + + // Manage the visibility of the toc and the sidebar + const marginScrollVisibility = manageSidebarVisiblity(marginSidebarEl, { + id: "quarto-toc-toggle", + titleSelector: "#toc-title", + dismissOnClick: true, + }); + const sidebarScrollVisiblity = manageSidebarVisiblity(sidebarEl, { + id: "quarto-sidebarnav-toggle", + titleSelector: ".title", + dismissOnClick: false, + }); + let tocLeftScrollVisibility; + if (leftTocEl) { + tocLeftScrollVisibility = manageSidebarVisiblity(leftTocEl, { + id: "quarto-lefttoc-toggle", + titleSelector: "#toc-title", + dismissOnClick: true, + }); + } + + // Find the first element that uses formatting in special columns + const conflictingEls = window.document.body.querySelectorAll( + '[class^="column-"], [class*=" column-"], aside, [class*="margin-caption"], [class*=" margin-caption"], [class*="margin-ref"], [class*=" margin-ref"]' + ); + + // Filter all the possibly conflicting elements into ones + // the do conflict on the left or ride side + const arrConflictingEls = Array.from(conflictingEls); + const leftSideConflictEls = arrConflictingEls.filter((el) => { + if (el.tagName === "ASIDE") { + return false; + } + return Array.from(el.classList).find((className) => { + return ( + className !== "column-body" && + className.startsWith("column-") && + !className.endsWith("right") && + !className.endsWith("container") && + className !== "column-margin" + ); + }); + }); + const rightSideConflictEls = arrConflictingEls.filter((el) => { + if (el.tagName === "ASIDE") { + return true; + } + + const hasMarginCaption = Array.from(el.classList).find((className) => { + return className == "margin-caption"; + }); + if (hasMarginCaption) { + return true; + } + + return Array.from(el.classList).find((className) => { + return ( + className !== "column-body" && + !className.endsWith("container") && + className.startsWith("column-") && + !className.endsWith("left") + ); + }); + }); + + const kOverlapPaddingSize = 10; + function toRegions(els) { + return els.map((el) => { + const boundRect = el.getBoundingClientRect(); + const top = + boundRect.top + + document.documentElement.scrollTop - + kOverlapPaddingSize; + return { + top, + bottom: top + el.scrollHeight + 2 * kOverlapPaddingSize, + }; + }); + } + + let hasObserved = false; + const visibleItemObserver = (els) => { + let visibleElements = [...els]; + const intersectionObserver = new IntersectionObserver( + (entries, _observer) => { + entries.forEach((entry) => { + if (entry.isIntersecting) { + if (visibleElements.indexOf(entry.target) === -1) { + visibleElements.push(entry.target); + } + } else { + visibleElements = visibleElements.filter((visibleEntry) => { + return visibleEntry !== entry; + }); + } + }); + + if (!hasObserved) { + hideOverlappedSidebars(); + } + hasObserved = true; + }, + {} + ); + els.forEach((el) => { + intersectionObserver.observe(el); + }); + + return { + getVisibleEntries: () => { + return visibleElements; + }, + }; + }; + + const rightElementObserver = visibleItemObserver(rightSideConflictEls); + const leftElementObserver = visibleItemObserver(leftSideConflictEls); + + const hideOverlappedSidebars = () => { + marginScrollVisibility(toRegions(rightElementObserver.getVisibleEntries())); + sidebarScrollVisiblity(toRegions(leftElementObserver.getVisibleEntries())); + if (tocLeftScrollVisibility) { + tocLeftScrollVisibility( + toRegions(leftElementObserver.getVisibleEntries()) + ); + } + }; + + window.quartoToggleReader = () => { + // Applies a slow class (or removes it) + // to update the transition speed + const slowTransition = (slow) => { + const manageTransition = (id, slow) => { + const el = document.getElementById(id); + if (el) { + if (slow) { + el.classList.add("slow"); + } else { + el.classList.remove("slow"); + } + } + }; + + manageTransition("TOC", slow); + manageTransition("quarto-sidebar", slow); + }; + const readerMode = !isReaderMode(); + setReaderModeValue(readerMode); + + // If we're entering reader mode, slow the transition + if (readerMode) { + slowTransition(readerMode); + } + highlightReaderToggle(readerMode); + hideOverlappedSidebars(); + + // If we're exiting reader mode, restore the non-slow transition + if (!readerMode) { + slowTransition(!readerMode); + } + }; + + const highlightReaderToggle = (readerMode) => { + const els = document.querySelectorAll(".quarto-reader-toggle"); + if (els) { + els.forEach((el) => { + if (readerMode) { + el.classList.add("reader"); + } else { + el.classList.remove("reader"); + } + }); + } + }; + + const setReaderModeValue = (val) => { + if (window.location.protocol !== "file:") { + window.localStorage.setItem("quarto-reader-mode", val); + } else { + localReaderMode = val; + } + }; + + const isReaderMode = () => { + if (window.location.protocol !== "file:") { + return window.localStorage.getItem("quarto-reader-mode") === "true"; + } else { + return localReaderMode; + } + }; + let localReaderMode = null; + + const tocOpenDepthStr = tocEl?.getAttribute("data-toc-expanded"); + const tocOpenDepth = tocOpenDepthStr ? Number(tocOpenDepthStr) : 1; + + // Walk the TOC and collapse/expand nodes + // Nodes are expanded if: + // - they are top level + // - they have children that are 'active' links + // - they are directly below an link that is 'active' + const walk = (el, depth) => { + // Tick depth when we enter a UL + if (el.tagName === "UL") { + depth = depth + 1; + } + + // It this is active link + let isActiveNode = false; + if (el.tagName === "A" && el.classList.contains("active")) { + isActiveNode = true; + } + + // See if there is an active child to this element + let hasActiveChild = false; + for (child of el.children) { + hasActiveChild = walk(child, depth) || hasActiveChild; + } + + // Process the collapse state if this is an UL + if (el.tagName === "UL") { + if (tocOpenDepth === -1 && depth > 1) { + el.classList.add("collapse"); + } else if ( + depth <= tocOpenDepth || + hasActiveChild || + prevSiblingIsActiveLink(el) + ) { + el.classList.remove("collapse"); + } else { + el.classList.add("collapse"); + } + + // untick depth when we leave a UL + depth = depth - 1; + } + return hasActiveChild || isActiveNode; + }; + + // walk the TOC and expand / collapse any items that should be shown + + if (tocEl) { + walk(tocEl, 0); + updateActiveLink(); + } + + // Throttle the scroll event and walk peridiocally + window.document.addEventListener( + "scroll", + throttle(() => { + if (tocEl) { + updateActiveLink(); + walk(tocEl, 0); + } + if (!isReaderMode()) { + hideOverlappedSidebars(); + } + }, 5) + ); + window.addEventListener( + "resize", + throttle(() => { + if (!isReaderMode()) { + hideOverlappedSidebars(); + } + }, 10) + ); + hideOverlappedSidebars(); + highlightReaderToggle(isReaderMode()); +}); + +// grouped tabsets +window.addEventListener("pageshow", (_event) => { + function getTabSettings() { + const data = localStorage.getItem("quarto-persistent-tabsets-data"); + if (!data) { + localStorage.setItem("quarto-persistent-tabsets-data", "{}"); + return {}; + } + if (data) { + return JSON.parse(data); + } + } + + function setTabSettings(data) { + localStorage.setItem( + "quarto-persistent-tabsets-data", + JSON.stringify(data) + ); + } + + function setTabState(groupName, groupValue) { + const data = getTabSettings(); + data[groupName] = groupValue; + setTabSettings(data); + } + + function toggleTab(tab, active) { + const tabPanelId = tab.getAttribute("aria-controls"); + const tabPanel = document.getElementById(tabPanelId); + if (active) { + tab.classList.add("active"); + tabPanel.classList.add("active"); + } else { + tab.classList.remove("active"); + tabPanel.classList.remove("active"); + } + } + + function toggleAll(selectedGroup, selectorsToSync) { + for (const [thisGroup, tabs] of Object.entries(selectorsToSync)) { + const active = selectedGroup === thisGroup; + for (const tab of tabs) { + toggleTab(tab, active); + } + } + } + + function findSelectorsToSyncByLanguage() { + const result = {}; + const tabs = Array.from( + document.querySelectorAll(`div[data-group] a[id^='tabset-']`) + ); + for (const item of tabs) { + const div = item.parentElement.parentElement.parentElement; + const group = div.getAttribute("data-group"); + if (!result[group]) { + result[group] = {}; + } + const selectorsToSync = result[group]; + const value = item.innerHTML; + if (!selectorsToSync[value]) { + selectorsToSync[value] = []; + } + selectorsToSync[value].push(item); + } + return result; + } + + function setupSelectorSync() { + const selectorsToSync = findSelectorsToSyncByLanguage(); + Object.entries(selectorsToSync).forEach(([group, tabSetsByValue]) => { + Object.entries(tabSetsByValue).forEach(([value, items]) => { + items.forEach((item) => { + item.addEventListener("click", (_event) => { + setTabState(group, value); + toggleAll(value, selectorsToSync[group]); + }); + }); + }); + }); + return selectorsToSync; + } + + const selectorsToSync = setupSelectorSync(); + for (const [group, selectedName] of Object.entries(getTabSettings())) { + const selectors = selectorsToSync[group]; + // it's possible that stale state gives us empty selections, so we explicitly check here. + if (selectors) { + toggleAll(selectedName, selectors); + } + } +}); + +function throttle(func, wait) { + let waiting = false; + return function () { + if (!waiting) { + func.apply(this, arguments); + waiting = true; + setTimeout(function () { + waiting = false; + }, wait); + } + }; +} + +function nexttick(func) { + return setTimeout(func, 0); +} diff --git a/site_libs/quarto-html/tippy.css b/site_libs/quarto-html/tippy.css new file mode 100644 index 00000000..e6ae635c --- /dev/null +++ b/site_libs/quarto-html/tippy.css @@ -0,0 +1 @@ +.tippy-box[data-animation=fade][data-state=hidden]{opacity:0}[data-tippy-root]{max-width:calc(100vw - 10px)}.tippy-box{position:relative;background-color:#333;color:#fff;border-radius:4px;font-size:14px;line-height:1.4;white-space:normal;outline:0;transition-property:transform,visibility,opacity}.tippy-box[data-placement^=top]>.tippy-arrow{bottom:0}.tippy-box[data-placement^=top]>.tippy-arrow:before{bottom:-7px;left:0;border-width:8px 8px 0;border-top-color:initial;transform-origin:center top}.tippy-box[data-placement^=bottom]>.tippy-arrow{top:0}.tippy-box[data-placement^=bottom]>.tippy-arrow:before{top:-7px;left:0;border-width:0 8px 8px;border-bottom-color:initial;transform-origin:center bottom}.tippy-box[data-placement^=left]>.tippy-arrow{right:0}.tippy-box[data-placement^=left]>.tippy-arrow:before{border-width:8px 0 8px 8px;border-left-color:initial;right:-7px;transform-origin:center left}.tippy-box[data-placement^=right]>.tippy-arrow{left:0}.tippy-box[data-placement^=right]>.tippy-arrow:before{left:-7px;border-width:8px 8px 8px 0;border-right-color:initial;transform-origin:center right}.tippy-box[data-inertia][data-state=visible]{transition-timing-function:cubic-bezier(.54,1.5,.38,1.11)}.tippy-arrow{width:16px;height:16px;color:#333}.tippy-arrow:before{content:"";position:absolute;border-color:transparent;border-style:solid}.tippy-content{position:relative;padding:5px 9px;z-index:1} \ No newline at end of file diff --git a/site_libs/quarto-html/tippy.umd.min.js b/site_libs/quarto-html/tippy.umd.min.js new file mode 100644 index 00000000..ca292be3 --- /dev/null +++ b/site_libs/quarto-html/tippy.umd.min.js @@ -0,0 +1,2 @@ +!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?module.exports=t(require("@popperjs/core")):"function"==typeof define&&define.amd?define(["@popperjs/core"],t):(e=e||self).tippy=t(e.Popper)}(this,(function(e){"use strict";var t={passive:!0,capture:!0},n=function(){return document.body};function r(e,t,n){if(Array.isArray(e)){var r=e[t];return null==r?Array.isArray(n)?n[t]:n:r}return e}function o(e,t){var n={}.toString.call(e);return 0===n.indexOf("[object")&&n.indexOf(t+"]")>-1}function i(e,t){return"function"==typeof e?e.apply(void 0,t):e}function a(e,t){return 0===t?e:function(r){clearTimeout(n),n=setTimeout((function(){e(r)}),t)};var n}function s(e,t){var n=Object.assign({},e);return t.forEach((function(e){delete n[e]})),n}function u(e){return[].concat(e)}function c(e,t){-1===e.indexOf(t)&&e.push(t)}function p(e){return e.split("-")[0]}function f(e){return[].slice.call(e)}function l(e){return Object.keys(e).reduce((function(t,n){return void 0!==e[n]&&(t[n]=e[n]),t}),{})}function d(){return document.createElement("div")}function v(e){return["Element","Fragment"].some((function(t){return o(e,t)}))}function m(e){return o(e,"MouseEvent")}function g(e){return!(!e||!e._tippy||e._tippy.reference!==e)}function h(e){return v(e)?[e]:function(e){return o(e,"NodeList")}(e)?f(e):Array.isArray(e)?e:f(document.querySelectorAll(e))}function b(e,t){e.forEach((function(e){e&&(e.style.transitionDuration=t+"ms")}))}function y(e,t){e.forEach((function(e){e&&e.setAttribute("data-state",t)}))}function w(e){var t,n=u(e)[0];return null!=n&&null!=(t=n.ownerDocument)&&t.body?n.ownerDocument:document}function E(e,t,n){var r=t+"EventListener";["transitionend","webkitTransitionEnd"].forEach((function(t){e[r](t,n)}))}function O(e,t){for(var n=t;n;){var r;if(e.contains(n))return!0;n=null==n.getRootNode||null==(r=n.getRootNode())?void 0:r.host}return!1}var x={isTouch:!1},C=0;function T(){x.isTouch||(x.isTouch=!0,window.performance&&document.addEventListener("mousemove",A))}function A(){var e=performance.now();e-C<20&&(x.isTouch=!1,document.removeEventListener("mousemove",A)),C=e}function L(){var e=document.activeElement;if(g(e)){var t=e._tippy;e.blur&&!t.state.isVisible&&e.blur()}}var D=!!("undefined"!=typeof window&&"undefined"!=typeof document)&&!!window.msCrypto,R=Object.assign({appendTo:n,aria:{content:"auto",expanded:"auto"},delay:0,duration:[300,250],getReferenceClientRect:null,hideOnClick:!0,ignoreAttributes:!1,interactive:!1,interactiveBorder:2,interactiveDebounce:0,moveTransition:"",offset:[0,10],onAfterUpdate:function(){},onBeforeUpdate:function(){},onCreate:function(){},onDestroy:function(){},onHidden:function(){},onHide:function(){},onMount:function(){},onShow:function(){},onShown:function(){},onTrigger:function(){},onUntrigger:function(){},onClickOutside:function(){},placement:"top",plugins:[],popperOptions:{},render:null,showOnCreate:!1,touch:!0,trigger:"mouseenter focus",triggerTarget:null},{animateFill:!1,followCursor:!1,inlinePositioning:!1,sticky:!1},{allowHTML:!1,animation:"fade",arrow:!0,content:"",inertia:!1,maxWidth:350,role:"tooltip",theme:"",zIndex:9999}),k=Object.keys(R);function P(e){var t=(e.plugins||[]).reduce((function(t,n){var r,o=n.name,i=n.defaultValue;o&&(t[o]=void 0!==e[o]?e[o]:null!=(r=R[o])?r:i);return t}),{});return Object.assign({},e,t)}function j(e,t){var n=Object.assign({},t,{content:i(t.content,[e])},t.ignoreAttributes?{}:function(e,t){return(t?Object.keys(P(Object.assign({},R,{plugins:t}))):k).reduce((function(t,n){var r=(e.getAttribute("data-tippy-"+n)||"").trim();if(!r)return t;if("content"===n)t[n]=r;else try{t[n]=JSON.parse(r)}catch(e){t[n]=r}return t}),{})}(e,t.plugins));return n.aria=Object.assign({},R.aria,n.aria),n.aria={expanded:"auto"===n.aria.expanded?t.interactive:n.aria.expanded,content:"auto"===n.aria.content?t.interactive?null:"describedby":n.aria.content},n}function M(e,t){e.innerHTML=t}function V(e){var t=d();return!0===e?t.className="tippy-arrow":(t.className="tippy-svg-arrow",v(e)?t.appendChild(e):M(t,e)),t}function I(e,t){v(t.content)?(M(e,""),e.appendChild(t.content)):"function"!=typeof t.content&&(t.allowHTML?M(e,t.content):e.textContent=t.content)}function S(e){var t=e.firstElementChild,n=f(t.children);return{box:t,content:n.find((function(e){return e.classList.contains("tippy-content")})),arrow:n.find((function(e){return e.classList.contains("tippy-arrow")||e.classList.contains("tippy-svg-arrow")})),backdrop:n.find((function(e){return e.classList.contains("tippy-backdrop")}))}}function N(e){var t=d(),n=d();n.className="tippy-box",n.setAttribute("data-state","hidden"),n.setAttribute("tabindex","-1");var r=d();function o(n,r){var o=S(t),i=o.box,a=o.content,s=o.arrow;r.theme?i.setAttribute("data-theme",r.theme):i.removeAttribute("data-theme"),"string"==typeof r.animation?i.setAttribute("data-animation",r.animation):i.removeAttribute("data-animation"),r.inertia?i.setAttribute("data-inertia",""):i.removeAttribute("data-inertia"),i.style.maxWidth="number"==typeof r.maxWidth?r.maxWidth+"px":r.maxWidth,r.role?i.setAttribute("role",r.role):i.removeAttribute("role"),n.content===r.content&&n.allowHTML===r.allowHTML||I(a,e.props),r.arrow?s?n.arrow!==r.arrow&&(i.removeChild(s),i.appendChild(V(r.arrow))):i.appendChild(V(r.arrow)):s&&i.removeChild(s)}return r.className="tippy-content",r.setAttribute("data-state","hidden"),I(r,e.props),t.appendChild(n),n.appendChild(r),o(e.props,e.props),{popper:t,onUpdate:o}}N.$$tippy=!0;var B=1,H=[],U=[];function _(o,s){var v,g,h,C,T,A,L,k,M=j(o,Object.assign({},R,P(l(s)))),V=!1,I=!1,N=!1,_=!1,F=[],W=a(we,M.interactiveDebounce),X=B++,Y=(k=M.plugins).filter((function(e,t){return k.indexOf(e)===t})),$={id:X,reference:o,popper:d(),popperInstance:null,props:M,state:{isEnabled:!0,isVisible:!1,isDestroyed:!1,isMounted:!1,isShown:!1},plugins:Y,clearDelayTimeouts:function(){clearTimeout(v),clearTimeout(g),cancelAnimationFrame(h)},setProps:function(e){if($.state.isDestroyed)return;ae("onBeforeUpdate",[$,e]),be();var t=$.props,n=j(o,Object.assign({},t,l(e),{ignoreAttributes:!0}));$.props=n,he(),t.interactiveDebounce!==n.interactiveDebounce&&(ce(),W=a(we,n.interactiveDebounce));t.triggerTarget&&!n.triggerTarget?u(t.triggerTarget).forEach((function(e){e.removeAttribute("aria-expanded")})):n.triggerTarget&&o.removeAttribute("aria-expanded");ue(),ie(),J&&J(t,n);$.popperInstance&&(Ce(),Ae().forEach((function(e){requestAnimationFrame(e._tippy.popperInstance.forceUpdate)})));ae("onAfterUpdate",[$,e])},setContent:function(e){$.setProps({content:e})},show:function(){var e=$.state.isVisible,t=$.state.isDestroyed,o=!$.state.isEnabled,a=x.isTouch&&!$.props.touch,s=r($.props.duration,0,R.duration);if(e||t||o||a)return;if(te().hasAttribute("disabled"))return;if(ae("onShow",[$],!1),!1===$.props.onShow($))return;$.state.isVisible=!0,ee()&&(z.style.visibility="visible");ie(),de(),$.state.isMounted||(z.style.transition="none");if(ee()){var u=re(),p=u.box,f=u.content;b([p,f],0)}A=function(){var e;if($.state.isVisible&&!_){if(_=!0,z.offsetHeight,z.style.transition=$.props.moveTransition,ee()&&$.props.animation){var t=re(),n=t.box,r=t.content;b([n,r],s),y([n,r],"visible")}se(),ue(),c(U,$),null==(e=$.popperInstance)||e.forceUpdate(),ae("onMount",[$]),$.props.animation&&ee()&&function(e,t){me(e,t)}(s,(function(){$.state.isShown=!0,ae("onShown",[$])}))}},function(){var e,t=$.props.appendTo,r=te();e=$.props.interactive&&t===n||"parent"===t?r.parentNode:i(t,[r]);e.contains(z)||e.appendChild(z);$.state.isMounted=!0,Ce()}()},hide:function(){var e=!$.state.isVisible,t=$.state.isDestroyed,n=!$.state.isEnabled,o=r($.props.duration,1,R.duration);if(e||t||n)return;if(ae("onHide",[$],!1),!1===$.props.onHide($))return;$.state.isVisible=!1,$.state.isShown=!1,_=!1,V=!1,ee()&&(z.style.visibility="hidden");if(ce(),ve(),ie(!0),ee()){var i=re(),a=i.box,s=i.content;$.props.animation&&(b([a,s],o),y([a,s],"hidden"))}se(),ue(),$.props.animation?ee()&&function(e,t){me(e,(function(){!$.state.isVisible&&z.parentNode&&z.parentNode.contains(z)&&t()}))}(o,$.unmount):$.unmount()},hideWithInteractivity:function(e){ne().addEventListener("mousemove",W),c(H,W),W(e)},enable:function(){$.state.isEnabled=!0},disable:function(){$.hide(),$.state.isEnabled=!1},unmount:function(){$.state.isVisible&&$.hide();if(!$.state.isMounted)return;Te(),Ae().forEach((function(e){e._tippy.unmount()})),z.parentNode&&z.parentNode.removeChild(z);U=U.filter((function(e){return e!==$})),$.state.isMounted=!1,ae("onHidden",[$])},destroy:function(){if($.state.isDestroyed)return;$.clearDelayTimeouts(),$.unmount(),be(),delete o._tippy,$.state.isDestroyed=!0,ae("onDestroy",[$])}};if(!M.render)return $;var q=M.render($),z=q.popper,J=q.onUpdate;z.setAttribute("data-tippy-root",""),z.id="tippy-"+$.id,$.popper=z,o._tippy=$,z._tippy=$;var G=Y.map((function(e){return e.fn($)})),K=o.hasAttribute("aria-expanded");return he(),ue(),ie(),ae("onCreate",[$]),M.showOnCreate&&Le(),z.addEventListener("mouseenter",(function(){$.props.interactive&&$.state.isVisible&&$.clearDelayTimeouts()})),z.addEventListener("mouseleave",(function(){$.props.interactive&&$.props.trigger.indexOf("mouseenter")>=0&&ne().addEventListener("mousemove",W)})),$;function Q(){var e=$.props.touch;return Array.isArray(e)?e:[e,0]}function Z(){return"hold"===Q()[0]}function ee(){var e;return!(null==(e=$.props.render)||!e.$$tippy)}function te(){return L||o}function ne(){var e=te().parentNode;return e?w(e):document}function re(){return S(z)}function oe(e){return $.state.isMounted&&!$.state.isVisible||x.isTouch||C&&"focus"===C.type?0:r($.props.delay,e?0:1,R.delay)}function ie(e){void 0===e&&(e=!1),z.style.pointerEvents=$.props.interactive&&!e?"":"none",z.style.zIndex=""+$.props.zIndex}function ae(e,t,n){var r;(void 0===n&&(n=!0),G.forEach((function(n){n[e]&&n[e].apply(n,t)})),n)&&(r=$.props)[e].apply(r,t)}function se(){var e=$.props.aria;if(e.content){var t="aria-"+e.content,n=z.id;u($.props.triggerTarget||o).forEach((function(e){var r=e.getAttribute(t);if($.state.isVisible)e.setAttribute(t,r?r+" "+n:n);else{var o=r&&r.replace(n,"").trim();o?e.setAttribute(t,o):e.removeAttribute(t)}}))}}function ue(){!K&&$.props.aria.expanded&&u($.props.triggerTarget||o).forEach((function(e){$.props.interactive?e.setAttribute("aria-expanded",$.state.isVisible&&e===te()?"true":"false"):e.removeAttribute("aria-expanded")}))}function ce(){ne().removeEventListener("mousemove",W),H=H.filter((function(e){return e!==W}))}function pe(e){if(!x.isTouch||!N&&"mousedown"!==e.type){var t=e.composedPath&&e.composedPath()[0]||e.target;if(!$.props.interactive||!O(z,t)){if(u($.props.triggerTarget||o).some((function(e){return O(e,t)}))){if(x.isTouch)return;if($.state.isVisible&&$.props.trigger.indexOf("click")>=0)return}else ae("onClickOutside",[$,e]);!0===$.props.hideOnClick&&($.clearDelayTimeouts(),$.hide(),I=!0,setTimeout((function(){I=!1})),$.state.isMounted||ve())}}}function fe(){N=!0}function le(){N=!1}function de(){var e=ne();e.addEventListener("mousedown",pe,!0),e.addEventListener("touchend",pe,t),e.addEventListener("touchstart",le,t),e.addEventListener("touchmove",fe,t)}function ve(){var e=ne();e.removeEventListener("mousedown",pe,!0),e.removeEventListener("touchend",pe,t),e.removeEventListener("touchstart",le,t),e.removeEventListener("touchmove",fe,t)}function me(e,t){var n=re().box;function r(e){e.target===n&&(E(n,"remove",r),t())}if(0===e)return t();E(n,"remove",T),E(n,"add",r),T=r}function ge(e,t,n){void 0===n&&(n=!1),u($.props.triggerTarget||o).forEach((function(r){r.addEventListener(e,t,n),F.push({node:r,eventType:e,handler:t,options:n})}))}function he(){var e;Z()&&(ge("touchstart",ye,{passive:!0}),ge("touchend",Ee,{passive:!0})),(e=$.props.trigger,e.split(/\s+/).filter(Boolean)).forEach((function(e){if("manual"!==e)switch(ge(e,ye),e){case"mouseenter":ge("mouseleave",Ee);break;case"focus":ge(D?"focusout":"blur",Oe);break;case"focusin":ge("focusout",Oe)}}))}function be(){F.forEach((function(e){var t=e.node,n=e.eventType,r=e.handler,o=e.options;t.removeEventListener(n,r,o)})),F=[]}function ye(e){var t,n=!1;if($.state.isEnabled&&!xe(e)&&!I){var r="focus"===(null==(t=C)?void 0:t.type);C=e,L=e.currentTarget,ue(),!$.state.isVisible&&m(e)&&H.forEach((function(t){return t(e)})),"click"===e.type&&($.props.trigger.indexOf("mouseenter")<0||V)&&!1!==$.props.hideOnClick&&$.state.isVisible?n=!0:Le(e),"click"===e.type&&(V=!n),n&&!r&&De(e)}}function we(e){var t=e.target,n=te().contains(t)||z.contains(t);"mousemove"===e.type&&n||function(e,t){var n=t.clientX,r=t.clientY;return e.every((function(e){var t=e.popperRect,o=e.popperState,i=e.props.interactiveBorder,a=p(o.placement),s=o.modifiersData.offset;if(!s)return!0;var u="bottom"===a?s.top.y:0,c="top"===a?s.bottom.y:0,f="right"===a?s.left.x:0,l="left"===a?s.right.x:0,d=t.top-r+u>i,v=r-t.bottom-c>i,m=t.left-n+f>i,g=n-t.right-l>i;return d||v||m||g}))}(Ae().concat(z).map((function(e){var t,n=null==(t=e._tippy.popperInstance)?void 0:t.state;return n?{popperRect:e.getBoundingClientRect(),popperState:n,props:M}:null})).filter(Boolean),e)&&(ce(),De(e))}function Ee(e){xe(e)||$.props.trigger.indexOf("click")>=0&&V||($.props.interactive?$.hideWithInteractivity(e):De(e))}function Oe(e){$.props.trigger.indexOf("focusin")<0&&e.target!==te()||$.props.interactive&&e.relatedTarget&&z.contains(e.relatedTarget)||De(e)}function xe(e){return!!x.isTouch&&Z()!==e.type.indexOf("touch")>=0}function Ce(){Te();var t=$.props,n=t.popperOptions,r=t.placement,i=t.offset,a=t.getReferenceClientRect,s=t.moveTransition,u=ee()?S(z).arrow:null,c=a?{getBoundingClientRect:a,contextElement:a.contextElement||te()}:o,p=[{name:"offset",options:{offset:i}},{name:"preventOverflow",options:{padding:{top:2,bottom:2,left:5,right:5}}},{name:"flip",options:{padding:5}},{name:"computeStyles",options:{adaptive:!s}},{name:"$$tippy",enabled:!0,phase:"beforeWrite",requires:["computeStyles"],fn:function(e){var t=e.state;if(ee()){var n=re().box;["placement","reference-hidden","escaped"].forEach((function(e){"placement"===e?n.setAttribute("data-placement",t.placement):t.attributes.popper["data-popper-"+e]?n.setAttribute("data-"+e,""):n.removeAttribute("data-"+e)})),t.attributes.popper={}}}}];ee()&&u&&p.push({name:"arrow",options:{element:u,padding:3}}),p.push.apply(p,(null==n?void 0:n.modifiers)||[]),$.popperInstance=e.createPopper(c,z,Object.assign({},n,{placement:r,onFirstUpdate:A,modifiers:p}))}function Te(){$.popperInstance&&($.popperInstance.destroy(),$.popperInstance=null)}function Ae(){return f(z.querySelectorAll("[data-tippy-root]"))}function Le(e){$.clearDelayTimeouts(),e&&ae("onTrigger",[$,e]),de();var t=oe(!0),n=Q(),r=n[0],o=n[1];x.isTouch&&"hold"===r&&o&&(t=o),t?v=setTimeout((function(){$.show()}),t):$.show()}function De(e){if($.clearDelayTimeouts(),ae("onUntrigger",[$,e]),$.state.isVisible){if(!($.props.trigger.indexOf("mouseenter")>=0&&$.props.trigger.indexOf("click")>=0&&["mouseleave","mousemove"].indexOf(e.type)>=0&&V)){var t=oe(!1);t?g=setTimeout((function(){$.state.isVisible&&$.hide()}),t):h=requestAnimationFrame((function(){$.hide()}))}}else ve()}}function F(e,n){void 0===n&&(n={});var r=R.plugins.concat(n.plugins||[]);document.addEventListener("touchstart",T,t),window.addEventListener("blur",L);var o=Object.assign({},n,{plugins:r}),i=h(e).reduce((function(e,t){var n=t&&_(t,o);return n&&e.push(n),e}),[]);return v(e)?i[0]:i}F.defaultProps=R,F.setDefaultProps=function(e){Object.keys(e).forEach((function(t){R[t]=e[t]}))},F.currentInput=x;var W=Object.assign({},e.applyStyles,{effect:function(e){var t=e.state,n={popper:{position:t.options.strategy,left:"0",top:"0",margin:"0"},arrow:{position:"absolute"},reference:{}};Object.assign(t.elements.popper.style,n.popper),t.styles=n,t.elements.arrow&&Object.assign(t.elements.arrow.style,n.arrow)}}),X={mouseover:"mouseenter",focusin:"focus",click:"click"};var Y={name:"animateFill",defaultValue:!1,fn:function(e){var t;if(null==(t=e.props.render)||!t.$$tippy)return{};var n=S(e.popper),r=n.box,o=n.content,i=e.props.animateFill?function(){var e=d();return e.className="tippy-backdrop",y([e],"hidden"),e}():null;return{onCreate:function(){i&&(r.insertBefore(i,r.firstElementChild),r.setAttribute("data-animatefill",""),r.style.overflow="hidden",e.setProps({arrow:!1,animation:"shift-away"}))},onMount:function(){if(i){var e=r.style.transitionDuration,t=Number(e.replace("ms",""));o.style.transitionDelay=Math.round(t/10)+"ms",i.style.transitionDuration=e,y([i],"visible")}},onShow:function(){i&&(i.style.transitionDuration="0ms")},onHide:function(){i&&y([i],"hidden")}}}};var $={clientX:0,clientY:0},q=[];function z(e){var t=e.clientX,n=e.clientY;$={clientX:t,clientY:n}}var J={name:"followCursor",defaultValue:!1,fn:function(e){var t=e.reference,n=w(e.props.triggerTarget||t),r=!1,o=!1,i=!0,a=e.props;function s(){return"initial"===e.props.followCursor&&e.state.isVisible}function u(){n.addEventListener("mousemove",f)}function c(){n.removeEventListener("mousemove",f)}function p(){r=!0,e.setProps({getReferenceClientRect:null}),r=!1}function f(n){var r=!n.target||t.contains(n.target),o=e.props.followCursor,i=n.clientX,a=n.clientY,s=t.getBoundingClientRect(),u=i-s.left,c=a-s.top;!r&&e.props.interactive||e.setProps({getReferenceClientRect:function(){var e=t.getBoundingClientRect(),n=i,r=a;"initial"===o&&(n=e.left+u,r=e.top+c);var s="horizontal"===o?e.top:r,p="vertical"===o?e.right:n,f="horizontal"===o?e.bottom:r,l="vertical"===o?e.left:n;return{width:p-l,height:f-s,top:s,right:p,bottom:f,left:l}}})}function l(){e.props.followCursor&&(q.push({instance:e,doc:n}),function(e){e.addEventListener("mousemove",z)}(n))}function d(){0===(q=q.filter((function(t){return t.instance!==e}))).filter((function(e){return e.doc===n})).length&&function(e){e.removeEventListener("mousemove",z)}(n)}return{onCreate:l,onDestroy:d,onBeforeUpdate:function(){a=e.props},onAfterUpdate:function(t,n){var i=n.followCursor;r||void 0!==i&&a.followCursor!==i&&(d(),i?(l(),!e.state.isMounted||o||s()||u()):(c(),p()))},onMount:function(){e.props.followCursor&&!o&&(i&&(f($),i=!1),s()||u())},onTrigger:function(e,t){m(t)&&($={clientX:t.clientX,clientY:t.clientY}),o="focus"===t.type},onHidden:function(){e.props.followCursor&&(p(),c(),i=!0)}}}};var G={name:"inlinePositioning",defaultValue:!1,fn:function(e){var t,n=e.reference;var r=-1,o=!1,i=[],a={name:"tippyInlinePositioning",enabled:!0,phase:"afterWrite",fn:function(o){var a=o.state;e.props.inlinePositioning&&(-1!==i.indexOf(a.placement)&&(i=[]),t!==a.placement&&-1===i.indexOf(a.placement)&&(i.push(a.placement),e.setProps({getReferenceClientRect:function(){return function(e){return function(e,t,n,r){if(n.length<2||null===e)return t;if(2===n.length&&r>=0&&n[0].left>n[1].right)return n[r]||t;switch(e){case"top":case"bottom":var o=n[0],i=n[n.length-1],a="top"===e,s=o.top,u=i.bottom,c=a?o.left:i.left,p=a?o.right:i.right;return{top:s,bottom:u,left:c,right:p,width:p-c,height:u-s};case"left":case"right":var f=Math.min.apply(Math,n.map((function(e){return e.left}))),l=Math.max.apply(Math,n.map((function(e){return e.right}))),d=n.filter((function(t){return"left"===e?t.left===f:t.right===l})),v=d[0].top,m=d[d.length-1].bottom;return{top:v,bottom:m,left:f,right:l,width:l-f,height:m-v};default:return t}}(p(e),n.getBoundingClientRect(),f(n.getClientRects()),r)}(a.placement)}})),t=a.placement)}};function s(){var t;o||(t=function(e,t){var n;return{popperOptions:Object.assign({},e.popperOptions,{modifiers:[].concat(((null==(n=e.popperOptions)?void 0:n.modifiers)||[]).filter((function(e){return e.name!==t.name})),[t])})}}(e.props,a),o=!0,e.setProps(t),o=!1)}return{onCreate:s,onAfterUpdate:s,onTrigger:function(t,n){if(m(n)){var o=f(e.reference.getClientRects()),i=o.find((function(e){return e.left-2<=n.clientX&&e.right+2>=n.clientX&&e.top-2<=n.clientY&&e.bottom+2>=n.clientY})),a=o.indexOf(i);r=a>-1?a:r}},onHidden:function(){r=-1}}}};var K={name:"sticky",defaultValue:!1,fn:function(e){var t=e.reference,n=e.popper;function r(t){return!0===e.props.sticky||e.props.sticky===t}var o=null,i=null;function a(){var s=r("reference")?(e.popperInstance?e.popperInstance.state.elements.reference:t).getBoundingClientRect():null,u=r("popper")?n.getBoundingClientRect():null;(s&&Q(o,s)||u&&Q(i,u))&&e.popperInstance&&e.popperInstance.update(),o=s,i=u,e.state.isMounted&&requestAnimationFrame(a)}return{onMount:function(){e.props.sticky&&a()}}}};function Q(e,t){return!e||!t||(e.top!==t.top||e.right!==t.right||e.bottom!==t.bottom||e.left!==t.left)}return F.setDefaultProps({plugins:[Y,J,G,K],render:N}),F.createSingleton=function(e,t){var n;void 0===t&&(t={});var r,o=e,i=[],a=[],c=t.overrides,p=[],f=!1;function l(){a=o.map((function(e){return u(e.props.triggerTarget||e.reference)})).reduce((function(e,t){return e.concat(t)}),[])}function v(){i=o.map((function(e){return e.reference}))}function m(e){o.forEach((function(t){e?t.enable():t.disable()}))}function g(e){return o.map((function(t){var n=t.setProps;return t.setProps=function(o){n(o),t.reference===r&&e.setProps(o)},function(){t.setProps=n}}))}function h(e,t){var n=a.indexOf(t);if(t!==r){r=t;var s=(c||[]).concat("content").reduce((function(e,t){return e[t]=o[n].props[t],e}),{});e.setProps(Object.assign({},s,{getReferenceClientRect:"function"==typeof s.getReferenceClientRect?s.getReferenceClientRect:function(){var e;return null==(e=i[n])?void 0:e.getBoundingClientRect()}}))}}m(!1),v(),l();var b={fn:function(){return{onDestroy:function(){m(!0)},onHidden:function(){r=null},onClickOutside:function(e){e.props.showOnCreate&&!f&&(f=!0,r=null)},onShow:function(e){e.props.showOnCreate&&!f&&(f=!0,h(e,i[0]))},onTrigger:function(e,t){h(e,t.currentTarget)}}}},y=F(d(),Object.assign({},s(t,["overrides"]),{plugins:[b].concat(t.plugins||[]),triggerTarget:a,popperOptions:Object.assign({},t.popperOptions,{modifiers:[].concat((null==(n=t.popperOptions)?void 0:n.modifiers)||[],[W])})})),w=y.show;y.show=function(e){if(w(),!r&&null==e)return h(y,i[0]);if(!r||null!=e){if("number"==typeof e)return i[e]&&h(y,i[e]);if(o.indexOf(e)>=0){var t=e.reference;return h(y,t)}return i.indexOf(e)>=0?h(y,e):void 0}},y.showNext=function(){var e=i[0];if(!r)return y.show(0);var t=i.indexOf(r);y.show(i[t+1]||e)},y.showPrevious=function(){var e=i[i.length-1];if(!r)return y.show(e);var t=i.indexOf(r),n=i[t-1]||e;y.show(n)};var E=y.setProps;return y.setProps=function(e){c=e.overrides||c,E(e)},y.setInstances=function(e){m(!0),p.forEach((function(e){return e()})),o=e,m(!1),v(),l(),p=g(y),y.setProps({triggerTarget:a})},p=g(y),y},F.delegate=function(e,n){var r=[],o=[],i=!1,a=n.target,c=s(n,["target"]),p=Object.assign({},c,{trigger:"manual",touch:!1}),f=Object.assign({touch:R.touch},c,{showOnCreate:!0}),l=F(e,p);function d(e){if(e.target&&!i){var t=e.target.closest(a);if(t){var r=t.getAttribute("data-tippy-trigger")||n.trigger||R.trigger;if(!t._tippy&&!("touchstart"===e.type&&"boolean"==typeof f.touch||"touchstart"!==e.type&&r.indexOf(X[e.type])<0)){var s=F(t,f);s&&(o=o.concat(s))}}}}function v(e,t,n,o){void 0===o&&(o=!1),e.addEventListener(t,n,o),r.push({node:e,eventType:t,handler:n,options:o})}return u(l).forEach((function(e){var n=e.destroy,a=e.enable,s=e.disable;e.destroy=function(e){void 0===e&&(e=!0),e&&o.forEach((function(e){e.destroy()})),o=[],r.forEach((function(e){var t=e.node,n=e.eventType,r=e.handler,o=e.options;t.removeEventListener(n,r,o)})),r=[],n()},e.enable=function(){a(),o.forEach((function(e){return e.enable()})),i=!1},e.disable=function(){s(),o.forEach((function(e){return e.disable()})),i=!0},function(e){var n=e.reference;v(n,"touchstart",d,t),v(n,"mouseover",d),v(n,"focusin",d),v(n,"click",d)}(e)})),l},F.hideAll=function(e){var t=void 0===e?{}:e,n=t.exclude,r=t.duration;U.forEach((function(e){var t=!1;if(n&&(t=g(n)?e.reference===n:e.popper===n.popper),!t){var o=e.props.duration;e.setProps({duration:r}),e.hide(),e.state.isDestroyed||e.setProps({duration:o})}}))},F.roundArrow='',F})); + diff --git a/site_libs/quarto-listing/list.min.js b/site_libs/quarto-listing/list.min.js new file mode 100644 index 00000000..81318815 --- /dev/null +++ b/site_libs/quarto-listing/list.min.js @@ -0,0 +1,2 @@ +var List;List=function(){var t={"./src/add-async.js":function(t){t.exports=function(t){return function e(r,n,s){var i=r.splice(0,50);s=(s=s||[]).concat(t.add(i)),r.length>0?setTimeout((function(){e(r,n,s)}),1):(t.update(),n(s))}}},"./src/filter.js":function(t){t.exports=function(t){return t.handlers.filterStart=t.handlers.filterStart||[],t.handlers.filterComplete=t.handlers.filterComplete||[],function(e){if(t.trigger("filterStart"),t.i=1,t.reset.filter(),void 0===e)t.filtered=!1;else{t.filtered=!0;for(var r=t.items,n=0,s=r.length;nv.page,a=new g(t[s],void 0,n),v.items.push(a),r.push(a)}return v.update(),r}m(t.slice(0),e)}},this.show=function(t,e){return this.i=t,this.page=e,v.update(),v},this.remove=function(t,e,r){for(var n=0,s=0,i=v.items.length;s-1&&r.splice(n,1),v},this.trigger=function(t){for(var e=v.handlers[t].length;e--;)v.handlers[t][e](v);return v},this.reset={filter:function(){for(var t=v.items,e=t.length;e--;)t[e].filtered=!1;return v},search:function(){for(var t=v.items,e=t.length;e--;)t[e].found=!1;return v}},this.update=function(){var t=v.items,e=t.length;v.visibleItems=[],v.matchingItems=[],v.templater.clear();for(var r=0;r=v.i&&v.visibleItems.lengthe},innerWindow:function(t,e,r){return t>=e-r&&t<=e+r},dotted:function(t,e,r,n,s,i,a){return this.dottedLeft(t,e,r,n,s,i)||this.dottedRight(t,e,r,n,s,i,a)},dottedLeft:function(t,e,r,n,s,i){return e==r+1&&!this.innerWindow(e,s,i)&&!this.right(e,n)},dottedRight:function(t,e,r,n,s,i,a){return!t.items[a-1].values().dotted&&(e==n&&!this.innerWindow(e,s,i)&&!this.right(e,n))}};return function(e){var n=new i(t.listContainer.id,{listClass:e.paginationClass||"pagination",item:e.item||"
  • ",valueNames:["page","dotted"],searchClass:"pagination-search-that-is-not-supposed-to-exist",sortClass:"pagination-sort-that-is-not-supposed-to-exist"});s.bind(n.listContainer,"click",(function(e){var r=e.target||e.srcElement,n=t.utils.getAttribute(r,"data-page"),s=t.utils.getAttribute(r,"data-i");s&&t.show((s-1)*n+1,n)})),t.on("updated",(function(){r(n,e)})),r(n,e)}}},"./src/parse.js":function(t,e,r){t.exports=function(t){var e=r("./src/item.js")(t),n=function(r,n){for(var s=0,i=r.length;s0?setTimeout((function(){e(r,s)}),1):(t.update(),t.trigger("parseComplete"))};return t.handlers.parseComplete=t.handlers.parseComplete||[],function(){var e=function(t){for(var e=t.childNodes,r=[],n=0,s=e.length;n]/g.exec(t)){var e=document.createElement("tbody");return e.innerHTML=t,e.firstElementChild}if(-1!==t.indexOf("<")){var r=document.createElement("div");return r.innerHTML=t,r.firstElementChild}}},a=function(e,r,n){var s=void 0,i=function(e){for(var r=0,n=t.valueNames.length;r=1;)t.list.removeChild(t.list.firstChild)},function(){var r;if("function"!=typeof t.item){if(!(r="string"==typeof t.item?-1===t.item.indexOf("<")?document.getElementById(t.item):i(t.item):s()))throw new Error("The list needs to have at least one item on init otherwise you'll have to add a template.");r=n(r,t.valueNames),e=function(){return r.cloneNode(!0)}}else e=function(e){var r=t.item(e);return i(r)}}()};t.exports=function(t){return new e(t)}},"./src/utils/classes.js":function(t,e,r){var n=r("./src/utils/index-of.js"),s=/\s+/;Object.prototype.toString;function i(t){if(!t||!t.nodeType)throw new Error("A DOM element reference is required");this.el=t,this.list=t.classList}t.exports=function(t){return new i(t)},i.prototype.add=function(t){if(this.list)return this.list.add(t),this;var e=this.array();return~n(e,t)||e.push(t),this.el.className=e.join(" "),this},i.prototype.remove=function(t){if(this.list)return this.list.remove(t),this;var e=this.array(),r=n(e,t);return~r&&e.splice(r,1),this.el.className=e.join(" "),this},i.prototype.toggle=function(t,e){return this.list?(void 0!==e?e!==this.list.toggle(t,e)&&this.list.toggle(t):this.list.toggle(t),this):(void 0!==e?e?this.add(t):this.remove(t):this.has(t)?this.remove(t):this.add(t),this)},i.prototype.array=function(){var t=(this.el.getAttribute("class")||"").replace(/^\s+|\s+$/g,"").split(s);return""===t[0]&&t.shift(),t},i.prototype.has=i.prototype.contains=function(t){return this.list?this.list.contains(t):!!~n(this.array(),t)}},"./src/utils/events.js":function(t,e,r){var n=window.addEventListener?"addEventListener":"attachEvent",s=window.removeEventListener?"removeEventListener":"detachEvent",i="addEventListener"!==n?"on":"",a=r("./src/utils/to-array.js");e.bind=function(t,e,r,s){for(var o=0,l=(t=a(t)).length;o32)return!1;var a=n,o=function(){var t,r={};for(t=0;t=p;b--){var j=o[t.charAt(b-1)];if(C[b]=0===m?(C[b+1]<<1|1)&j:(C[b+1]<<1|1)&j|(v[b+1]|v[b])<<1|1|v[b+1],C[b]&d){var x=l(m,b-1);if(x<=u){if(u=x,!((c=b-1)>a))break;p=Math.max(1,2*a-c)}}}if(l(m+1,a)>u)break;v=C}return!(c<0)}},"./src/utils/get-attribute.js":function(t){t.exports=function(t,e){var r=t.getAttribute&&t.getAttribute(e)||null;if(!r)for(var n=t.attributes,s=n.length,i=0;i=48&&t<=57}function i(t,e){for(var i=(t+="").length,a=(e+="").length,o=0,l=0;o=i&&l=a?-1:l>=a&&o=i?1:i-a}i.caseInsensitive=i.i=function(t,e){return i((""+t).toLowerCase(),(""+e).toLowerCase())},Object.defineProperties(i,{alphabet:{get:function(){return e},set:function(t){r=[];var s=0;if(e=t)for(;s { + if (categoriesLoaded) { + activateCategory(category); + setCategoryHash(category); + } +}; + +window["quarto-listing-loaded"] = () => { + // Process any existing hash + const hash = getHash(); + + if (hash) { + // If there is a category, switch to that + if (hash.category) { + activateCategory(hash.category); + } + // Paginate a specific listing + const listingIds = Object.keys(window["quarto-listings"]); + for (const listingId of listingIds) { + const page = hash[getListingPageKey(listingId)]; + if (page) { + showPage(listingId, page); + } + } + } + + const listingIds = Object.keys(window["quarto-listings"]); + for (const listingId of listingIds) { + // The actual list + const list = window["quarto-listings"][listingId]; + + // Update the handlers for pagination events + refreshPaginationHandlers(listingId); + + // Render any visible items that need it + renderVisibleProgressiveImages(list); + + // Whenever the list is updated, we also need to + // attach handlers to the new pagination elements + // and refresh any newly visible items. + list.on("updated", function () { + renderVisibleProgressiveImages(list); + setTimeout(() => refreshPaginationHandlers(listingId)); + + // Show or hide the no matching message + toggleNoMatchingMessage(list); + }); + } +}; + +window.document.addEventListener("DOMContentLoaded", function (_event) { + // Attach click handlers to categories + const categoryEls = window.document.querySelectorAll( + ".quarto-listing-category .category" + ); + + for (const categoryEl of categoryEls) { + const category = categoryEl.getAttribute("data-category"); + categoryEl.onclick = () => { + activateCategory(category); + setCategoryHash(category); + }; + } + + // Attach a click handler to the category title + // (there should be only one, but since it is a class name, handle N) + const categoryTitleEls = window.document.querySelectorAll( + ".quarto-listing-category-title" + ); + for (const categoryTitleEl of categoryTitleEls) { + categoryTitleEl.onclick = () => { + activateCategory(""); + setCategoryHash(""); + }; + } + + categoriesLoaded = true; +}); + +function toggleNoMatchingMessage(list) { + const selector = `#${list.listContainer.id} .listing-no-matching`; + const noMatchingEl = window.document.querySelector(selector); + if (noMatchingEl) { + if (list.visibleItems.length === 0) { + noMatchingEl.classList.remove("d-none"); + } else { + if (!noMatchingEl.classList.contains("d-none")) { + noMatchingEl.classList.add("d-none"); + } + } + } +} + +function setCategoryHash(category) { + setHash({ category }); +} + +function setPageHash(listingId, page) { + const currentHash = getHash() || {}; + currentHash[getListingPageKey(listingId)] = page; + setHash(currentHash); +} + +function getListingPageKey(listingId) { + return `${listingId}-page`; +} + +function refreshPaginationHandlers(listingId) { + const listingEl = window.document.getElementById(listingId); + const paginationEls = listingEl.querySelectorAll( + ".pagination li.page-item:not(.disabled) .page.page-link" + ); + for (const paginationEl of paginationEls) { + paginationEl.onclick = (sender) => { + setPageHash(listingId, sender.target.getAttribute("data-i")); + showPage(listingId, sender.target.getAttribute("data-i")); + return false; + }; + } +} + +function renderVisibleProgressiveImages(list) { + // Run through the visible items and render any progressive images + for (const item of list.visibleItems) { + const itemEl = item.elm; + if (itemEl) { + const progressiveImgs = itemEl.querySelectorAll( + `img[${kProgressiveAttr}]` + ); + for (const progressiveImg of progressiveImgs) { + const srcValue = progressiveImg.getAttribute(kProgressiveAttr); + if (srcValue) { + progressiveImg.setAttribute("src", srcValue); + } + progressiveImg.removeAttribute(kProgressiveAttr); + } + } + } +} + +function getHash() { + // Hashes are of the form + // #name:value|name1:value1|name2:value2 + const currentUrl = new URL(window.location); + const hashRaw = currentUrl.hash ? currentUrl.hash.slice(1) : undefined; + return parseHash(hashRaw); +} + +const kAnd = "&"; +const kEquals = "="; + +function parseHash(hash) { + if (!hash) { + return undefined; + } + const hasValuesStrs = hash.split(kAnd); + const hashValues = hasValuesStrs + .map((hashValueStr) => { + const vals = hashValueStr.split(kEquals); + if (vals.length === 2) { + return { name: vals[0], value: vals[1] }; + } else { + return undefined; + } + }) + .filter((value) => { + return value !== undefined; + }); + + const hashObj = {}; + hashValues.forEach((hashValue) => { + hashObj[hashValue.name] = decodeURIComponent(hashValue.value); + }); + return hashObj; +} + +function makeHash(obj) { + return Object.keys(obj) + .map((key) => { + return `${key}${kEquals}${obj[key]}`; + }) + .join(kAnd); +} + +function setHash(obj) { + const hash = makeHash(obj); + window.history.pushState(null, null, `#${hash}`); +} + +function showPage(listingId, page) { + const list = window["quarto-listings"][listingId]; + if (list) { + list.show((page - 1) * list.page + 1, list.page); + } +} + +function activateCategory(category) { + // Deactivate existing categories + const activeEls = window.document.querySelectorAll( + ".quarto-listing-category .category.active" + ); + for (const activeEl of activeEls) { + activeEl.classList.remove("active"); + } + + // Activate this category + const categoryEl = window.document.querySelector( + `.quarto-listing-category .category[data-category='${category}'` + ); + if (categoryEl) { + categoryEl.classList.add("active"); + } + + // Filter the listings to this category + filterListingCategory(category); +} + +function filterListingCategory(category) { + const listingIds = Object.keys(window["quarto-listings"]); + for (const listingId of listingIds) { + const list = window["quarto-listings"][listingId]; + if (list) { + if (category === "") { + // resets the filter + list.filter(); + } else { + // filter to this category + list.filter(function (item) { + const itemValues = item.values(); + if (itemValues.categories !== null) { + const categories = itemValues.categories.split(","); + return categories.includes(category); + } else { + return false; + } + }); + } + } + } +} diff --git a/site_libs/quarto-nav/headroom.min.js b/site_libs/quarto-nav/headroom.min.js new file mode 100644 index 00000000..b08f1dff --- /dev/null +++ b/site_libs/quarto-nav/headroom.min.js @@ -0,0 +1,7 @@ +/*! + * headroom.js v0.12.0 - Give your page some headroom. Hide your header until you need it + * Copyright (c) 2020 Nick Williams - http://wicky.nillia.ms/headroom.js + * License: MIT + */ + +!function(t,n){"object"==typeof exports&&"undefined"!=typeof module?module.exports=n():"function"==typeof define&&define.amd?define(n):(t=t||self).Headroom=n()}(this,function(){"use strict";function t(){return"undefined"!=typeof window}function d(t){return function(t){return t&&t.document&&function(t){return 9===t.nodeType}(t.document)}(t)?function(t){var n=t.document,o=n.body,s=n.documentElement;return{scrollHeight:function(){return Math.max(o.scrollHeight,s.scrollHeight,o.offsetHeight,s.offsetHeight,o.clientHeight,s.clientHeight)},height:function(){return t.innerHeight||s.clientHeight||o.clientHeight},scrollY:function(){return void 0!==t.pageYOffset?t.pageYOffset:(s||o.parentNode||o).scrollTop}}}(t):function(t){return{scrollHeight:function(){return Math.max(t.scrollHeight,t.offsetHeight,t.clientHeight)},height:function(){return Math.max(t.offsetHeight,t.clientHeight)},scrollY:function(){return t.scrollTop}}}(t)}function n(t,s,e){var n,o=function(){var n=!1;try{var t={get passive(){n=!0}};window.addEventListener("test",t,t),window.removeEventListener("test",t,t)}catch(t){n=!1}return n}(),i=!1,r=d(t),l=r.scrollY(),a={};function c(){var t=Math.round(r.scrollY()),n=r.height(),o=r.scrollHeight();a.scrollY=t,a.lastScrollY=l,a.direction=ls.tolerance[a.direction],e(a),l=t,i=!1}function h(){i||(i=!0,n=requestAnimationFrame(c))}var u=!!o&&{passive:!0,capture:!1};return t.addEventListener("scroll",h,u),c(),{destroy:function(){cancelAnimationFrame(n),t.removeEventListener("scroll",h,u)}}}function o(t){return t===Object(t)?t:{down:t,up:t}}function s(t,n){n=n||{},Object.assign(this,s.options,n),this.classes=Object.assign({},s.options.classes,n.classes),this.elem=t,this.tolerance=o(this.tolerance),this.offset=o(this.offset),this.initialised=!1,this.frozen=!1}return s.prototype={constructor:s,init:function(){return s.cutsTheMustard&&!this.initialised&&(this.addClass("initial"),this.initialised=!0,setTimeout(function(t){t.scrollTracker=n(t.scroller,{offset:t.offset,tolerance:t.tolerance},t.update.bind(t))},100,this)),this},destroy:function(){this.initialised=!1,Object.keys(this.classes).forEach(this.removeClass,this),this.scrollTracker.destroy()},unpin:function(){!this.hasClass("pinned")&&this.hasClass("unpinned")||(this.addClass("unpinned"),this.removeClass("pinned"),this.onUnpin&&this.onUnpin.call(this))},pin:function(){this.hasClass("unpinned")&&(this.addClass("pinned"),this.removeClass("unpinned"),this.onPin&&this.onPin.call(this))},freeze:function(){this.frozen=!0,this.addClass("frozen")},unfreeze:function(){this.frozen=!1,this.removeClass("frozen")},top:function(){this.hasClass("top")||(this.addClass("top"),this.removeClass("notTop"),this.onTop&&this.onTop.call(this))},notTop:function(){this.hasClass("notTop")||(this.addClass("notTop"),this.removeClass("top"),this.onNotTop&&this.onNotTop.call(this))},bottom:function(){this.hasClass("bottom")||(this.addClass("bottom"),this.removeClass("notBottom"),this.onBottom&&this.onBottom.call(this))},notBottom:function(){this.hasClass("notBottom")||(this.addClass("notBottom"),this.removeClass("bottom"),this.onNotBottom&&this.onNotBottom.call(this))},shouldUnpin:function(t){return"down"===t.direction&&!t.top&&t.toleranceExceeded},shouldPin:function(t){return"up"===t.direction&&t.toleranceExceeded||t.top},addClass:function(t){this.elem.classList.add.apply(this.elem.classList,this.classes[t].split(" "))},removeClass:function(t){this.elem.classList.remove.apply(this.elem.classList,this.classes[t].split(" "))},hasClass:function(t){return this.classes[t].split(" ").every(function(t){return this.classList.contains(t)},this.elem)},update:function(t){t.isOutOfBounds||!0!==this.frozen&&(t.top?this.top():this.notTop(),t.bottom?this.bottom():this.notBottom(),this.shouldUnpin(t)?this.unpin():this.shouldPin(t)&&this.pin())}},s.options={tolerance:{up:0,down:0},offset:0,scroller:t()?window:null,classes:{frozen:"headroom--frozen",pinned:"headroom--pinned",unpinned:"headroom--unpinned",top:"headroom--top",notTop:"headroom--not-top",bottom:"headroom--bottom",notBottom:"headroom--not-bottom",initial:"headroom"}},s.cutsTheMustard=!!(t()&&function(){}.bind&&"classList"in document.documentElement&&Object.assign&&Object.keys&&requestAnimationFrame),s}); diff --git a/site_libs/quarto-nav/quarto-nav.js b/site_libs/quarto-nav/quarto-nav.js new file mode 100644 index 00000000..3b21201f --- /dev/null +++ b/site_libs/quarto-nav/quarto-nav.js @@ -0,0 +1,277 @@ +const headroomChanged = new CustomEvent("quarto-hrChanged", { + detail: {}, + bubbles: true, + cancelable: false, + composed: false, +}); + +window.document.addEventListener("DOMContentLoaded", function () { + let init = false; + + // Manage the back to top button, if one is present. + let lastScrollTop = window.pageYOffset || document.documentElement.scrollTop; + const scrollDownBuffer = 5; + const scrollUpBuffer = 35; + const btn = document.getElementById("quarto-back-to-top"); + const hideBackToTop = () => { + btn.style.display = "none"; + }; + const showBackToTop = () => { + btn.style.display = "inline-block"; + }; + if (btn) { + window.document.addEventListener( + "scroll", + function () { + const currentScrollTop = + window.pageYOffset || document.documentElement.scrollTop; + + // Shows and hides the button 'intelligently' as the user scrolls + if (currentScrollTop - scrollDownBuffer > lastScrollTop) { + hideBackToTop(); + lastScrollTop = currentScrollTop <= 0 ? 0 : currentScrollTop; + } else if (currentScrollTop < lastScrollTop - scrollUpBuffer) { + showBackToTop(); + lastScrollTop = currentScrollTop <= 0 ? 0 : currentScrollTop; + } + + // Show the button at the bottom, hides it at the top + if (currentScrollTop <= 0) { + hideBackToTop(); + } else if ( + window.innerHeight + currentScrollTop >= + document.body.offsetHeight + ) { + showBackToTop(); + } + }, + false + ); + } + + function throttle(func, wait) { + var timeout; + return function () { + const context = this; + const args = arguments; + const later = function () { + clearTimeout(timeout); + timeout = null; + func.apply(context, args); + }; + + if (!timeout) { + timeout = setTimeout(later, wait); + } + }; + } + + function headerOffset() { + // Set an offset if there is are fixed top navbar + const headerEl = window.document.querySelector("header.fixed-top"); + if (headerEl) { + return headerEl.clientHeight; + } else { + return 0; + } + } + + function footerOffset() { + const footerEl = window.document.querySelector("footer.footer"); + if (footerEl) { + return footerEl.clientHeight; + } else { + return 0; + } + } + + function updateDocumentOffsetWithoutAnimation() { + updateDocumentOffset(false); + } + + function updateDocumentOffset(animated) { + // set body offset + const topOffset = headerOffset(); + const bodyOffset = topOffset + footerOffset(); + const bodyEl = window.document.body; + bodyEl.setAttribute("data-bs-offset", topOffset); + bodyEl.style.paddingTop = topOffset + "px"; + + // deal with sidebar offsets + const sidebars = window.document.querySelectorAll( + ".sidebar, .headroom-target" + ); + sidebars.forEach((sidebar) => { + if (!animated) { + sidebar.classList.add("notransition"); + // Remove the no transition class after the animation has time to complete + setTimeout(function () { + sidebar.classList.remove("notransition"); + }, 201); + } + + if (window.Headroom && sidebar.classList.contains("sidebar-unpinned")) { + sidebar.style.top = "0"; + sidebar.style.maxHeight = "100vh"; + } else { + sidebar.style.top = topOffset + "px"; + sidebar.style.maxHeight = "calc(100vh - " + topOffset + "px)"; + } + }); + + // allow space for footer + const mainContainer = window.document.querySelector(".quarto-container"); + if (mainContainer) { + mainContainer.style.minHeight = "calc(100vh - " + bodyOffset + "px)"; + } + + // link offset + let linkStyle = window.document.querySelector("#quarto-target-style"); + if (!linkStyle) { + linkStyle = window.document.createElement("style"); + linkStyle.setAttribute("id", "quarto-target-style"); + window.document.head.appendChild(linkStyle); + } + while (linkStyle.firstChild) { + linkStyle.removeChild(linkStyle.firstChild); + } + if (topOffset > 0) { + linkStyle.appendChild( + window.document.createTextNode(` + section:target::before { + content: ""; + display: block; + height: ${topOffset}px; + margin: -${topOffset}px 0 0; + }`) + ); + } + if (init) { + window.dispatchEvent(headroomChanged); + } + init = true; + } + + // initialize headroom + var header = window.document.querySelector("#quarto-header"); + if (header && window.Headroom) { + const headroom = new window.Headroom(header, { + tolerance: 5, + onPin: function () { + const sidebars = window.document.querySelectorAll( + ".sidebar, .headroom-target" + ); + sidebars.forEach((sidebar) => { + sidebar.classList.remove("sidebar-unpinned"); + }); + updateDocumentOffset(); + }, + onUnpin: function () { + const sidebars = window.document.querySelectorAll( + ".sidebar, .headroom-target" + ); + sidebars.forEach((sidebar) => { + sidebar.classList.add("sidebar-unpinned"); + }); + updateDocumentOffset(); + }, + }); + headroom.init(); + + let frozen = false; + window.quartoToggleHeadroom = function () { + if (frozen) { + headroom.unfreeze(); + frozen = false; + } else { + headroom.freeze(); + frozen = true; + } + }; + } + + window.addEventListener( + "hashchange", + function (e) { + if ( + getComputedStyle(document.documentElement).scrollBehavior !== "smooth" + ) { + window.scrollTo(0, window.pageYOffset - headerOffset()); + } + }, + false + ); + + // Observe size changed for the header + const headerEl = window.document.querySelector("header.fixed-top"); + if (headerEl && window.ResizeObserver) { + const observer = new window.ResizeObserver( + updateDocumentOffsetWithoutAnimation + ); + observer.observe(headerEl, { + attributes: true, + childList: true, + characterData: true, + }); + } else { + window.addEventListener( + "resize", + throttle(updateDocumentOffsetWithoutAnimation, 50) + ); + } + setTimeout(updateDocumentOffsetWithoutAnimation, 250); + + // fixup index.html links if we aren't on the filesystem + if (window.location.protocol !== "file:") { + const links = window.document.querySelectorAll("a"); + for (let i = 0; i < links.length; i++) { + if (links[i].href) { + links[i].href = links[i].href.replace(/\/index\.html/, "/"); + } + } + + // Fixup any sharing links that require urls + // Append url to any sharing urls + const sharingLinks = window.document.querySelectorAll( + "a.sidebar-tools-main-item" + ); + for (let i = 0; i < sharingLinks.length; i++) { + const sharingLink = sharingLinks[i]; + const href = sharingLink.getAttribute("href"); + if (href) { + sharingLink.setAttribute( + "href", + href.replace("|url|", window.location.href) + ); + } + } + + // Scroll the active navigation item into view, if necessary + const navSidebar = window.document.querySelector("nav#quarto-sidebar"); + if (navSidebar) { + // Find the active item + const activeItem = navSidebar.querySelector("li.sidebar-item a.active"); + if (activeItem) { + // Wait for the scroll height and height to resolve by observing size changes on the + // nav element that is scrollable + const resizeObserver = new ResizeObserver((_entries) => { + // The bottom of the element + const elBottom = activeItem.offsetTop; + const viewBottom = navSidebar.scrollTop + navSidebar.clientHeight; + + // The element height and scroll height are the same, then we are still loading + if (viewBottom !== navSidebar.scrollHeight) { + // Determine if the item isn't visible and scroll to it + if (elBottom >= viewBottom) { + navSidebar.scrollTop = elBottom; + } + + // stop observing now since we've completed the scroll + resizeObserver.unobserve(navSidebar); + } + }); + resizeObserver.observe(navSidebar); + } + } + } +}); diff --git a/site_libs/quarto-search/autocomplete.umd.js b/site_libs/quarto-search/autocomplete.umd.js new file mode 100644 index 00000000..619c57cc --- /dev/null +++ b/site_libs/quarto-search/autocomplete.umd.js @@ -0,0 +1,3 @@ +/*! @algolia/autocomplete-js 1.7.3 | MIT License | Ā© Algolia, Inc. and contributors | https://github.com/algolia/autocomplete */ +!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?t(exports):"function"==typeof define&&define.amd?define(["exports"],t):t((e="undefined"!=typeof globalThis?globalThis:e||self)["@algolia/autocomplete-js"]={})}(this,(function(e){"use strict";function t(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),n.push.apply(n,r)}return n}function n(e){for(var n=1;n=0||(o[n]=e[n]);return o}(e,t);if(Object.getOwnPropertySymbols){var i=Object.getOwnPropertySymbols(e);for(r=0;r=0||Object.prototype.propertyIsEnumerable.call(e,n)&&(o[n]=e[n])}return o}function a(e,t){return function(e){if(Array.isArray(e))return e}(e)||function(e,t){var n=null==e?null:"undefined"!=typeof Symbol&&e[Symbol.iterator]||e["@@iterator"];if(null==n)return;var r,o,i=[],u=!0,a=!1;try{for(n=n.call(e);!(u=(r=n.next()).done)&&(i.push(r.value),!t||i.length!==t);u=!0);}catch(e){a=!0,o=e}finally{try{u||null==n.return||n.return()}finally{if(a)throw o}}return i}(e,t)||l(e,t)||function(){throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}()}function c(e){return function(e){if(Array.isArray(e))return s(e)}(e)||function(e){if("undefined"!=typeof Symbol&&null!=e[Symbol.iterator]||null!=e["@@iterator"])return Array.from(e)}(e)||l(e)||function(){throw new TypeError("Invalid attempt to spread non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}()}function l(e,t){if(e){if("string"==typeof e)return s(e,t);var n=Object.prototype.toString.call(e).slice(8,-1);return"Object"===n&&e.constructor&&(n=e.constructor.name),"Map"===n||"Set"===n?Array.from(e):"Arguments"===n||/^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)?s(e,t):void 0}}function s(e,t){(null==t||t>e.length)&&(t=e.length);for(var n=0,r=new Array(t);n=n?null===r?null:0:o}function S(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),n.push.apply(n,r)}return n}function I(e,t,n){return t in e?Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[t]=n,e}function E(e,t){var n=[];return Promise.resolve(e(t)).then((function(e){return Promise.all(e.filter((function(e){return Boolean(e)})).map((function(e){if(e.sourceId,n.includes(e.sourceId))throw new Error("[Autocomplete] The `sourceId` ".concat(JSON.stringify(e.sourceId)," is not unique."));n.push(e.sourceId);var t=function(e){for(var t=1;te.length)&&(t=e.length);for(var n=0,r=new Array(t);ne.length)&&(t=e.length);for(var n=0,r=new Array(t);n=0||(o[n]=e[n]);return o}(e,t);if(Object.getOwnPropertySymbols){var i=Object.getOwnPropertySymbols(e);for(r=0;r=0||Object.prototype.propertyIsEnumerable.call(e,n)&&(o[n]=e[n])}return o}var ae,ce,le,se=null,pe=(ae=-1,ce=-1,le=void 0,function(e){var t=++ae;return Promise.resolve(e).then((function(e){return le&&t=0||(o[n]=e[n]);return o}(e,t);if(Object.getOwnPropertySymbols){var i=Object.getOwnPropertySymbols(e);for(r=0;r=0||Object.prototype.propertyIsEnumerable.call(e,n)&&(o[n]=e[n])}return o}var ye=["props","refresh","store"],be=["inputElement","formElement","panelElement"],Oe=["inputElement"],_e=["inputElement","maxLength"],Pe=["item","source"];function je(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),n.push.apply(n,r)}return n}function we(e){for(var t=1;t=0||(o[n]=e[n]);return o}(e,t);if(Object.getOwnPropertySymbols){var i=Object.getOwnPropertySymbols(e);for(r=0;r=0||Object.prototype.propertyIsEnumerable.call(e,n)&&(o[n]=e[n])}return o}function Ee(e){var t=e.props,n=e.refresh,r=e.store,o=Ie(e,ye);return{getEnvironmentProps:function(e){var n=e.inputElement,o=e.formElement,i=e.panelElement;function u(e){!r.getState().isOpen&&r.pendingRequests.isEmpty()||e.target===n||!1===[o,i].some((function(t){return n=t,r=e.target,n===r||n.contains(r);var n,r}))&&(r.dispatch("blur",null),t.debug||r.pendingRequests.cancelAll())}return we({onTouchStart:u,onMouseDown:u,onTouchMove:function(e){!1!==r.getState().isOpen&&n===t.environment.document.activeElement&&e.target!==n&&n.blur()}},Ie(e,be))},getRootProps:function(e){return we({role:"combobox","aria-expanded":r.getState().isOpen,"aria-haspopup":"listbox","aria-owns":r.getState().isOpen?"".concat(t.id,"-list"):void 0,"aria-labelledby":"".concat(t.id,"-label")},e)},getFormProps:function(e){return e.inputElement,we({action:"",noValidate:!0,role:"search",onSubmit:function(i){var u;i.preventDefault(),t.onSubmit(we({event:i,refresh:n,state:r.getState()},o)),r.dispatch("submit",null),null===(u=e.inputElement)||void 0===u||u.blur()},onReset:function(i){var u;i.preventDefault(),t.onReset(we({event:i,refresh:n,state:r.getState()},o)),r.dispatch("reset",null),null===(u=e.inputElement)||void 0===u||u.focus()}},Ie(e,Oe))},getLabelProps:function(e){return we({htmlFor:"".concat(t.id,"-input"),id:"".concat(t.id,"-label")},e)},getInputProps:function(e){var i;function u(e){(t.openOnFocus||Boolean(r.getState().query))&&fe(we({event:e,props:t,query:r.getState().completion||r.getState().query,refresh:n,store:r},o)),r.dispatch("focus",null)}var a=e||{};a.inputElement;var c=a.maxLength,l=void 0===c?512:c,s=Ie(a,_e),p=A(r.getState()),f=function(e){return Boolean(e&&e.match(C))}((null===(i=t.environment.navigator)||void 0===i?void 0:i.userAgent)||""),d=null!=p&&p.itemUrl&&!f?"go":"search";return we({"aria-autocomplete":"both","aria-activedescendant":r.getState().isOpen&&null!==r.getState().activeItemId?"".concat(t.id,"-item-").concat(r.getState().activeItemId):void 0,"aria-controls":r.getState().isOpen?"".concat(t.id,"-list"):void 0,"aria-labelledby":"".concat(t.id,"-label"),value:r.getState().completion||r.getState().query,id:"".concat(t.id,"-input"),autoComplete:"off",autoCorrect:"off",autoCapitalize:"off",enterKeyHint:d,spellCheck:"false",autoFocus:t.autoFocus,placeholder:t.placeholder,maxLength:l,type:"search",onChange:function(e){fe(we({event:e,props:t,query:e.currentTarget.value.slice(0,l),refresh:n,store:r},o))},onKeyDown:function(e){!function(e){var t=e.event,n=e.props,r=e.refresh,o=e.store,i=ge(e,de);if("ArrowUp"===t.key||"ArrowDown"===t.key){var u=function(){var e=n.environment.document.getElementById("".concat(n.id,"-item-").concat(o.getState().activeItemId));e&&(e.scrollIntoViewIfNeeded?e.scrollIntoViewIfNeeded(!1):e.scrollIntoView(!1))},a=function(){var e=A(o.getState());if(null!==o.getState().activeItemId&&e){var n=e.item,u=e.itemInputValue,a=e.itemUrl,c=e.source;c.onActive(ve({event:t,item:n,itemInputValue:u,itemUrl:a,refresh:r,source:c,state:o.getState()},i))}};t.preventDefault(),!1===o.getState().isOpen&&(n.openOnFocus||Boolean(o.getState().query))?fe(ve({event:t,props:n,query:o.getState().query,refresh:r,store:o},i)).then((function(){o.dispatch(t.key,{nextActiveItemId:n.defaultActiveItemId}),a(),setTimeout(u,0)})):(o.dispatch(t.key,{}),a(),u())}else if("Escape"===t.key)t.preventDefault(),o.dispatch(t.key,null),o.pendingRequests.cancelAll();else if("Tab"===t.key)o.dispatch("blur",null),o.pendingRequests.cancelAll();else if("Enter"===t.key){if(null===o.getState().activeItemId||o.getState().collections.every((function(e){return 0===e.items.length})))return void(n.debug||o.pendingRequests.cancelAll());t.preventDefault();var c=A(o.getState()),l=c.item,s=c.itemInputValue,p=c.itemUrl,f=c.source;if(t.metaKey||t.ctrlKey)void 0!==p&&(f.onSelect(ve({event:t,item:l,itemInputValue:s,itemUrl:p,refresh:r,source:f,state:o.getState()},i)),n.navigator.navigateNewTab({itemUrl:p,item:l,state:o.getState()}));else if(t.shiftKey)void 0!==p&&(f.onSelect(ve({event:t,item:l,itemInputValue:s,itemUrl:p,refresh:r,source:f,state:o.getState()},i)),n.navigator.navigateNewWindow({itemUrl:p,item:l,state:o.getState()}));else if(t.altKey);else{if(void 0!==p)return f.onSelect(ve({event:t,item:l,itemInputValue:s,itemUrl:p,refresh:r,source:f,state:o.getState()},i)),void n.navigator.navigate({itemUrl:p,item:l,state:o.getState()});fe(ve({event:t,nextState:{isOpen:!1},props:n,query:s,refresh:r,store:o},i)).then((function(){f.onSelect(ve({event:t,item:l,itemInputValue:s,itemUrl:p,refresh:r,source:f,state:o.getState()},i))}))}}}(we({event:e,props:t,refresh:n,store:r},o))},onFocus:u,onBlur:y,onClick:function(n){e.inputElement!==t.environment.document.activeElement||r.getState().isOpen||u(n)}},s)},getPanelProps:function(e){return we({onMouseDown:function(e){e.preventDefault()},onMouseLeave:function(){r.dispatch("mouseleave",null)}},e)},getListProps:function(e){return we({role:"listbox","aria-labelledby":"".concat(t.id,"-label"),id:"".concat(t.id,"-list")},e)},getItemProps:function(e){var i=e.item,u=e.source,a=Ie(e,Pe);return we({id:"".concat(t.id,"-item-").concat(i.__autocomplete_id),role:"option","aria-selected":r.getState().activeItemId===i.__autocomplete_id,onMouseMove:function(e){if(i.__autocomplete_id!==r.getState().activeItemId){r.dispatch("mousemove",i.__autocomplete_id);var t=A(r.getState());if(null!==r.getState().activeItemId&&t){var u=t.item,a=t.itemInputValue,c=t.itemUrl,l=t.source;l.onActive(we({event:e,item:u,itemInputValue:a,itemUrl:c,refresh:n,source:l,state:r.getState()},o))}}},onMouseDown:function(e){e.preventDefault()},onClick:function(e){var a=u.getItemInputValue({item:i,state:r.getState()}),c=u.getItemUrl({item:i,state:r.getState()});(c?Promise.resolve():fe(we({event:e,nextState:{isOpen:!1},props:t,query:a,refresh:n,store:r},o))).then((function(){u.onSelect(we({event:e,item:i,itemInputValue:a,itemUrl:c,refresh:n,source:u,state:r.getState()},o))}))}},a)}}}function Ae(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),n.push.apply(n,r)}return n}function Ce(e){for(var t=1;t0},reshape:function(e){return e.sources}},e),{},{id:null!==(n=e.id)&&void 0!==n?n:v(),plugins:o,initialState:H({activeItemId:null,query:"",completion:null,collections:[],isOpen:!1,status:"idle",context:{}},e.initialState),onStateChange:function(t){var n;null===(n=e.onStateChange)||void 0===n||n.call(e,t),o.forEach((function(e){var n;return null===(n=e.onStateChange)||void 0===n?void 0:n.call(e,t)}))},onSubmit:function(t){var n;null===(n=e.onSubmit)||void 0===n||n.call(e,t),o.forEach((function(e){var n;return null===(n=e.onSubmit)||void 0===n?void 0:n.call(e,t)}))},onReset:function(t){var n;null===(n=e.onReset)||void 0===n||n.call(e,t),o.forEach((function(e){var n;return null===(n=e.onReset)||void 0===n?void 0:n.call(e,t)}))},getSources:function(n){return Promise.all([].concat(F(o.map((function(e){return e.getSources}))),[e.getSources]).filter(Boolean).map((function(e){return E(e,n)}))).then((function(e){return d(e)})).then((function(e){return e.map((function(e){return H(H({},e),{},{onSelect:function(n){e.onSelect(n),t.forEach((function(e){var t;return null===(t=e.onSelect)||void 0===t?void 0:t.call(e,n)}))},onActive:function(n){e.onActive(n),t.forEach((function(e){var t;return null===(t=e.onActive)||void 0===t?void 0:t.call(e,n)}))}})}))}))},navigator:H({navigate:function(e){var t=e.itemUrl;r.location.assign(t)},navigateNewTab:function(e){var t=e.itemUrl,n=r.open(t,"_blank","noopener");null==n||n.focus()},navigateNewWindow:function(e){var t=e.itemUrl;r.open(t,"_blank","noopener")}},e.navigator)})}(e,t),r=R(Te,n,(function(e){var t=e.prevState,r=e.state;n.onStateChange(Be({prevState:t,state:r,refresh:u},o))})),o=function(e){var t=e.store;return{setActiveItemId:function(e){t.dispatch("setActiveItemId",e)},setQuery:function(e){t.dispatch("setQuery",e)},setCollections:function(e){var n=0,r=e.map((function(e){return L(L({},e),{},{items:d(e.items).map((function(e){return L(L({},e),{},{__autocomplete_id:n++})}))})}));t.dispatch("setCollections",r)},setIsOpen:function(e){t.dispatch("setIsOpen",e)},setStatus:function(e){t.dispatch("setStatus",e)},setContext:function(e){t.dispatch("setContext",e)}}}({store:r}),i=Ee(Be({props:n,refresh:u,store:r},o));function u(){return fe(Be({event:new Event("input"),nextState:{isOpen:r.getState().isOpen},props:n,query:r.getState().query,refresh:u,store:r},o))}return n.plugins.forEach((function(e){var n;return null===(n=e.subscribe)||void 0===n?void 0:n.call(e,Be(Be({},o),{},{refresh:u,onSelect:function(e){t.push({onSelect:e})},onActive:function(e){t.push({onActive:e})}}))})),function(e){var t,n,r=e.metadata,o=e.environment;if(null===(t=o.navigator)||void 0===t||null===(n=t.userAgent)||void 0===n?void 0:n.includes("Algolia Crawler")){var i=o.document.createElement("meta"),u=o.document.querySelector("head");i.name="algolia:metadata",setTimeout((function(){i.content=JSON.stringify(r),u.appendChild(i)}),0)}}({metadata:ke({plugins:n.plugins,options:e}),environment:n.environment}),Be(Be({refresh:u},i),o)}var Ue=function(e,t,n,r){var o;t[0]=0;for(var i=1;i=5&&((o||!e&&5===r)&&(u.push(r,0,o,n),r=6),e&&(u.push(r,e,0,n),r=6)),o=""},c=0;c"===t?(r=1,o=""):o=t+o[0]:i?t===i?i="":o+=t:'"'===t||"'"===t?i=t:">"===t?(a(),r=1):r&&("="===t?(r=5,n=o,o=""):"/"===t&&(r<5||">"===e[c][l+1])?(a(),3===r&&(u=u[0]),r=u,(u=u[0]).push(2,0,r),r=0):" "===t||"\t"===t||"\n"===t||"\r"===t?(a(),r=2):o+=t),3===r&&"!--"===o&&(r=4,u=u[0])}return a(),u}(e)),t),arguments,[])).length>1?t:t[0]}var We=function(e){var t=e.environment,n=t.document.createElementNS("http://www.w3.org/2000/svg","svg");n.setAttribute("class","aa-ClearIcon"),n.setAttribute("viewBox","0 0 24 24"),n.setAttribute("width","18"),n.setAttribute("height","18"),n.setAttribute("fill","currentColor");var r=t.document.createElementNS("http://www.w3.org/2000/svg","path");return r.setAttribute("d","M5.293 6.707l5.293 5.293-5.293 5.293c-0.391 0.391-0.391 1.024 0 1.414s1.024 0.391 1.414 0l5.293-5.293 5.293 5.293c0.391 0.391 1.024 0.391 1.414 0s0.391-1.024 0-1.414l-5.293-5.293 5.293-5.293c0.391-0.391 0.391-1.024 0-1.414s-1.024-0.391-1.414 0l-5.293 5.293-5.293-5.293c-0.391-0.391-1.024-0.391-1.414 0s-0.391 1.024 0 1.414z"),n.appendChild(r),n};function Qe(e,t){if("string"==typeof t){var n=e.document.querySelector(t);return"The element ".concat(JSON.stringify(t)," is not in the document."),n}return t}function $e(){for(var e=arguments.length,t=new Array(e),n=0;n2&&(u.children=arguments.length>3?lt.call(arguments,2):n),"function"==typeof e&&null!=e.defaultProps)for(i in e.defaultProps)void 0===u[i]&&(u[i]=e.defaultProps[i]);return _t(e,u,r,o,null)}function _t(e,t,n,r,o){var i={type:e,props:t,key:n,ref:r,__k:null,__:null,__b:0,__e:null,__d:void 0,__c:null,__h:null,constructor:void 0,__v:null==o?++pt:o};return null==o&&null!=st.vnode&&st.vnode(i),i}function Pt(e){return e.children}function jt(e,t){this.props=e,this.context=t}function wt(e,t){if(null==t)return e.__?wt(e.__,e.__.__k.indexOf(e)+1):null;for(var n;t0?_t(d.type,d.props,d.key,null,d.__v):d)){if(d.__=n,d.__b=n.__b+1,null===(f=g[s])||f&&d.key==f.key&&d.type===f.type)g[s]=void 0;else for(p=0;p0&&void 0!==arguments[0]?arguments[0]:[];return{get:function(){return e},add:function(t){var n=e[e.length-1];(null==n?void 0:n.isHighlighted)===t.isHighlighted?e[e.length-1]={value:n.value+t.value,isHighlighted:n.isHighlighted}:e.push(t)}}}(n?[{value:n,isHighlighted:!1}]:[]);return t.forEach((function(e){var t=e.split(Ht);r.add({value:t[0],isHighlighted:!0}),""!==t[1]&&r.add({value:t[1],isHighlighted:!1})})),r.get()}function Wt(e){return function(e){if(Array.isArray(e))return Qt(e)}(e)||function(e){if("undefined"!=typeof Symbol&&null!=e[Symbol.iterator]||null!=e["@@iterator"])return Array.from(e)}(e)||function(e,t){if(!e)return;if("string"==typeof e)return Qt(e,t);var n=Object.prototype.toString.call(e).slice(8,-1);"Object"===n&&e.constructor&&(n=e.constructor.name);if("Map"===n||"Set"===n)return Array.from(e);if("Arguments"===n||/^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n))return Qt(e,t)}(e)||function(){throw new TypeError("Invalid attempt to spread non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}()}function Qt(e,t){(null==t||t>e.length)&&(t=e.length);for(var n=0,r=new Array(t);n",""":'"',"'":"'"},Gt=new RegExp(/\w/i),Kt=/&(amp|quot|lt|gt|#39);/g,Jt=RegExp(Kt.source);function Yt(e,t){var n,r,o,i=e[t],u=(null===(n=e[t+1])||void 0===n?void 0:n.isHighlighted)||!0,a=(null===(r=e[t-1])||void 0===r?void 0:r.isHighlighted)||!0;return Gt.test((o=i.value)&&Jt.test(o)?o.replace(Kt,(function(e){return zt[e]})):o)||a!==u?i.isHighlighted:a}function Xt(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),n.push.apply(n,r)}return n}function Zt(e){for(var t=1;te.length)&&(t=e.length);for(var n=0,r=new Array(t);n=0||(o[n]=e[n]);return o}(e,t);if(Object.getOwnPropertySymbols){var i=Object.getOwnPropertySymbols(e);for(r=0;r=0||Object.prototype.propertyIsEnumerable.call(e,n)&&(o[n]=e[n])}return o}function mn(e){return function(e){if(Array.isArray(e))return vn(e)}(e)||function(e){if("undefined"!=typeof Symbol&&null!=e[Symbol.iterator]||null!=e["@@iterator"])return Array.from(e)}(e)||function(e,t){if(!e)return;if("string"==typeof e)return vn(e,t);var n=Object.prototype.toString.call(e).slice(8,-1);"Object"===n&&e.constructor&&(n=e.constructor.name);if("Map"===n||"Set"===n)return Array.from(e);if("Arguments"===n||/^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n))return vn(e,t)}(e)||function(){throw new TypeError("Invalid attempt to spread non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}()}function vn(e,t){(null==t||t>e.length)&&(t=e.length);for(var n=0,r=new Array(t);n0;if(!O.value.core.openOnFocus&&!t.query)return n;var r=Boolean(h.current||O.value.renderer.renderNoResults);return!n&&r||n},__autocomplete_metadata:{userAgents:Sn,options:e}}))})),j=p(n({collections:[],completion:null,context:{},isOpen:!1,query:"",activeItemId:null,status:"idle"},O.value.core.initialState)),w={getEnvironmentProps:O.value.renderer.getEnvironmentProps,getFormProps:O.value.renderer.getFormProps,getInputProps:O.value.renderer.getInputProps,getItemProps:O.value.renderer.getItemProps,getLabelProps:O.value.renderer.getLabelProps,getListProps:O.value.renderer.getListProps,getPanelProps:O.value.renderer.getPanelProps,getRootProps:O.value.renderer.getRootProps},S={setActiveItemId:P.value.setActiveItemId,setQuery:P.value.setQuery,setCollections:P.value.setCollections,setIsOpen:P.value.setIsOpen,setStatus:P.value.setStatus,setContext:P.value.setContext,refresh:P.value.refresh},I=d((function(){return Ve.bind(O.value.renderer.renderer.createElement)})),E=d((function(){return ct({autocomplete:P.value,autocompleteScopeApi:S,classNames:O.value.renderer.classNames,environment:O.value.core.environment,isDetached:_.value,placeholder:O.value.core.placeholder,propGetters:w,setIsModalOpen:k,state:j.current,translations:O.value.renderer.translations})}));function A(){tt(E.value.panel,{style:_.value?{}:wn({panelPlacement:O.value.renderer.panelPlacement,container:E.value.root,form:E.value.form,environment:O.value.core.environment})})}function C(e){j.current=e;var t={autocomplete:P.value,autocompleteScopeApi:S,classNames:O.value.renderer.classNames,components:O.value.renderer.components,container:O.value.renderer.container,html:I.value,dom:E.value,panelContainer:_.value?E.value.detachedContainer:O.value.renderer.panelContainer,propGetters:w,state:j.current,renderer:O.value.renderer.renderer},r=!g(e)&&!h.current&&O.value.renderer.renderNoResults||O.value.renderer.render;!function(e){var t=e.autocomplete,r=e.autocompleteScopeApi,o=e.dom,i=e.propGetters,u=e.state;nt(o.root,i.getRootProps(n({state:u,props:t.getRootProps({})},r))),nt(o.input,i.getInputProps(n({state:u,props:t.getInputProps({inputElement:o.input}),inputElement:o.input},r))),tt(o.label,{hidden:"stalled"===u.status}),tt(o.loadingIndicator,{hidden:"stalled"!==u.status}),tt(o.clearButton,{hidden:!u.query})}(t),function(e,t){var r=t.autocomplete,o=t.autocompleteScopeApi,u=t.classNames,a=t.html,c=t.dom,l=t.panelContainer,s=t.propGetters,p=t.state,f=t.components,d=t.renderer;if(p.isOpen){l.contains(c.panel)||"loading"===p.status||l.appendChild(c.panel),c.panel.classList.toggle("aa-Panel--stalled","stalled"===p.status);var m=p.collections.filter((function(e){var t=e.source,n=e.items;return t.templates.noResults||n.length>0})).map((function(e,t){var c=e.source,l=e.items;return d.createElement("section",{key:t,className:u.source,"data-autocomplete-source-id":c.sourceId},c.templates.header&&d.createElement("div",{className:u.sourceHeader},c.templates.header({components:f,createElement:d.createElement,Fragment:d.Fragment,items:l,source:c,state:p,html:a})),c.templates.noResults&&0===l.length?d.createElement("div",{className:u.sourceNoResults},c.templates.noResults({components:f,createElement:d.createElement,Fragment:d.Fragment,source:c,state:p,html:a})):d.createElement("ul",i({className:u.list},s.getListProps(n({state:p,props:r.getListProps({})},o))),l.map((function(e){var t=r.getItemProps({item:e,source:c});return d.createElement("li",i({key:t.id,className:u.item},s.getItemProps(n({state:p,props:t},o))),c.templates.item({components:f,createElement:d.createElement,Fragment:d.Fragment,item:e,state:p,html:a}))}))),c.templates.footer&&d.createElement("div",{className:u.sourceFooter},c.templates.footer({components:f,createElement:d.createElement,Fragment:d.Fragment,items:l,source:c,state:p,html:a})))})),v=d.createElement(d.Fragment,null,d.createElement("div",{className:u.panelLayout},m),d.createElement("div",{className:"aa-GradientBottom"})),h=m.reduce((function(e,t){return e[t.props["data-autocomplete-source-id"]]=t,e}),{});e(n(n({children:v,state:p,sections:m,elements:h},d),{},{components:f,html:a},o),c.panel)}else l.contains(c.panel)&&l.removeChild(c.panel)}(r,t)}function D(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{};c();var t=O.value.renderer,n=t.components,r=u(t,In);y.current=Ge(r,O.value.core,{components:Ke(n,(function(e){return!e.value.hasOwnProperty("__autocomplete_componentName")})),initialState:j.current},e),m(),l(),P.value.refresh().then((function(){C(j.current)}))}function k(e){requestAnimationFrame((function(){var t=O.value.core.environment.document.body.contains(E.value.detachedOverlay);e!==t&&(e?(O.value.core.environment.document.body.appendChild(E.value.detachedOverlay),O.value.core.environment.document.body.classList.add("aa-Detached"),E.value.input.focus()):(O.value.core.environment.document.body.removeChild(E.value.detachedOverlay),O.value.core.environment.document.body.classList.remove("aa-Detached"),P.value.setQuery(""),P.value.refresh()))}))}return a((function(){var e=P.value.getEnvironmentProps({formElement:E.value.form,panelElement:E.value.panel,inputElement:E.value.input});return tt(O.value.core.environment,e),function(){tt(O.value.core.environment,Object.keys(e).reduce((function(e,t){return n(n({},e),{},o({},t,void 0))}),{}))}})),a((function(){var e=_.value?O.value.core.environment.document.body:O.value.renderer.panelContainer,t=_.value?E.value.detachedOverlay:E.value.panel;return _.value&&j.current.isOpen&&k(!0),C(j.current),function(){e.contains(t)&&e.removeChild(t)}})),a((function(){var e=O.value.renderer.container;return e.appendChild(E.value.root),function(){e.removeChild(E.value.root)}})),a((function(){var e=f((function(e){C(e.state)}),0);return b.current=function(t){var n=t.state,r=t.prevState;(_.value&&r.isOpen!==n.isOpen&&k(n.isOpen),_.value||!n.isOpen||r.isOpen||A(),n.query!==r.query)&&O.value.core.environment.document.querySelectorAll(".aa-Panel--scrollable").forEach((function(e){0!==e.scrollTop&&(e.scrollTop=0)}));e({state:n})},function(){b.current=void 0}})),a((function(){var e=f((function(){var e=_.value;_.value=O.value.core.environment.matchMedia(O.value.renderer.detachedMediaQuery).matches,e!==_.value?D({}):requestAnimationFrame(A)}),20);return O.value.core.environment.addEventListener("resize",e),function(){O.value.core.environment.removeEventListener("resize",e)}})),a((function(){if(!_.value)return function(){};function e(e){E.value.detachedContainer.classList.toggle("aa-DetachedContainer--modal",e)}function t(t){e(t.matches)}var n=O.value.core.environment.matchMedia(getComputedStyle(O.value.core.environment.document.documentElement).getPropertyValue("--aa-detached-modal-media-query"));e(n.matches);var r=Boolean(n.addEventListener);return r?n.addEventListener("change",t):n.addListener(t),function(){r?n.removeEventListener("change",t):n.removeListener(t)}})),a((function(){return requestAnimationFrame(A),function(){}})),n(n({},S),{},{update:D,destroy:function(){c()}})},e.getAlgoliaFacets=function(e){var t=En({transformResponse:function(e){return e.facetHits}}),r=e.queries.map((function(e){return n(n({},e),{},{type:"facet"})}));return t(n(n({},e),{},{queries:r}))},e.getAlgoliaResults=An,Object.defineProperty(e,"__esModule",{value:!0})})); + diff --git a/site_libs/quarto-search/fuse.min.js b/site_libs/quarto-search/fuse.min.js new file mode 100644 index 00000000..adc28356 --- /dev/null +++ b/site_libs/quarto-search/fuse.min.js @@ -0,0 +1,9 @@ +/** + * Fuse.js v6.6.2 - Lightweight fuzzy-search (http://fusejs.io) + * + * Copyright (c) 2022 Kiro Risk (http://kiro.me) + * All Rights Reserved. Apache Software License 2.0 + * + * http://www.apache.org/licenses/LICENSE-2.0 + */ +var e,t;e=this,t=function(){"use strict";function e(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),n.push.apply(n,r)}return n}function t(t){for(var n=1;ne.length)&&(t=e.length);for(var n=0,r=new Array(t);n0&&void 0!==arguments[0]?arguments[0]:1,t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:3,n=new Map,r=Math.pow(10,t);return{get:function(t){var i=t.match(C).length;if(n.has(i))return n.get(i);var o=1/Math.pow(i,.5*e),c=parseFloat(Math.round(o*r)/r);return n.set(i,c),c},clear:function(){n.clear()}}}var $=function(){function e(){var t=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{},n=t.getFn,i=void 0===n?I.getFn:n,o=t.fieldNormWeight,c=void 0===o?I.fieldNormWeight:o;r(this,e),this.norm=E(c,3),this.getFn=i,this.isCreated=!1,this.setIndexRecords()}return o(e,[{key:"setSources",value:function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:[];this.docs=e}},{key:"setIndexRecords",value:function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:[];this.records=e}},{key:"setKeys",value:function(){var e=this,t=arguments.length>0&&void 0!==arguments[0]?arguments[0]:[];this.keys=t,this._keysMap={},t.forEach((function(t,n){e._keysMap[t.id]=n}))}},{key:"create",value:function(){var e=this;!this.isCreated&&this.docs.length&&(this.isCreated=!0,g(this.docs[0])?this.docs.forEach((function(t,n){e._addString(t,n)})):this.docs.forEach((function(t,n){e._addObject(t,n)})),this.norm.clear())}},{key:"add",value:function(e){var t=this.size();g(e)?this._addString(e,t):this._addObject(e,t)}},{key:"removeAt",value:function(e){this.records.splice(e,1);for(var t=e,n=this.size();t2&&void 0!==arguments[2]?arguments[2]:{},r=n.getFn,i=void 0===r?I.getFn:r,o=n.fieldNormWeight,c=void 0===o?I.fieldNormWeight:o,a=new $({getFn:i,fieldNormWeight:c});return a.setKeys(e.map(_)),a.setSources(t),a.create(),a}function R(e){var t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{},n=t.errors,r=void 0===n?0:n,i=t.currentLocation,o=void 0===i?0:i,c=t.expectedLocation,a=void 0===c?0:c,s=t.distance,u=void 0===s?I.distance:s,h=t.ignoreLocation,l=void 0===h?I.ignoreLocation:h,f=r/e.length;if(l)return f;var d=Math.abs(a-o);return u?f+d/u:d?1:f}function N(){for(var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:[],t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:I.minMatchCharLength,n=[],r=-1,i=-1,o=0,c=e.length;o=t&&n.push([r,i]),r=-1)}return e[o-1]&&o-r>=t&&n.push([r,o-1]),n}var P=32;function W(e){for(var t={},n=0,r=e.length;n1&&void 0!==arguments[1]?arguments[1]:{},o=i.location,c=void 0===o?I.location:o,a=i.threshold,s=void 0===a?I.threshold:a,u=i.distance,h=void 0===u?I.distance:u,l=i.includeMatches,f=void 0===l?I.includeMatches:l,d=i.findAllMatches,v=void 0===d?I.findAllMatches:d,g=i.minMatchCharLength,y=void 0===g?I.minMatchCharLength:g,p=i.isCaseSensitive,m=void 0===p?I.isCaseSensitive:p,k=i.ignoreLocation,M=void 0===k?I.ignoreLocation:k;if(r(this,e),this.options={location:c,threshold:s,distance:h,includeMatches:f,findAllMatches:v,minMatchCharLength:y,isCaseSensitive:m,ignoreLocation:M},this.pattern=m?t:t.toLowerCase(),this.chunks=[],this.pattern.length){var b=function(e,t){n.chunks.push({pattern:e,alphabet:W(e),startIndex:t})},x=this.pattern.length;if(x>P){for(var w=0,L=x%P,S=x-L;w3&&void 0!==arguments[3]?arguments[3]:{},i=r.location,o=void 0===i?I.location:i,c=r.distance,a=void 0===c?I.distance:c,s=r.threshold,u=void 0===s?I.threshold:s,h=r.findAllMatches,l=void 0===h?I.findAllMatches:h,f=r.minMatchCharLength,d=void 0===f?I.minMatchCharLength:f,v=r.includeMatches,g=void 0===v?I.includeMatches:v,y=r.ignoreLocation,p=void 0===y?I.ignoreLocation:y;if(t.length>P)throw new Error(w(P));for(var m,k=t.length,M=e.length,b=Math.max(0,Math.min(o,M)),x=u,L=b,S=d>1||g,_=S?Array(M):[];(m=e.indexOf(t,L))>-1;){var O=R(t,{currentLocation:m,expectedLocation:b,distance:a,ignoreLocation:p});if(x=Math.min(O,x),L=m+k,S)for(var j=0;j=z;q-=1){var B=q-1,J=n[e.charAt(B)];if(S&&(_[B]=+!!J),K[q]=(K[q+1]<<1|1)&J,F&&(K[q]|=(A[q+1]|A[q])<<1|1|A[q+1]),K[q]&$&&(C=R(t,{errors:F,currentLocation:B,expectedLocation:b,distance:a,ignoreLocation:p}))<=x){if(x=C,(L=B)<=b)break;z=Math.max(1,2*b-L)}}if(R(t,{errors:F+1,currentLocation:b,expectedLocation:b,distance:a,ignoreLocation:p})>x)break;A=K}var U={isMatch:L>=0,score:Math.max(.001,C)};if(S){var V=N(_,d);V.length?g&&(U.indices=V):U.isMatch=!1}return U}(e,n,i,{location:c+o,distance:a,threshold:s,findAllMatches:u,minMatchCharLength:h,includeMatches:r,ignoreLocation:l}),p=y.isMatch,m=y.score,k=y.indices;p&&(g=!0),v+=m,p&&k&&(d=[].concat(f(d),f(k)))}));var y={isMatch:g,score:g?v/this.chunks.length:1};return g&&r&&(y.indices=d),y}}]),e}(),z=function(){function e(t){r(this,e),this.pattern=t}return o(e,[{key:"search",value:function(){}}],[{key:"isMultiMatch",value:function(e){return D(e,this.multiRegex)}},{key:"isSingleMatch",value:function(e){return D(e,this.singleRegex)}}]),e}();function D(e,t){var n=e.match(t);return n?n[1]:null}var K=function(e){a(n,e);var t=l(n);function n(e){return r(this,n),t.call(this,e)}return o(n,[{key:"search",value:function(e){var t=e===this.pattern;return{isMatch:t,score:t?0:1,indices:[0,this.pattern.length-1]}}}],[{key:"type",get:function(){return"exact"}},{key:"multiRegex",get:function(){return/^="(.*)"$/}},{key:"singleRegex",get:function(){return/^=(.*)$/}}]),n}(z),q=function(e){a(n,e);var t=l(n);function n(e){return r(this,n),t.call(this,e)}return o(n,[{key:"search",value:function(e){var t=-1===e.indexOf(this.pattern);return{isMatch:t,score:t?0:1,indices:[0,e.length-1]}}}],[{key:"type",get:function(){return"inverse-exact"}},{key:"multiRegex",get:function(){return/^!"(.*)"$/}},{key:"singleRegex",get:function(){return/^!(.*)$/}}]),n}(z),B=function(e){a(n,e);var t=l(n);function n(e){return r(this,n),t.call(this,e)}return o(n,[{key:"search",value:function(e){var t=e.startsWith(this.pattern);return{isMatch:t,score:t?0:1,indices:[0,this.pattern.length-1]}}}],[{key:"type",get:function(){return"prefix-exact"}},{key:"multiRegex",get:function(){return/^\^"(.*)"$/}},{key:"singleRegex",get:function(){return/^\^(.*)$/}}]),n}(z),J=function(e){a(n,e);var t=l(n);function n(e){return r(this,n),t.call(this,e)}return o(n,[{key:"search",value:function(e){var t=!e.startsWith(this.pattern);return{isMatch:t,score:t?0:1,indices:[0,e.length-1]}}}],[{key:"type",get:function(){return"inverse-prefix-exact"}},{key:"multiRegex",get:function(){return/^!\^"(.*)"$/}},{key:"singleRegex",get:function(){return/^!\^(.*)$/}}]),n}(z),U=function(e){a(n,e);var t=l(n);function n(e){return r(this,n),t.call(this,e)}return o(n,[{key:"search",value:function(e){var t=e.endsWith(this.pattern);return{isMatch:t,score:t?0:1,indices:[e.length-this.pattern.length,e.length-1]}}}],[{key:"type",get:function(){return"suffix-exact"}},{key:"multiRegex",get:function(){return/^"(.*)"\$$/}},{key:"singleRegex",get:function(){return/^(.*)\$$/}}]),n}(z),V=function(e){a(n,e);var t=l(n);function n(e){return r(this,n),t.call(this,e)}return o(n,[{key:"search",value:function(e){var t=!e.endsWith(this.pattern);return{isMatch:t,score:t?0:1,indices:[0,e.length-1]}}}],[{key:"type",get:function(){return"inverse-suffix-exact"}},{key:"multiRegex",get:function(){return/^!"(.*)"\$$/}},{key:"singleRegex",get:function(){return/^!(.*)\$$/}}]),n}(z),G=function(e){a(n,e);var t=l(n);function n(e){var i,o=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{},c=o.location,a=void 0===c?I.location:c,s=o.threshold,u=void 0===s?I.threshold:s,h=o.distance,l=void 0===h?I.distance:h,f=o.includeMatches,d=void 0===f?I.includeMatches:f,v=o.findAllMatches,g=void 0===v?I.findAllMatches:v,y=o.minMatchCharLength,p=void 0===y?I.minMatchCharLength:y,m=o.isCaseSensitive,k=void 0===m?I.isCaseSensitive:m,M=o.ignoreLocation,b=void 0===M?I.ignoreLocation:M;return r(this,n),(i=t.call(this,e))._bitapSearch=new T(e,{location:a,threshold:u,distance:l,includeMatches:d,findAllMatches:g,minMatchCharLength:p,isCaseSensitive:k,ignoreLocation:b}),i}return o(n,[{key:"search",value:function(e){return this._bitapSearch.searchIn(e)}}],[{key:"type",get:function(){return"fuzzy"}},{key:"multiRegex",get:function(){return/^"(.*)"$/}},{key:"singleRegex",get:function(){return/^(.*)$/}}]),n}(z),H=function(e){a(n,e);var t=l(n);function n(e){return r(this,n),t.call(this,e)}return o(n,[{key:"search",value:function(e){for(var t,n=0,r=[],i=this.pattern.length;(t=e.indexOf(this.pattern,n))>-1;)n=t+i,r.push([t,n-1]);var o=!!r.length;return{isMatch:o,score:o?0:1,indices:r}}}],[{key:"type",get:function(){return"include"}},{key:"multiRegex",get:function(){return/^'"(.*)"$/}},{key:"singleRegex",get:function(){return/^'(.*)$/}}]),n}(z),Q=[K,H,B,J,V,U,q,G],X=Q.length,Y=/ +(?=(?:[^\"]*\"[^\"]*\")*[^\"]*$)/;function Z(e){var t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{};return e.split("|").map((function(e){for(var n=e.trim().split(Y).filter((function(e){return e&&!!e.trim()})),r=[],i=0,o=n.length;i1&&void 0!==arguments[1]?arguments[1]:{},i=n.isCaseSensitive,o=void 0===i?I.isCaseSensitive:i,c=n.includeMatches,a=void 0===c?I.includeMatches:c,s=n.minMatchCharLength,u=void 0===s?I.minMatchCharLength:s,h=n.ignoreLocation,l=void 0===h?I.ignoreLocation:h,f=n.findAllMatches,d=void 0===f?I.findAllMatches:f,v=n.location,g=void 0===v?I.location:v,y=n.threshold,p=void 0===y?I.threshold:y,m=n.distance,k=void 0===m?I.distance:m;r(this,e),this.query=null,this.options={isCaseSensitive:o,includeMatches:a,minMatchCharLength:u,findAllMatches:d,ignoreLocation:l,location:g,threshold:p,distance:k},this.pattern=o?t:t.toLowerCase(),this.query=Z(this.pattern,this.options)}return o(e,[{key:"searchIn",value:function(e){var t=this.query;if(!t)return{isMatch:!1,score:1};var n=this.options,r=n.includeMatches;e=n.isCaseSensitive?e:e.toLowerCase();for(var i=0,o=[],c=0,a=0,s=t.length;a-1&&(n.refIndex=e.idx),t.matches.push(n)}}))}function ve(e,t){t.score=e.score}function ge(e,t){var n=arguments.length>2&&void 0!==arguments[2]?arguments[2]:{},r=n.includeMatches,i=void 0===r?I.includeMatches:r,o=n.includeScore,c=void 0===o?I.includeScore:o,a=[];return i&&a.push(de),c&&a.push(ve),e.map((function(e){var n=e.idx,r={item:t[n],refIndex:n};return a.length&&a.forEach((function(t){t(e,r)})),r}))}var ye=function(){function e(n){var i=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{},o=arguments.length>2?arguments[2]:void 0;r(this,e),this.options=t(t({},I),i),this.options.useExtendedSearch,this._keyStore=new S(this.options.keys),this.setCollection(n,o)}return o(e,[{key:"setCollection",value:function(e,t){if(this._docs=e,t&&!(t instanceof $))throw new Error("Incorrect 'index' type");this._myIndex=t||F(this.options.keys,this._docs,{getFn:this.options.getFn,fieldNormWeight:this.options.fieldNormWeight})}},{key:"add",value:function(e){k(e)&&(this._docs.push(e),this._myIndex.add(e))}},{key:"remove",value:function(){for(var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:function(){return!1},t=[],n=0,r=this._docs.length;n1&&void 0!==arguments[1]?arguments[1]:{},n=t.limit,r=void 0===n?-1:n,i=this.options,o=i.includeMatches,c=i.includeScore,a=i.shouldSort,s=i.sortFn,u=i.ignoreFieldNorm,h=g(e)?g(this._docs[0])?this._searchStringList(e):this._searchObjectList(e):this._searchLogical(e);return fe(h,{ignoreFieldNorm:u}),a&&h.sort(s),y(r)&&r>-1&&(h=h.slice(0,r)),ge(h,this._docs,{includeMatches:o,includeScore:c})}},{key:"_searchStringList",value:function(e){var t=re(e,this.options),n=this._myIndex.records,r=[];return n.forEach((function(e){var n=e.v,i=e.i,o=e.n;if(k(n)){var c=t.searchIn(n),a=c.isMatch,s=c.score,u=c.indices;a&&r.push({item:n,idx:i,matches:[{score:s,value:n,norm:o,indices:u}]})}})),r}},{key:"_searchLogical",value:function(e){var t=this,n=function(e,t){var n=(arguments.length>2&&void 0!==arguments[2]?arguments[2]:{}).auto,r=void 0===n||n,i=function e(n){var i=Object.keys(n),o=ue(n);if(!o&&i.length>1&&!se(n))return e(le(n));if(he(n)){var c=o?n[ce]:i[0],a=o?n[ae]:n[c];if(!g(a))throw new Error(x(c));var s={keyId:j(c),pattern:a};return r&&(s.searcher=re(a,t)),s}var u={children:[],operator:i[0]};return i.forEach((function(t){var r=n[t];v(r)&&r.forEach((function(t){u.children.push(e(t))}))})),u};return se(e)||(e=le(e)),i(e)}(e,this.options),r=function e(n,r,i){if(!n.children){var o=n.keyId,c=n.searcher,a=t._findMatches({key:t._keyStore.get(o),value:t._myIndex.getValueForItemAtKeyId(r,o),searcher:c});return a&&a.length?[{idx:i,item:r,matches:a}]:[]}for(var s=[],u=0,h=n.children.length;u1&&void 0!==arguments[1]?arguments[1]:{},n=t.getFn,r=void 0===n?I.getFn:n,i=t.fieldNormWeight,o=void 0===i?I.fieldNormWeight:i,c=e.keys,a=e.records,s=new $({getFn:r,fieldNormWeight:o});return s.setKeys(c),s.setIndexRecords(a),s},ye.config=I,function(){ne.push.apply(ne,arguments)}(te),ye},"object"==typeof exports&&"undefined"!=typeof module?module.exports=t():"function"==typeof define&&define.amd?define(t):(e="undefined"!=typeof globalThis?globalThis:e||self).Fuse=t(); \ No newline at end of file diff --git a/site_libs/quarto-search/quarto-search.js b/site_libs/quarto-search/quarto-search.js new file mode 100644 index 00000000..f5d852d1 --- /dev/null +++ b/site_libs/quarto-search/quarto-search.js @@ -0,0 +1,1140 @@ +const kQueryArg = "q"; +const kResultsArg = "show-results"; + +// If items don't provide a URL, then both the navigator and the onSelect +// function aren't called (and therefore, the default implementation is used) +// +// We're using this sentinel URL to signal to those handlers that this +// item is a more item (along with the type) and can be handled appropriately +const kItemTypeMoreHref = "0767FDFD-0422-4E5A-BC8A-3BE11E5BBA05"; + +window.document.addEventListener("DOMContentLoaded", function (_event) { + // Ensure that search is available on this page. If it isn't, + // should return early and not do anything + var searchEl = window.document.getElementById("quarto-search"); + if (!searchEl) return; + + const { autocomplete } = window["@algolia/autocomplete-js"]; + + let quartoSearchOptions = {}; + let language = {}; + const searchOptionEl = window.document.getElementById( + "quarto-search-options" + ); + if (searchOptionEl) { + const jsonStr = searchOptionEl.textContent; + quartoSearchOptions = JSON.parse(jsonStr); + language = quartoSearchOptions.language; + } + + // note the search mode + if (quartoSearchOptions.type === "overlay") { + searchEl.classList.add("type-overlay"); + } else { + searchEl.classList.add("type-textbox"); + } + + // Used to determine highlighting behavior for this page + // A `q` query param is expected when the user follows a search + // to this page + const currentUrl = new URL(window.location); + const query = currentUrl.searchParams.get(kQueryArg); + const showSearchResults = currentUrl.searchParams.get(kResultsArg); + const mainEl = window.document.querySelector("main"); + + // highlight matches on the page + if (query !== null && mainEl) { + // perform any highlighting + highlight(escapeRegExp(query), mainEl); + + // fix up the URL to remove the q query param + const replacementUrl = new URL(window.location); + replacementUrl.searchParams.delete(kQueryArg); + window.history.replaceState({}, "", replacementUrl); + } + + // function to clear highlighting on the page when the search query changes + // (e.g. if the user edits the query or clears it) + let highlighting = true; + const resetHighlighting = (searchTerm) => { + if (mainEl && highlighting && query !== null && searchTerm !== query) { + clearHighlight(query, mainEl); + highlighting = false; + } + }; + + // Clear search highlighting when the user scrolls sufficiently + const resetFn = () => { + resetHighlighting(""); + window.removeEventListener("quarto-hrChanged", resetFn); + window.removeEventListener("quarto-sectionChanged", resetFn); + }; + + // Register this event after the initial scrolling and settling of events + // on the page + window.addEventListener("quarto-hrChanged", resetFn); + window.addEventListener("quarto-sectionChanged", resetFn); + + // Responsively switch to overlay mode if the search is present on the navbar + // Note that switching the sidebar to overlay mode requires more coordinate (not just + // the media query since we generate different HTML for sidebar overlays than we do + // for sidebar input UI) + const detachedMediaQuery = + quartoSearchOptions.type === "overlay" ? "all" : "(max-width: 991px)"; + + // If configured, include the analytics client to send insights + const plugins = configurePlugins(quartoSearchOptions); + + let lastState = null; + const { setIsOpen, setQuery, setCollections } = autocomplete({ + container: searchEl, + detachedMediaQuery: detachedMediaQuery, + defaultActiveItemId: 0, + panelContainer: "#quarto-search-results", + panelPlacement: quartoSearchOptions["panel-placement"], + debug: false, + openOnFocus: true, + plugins, + classNames: { + form: "d-flex", + }, + translations: { + clearButtonTitle: language["search-clear-button-title"], + detachedCancelButtonText: language["search-detached-cancel-button-title"], + submitButtonTitle: language["search-submit-button-title"], + }, + initialState: { + query, + }, + getItemUrl({ item }) { + return item.href; + }, + onStateChange({ state }) { + // Perhaps reset highlighting + resetHighlighting(state.query); + + // If the panel just opened, ensure the panel is positioned properly + if (state.isOpen) { + if (lastState && !lastState.isOpen) { + setTimeout(() => { + positionPanel(quartoSearchOptions["panel-placement"]); + }, 150); + } + } + + // Perhaps show the copy link + showCopyLink(state.query, quartoSearchOptions); + + lastState = state; + }, + reshape({ sources, state }) { + return sources.map((source) => { + try { + const items = source.getItems(); + + // Validate the items + validateItems(items); + + // group the items by document + const groupedItems = new Map(); + items.forEach((item) => { + const hrefParts = item.href.split("#"); + const baseHref = hrefParts[0]; + const isDocumentItem = hrefParts.length === 1; + + const items = groupedItems.get(baseHref); + if (!items) { + groupedItems.set(baseHref, [item]); + } else { + // If the href for this item matches the document + // exactly, place this item first as it is the item that represents + // the document itself + if (isDocumentItem) { + items.unshift(item); + } else { + items.push(item); + } + groupedItems.set(baseHref, items); + } + }); + + const reshapedItems = []; + let count = 1; + for (const [_key, value] of groupedItems) { + const firstItem = value[0]; + reshapedItems.push({ + ...firstItem, + type: kItemTypeDoc, + }); + + const collapseMatches = quartoSearchOptions["collapse-after"]; + const collapseCount = + typeof collapseMatches === "number" ? collapseMatches : 1; + + if (value.length > 1) { + const target = `search-more-${count}`; + const isExpanded = + state.context.expanded && + state.context.expanded.includes(target); + + const remainingCount = value.length - collapseCount; + + for (let i = 1; i < value.length; i++) { + if (collapseMatches && i === collapseCount) { + reshapedItems.push({ + target, + title: isExpanded + ? language["search-hide-matches-text"] + : remainingCount === 1 + ? `${remainingCount} ${language["search-more-match-text"]}` + : `${remainingCount} ${language["search-more-matches-text"]}`, + type: kItemTypeMore, + href: kItemTypeMoreHref, + }); + } + + if (isExpanded || !collapseMatches || i < collapseCount) { + reshapedItems.push({ + ...value[i], + type: kItemTypeItem, + target, + }); + } + } + } + count += 1; + } + + return { + ...source, + getItems() { + return reshapedItems; + }, + }; + } catch (error) { + // Some form of error occurred + return { + ...source, + getItems() { + return [ + { + title: error.name || "An Error Occurred While Searching", + text: + error.message || + "An unknown error occurred while attempting to perform the requested search.", + type: kItemTypeError, + }, + ]; + }, + }; + } + }); + }, + navigator: { + navigate({ itemUrl }) { + if (itemUrl !== offsetURL(kItemTypeMoreHref)) { + window.location.assign(itemUrl); + } + }, + navigateNewTab({ itemUrl }) { + if (itemUrl !== offsetURL(kItemTypeMoreHref)) { + const windowReference = window.open(itemUrl, "_blank", "noopener"); + if (windowReference) { + windowReference.focus(); + } + } + }, + navigateNewWindow({ itemUrl }) { + if (itemUrl !== offsetURL(kItemTypeMoreHref)) { + window.open(itemUrl, "_blank", "noopener"); + } + }, + }, + getSources({ state, setContext, setActiveItemId, refresh }) { + return [ + { + sourceId: "documents", + getItemUrl({ item }) { + if (item.href) { + return offsetURL(item.href); + } else { + return undefined; + } + }, + onSelect({ + item, + state, + setContext, + setIsOpen, + setActiveItemId, + refresh, + }) { + if (item.type === kItemTypeMore) { + toggleExpanded(item, state, setContext, setActiveItemId, refresh); + + // Toggle more + setIsOpen(true); + } + }, + getItems({ query }) { + if (query === null || query === "") { + return []; + } + + const limit = quartoSearchOptions.limit; + if (quartoSearchOptions.algolia) { + return algoliaSearch(query, limit, quartoSearchOptions.algolia); + } else { + // Fuse search options + const fuseSearchOptions = { + isCaseSensitive: false, + shouldSort: true, + minMatchCharLength: 2, + limit: limit, + }; + + return readSearchData().then(function (fuse) { + return fuseSearch(query, fuse, fuseSearchOptions); + }); + } + }, + templates: { + noResults({ createElement }) { + const hasQuery = lastState.query; + + return createElement( + "div", + { + class: `quarto-search-no-results${ + hasQuery ? "" : " no-query" + }`, + }, + language["search-no-results-text"] + ); + }, + header({ items, createElement }) { + // count the documents + const count = items.filter((item) => { + return item.type === kItemTypeDoc; + }).length; + + if (count > 0) { + return createElement( + "div", + { class: "search-result-header" }, + `${count} ${language["search-matching-documents-text"]}` + ); + } else { + return createElement( + "div", + { class: "search-result-header-no-results" }, + `` + ); + } + }, + footer({ _items, createElement }) { + if ( + quartoSearchOptions.algolia && + quartoSearchOptions.algolia["show-logo"] + ) { + const libDir = quartoSearchOptions.algolia["libDir"]; + const logo = createElement("img", { + src: offsetURL( + `${libDir}/quarto-search/search-by-algolia.svg` + ), + class: "algolia-search-logo", + }); + return createElement( + "a", + { href: "http://www.algolia.com/" }, + logo + ); + } + }, + + item({ item, createElement }) { + return renderItem( + item, + createElement, + state, + setActiveItemId, + setContext, + refresh + ); + }, + }, + }, + ]; + }, + }); + + window.quartoOpenSearch = () => { + setIsOpen(false); + setIsOpen(true); + focusSearchInput(); + }; + + // Remove the labeleledby attribute since it is pointing + // to a non-existent label + if (quartoSearchOptions.type === "overlay") { + const inputEl = window.document.querySelector( + "#quarto-search .aa-Autocomplete" + ); + if (inputEl) { + inputEl.removeAttribute("aria-labelledby"); + } + } + + // If the main document scrolls dismiss the search results + // (otherwise, since they're floating in the document they can scroll with the document) + window.document.body.onscroll = () => { + setIsOpen(false); + }; + + if (showSearchResults) { + setIsOpen(true); + focusSearchInput(); + } +}); + +function configurePlugins(quartoSearchOptions) { + const autocompletePlugins = []; + const algoliaOptions = quartoSearchOptions.algolia; + if ( + algoliaOptions && + algoliaOptions["analytics-events"] && + algoliaOptions["search-only-api-key"] && + algoliaOptions["application-id"] + ) { + const apiKey = algoliaOptions["search-only-api-key"]; + const appId = algoliaOptions["application-id"]; + + // Aloglia insights may not be loaded because they require cookie consent + // Use deferred loading so events will start being recorded when/if consent + // is granted. + const algoliaInsightsDeferredPlugin = deferredLoadPlugin(() => { + if ( + window.aa && + window["@algolia/autocomplete-plugin-algolia-insights"] + ) { + window.aa("init", { + appId, + apiKey, + useCookie: true, + }); + + const { createAlgoliaInsightsPlugin } = + window["@algolia/autocomplete-plugin-algolia-insights"]; + // Register the insights client + const algoliaInsightsPlugin = createAlgoliaInsightsPlugin({ + insightsClient: window.aa, + onItemsChange({ insights, insightsEvents }) { + const events = insightsEvents.map((event) => { + const maxEvents = event.objectIDs.slice(0, 20); + return { + ...event, + objectIDs: maxEvents, + }; + }); + + insights.viewedObjectIDs(...events); + }, + }); + return algoliaInsightsPlugin; + } + }); + + // Add the plugin + autocompletePlugins.push(algoliaInsightsDeferredPlugin); + return autocompletePlugins; + } +} + +// For plugins that may not load immediately, create a wrapper +// plugin and forward events and plugin data once the plugin +// is initialized. This is useful for cases like cookie consent +// which may prevent the analytics insights event plugin from initializing +// immediately. +function deferredLoadPlugin(createPlugin) { + let plugin = undefined; + let subscribeObj = undefined; + const wrappedPlugin = () => { + if (!plugin && subscribeObj) { + plugin = createPlugin(); + if (plugin && plugin.subscribe) { + plugin.subscribe(subscribeObj); + } + } + return plugin; + }; + + return { + subscribe: (obj) => { + subscribeObj = obj; + }, + onStateChange: (obj) => { + const plugin = wrappedPlugin(); + if (plugin && plugin.onStateChange) { + plugin.onStateChange(obj); + } + }, + onSubmit: (obj) => { + const plugin = wrappedPlugin(); + if (plugin && plugin.onSubmit) { + plugin.onSubmit(obj); + } + }, + onReset: (obj) => { + const plugin = wrappedPlugin(); + if (plugin && plugin.onReset) { + plugin.onReset(obj); + } + }, + getSources: (obj) => { + const plugin = wrappedPlugin(); + if (plugin && plugin.getSources) { + return plugin.getSources(obj); + } else { + return Promise.resolve([]); + } + }, + data: (obj) => { + const plugin = wrappedPlugin(); + if (plugin && plugin.data) { + plugin.data(obj); + } + }, + }; +} + +function validateItems(items) { + // Validate the first item + if (items.length > 0) { + const item = items[0]; + const missingFields = []; + if (item.href == undefined) { + missingFields.push("href"); + } + if (!item.title == undefined) { + missingFields.push("title"); + } + if (!item.text == undefined) { + missingFields.push("text"); + } + + if (missingFields.length === 1) { + throw { + name: `Error: Search index is missing the ${missingFields[0]} field.`, + message: `The items being returned for this search do not include all the required fields. Please ensure that your index items include the ${missingFields[0]} field or use index-fields in your _quarto.yml file to specify the field names.`, + }; + } else if (missingFields.length > 1) { + const missingFieldList = missingFields + .map((field) => { + return `${field}`; + }) + .join(", "); + + throw { + name: `Error: Search index is missing the following fields: ${missingFieldList}.`, + message: `The items being returned for this search do not include all the required fields. Please ensure that your index items includes the following fields: ${missingFieldList}, or use index-fields in your _quarto.yml file to specify the field names.`, + }; + } + } +} + +let lastQuery = null; +function showCopyLink(query, options) { + const language = options.language; + lastQuery = query; + // Insert share icon + const inputSuffixEl = window.document.body.querySelector( + ".aa-Form .aa-InputWrapperSuffix" + ); + + if (inputSuffixEl) { + let copyButtonEl = window.document.body.querySelector( + ".aa-Form .aa-InputWrapperSuffix .aa-CopyButton" + ); + + if (copyButtonEl === null) { + copyButtonEl = window.document.createElement("button"); + copyButtonEl.setAttribute("class", "aa-CopyButton"); + copyButtonEl.setAttribute("type", "button"); + copyButtonEl.setAttribute("title", language["search-copy-link-title"]); + copyButtonEl.onmousedown = (e) => { + e.preventDefault(); + e.stopPropagation(); + }; + + const linkIcon = "bi-clipboard"; + const checkIcon = "bi-check2"; + + const shareIconEl = window.document.createElement("i"); + shareIconEl.setAttribute("class", `bi ${linkIcon}`); + copyButtonEl.appendChild(shareIconEl); + inputSuffixEl.prepend(copyButtonEl); + + const clipboard = new window.ClipboardJS(".aa-CopyButton", { + text: function (_trigger) { + const copyUrl = new URL(window.location); + copyUrl.searchParams.set(kQueryArg, lastQuery); + copyUrl.searchParams.set(kResultsArg, "1"); + return copyUrl.toString(); + }, + }); + clipboard.on("success", function (e) { + // Focus the input + + // button target + const button = e.trigger; + const icon = button.querySelector("i.bi"); + + // flash "checked" + icon.classList.add(checkIcon); + icon.classList.remove(linkIcon); + setTimeout(function () { + icon.classList.remove(checkIcon); + icon.classList.add(linkIcon); + }, 1000); + }); + } + + // If there is a query, show the link icon + if (copyButtonEl) { + if (lastQuery && options["copy-button"]) { + copyButtonEl.style.display = "flex"; + } else { + copyButtonEl.style.display = "none"; + } + } + } +} + +/* Search Index Handling */ +// create the index +var fuseIndex = undefined; +async function readSearchData() { + // Initialize the search index on demand + if (fuseIndex === undefined) { + // create fuse index + const options = { + keys: [ + { name: "title", weight: 20 }, + { name: "section", weight: 20 }, + { name: "text", weight: 10 }, + ], + ignoreLocation: true, + threshold: 0.1, + }; + const fuse = new window.Fuse([], options); + + // fetch the main search.json + const response = await fetch(offsetURL("search.json")); + if (response.status == 200) { + return response.json().then(function (searchDocs) { + searchDocs.forEach(function (searchDoc) { + fuse.add(searchDoc); + }); + fuseIndex = fuse; + return fuseIndex; + }); + } else { + return Promise.reject( + new Error( + "Unexpected status from search index request: " + response.status + ) + ); + } + } + return fuseIndex; +} + +function inputElement() { + return window.document.body.querySelector(".aa-Form .aa-Input"); +} + +function focusSearchInput() { + setTimeout(() => { + const inputEl = inputElement(); + if (inputEl) { + inputEl.focus(); + } + }, 50); +} + +/* Panels */ +const kItemTypeDoc = "document"; +const kItemTypeMore = "document-more"; +const kItemTypeItem = "document-item"; +const kItemTypeError = "error"; + +function renderItem( + item, + createElement, + state, + setActiveItemId, + setContext, + refresh +) { + switch (item.type) { + case kItemTypeDoc: + return createDocumentCard( + createElement, + "file-richtext", + item.title, + item.section, + item.text, + item.href + ); + case kItemTypeMore: + return createMoreCard( + createElement, + item, + state, + setActiveItemId, + setContext, + refresh + ); + case kItemTypeItem: + return createSectionCard( + createElement, + item.section, + item.text, + item.href + ); + case kItemTypeError: + return createErrorCard(createElement, item.title, item.text); + default: + return undefined; + } +} + +function createDocumentCard(createElement, icon, title, section, text, href) { + const iconEl = createElement("i", { + class: `bi bi-${icon} search-result-icon`, + }); + const titleEl = createElement("p", { class: "search-result-title" }, title); + const titleContainerEl = createElement( + "div", + { class: "search-result-title-container" }, + [iconEl, titleEl] + ); + + const textEls = []; + if (section) { + const sectionEl = createElement( + "p", + { class: "search-result-section" }, + section + ); + textEls.push(sectionEl); + } + const descEl = createElement("p", { + class: "search-result-text", + dangerouslySetInnerHTML: { + __html: text, + }, + }); + textEls.push(descEl); + + const textContainerEl = createElement( + "div", + { class: "search-result-text-container" }, + textEls + ); + + const containerEl = createElement( + "div", + { + class: "search-result-container", + }, + [titleContainerEl, textContainerEl] + ); + + const linkEl = createElement( + "a", + { + href: offsetURL(href), + class: "search-result-link", + }, + containerEl + ); + + const classes = ["search-result-doc", "search-item"]; + if (!section) { + classes.push("document-selectable"); + } + + return createElement( + "div", + { + class: classes.join(" "), + }, + linkEl + ); +} + +function createMoreCard( + createElement, + item, + state, + setActiveItemId, + setContext, + refresh +) { + const moreCardEl = createElement( + "div", + { + class: "search-result-more search-item", + onClick: (e) => { + // Handle expanding the sections by adding the expanded + // section to the list of expanded sections + toggleExpanded(item, state, setContext, setActiveItemId, refresh); + e.stopPropagation(); + }, + }, + item.title + ); + + return moreCardEl; +} + +function toggleExpanded(item, state, setContext, setActiveItemId, refresh) { + const expanded = state.context.expanded || []; + if (expanded.includes(item.target)) { + setContext({ + expanded: expanded.filter((target) => target !== item.target), + }); + } else { + setContext({ expanded: [...expanded, item.target] }); + } + + refresh(); + setActiveItemId(item.__autocomplete_id); +} + +function createSectionCard(createElement, section, text, href) { + const sectionEl = createSection(createElement, section, text, href); + return createElement( + "div", + { + class: "search-result-doc-section search-item", + }, + sectionEl + ); +} + +function createSection(createElement, title, text, href) { + const descEl = createElement("p", { + class: "search-result-text", + dangerouslySetInnerHTML: { + __html: text, + }, + }); + + const titleEl = createElement("p", { class: "search-result-section" }, title); + const linkEl = createElement( + "a", + { + href: offsetURL(href), + class: "search-result-link", + }, + [titleEl, descEl] + ); + return linkEl; +} + +function createErrorCard(createElement, title, text) { + const descEl = createElement("p", { + class: "search-error-text", + dangerouslySetInnerHTML: { + __html: text, + }, + }); + + const titleEl = createElement("p", { + class: "search-error-title", + dangerouslySetInnerHTML: { + __html: ` ${title}`, + }, + }); + const errorEl = createElement("div", { class: "search-error" }, [ + titleEl, + descEl, + ]); + return errorEl; +} + +function positionPanel(pos) { + const panelEl = window.document.querySelector( + "#quarto-search-results .aa-Panel" + ); + const inputEl = window.document.querySelector( + "#quarto-search .aa-Autocomplete" + ); + + if (panelEl && inputEl) { + panelEl.style.top = `${Math.round(panelEl.offsetTop)}px`; + if (pos === "start") { + panelEl.style.left = `${Math.round(inputEl.left)}px`; + } else { + panelEl.style.right = `${Math.round(inputEl.offsetRight)}px`; + } + } +} + +/* Highlighting */ +// highlighting functions +function highlightMatch(query, text) { + if (text) { + const start = text.toLowerCase().indexOf(query.toLowerCase()); + if (start !== -1) { + const startMark = ""; + const endMark = ""; + + const end = start + query.length; + text = + text.slice(0, start) + + startMark + + text.slice(start, end) + + endMark + + text.slice(end); + const startInfo = clipStart(text, start); + const endInfo = clipEnd( + text, + startInfo.position + startMark.length + endMark.length + ); + text = + startInfo.prefix + + text.slice(startInfo.position, endInfo.position) + + endInfo.suffix; + + return text; + } else { + return text; + } + } else { + return text; + } +} + +function clipStart(text, pos) { + const clipStart = pos - 50; + if (clipStart < 0) { + // This will just return the start of the string + return { + position: 0, + prefix: "", + }; + } else { + // We're clipping before the start of the string, walk backwards to the first space. + const spacePos = findSpace(text, pos, -1); + return { + position: spacePos.position, + prefix: "", + }; + } +} + +function clipEnd(text, pos) { + const clipEnd = pos + 200; + if (clipEnd > text.length) { + return { + position: text.length, + suffix: "", + }; + } else { + const spacePos = findSpace(text, clipEnd, 1); + return { + position: spacePos.position, + suffix: spacePos.clipped ? "ā€¦" : "", + }; + } +} + +function findSpace(text, start, step) { + let stepPos = start; + while (stepPos > -1 && stepPos < text.length) { + const char = text[stepPos]; + if (char === " " || char === "," || char === ":") { + return { + position: step === 1 ? stepPos : stepPos - step, + clipped: stepPos > 1 && stepPos < text.length, + }; + } + stepPos = stepPos + step; + } + + return { + position: stepPos - step, + clipped: false, + }; +} + +// removes highlighting as implemented by the mark tag +function clearHighlight(searchterm, el) { + const childNodes = el.childNodes; + for (let i = childNodes.length - 1; i >= 0; i--) { + const node = childNodes[i]; + if (node.nodeType === Node.ELEMENT_NODE) { + if ( + node.tagName === "MARK" && + node.innerText.toLowerCase() === searchterm.toLowerCase() + ) { + el.replaceChild(document.createTextNode(node.innerText), node); + } else { + clearHighlight(searchterm, node); + } + } + } +} + +function escapeRegExp(string) { + return string.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); // $& means the whole matched string +} + +// highlight matches +function highlight(term, el) { + const termRegex = new RegExp(term, "ig"); + const childNodes = el.childNodes; + + // walk back to front avoid mutating elements in front of us + for (let i = childNodes.length - 1; i >= 0; i--) { + const node = childNodes[i]; + + if (node.nodeType === Node.TEXT_NODE) { + // Search text nodes for text to highlight + const text = node.nodeValue; + + let startIndex = 0; + let matchIndex = text.search(termRegex); + if (matchIndex > -1) { + const markFragment = document.createDocumentFragment(); + while (matchIndex > -1) { + const prefix = text.slice(startIndex, matchIndex); + markFragment.appendChild(document.createTextNode(prefix)); + + const mark = document.createElement("mark"); + mark.appendChild( + document.createTextNode( + text.slice(matchIndex, matchIndex + term.length) + ) + ); + markFragment.appendChild(mark); + + startIndex = matchIndex + term.length; + matchIndex = text.slice(startIndex).search(new RegExp(term, "ig")); + if (matchIndex > -1) { + matchIndex = startIndex + matchIndex; + } + } + if (startIndex < text.length) { + markFragment.appendChild( + document.createTextNode(text.slice(startIndex, text.length)) + ); + } + + el.replaceChild(markFragment, node); + } + } else if (node.nodeType === Node.ELEMENT_NODE) { + // recurse through elements + highlight(term, node); + } + } +} + +/* Link Handling */ +// get the offset from this page for a given site root relative url +function offsetURL(url) { + var offset = getMeta("quarto:offset"); + return offset ? offset + url : url; +} + +// read a meta tag value +function getMeta(metaName) { + var metas = window.document.getElementsByTagName("meta"); + for (let i = 0; i < metas.length; i++) { + if (metas[i].getAttribute("name") === metaName) { + return metas[i].getAttribute("content"); + } + } + return ""; +} + +function algoliaSearch(query, limit, algoliaOptions) { + const { getAlgoliaResults } = window["@algolia/autocomplete-preset-algolia"]; + + const applicationId = algoliaOptions["application-id"]; + const searchOnlyApiKey = algoliaOptions["search-only-api-key"]; + const indexName = algoliaOptions["index-name"]; + const indexFields = algoliaOptions["index-fields"]; + const searchClient = window.algoliasearch(applicationId, searchOnlyApiKey); + const searchParams = algoliaOptions["params"]; + const searchAnalytics = !!algoliaOptions["analytics-events"]; + + return getAlgoliaResults({ + searchClient, + queries: [ + { + indexName: indexName, + query, + params: { + hitsPerPage: limit, + clickAnalytics: searchAnalytics, + ...searchParams, + }, + }, + ], + transformResponse: (response) => { + if (!indexFields) { + return response.hits.map((hit) => { + return hit.map((item) => { + return { + ...item, + text: highlightMatch(query, item.text), + }; + }); + }); + } else { + const remappedHits = response.hits.map((hit) => { + return hit.map((item) => { + const newItem = { ...item }; + ["href", "section", "title", "text"].forEach((keyName) => { + const mappedName = indexFields[keyName]; + if ( + mappedName && + item[mappedName] !== undefined && + mappedName !== keyName + ) { + newItem[keyName] = item[mappedName]; + delete newItem[mappedName]; + } + }); + newItem.text = highlightMatch(query, newItem.text); + return newItem; + }); + }); + return remappedHits; + } + }, + }); +} + +function fuseSearch(query, fuse, fuseOptions) { + return fuse.search(query, fuseOptions).map((result) => { + const addParam = (url, name, value) => { + const anchorParts = url.split("#"); + const baseUrl = anchorParts[0]; + const sep = baseUrl.search("\\?") > 0 ? "&" : "?"; + anchorParts[0] = baseUrl + sep + name + "=" + value; + return anchorParts.join("#"); + }; + + return { + title: result.item.title, + section: result.item.section, + href: addParam(result.item.href, kQueryArg, query), + text: highlightMatch(query, result.item.text), + }; + }); +} diff --git a/sitemap.xml b/sitemap.xml new file mode 100644 index 00000000..1444ab49 --- /dev/null +++ b/sitemap.xml @@ -0,0 +1,79 @@ + + + + https://Nixtla.github.io/hierarchicalforecast/evaluation.html + 2023-09-13T19:28:09.419Z + + + https://Nixtla.github.io/hierarchicalforecast/examples/tourismlarge-evaluation.html + 2023-09-13T19:28:07.899Z + + + https://Nixtla.github.io/hierarchicalforecast/examples/hierarchicalforecast-gluonts.html + 2023-09-13T19:28:05.011Z + + + https://Nixtla.github.io/hierarchicalforecast/examples/mlframeworksexample.html + 2023-09-13T19:28:03.651Z + + + https://Nixtla.github.io/hierarchicalforecast/examples/australianprisonpopulation.html + 2023-09-13T19:28:01.579Z + + + https://Nixtla.github.io/hierarchicalforecast/examples/nonnegativereconciliation.html + 2023-09-13T19:27:59.943Z + + + https://Nixtla.github.io/hierarchicalforecast/examples/australiandomestictourism.html + 2023-09-13T19:27:58.223Z + + + https://Nixtla.github.io/hierarchicalforecast/index.html + 2023-09-13T19:27:56.855Z + + + https://Nixtla.github.io/hierarchicalforecast/utils.html + 2023-09-13T19:27:55.807Z + + + https://Nixtla.github.io/hierarchicalforecast/methods.html + 2023-09-13T19:27:54.391Z + + + https://Nixtla.github.io/hierarchicalforecast/core.html + 2023-09-13T19:27:55.283Z + + + https://Nixtla.github.io/hierarchicalforecast/probabilistic_methods.html + 2023-09-13T19:27:56.359Z + + + https://Nixtla.github.io/hierarchicalforecast/examples/index.html + 2023-09-13T19:27:57.331Z + + + https://Nixtla.github.io/hierarchicalforecast/examples/australiandomestictourism-bootstraped-intervals.html + 2023-09-13T19:27:59.111Z + + + https://Nixtla.github.io/hierarchicalforecast/examples/introduction.html + 2023-09-13T19:28:00.807Z + + + https://Nixtla.github.io/hierarchicalforecast/examples/tourismsmall.html + 2023-09-13T19:28:02.227Z + + + https://Nixtla.github.io/hierarchicalforecast/examples/installation.html + 2023-09-13T19:28:04.071Z + + + https://Nixtla.github.io/hierarchicalforecast/examples/australiandomestictourism-intervals.html + 2023-09-13T19:28:05.923Z + + + https://Nixtla.github.io/hierarchicalforecast/examples/australiandomestictourism-permbu-intervals.html + 2023-09-13T19:28:08.755Z + + diff --git a/styles.css b/styles.css new file mode 100644 index 00000000..4be3d9b0 --- /dev/null +++ b/styles.css @@ -0,0 +1,57 @@ +.cell { + margin-bottom: 1rem; +} + +.cell > .sourceCode { + margin-bottom: 0; +} + +.cell-output > pre { + margin-bottom: 0; +} + +.cell-output > pre, .cell-output > .sourceCode > pre, .cell-output-stdout > pre { + margin-left: 0.8rem; + margin-top: 0; + background: none; + border-left: 2px solid lightsalmon; + border-top-left-radius: 0; + border-top-right-radius: 0; +} + +.cell-output > .sourceCode { + border: none; +} + +.cell-output > .sourceCode { + background: none; + margin-top: 0; +} + +div.description { + padding-left: 2px; + padding-top: 5px; + font-style: italic; + font-size: 135%; + opacity: 70%; +} + +/* show_doc signature */ +blockquote > pre { + font-size: 14px; +} + +.table { + font-size: 16px; + /* disable striped tables */ + --bs-table-striped-bg: var(--bs-table-bg); +} + +.quarto-figure-center > figure > figcaption { + text-align: center; +} + +.figure-caption { + font-size: 75%; + font-style: italic; +} \ No newline at end of file diff --git a/utils.html b/utils.html new file mode 100644 index 00000000..43aaa493 --- /dev/null +++ b/utils.html @@ -0,0 +1,826 @@ + + + + + + + + + +hierarchicalforecast - Aggregation/Visualization Utils + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    +
    + + +
    + +
    + + +
    + + + +
    + +
    +
    +

    Aggregation/Visualization Utils

    +
    + + + +
    + + + + +
    + + +
    + + +

    The HierarchicalForecast package contains utility functions to wrangle and visualize hierarchical series datasets. The aggregate function of the module allows you to create a hierarchy from categorical variables representing the structure levels, returning also the aggregation contraints matrix \(\mathbf{S}\).

    +

    In addition, HierarchicalForecast ensures compatibility of its reconciliation methods with other popular machine-learning libraries via its external forecast adapters that transform output base forecasts from external libraries into a compatible data frame format.

    +
    +

    Aggregate Function

    +
    +

    source

    +
    +

    aggregate

    +
    +
     aggregate (df:pandas.core.frame.DataFrame, spec:List[List[str]],
    +            is_balanced:bool=False, sparse_s:bool=False)
    +
    +

    Utils Aggregation Function. Aggregates bottom level series contained in the pd.DataFrame df according to levels defined in the spec list applying the agg_fn (sum, mean).

    +

    Parameters:
    df: pd.DataFrame with columns ['ds', 'y'] and columns to aggregate.
    spec: List of levels. Each element of the list contains a list of columns of df to aggregate.
    is_balanced: bool=False, whether Y_bottom_df is balanced, if not we balance.
    sparse_s: bool=False, whether the returned S_df should be a sparse DataFrame.
    Returns:
    Y_df, S_df, tags: tuple with hierarchically structured series Y_df (\(\mathbf{y}_{[a,b]}\)), summing dataframe S_df, and hierarchical aggregation indexes tags.

    +
    +
    +
    +

    Hierarchical Visualization

    +
    +

    source

    +
    +

    HierarchicalPlot

    +
    +
     HierarchicalPlot (S:pandas.core.frame.DataFrame,
    +                   tags:Dict[str,numpy.ndarray])
    +
    +

    Hierarchical Plot

    +

    This class contains a collection of matplotlib visualization methods, suited for small to medium sized hierarchical series.

    +

    Parameters:
    S: pd.DataFrame with summing matrix of size (base, bottom), see aggregate function.
    tags: np.ndarray, with hierarchical aggregation indexes, where each key is a level and its value contains tags associated to that level.

    +
    +

    source

    +
    +
    +

    plot_summing_matrix

    +
    +
     plot_summing_matrix ()
    +
    +

    Summation Constraints plot

    +

    This method simply plots the hierarchical aggregation constraints matrix \(\mathbf{S}\).

    +
    +

    source

    +
    +
    +

    plot_series

    +
    +
     plot_series (series:str, Y_df:Optional[pandas.core.frame.DataFrame]=None,
    +              models:Optional[List[str]]=None,
    +              level:Optional[List[int]]=None)
    +
    +

    Single Series plot

    +

    Parameters:
    series: str, string identifying the 'unique_id' any-level series to plot.
    Y_df: pd.DataFrame, hierarchically structured series (\(\mathbf{y}_{[a,b]}\)). It contains columns ['unique_id', 'ds', 'y'], it may have 'models'.
    models: List[str], string identifying filtering model columns. level: float list 0-100, confidence levels for prediction intervals available in Y_df.

    +

    Returns:
    Single series plot with filtered models and prediction interval level.

    +
    +

    source

    +
    +
    +

    plot_hierarchically_linked_series

    +
    +
     plot_hierarchically_linked_series (bottom_series:str,
    +                                    Y_df:Optional[pandas.core.frame.DataFr
    +                                    ame]=None,
    +                                    models:Optional[List[str]]=None,
    +                                    level:Optional[List[int]]=None)
    +
    +

    Hierarchically Linked Series plot

    +

    Parameters:
    bottom_series: str, string identifying the 'unique_id' bottom-level series to plot.
    Y_df: pd.DataFrame, hierarchically structured series (\(\mathbf{y}_{[a,b]}\)). It contains columns [ā€˜unique_idā€™, ā€˜dsā€™, ā€˜yā€™] and models.
    models: List[str], string identifying filtering model columns. level: float list 0-100, confidence levels for prediction intervals available in Y_df.

    +

    Returns:
    Collection of hierarchilly linked series plots associated with the bottom_series and filtered models and prediction interval level.

    +
    +

    source

    +
    +
    +

    plot_hierarchical_predictions_gap

    +
    +
     plot_hierarchical_predictions_gap (Y_df:pandas.core.frame.DataFrame,
    +                                    models:Optional[List[str]]=None,
    +                                    xlabel:Optional=None,
    +                                    ylabel:Optional=None)
    +
    +

    Hierarchically Predictions Gap plot

    +

    Parameters:
    Y_df: pd.DataFrame, hierarchically structured series (\(\mathbf{y}_{[a,b]}\)). It contains columns [ā€˜unique_idā€™, ā€˜dsā€™, ā€˜yā€™] and models.
    models: List[str], string identifying filtering model columns. xlabel: str, string for the plotā€™s x axis label. ylable: str, string for the plotā€™s y axis label.

    +

    Returns:
    Plots of aggregated predictions at different levels of the hierarchical structure. The aggregation is performed according to the tag levels see aggregate function.

    +
    +
    from statsforecast.core import StatsForecast
    +from statsforecast.models import AutoARIMA, ETS, Naive
    +from datasetsforecast.hierarchical import HierarchicalData
    +
    +Y_df, S, tags = HierarchicalData.load('./data', 'Labour')
    +Y_df['ds'] = pd.to_datetime(Y_df['ds'])
    +
    +Y_test_df  = Y_df.groupby('unique_id').tail(24)
    +Y_train_df = Y_df.drop(Y_test_df.index)
    +Y_test_df  = Y_test_df.set_index('unique_id')
    +Y_train_df = Y_train_df.set_index('unique_id')
    +
    +fcst = StatsForecast(
    +    df=Y_train_df, 
    +    #models=[AutoARIMA(season_length=12), Naive()], 
    +    models=[ETS(season_length=12, model='AAZ')],
    +    freq='MS', 
    +    n_jobs=-1
    +)
    +Y_hat_df = fcst.forecast(h=24)
    +
    +# Plot prediction difference of different aggregation
    +# Levels Country, Country/Region, Country/Gender/Region ...
    +hplots = HierarchicalPlot(S=S, tags=tags)
    +
    +hplots.plot_hierarchical_predictions_gap(
    +    Y_df=Y_hat_df, models='ETS',
    +    xlabel='Month', ylabel='Predictions',
    +)
    +
    +
    +
    +
    +

    External Forecast Adapters

    +
    +

    source

    +
    +

    samples_to_quantiles_df

    +
    +
     samples_to_quantiles_df (samples:numpy.ndarray, unique_ids:Iterable[str],
    +                          dates:Iterable,
    +                          quantiles:Optional[Iterable[float]]=None,
    +                          level:Optional[Iterable[int]]=None,
    +                          model_name:Optional[str]='model')
    +
    +

    Transform Random Samples into HierarchicalForecast input. Auxiliary function to create compatible HierarchicalForecast input Y_hat_df dataframe.

    +

    Parameters:
    samples: numpy array. Samples from forecast distribution of shape [n_series, n_samples, horizon].
    unique_ids: string list. Unique identifiers for each time series.
    dates: datetime list. List of forecast dates.
    quantiles: float list in [0., 1.]. Alternative to level, quantiles to estimate from y distribution.
    level: int list in [0,100]. Probability levels for prediction intervals.
    model_name: string. Name of forecasting model.

    +

    Returns:
    quantiles: float list in [0., 1.]. quantiles to estimate from y distribution .
    Y_hat_df: pd.DataFrame. With base quantile forecasts with columns ds and models to reconcile indexed by unique_id.

    + + +
    +
    + +

    If you find the code useful, please ā­ us on Github

    + +
    + + + + \ No newline at end of file